using NeonSprawl.Server.Game.Contracts; using NeonSprawl.Server.Game.Rewards; using Xunit; namespace NeonSprawl.Server.Tests.Game.Contracts; public sealed class InMemoryContractOutcomeStoreTests { private const string PlayerId = "dev-local-1"; private const string InstanceId = "prototype_contract_instance_001"; private const string OutcomeRowId = "outcome-row-001"; private static readonly DateTimeOffset RecordedAt = new(2026, 6, 22, 12, 0, 0, TimeSpan.Zero); [Fact] public void TryAppend_ShouldPersistAndQueryByInstanceAndIdempotencyKey() { // Arrange var store = new InMemoryContractOutcomeStore(); var row = CreatePrototypeOutcome(); // Act var appended = store.TryAppend(row); var foundByKey = store.TryGetByIdempotencyKey(row.IdempotencyKey, out var byKey); var listed = store.GetOutcomesForInstance(InstanceId); // Assert Assert.True(appended); Assert.True(foundByKey); Assert.Equal(row.Id, byKey.Id); Assert.Single(listed); Assert.Equal("scrap_metal_bulk", listed[0].GrantedItems[0].ItemId); Assert.Equal(5, listed[0].GrantedItems[0].Quantity); Assert.Equal("salvage", listed[0].GrantedSkillXp[0].SkillId); Assert.Equal(15, listed[0].GrantedSkillXp[0].Amount); } [Fact] public void TryAppend_ShouldDenyDuplicateRowId() { // Arrange var store = new InMemoryContractOutcomeStore(); var row = CreatePrototypeOutcome(); Assert.True(store.TryAppend(row)); // Act var duplicateId = store.TryAppend(row with { IdempotencyKey = "other-key" }); // Assert Assert.False(duplicateId); } [Fact] public void TryAppend_ShouldDenyDuplicateIdempotencyKey() { // Arrange var store = new InMemoryContractOutcomeStore(); var row = CreatePrototypeOutcome(); Assert.True(store.TryAppend(row)); // Act var duplicateKey = store.TryAppend(row with { Id = "outcome-row-002" }); // Assert Assert.False(duplicateKey); } [Fact] public void TryAppend_ShouldFailClosed_WhenRequiredFieldsEmpty() { // Arrange var store = new InMemoryContractOutcomeStore(); var invalidRow = CreatePrototypeOutcome() with { PlayerId = " " }; // Act var appended = store.TryAppend(invalidRow); // Assert Assert.False(appended); Assert.Empty(store.GetOutcomesForInstance(InstanceId)); } private static ContractOutcomeRow CreatePrototypeOutcome() { var idempotencyKey = ContractOutcomeIds.MakeIdempotencyKey(PlayerId, InstanceId); return new ContractOutcomeRow( OutcomeRowId, InstanceId, PlayerId, idempotencyKey, [new RewardItemGrantApplied("scrap_metal_bulk", 5)], [new RewardSkillXpGrantApplied("salvage", 15)], [], RecordedAt); } }