neon-sprawl/server/NeonSprawl.Server.Tests/Game/Items/PlayerInventoryOperationsTe...

405 lines
14 KiB
C#

using NeonSprawl.Server.Game.Items;
namespace NeonSprawl.Server.Tests.Game.Items;
public sealed class PlayerInventoryOperationsTests
{
[Fact]
public void TryAddStack_ToEmptyBag_ShouldPlaceInFirstSlot()
{
// Arrange
using var factory = new InMemoryWebApplicationFactory();
var registry = factory.Services.GetRequiredService<IItemDefinitionRegistry>();
var store = factory.Services.GetRequiredService<IPlayerInventoryStore>();
// Act
var outcome = PlayerInventoryOperations.TryAddStack(
"dev-local-1",
"scrap_metal_bulk",
quantity: 10,
registry,
store);
// Assert
Assert.Equal(PlayerInventoryMutationKind.Applied, outcome.Kind);
Assert.NotNull(outcome.Snapshot);
Assert.Equal("scrap_metal_bulk", outcome.Snapshot!.BagSlots[0].ItemId);
Assert.Equal(10, outcome.Snapshot.BagSlots[0].Quantity);
}
[Fact]
public void TryAddStack_ShouldMergeIntoPartialStackRespectingStackMax()
{
// Arrange
using var factory = new InMemoryWebApplicationFactory();
var registry = factory.Services.GetRequiredService<IItemDefinitionRegistry>();
var store = factory.Services.GetRequiredService<IPlayerInventoryStore>();
_ = PlayerInventoryOperations.TryAddStack("dev-local-1", "field_stim_mk0", 15, registry, store);
// Act
var outcome = PlayerInventoryOperations.TryAddStack("dev-local-1", "field_stim_mk0", 5, registry, store);
// Assert
Assert.Equal(PlayerInventoryMutationKind.Applied, outcome.Kind);
Assert.Equal(20, CountItem(outcome.Snapshot!, "field_stim_mk0"));
Assert.Equal(1, OccupiedBagSlotCount(outcome.Snapshot!));
}
[Fact]
public void TryAddStack_WhenBagFull_ShouldDenyWithInventoryFull()
{
// Arrange
using var factory = new InMemoryWebApplicationFactory();
var registry = factory.Services.GetRequiredService<IItemDefinitionRegistry>();
var store = factory.Services.GetRequiredService<IPlayerInventoryStore>();
for (var i = 0; i < PlayerInventorySnapshot.BagSlotCount; i++)
{
var add = PlayerInventoryOperations.TryAddStack(
"dev-local-1",
"survey_drone_kit",
quantity: 1,
registry,
store);
Assert.Equal(PlayerInventoryMutationKind.Applied, add.Kind);
}
store.TryGetSnapshot("dev-local-1", out var before);
// Act
var outcome = PlayerInventoryOperations.TryAddStack(
"dev-local-1",
"scrap_metal_bulk",
quantity: 1,
registry,
store);
// Assert
Assert.Equal(PlayerInventoryMutationKind.Denied, outcome.Kind);
Assert.Equal(PlayerInventoryReasonCodes.InventoryFull, outcome.ReasonCode);
store.TryGetSnapshot("dev-local-1", out var after);
Assert.Equal(TotalBagQuantity(before!), TotalBagQuantity(after!));
}
[Fact]
public void TryAddStack_ToEquipment_ShouldUseEquipmentContainer()
{
// Arrange
using var factory = new InMemoryWebApplicationFactory();
var registry = factory.Services.GetRequiredService<IItemDefinitionRegistry>();
var store = factory.Services.GetRequiredService<IPlayerInventoryStore>();
// Act
var outcome = PlayerInventoryOperations.TryAddStack(
"dev-local-1",
"prototype_armor_shell",
quantity: 1,
registry,
store);
// Assert
Assert.Equal(PlayerInventoryMutationKind.Applied, outcome.Kind);
Assert.Equal("prototype_armor_shell", outcome.Snapshot!.EquipmentSlots[0].ItemId);
Assert.Equal(1, outcome.Snapshot.EquipmentSlots[0].Quantity);
Assert.All(outcome.Snapshot.BagSlots, static s => Assert.True(s.IsEmpty));
}
[Fact]
public void TryAddStack_WhenEquipmentOccupied_ShouldDenyWithInventoryFull()
{
// Arrange
using var factory = new InMemoryWebApplicationFactory();
var registry = factory.Services.GetRequiredService<IItemDefinitionRegistry>();
var store = factory.Services.GetRequiredService<IPlayerInventoryStore>();
_ = PlayerInventoryOperations.TryAddStack(
"dev-local-1",
"prototype_armor_shell",
quantity: 1,
registry,
store);
// Act
var outcome = PlayerInventoryOperations.TryAddStack(
"dev-local-1",
"prototype_armor_shell",
quantity: 1,
registry,
store);
// Assert
Assert.Equal(PlayerInventoryMutationKind.Denied, outcome.Kind);
Assert.Equal(PlayerInventoryReasonCodes.InventoryFull, outcome.ReasonCode);
}
[Fact]
public void TryAddStack_ForUnknownItem_ShouldDenyWithInvalidItem()
{
// Arrange
using var factory = new InMemoryWebApplicationFactory();
var registry = factory.Services.GetRequiredService<IItemDefinitionRegistry>();
var store = factory.Services.GetRequiredService<IPlayerInventoryStore>();
// Act
var outcome = PlayerInventoryOperations.TryAddStack(
"dev-local-1",
"nonexistent_item_id",
quantity: 1,
registry,
store);
// Assert
Assert.Equal(PlayerInventoryMutationKind.Denied, outcome.Kind);
Assert.Equal(PlayerInventoryReasonCodes.InvalidItem, outcome.ReasonCode);
}
[Fact]
public void TryAddStack_ForNonPositiveQuantity_ShouldDenyWithInvalidQuantity()
{
// Arrange
using var factory = new InMemoryWebApplicationFactory();
var registry = factory.Services.GetRequiredService<IItemDefinitionRegistry>();
var store = factory.Services.GetRequiredService<IPlayerInventoryStore>();
// Act
var outcome = PlayerInventoryOperations.TryAddStack(
"dev-local-1",
"scrap_metal_bulk",
quantity: 0,
registry,
store);
// Assert
Assert.Equal(PlayerInventoryMutationKind.Denied, outcome.Kind);
Assert.Equal(PlayerInventoryReasonCodes.InvalidQuantity, outcome.ReasonCode);
}
[Fact]
public void TryRemoveStack_ShouldDrainLowestSlotIndexFirst()
{
// Arrange
using var factory = new InMemoryWebApplicationFactory();
var registry = factory.Services.GetRequiredService<IItemDefinitionRegistry>();
var store = factory.Services.GetRequiredService<IPlayerInventoryStore>();
_ = PlayerInventoryOperations.TryAddStack("dev-local-1", "field_stim_mk0", 20, registry, store);
_ = PlayerInventoryOperations.TryAddStack("dev-local-1", "field_stim_mk0", 5, registry, store);
// Act
var outcome = PlayerInventoryOperations.TryRemoveStack(
"dev-local-1",
"field_stim_mk0",
quantity: 20,
registry,
store);
// Assert
Assert.Equal(PlayerInventoryMutationKind.Applied, outcome.Kind);
Assert.True(outcome.Snapshot!.BagSlots[0].IsEmpty);
Assert.Equal("field_stim_mk0", outcome.Snapshot.BagSlots[1].ItemId);
Assert.Equal(5, outcome.Snapshot.BagSlots[1].Quantity);
}
[Fact]
public void TryRemoveStack_WhenInsufficientQuantity_ShouldDenyWithoutMutation()
{
// Arrange
using var factory = new InMemoryWebApplicationFactory();
var registry = factory.Services.GetRequiredService<IItemDefinitionRegistry>();
var store = factory.Services.GetRequiredService<IPlayerInventoryStore>();
_ = PlayerInventoryOperations.TryAddStack("dev-local-1", "scrap_metal_bulk", 5, registry, store);
store.TryGetSnapshot("dev-local-1", out var before);
// Act
var outcome = PlayerInventoryOperations.TryRemoveStack(
"dev-local-1",
"scrap_metal_bulk",
quantity: 6,
registry,
store);
// Assert
Assert.Equal(PlayerInventoryMutationKind.Denied, outcome.Kind);
Assert.Equal(PlayerInventoryReasonCodes.InsufficientQuantity, outcome.ReasonCode);
store.TryGetSnapshot("dev-local-1", out var after);
Assert.Equal(TotalBagQuantity(before!), TotalBagQuantity(after!));
}
[Fact]
public void TryAddStack_WhenQuantityExceedsStackMax_ShouldSpanMultipleEmptySlots()
{
// Arrange
using var factory = new InMemoryWebApplicationFactory();
var registry = factory.Services.GetRequiredService<IItemDefinitionRegistry>();
var store = factory.Services.GetRequiredService<IPlayerInventoryStore>();
// Act
var outcome = PlayerInventoryOperations.TryAddStack(
"dev-local-1",
"field_stim_mk0",
quantity: 41,
registry,
store);
// Assert
Assert.Equal(PlayerInventoryMutationKind.Applied, outcome.Kind);
Assert.Equal(41, CountItem(outcome.Snapshot!, "field_stim_mk0"));
Assert.Equal(3, OccupiedBagSlotCount(outcome.Snapshot!));
Assert.Equal(20, outcome.Snapshot!.BagSlots[0].Quantity);
Assert.Equal(20, outcome.Snapshot.BagSlots[1].Quantity);
Assert.Equal(1, outcome.Snapshot.BagSlots[2].Quantity);
}
[Fact]
public void TryAddStack_ForUnknownPlayer_ShouldReturnStoreMissing()
{
// 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: 1,
registry,
store);
// Assert
Assert.Equal(PlayerInventoryMutationKind.StoreMissing, outcome.Kind);
Assert.Null(outcome.ReasonCode);
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);
}
[Fact]
public void TrySimulateAddStack_WhenBagFullForBagItem_ShouldReturnFalse()
{
// Arrange
using var factory = new InMemoryWebApplicationFactory();
var registry = factory.Services.GetRequiredService<IItemDefinitionRegistry>();
var store = factory.Services.GetRequiredService<IPlayerInventoryStore>();
for (var i = 0; i < PlayerInventorySnapshot.BagSlotCount; i++)
{
_ = PlayerInventoryOperations.TryAddStack(
"dev-local-1",
"survey_drone_kit",
quantity: 1,
registry,
store);
}
store.TryGetSnapshot("dev-local-1", out var snapshot);
var simulated = new PlayerInventorySnapshot
{
BagSlots = PlayerInventorySnapshot.CloneSlots(snapshot!.BagSlots),
EquipmentSlots = PlayerInventorySnapshot.CloneSlots(snapshot.EquipmentSlots),
};
// Act
var canPlace = PlayerInventoryOperations.TrySimulateAddStack(
ref simulated,
"refined_plate_stock",
quantity: 1,
registry);
// Assert
Assert.False(canPlace);
}
[Fact]
public void TrySimulateAddStack_WhenBagFullButEquipmentRouteAvailable_ShouldReturnTrue()
{
// Arrange
using var factory = new InMemoryWebApplicationFactory();
var registry = factory.Services.GetRequiredService<IItemDefinitionRegistry>();
var store = factory.Services.GetRequiredService<IPlayerInventoryStore>();
for (var i = 0; i < PlayerInventorySnapshot.BagSlotCount; i++)
{
_ = PlayerInventoryOperations.TryAddStack(
"dev-local-1",
"survey_drone_kit",
quantity: 1,
registry,
store);
}
store.TryGetSnapshot("dev-local-1", out var snapshot);
var simulated = new PlayerInventorySnapshot
{
BagSlots = PlayerInventorySnapshot.CloneSlots(snapshot!.BagSlots),
EquipmentSlots = PlayerInventorySnapshot.CloneSlots(snapshot.EquipmentSlots),
};
// Act
var canPlace = PlayerInventoryOperations.TrySimulateAddStack(
ref simulated,
"prototype_armor_shell",
quantity: 1,
registry);
// Assert
Assert.True(canPlace);
Assert.Equal("prototype_armor_shell", simulated.EquipmentSlots[0].ItemId);
Assert.Equal(1, simulated.EquipmentSlots[0].Quantity);
}
private static int CountItem(PlayerInventorySnapshot snapshot, string itemId)
{
var total = 0;
foreach (var slot in snapshot.BagSlots)
{
if (!slot.IsEmpty && slot.ItemId == itemId)
{
total += slot.Quantity;
}
}
return total;
}
private static int OccupiedBagSlotCount(PlayerInventorySnapshot snapshot)
{
var count = 0;
foreach (var slot in snapshot.BagSlots)
{
if (!slot.IsEmpty)
{
count++;
}
}
return count;
}
private static int TotalBagQuantity(PlayerInventorySnapshot snapshot)
{
var total = 0;
foreach (var slot in snapshot.BagSlots)
{
total += slot.Quantity;
}
return total;
}
}