NEO-54: preserve validation reason codes when player store missing

Deny helper returns Denied + reasonCode even without a snapshot bucket,
so invalid_quantity/invalid_item are stable before NEO-55 HTTP mapping.
pull/89/head
VinPropane 2026-05-23 19:44:07 -04:00
parent dafe3cb92f
commit 3f664261b7
2 changed files with 25 additions and 3 deletions

View File

@ -271,6 +271,28 @@ public sealed class PlayerInventoryOperationsTests
Assert.Null(outcome.Snapshot); Assert.Null(outcome.Snapshot);
} }
[Fact]
public void TryAddStack_ForUnknownPlayer_WithInvalidQuantity_ShouldDenyWithReasonCode()
{
// Arrange
using var factory = new InMemoryWebApplicationFactory();
var registry = factory.Services.GetRequiredService<IItemDefinitionRegistry>();
var store = factory.Services.GetRequiredService<IPlayerInventoryStore>();
// Act
var outcome = PlayerInventoryOperations.TryAddStack(
"unknown-player-xyz",
"scrap_metal_bulk",
quantity: 0,
registry,
store);
// Assert
Assert.Equal(PlayerInventoryMutationKind.Denied, outcome.Kind);
Assert.Equal(PlayerInventoryReasonCodes.InvalidQuantity, outcome.ReasonCode);
Assert.Null(outcome.Snapshot);
}
private static int CountItem(PlayerInventorySnapshot snapshot, string itemId) private static int CountItem(PlayerInventorySnapshot snapshot, string itemId)
{ {
var total = 0; var total = 0;

View File

@ -124,12 +124,12 @@ public static class PlayerInventoryOperations
private static PlayerInventoryMutationOutcome Deny(string playerId, IPlayerInventoryStore store, string reasonCode) private static PlayerInventoryMutationOutcome Deny(string playerId, IPlayerInventoryStore store, string reasonCode)
{ {
if (!store.TryGetSnapshot(playerId, out var snapshot)) if (store.TryGetSnapshot(playerId, out var snapshot))
{ {
return new PlayerInventoryMutationOutcome(PlayerInventoryMutationKind.StoreMissing, null, null); return new PlayerInventoryMutationOutcome(PlayerInventoryMutationKind.Denied, reasonCode, snapshot);
} }
return new PlayerInventoryMutationOutcome(PlayerInventoryMutationKind.Denied, reasonCode, snapshot); return new PlayerInventoryMutationOutcome(PlayerInventoryMutationKind.Denied, reasonCode, null);
} }
private static int MergeIntoExistingStacks(InventorySlotState[] slots, string itemId, int remaining, int stackMax) private static int MergeIntoExistingStacks(InventorySlotState[] slots, string itemId, int remaining, int stackMax)