Files
Radicade/ServerTests/QuestRewardDeliveryTests.cs
2026-10-16 23:33:13 -07:00

161 lines
6.7 KiB
C#

using Microsoft.EntityFrameworkCore;
using RadicadeServer.API.Storefront;
using RadicadeServer.DB;
using RadicadeServer.Extensions;
using RadicadeServer.RecNet.Avatar;
using RadicadeServer.RecNet.Enums.Store;
using RadicadeServer.RecNet.Progression;
using RadicadeServer.RecNet.Rooms;
using RadicadeServer.RecNet.Storefront;
using RadicadeServer.Utils;
using Xunit;
namespace RadicadeServer.Tests;
/// <summary>
/// What happens to a quest's reward box when the player walks away from it.
///
/// A quest's reward is not a choice and is not openable anywhere else - the server picked one unowned
/// item out of the run's rank pool when the reward was generated, and the box is only the delivery of
/// that item. Leaving the quest with the box still shut therefore has to deliver it, not strand it,
/// not re-roll it, and not pay tokens for it. These tests cover the delivery, and the three things
/// the delivery must not touch: a box for the quest the player is standing in, a box that is no
/// quest's, and the item's identity.
/// </summary>
public class QuestRewardDeliveryTests
{
private const long PlayerId = 4411;
private static RecDBContext NewDb() =>
new(TestDbOptions.Create()
.UseInMemoryDatabase(Guid.NewGuid().ToString())
.Options);
private static async Task<RoomData> SeedRoomAsync(RecDBContext db, string name)
{
var room = new RoomData { Name = name };
db.Rooms.Add(room);
await db.SaveChangesAsync();
return room;
}
private static Gift QuestBox(GiftContext context = GiftContext.StuntRunner_TheMainEvent_C) => new()
{
GiftDropId = 8801,
FriendlyName = "Runner Harness (Green)",
Tooltip = "Stunt Runner: The Main Event",
AvatarItemDesc = "d2d3264b-fbf6-40b5-9f10-c85ec737d112,1ba65dd5-9df9-4ff3-be96-f83a7abf67b3",
CurrencyType = CurrencyType.Invalid,
Currency = 0,
Rarity = Rarity.Uncommon,
Context = context
};
private static async Task SeedRewardAsync(RecDBContext db, Gift box, bool consumed = false)
{
var reward = new GameReward
{
RewardSelectionId = 5150,
Message = "Stunt Runner: The Main Event",
GiftContext = box.Context,
RewardType = RewardType.PostQuestActivity,
GiftDrop1 = box,
GiftDrop2 = box,
GiftDrop3 = box,
CreatedAt = DateTime.UtcNow.AddMinutes(-2),
Consumed = consumed,
ConsumedGiftDropId = consumed ? box.GiftDropId : 0
};
var json = new ProgressionJson { PendingGameRewards = new List<GameReward> { reward } };
db.Progression.Add(new ProgressionData { PlayerId = PlayerId, Progress = json.ToJson() });
await db.SaveChangesAsync();
}
/// <summary>
/// The player leaves the quest without opening the box: they get the item that was already inside
/// it, the box is taken back, and the reward is recorded as collected against that same box.
/// </summary>
[Fact]
public async Task LeavingTheQuestWithTheBoxUnopenedDeliversTheItemItAlreadyHeld()
{
await using var db = NewDb();
var recCenter = await SeedRoomAsync(db, "RecCenter");
var box = QuestBox();
await SeedRewardAsync(db, box);
await StorefrontShared.PersistGiftBoxAsync(db, PlayerId, box, notify: false);
var delivered = await QuestRewardDelivery.DeliverOnArrivalAsync(db, PlayerId, recCenter.RoomId);
Assert.Equal(1, delivered);
Assert.Empty(await StorefrontShared.GetPendingGiftsAsync(db, PlayerId));
var unlocks = await db.Unlocks.AsNoTracking().FirstOrDefaultAsync(x => x.AccountId == PlayerId);
var item = Assert.Single(unlocks!.GetAvatarItems(), x => x.AvatarItemDesc == box.AvatarItemDesc);
Assert.Equal("Runner Harness (Green)", item.FriendlyName);
var progression = await db.Progression.AsNoTracking().FirstAsync(x => x.PlayerId == PlayerId);
var saved = Assert.Single(progression.GetProgressionJson()!.PendingGameRewards);
Assert.True(saved.Consumed);
Assert.Equal(box.GiftDropId, saved.ConsumedGiftDropId);
}
/// <summary>
/// Arriving back in the quest itself - a different instance of it, or a different rank of it -
/// leaves the box alone, because it is still openable there.
/// </summary>
[Fact]
public async Task ArrivingInTheQuest_KeepsTheBoxOpenable()
{
await using var db = NewDb();
var stuntRunner = await SeedRoomAsync(db, "StuntRunner");
var box = QuestBox();
await SeedRewardAsync(db, box);
await StorefrontShared.PersistGiftBoxAsync(db, PlayerId, box, notify: false);
var delivered = await QuestRewardDelivery.DeliverOnArrivalAsync(db, PlayerId, stuntRunner.RoomId);
Assert.Equal(0, delivered);
Assert.Single(await StorefrontShared.GetPendingGiftsAsync(db, PlayerId));
Assert.Null(await db.Unlocks.AsNoTracking().FirstOrDefaultAsync(x => x.AccountId == PlayerId));
}
/// <summary>
/// A box that is no quest's - a storefront purchase, a level-up box, a daily-challenge box - is
/// not the quest's special box and is left where it is: it can be opened anywhere.
/// </summary>
[Theory]
[InlineData(GiftContext.LevelUp)]
[InlineData(GiftContext.All_Daily_Challenges_Complete)]
[InlineData(GiftContext.Purchased_Gift_A)]
[InlineData(GiftContext.Dodgeball)]
public async Task ABoxThatIsNoQuestsIsLeftAlone(GiftContext context)
{
await using var db = NewDb();
var recCenter = await SeedRoomAsync(db, "RecCenter");
var box = QuestBox(context);
await StorefrontShared.PersistGiftBoxAsync(db, PlayerId, box, notify: false);
var delivered = await QuestRewardDelivery.DeliverOnArrivalAsync(db, PlayerId, recCenter.RoomId);
Assert.Equal(0, delivered);
Assert.Single(await StorefrontShared.GetPendingGiftsAsync(db, PlayerId));
}
/// <summary>Which contexts count as a quest's own, and which do not.</summary>
[Theory]
[InlineData(GiftContext.StuntRunner_TheMainEvent_C, true)]
[InlineData(GiftContext.StuntRunner_TheMainEvent_SS, true)]
[InlineData(GiftContext.Quest_Goblin_S, true)]
[InlineData(GiftContext.Quest_Pirate1_C, true)]
[InlineData(GiftContext.Dodgeball, false)]
[InlineData(GiftContext.Paintball_ClearCut, false)]
[InlineData(GiftContext.Lasertag, false)]
[InlineData(GiftContext.Reacquisition, false)]
[InlineData(GiftContext.LevelUp, false)]
[InlineData(GiftContext.GameRewards_Tokens, false)]
public void OnlyARankedQuestsContextIsAQuestContext(GiftContext context, bool expected)
{
Assert.Equal(expected, RroRewardPools.IsQuestContext(context));
}
}