neon-sprawl/server/NeonSprawl.Server.Tests/Game/Rewards/InMemoryRewardDeliveryStore...

104 lines
3.8 KiB
C#

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) });
// Assert
Assert.True(first);
Assert.False(second);
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<IRewardDeliveryStore>();
// Act
var recorded = deliveryStore.TryRecord(CreatePrototypeEvent());
var found = deliveryStore.TryGet(PlayerId, QuestId, out var stored);
// Assert
Assert.IsType<InMemoryRewardDeliveryStore>(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));
}