using Microsoft.Extensions.DependencyInjection; using NeonSprawl.Server.Game.Rewards; using NeonSprawl.Server.Tests; using Xunit; namespace NeonSprawl.Server.Tests.Game.Rewards; public sealed class InMemoryRewardDeliveryStoreTests { private const string PlayerId = "dev-local-1"; private const string QuestId = "prototype_quest_operator_chain"; private static readonly DateTimeOffset DeliveredAt = new(2026, 6, 7, 12, 0, 0, TimeSpan.Zero); [Fact] public void TryRecord_ShouldBeIdempotent_AtStoreLayer() { // Arrange var store = new InMemoryRewardDeliveryStore(); var deliveryEvent = CreatePrototypeEvent(); // Act var first = store.TryRecord(deliveryEvent); var second = store.TryRecord(deliveryEvent with { DeliveredAt = DeliveredAt.AddHours(1) }); var third = store.TryRecord( deliveryEvent with { PlayerId = "DEV-LOCAL-1", QuestId = "PROTOTYPE_QUEST_OPERATOR_CHAIN", }); // Assert Assert.True(first); Assert.False(second); Assert.False(third); Assert.True(store.TryGet(PlayerId, QuestId, out var stored)); Assert.Equal(deliveryEvent.IdempotencyKey, stored.IdempotencyKey); Assert.Equal(DeliveredAt, stored.DeliveredAt); Assert.Single(stored.GrantedItems); Assert.Single(stored.GrantedSkillXp); } [Fact] public void TryRecord_ShouldFailClosed_WhenPlayerIdIsEmpty() { // Arrange var store = new InMemoryRewardDeliveryStore(); var invalidEvent = CreatePrototypeEvent() with { PlayerId = " " }; // Act var recorded = store.TryRecord(invalidEvent); // Assert Assert.False(recorded); Assert.False(store.TryGet(" ", QuestId, out _)); } [Fact] public void TryRecord_ShouldFailClosed_WhenQuestIdIsEmpty() { // Arrange var store = new InMemoryRewardDeliveryStore(); var invalidEvent = CreatePrototypeEvent() with { QuestId = " " }; // Act var recorded = store.TryRecord(invalidEvent); // Assert Assert.False(recorded); Assert.False(store.TryGet(PlayerId, " ", out _)); } [Fact] public void TryGet_ShouldExposeReadableGrantSummary() { // Arrange var store = new InMemoryRewardDeliveryStore(); var deliveryEvent = CreatePrototypeEvent(); // Act var recorded = store.TryRecord(deliveryEvent); var found = store.TryGet(PlayerId, QuestId, out var stored); // Assert Assert.True(recorded); Assert.True(found); Assert.Equal("survey_drone_kit", stored.GrantedItems[0].ItemId); Assert.Equal(1, stored.GrantedItems[0].Quantity); Assert.Equal("salvage", stored.GrantedSkillXp[0].SkillId); Assert.Equal(50, stored.GrantedSkillXp[0].Amount); } [Fact] public async Task Host_ShouldResolveRewardDeliveryStoreFromDi_WhenStartupSucceeds() { // Arrange await using var factory = new InMemoryWebApplicationFactory(); using var client = factory.CreateClient(); _ = await client.GetAsync("/health"); var deliveryStore = factory.Services.GetRequiredService(); // Act var recorded = deliveryStore.TryRecord(CreatePrototypeEvent()); var found = deliveryStore.TryGet(PlayerId, QuestId, out var stored); // Assert Assert.IsType(deliveryStore); Assert.True(recorded); Assert.True(found); Assert.Equal(RewardDeliveryIds.MakeIdempotencyKey(PlayerId, QuestId), stored.IdempotencyKey); } private static RewardDeliveryEvent CreatePrototypeEvent() => new( PlayerId, QuestId, DeliveredAt, [new RewardItemGrantApplied("survey_drone_kit", 1)], [new RewardSkillXpGrantApplied("salvage", 50)], [], RewardDeliveryIds.MakeIdempotencyKey(PlayerId, QuestId)); }