neon-sprawl/server/NeonSprawl.Server.Tests/Game/Items/PlayerInventoryApiTests.cs

231 lines
8.6 KiB
C#

using NeonSprawl.Server.Game.Items;
namespace NeonSprawl.Server.Tests.Game.Items;
/// <summary>Regression tests for <c>GET</c>/<c>POST …/inventory</c> (NEO-55).</summary>
public sealed class PlayerInventoryApiTests
{
private static PlayerInventoryMutationRequest Mutation(
string mutationKind,
string itemId,
int quantity,
int schemaVersion = PlayerInventoryMutationRequest.CurrentSchemaVersion) =>
new()
{
SchemaVersion = schemaVersion,
MutationKind = mutationKind,
ItemId = itemId,
Quantity = quantity,
};
[Fact]
public async Task GetInventory_ShouldReturnNotFound_WhenPlayerUnknown()
{
// Arrange
await using var factory = new InMemoryWebApplicationFactory();
var client = factory.CreateClient();
// Act
var response = await client.GetAsync("/game/players/missing-player/inventory");
// Assert
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
public async Task GetInventory_ShouldReturnSchemaV1_WithEmptyFixedSlotArrays()
{
// Arrange
await using var factory = new InMemoryWebApplicationFactory();
var client = factory.CreateClient();
// Act
var response = await client.GetAsync("/game/players/dev-local-1/inventory");
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadFromJsonAsync<PlayerInventorySnapshotResponse>();
Assert.NotNull(body);
Assert.Equal(PlayerInventorySnapshotResponse.CurrentSchemaVersion, body!.SchemaVersion);
Assert.Equal("dev-local-1", body.PlayerId);
Assert.Equal(PlayerInventorySnapshot.BagSlotCount, body.BagSlots.Count);
Assert.Single(body.EquipmentSlots);
Assert.All(body.BagSlots, static slot =>
{
Assert.Null(slot.ItemId);
Assert.Equal(0, slot.Quantity);
});
Assert.All(body.EquipmentSlots, static slot =>
{
Assert.Null(slot.ItemId);
Assert.Equal(0, slot.Quantity);
});
Assert.Equal(Enumerable.Range(0, PlayerInventorySnapshot.BagSlotCount), body.BagSlots.Select(static s => s.SlotIndex));
Assert.Equal(0, body.EquipmentSlots[0].SlotIndex);
}
[Fact]
public async Task PostInventory_ShouldReturnBadRequest_WhenSchemaVersionMismatch()
{
// Arrange
await using var factory = new InMemoryWebApplicationFactory();
var client = factory.CreateClient();
var request = Mutation("add", "scrap_metal_bulk", quantity: 1, schemaVersion: 99);
// Act
var response = await client.PostAsJsonAsync("/game/players/dev-local-1/inventory", request);
// Assert
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
[Fact]
public async Task PostInventory_ShouldReturnBadRequest_WhenMutationKindUnknown()
{
// Arrange
await using var factory = new InMemoryWebApplicationFactory();
var client = factory.CreateClient();
var request = Mutation("consume", "scrap_metal_bulk", quantity: 1);
// Act
var response = await client.PostAsJsonAsync("/game/players/dev-local-1/inventory", request);
// Assert
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
}
[Fact]
public async Task PostInventory_ShouldReturnNotFound_WhenPlayerUnknown()
{
// Arrange
await using var factory = new InMemoryWebApplicationFactory();
var client = factory.CreateClient();
// Act
var response = await client.PostAsJsonAsync(
"/game/players/missing-player/inventory",
Mutation("add", "scrap_metal_bulk", quantity: 1));
// Assert
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
}
[Fact]
public async Task PostInventory_ShouldApplyAdd_WithUpdatedSnapshot()
{
// Arrange
await using var factory = new InMemoryWebApplicationFactory();
var client = factory.CreateClient();
// Act
var response = await client.PostAsJsonAsync(
"/game/players/dev-local-1/inventory",
Mutation("add", "scrap_metal_bulk", quantity: 10));
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadFromJsonAsync<PlayerInventoryMutationResponse>();
Assert.NotNull(body);
Assert.True(body!.Applied);
Assert.Null(body.ReasonCode);
Assert.Equal("dev-local-1", body.Inventory.PlayerId);
var occupied = body.Inventory.BagSlots.Where(static s => s.Quantity > 0).ToList();
Assert.Single(occupied);
Assert.Equal("scrap_metal_bulk", occupied[0].ItemId);
Assert.Equal(10, occupied[0].Quantity);
}
[Fact]
public async Task PostInventory_ShouldApplyRemove_AfterAdd_WithUpdatedSnapshot()
{
// Arrange
await using var factory = new InMemoryWebApplicationFactory();
var client = factory.CreateClient();
// Act
var addResponse = await client.PostAsJsonAsync(
"/game/players/dev-local-1/inventory",
Mutation("add", "scrap_metal_bulk", quantity: 10));
var response = await client.PostAsJsonAsync(
"/game/players/dev-local-1/inventory",
Mutation("remove", "scrap_metal_bulk", quantity: 4));
// Assert
Assert.Equal(HttpStatusCode.OK, addResponse.StatusCode);
var addBody = await addResponse.Content.ReadFromJsonAsync<PlayerInventoryMutationResponse>();
Assert.NotNull(addBody);
Assert.True(addBody!.Applied);
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadFromJsonAsync<PlayerInventoryMutationResponse>();
Assert.NotNull(body);
Assert.True(body!.Applied);
Assert.Null(body.ReasonCode);
var occupied = body.Inventory.BagSlots.Where(static s => s.Quantity > 0).ToList();
Assert.Single(occupied);
Assert.Equal("scrap_metal_bulk", occupied[0].ItemId);
Assert.Equal(6, occupied[0].Quantity);
}
[Fact]
public async Task PostInventory_ShouldDenyNonPositiveQuantity_WithInvalidQuantity()
{
// Arrange
await using var factory = new InMemoryWebApplicationFactory();
var client = factory.CreateClient();
// Act
var response = await client.PostAsJsonAsync(
"/game/players/dev-local-1/inventory",
Mutation("add", "scrap_metal_bulk", quantity: 0));
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadFromJsonAsync<PlayerInventoryMutationResponse>();
Assert.NotNull(body);
Assert.False(body!.Applied);
Assert.Equal(PlayerInventoryReasonCodes.InvalidQuantity, body.ReasonCode);
Assert.All(body.Inventory.BagSlots, static slot => Assert.Equal(0, slot.Quantity));
}
[Fact]
public async Task PostInventory_ShouldDenyUnknownItem_WithInvalidItem_AndLeaveInventoryEmpty()
{
// Arrange
await using var factory = new InMemoryWebApplicationFactory();
var client = factory.CreateClient();
// Act
var response = await client.PostAsJsonAsync(
"/game/players/dev-local-1/inventory",
Mutation("add", "not-a-prototype-item", quantity: 1));
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadFromJsonAsync<PlayerInventoryMutationResponse>();
Assert.NotNull(body);
Assert.False(body!.Applied);
Assert.Equal(PlayerInventoryReasonCodes.InvalidItem, body.ReasonCode);
Assert.All(body.Inventory.BagSlots, static slot => Assert.Equal(0, slot.Quantity));
}
[Fact]
public async Task PostInventory_ShouldDenyRemove_WithInsufficientQuantity()
{
// Arrange
await using var factory = new InMemoryWebApplicationFactory();
var client = factory.CreateClient();
// Act
var response = await client.PostAsJsonAsync(
"/game/players/dev-local-1/inventory",
Mutation("remove", "scrap_metal_bulk", quantity: 1));
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadFromJsonAsync<PlayerInventoryMutationResponse>();
Assert.NotNull(body);
Assert.False(body!.Applied);
Assert.Equal(PlayerInventoryReasonCodes.InsufficientQuantity, body.ReasonCode);
}
}