neon-sprawl/server/NeonSprawl.Server.Tests/Game/Interaction/InteractionResourceNodeGath...

210 lines
9.1 KiB
C#

using System.Linq;
using System.Net;
using System.Net.Http.Json;
using NeonSprawl.Server.Game.Gathering;
using NeonSprawl.Server.Game.Interaction;
using NeonSprawl.Server.Game.Items;
using NeonSprawl.Server.Game.PositionState;
using NeonSprawl.Server.Game.Skills;
using NeonSprawl.Server.Game.World;
using Xunit;
namespace NeonSprawl.Server.Tests.Game.Interaction;
/// <summary>NEO-63: <c>resource_node</c> interact delegates to <see cref="GatherOperations"/>.</summary>
public sealed class InteractionResourceNodeGatherIntegrationTests
{
[Fact]
public async Task PostInteract_ResourceNode_WhenInRange_ShouldGrantInventoryAndSalvageXp()
{
// Arrange — node at (12, -6), radius 3; stand at (10, -6) → horizontal distance 2
await using var factory = new InMemoryWebApplicationFactory();
var client = factory.CreateClient();
await MoveNearAlphaAsync(client);
// Act
var interact = await client.PostAsJsonAsync(
"/game/players/dev-local-1/interact",
new InteractionRequest
{
SchemaVersion = InteractionRequest.CurrentSchemaVersion,
InteractableId = PrototypeInteractableRegistry.PrototypeResourceNodeAlphaId,
});
var inventory = await client.GetAsync("/game/players/dev-local-1/inventory");
var progression = await client.GetAsync("/game/players/dev-local-1/skill-progression");
// Assert
Assert.Equal(HttpStatusCode.OK, interact.StatusCode);
var envelope = await interact.Content.ReadFromJsonAsync<InteractionResponse>();
Assert.NotNull(envelope);
Assert.True(envelope!.Allowed);
Assert.Equal(PrototypeInteractableRegistry.PrototypeResourceNodeAlphaId, envelope.InteractableId);
Assert.Equal(HttpStatusCode.OK, inventory.StatusCode);
var bag = await inventory.Content.ReadFromJsonAsync<PlayerInventorySnapshotResponse>();
Assert.NotNull(bag);
Assert.Equal(1, CountItem(bag!, "scrap_metal_bulk"));
Assert.Equal(HttpStatusCode.OK, progression.StatusCode);
var skills = await progression.Content.ReadFromJsonAsync<SkillProgressionSnapshotResponse>();
Assert.NotNull(skills);
var salvage = skills!.Skills!.Single(static s => s.Id == "salvage");
Assert.Equal(GatherSkillXpConstants.ActivityXpPerResourceNodeGather, salvage.Xp);
}
[Fact]
public async Task PostInteract_ResourceNode_WhenDepleted_ShouldDenyWithNodeDepletedWithoutGrants()
{
// Arrange
await using var factory = new InMemoryWebApplicationFactory();
var client = factory.CreateClient();
await MoveNearAlphaAsync(client);
for (var i = 0; i < 10; i++)
{
var gather = await client.PostAsJsonAsync(
"/game/players/dev-local-1/interact",
new InteractionRequest
{
SchemaVersion = InteractionRequest.CurrentSchemaVersion,
InteractableId = PrototypeInteractableRegistry.PrototypeResourceNodeAlphaId,
});
Assert.Equal(HttpStatusCode.OK, gather.StatusCode);
var body = await gather.Content.ReadFromJsonAsync<InteractionResponse>();
Assert.NotNull(body);
Assert.True(body!.Allowed);
}
var inventoryBefore = await client.GetAsync("/game/players/dev-local-1/inventory");
var progressionBefore = await client.GetAsync("/game/players/dev-local-1/skill-progression");
var bagBefore = await inventoryBefore.Content.ReadFromJsonAsync<PlayerInventorySnapshotResponse>();
var skillsBefore = await progressionBefore.Content.ReadFromJsonAsync<SkillProgressionSnapshotResponse>();
var scrapBefore = CountItem(bagBefore!, "scrap_metal_bulk");
var salvageBefore = skillsBefore!.Skills!.Single(static s => s.Id == "salvage").Xp;
// Act
var interact = await client.PostAsJsonAsync(
"/game/players/dev-local-1/interact",
new InteractionRequest
{
SchemaVersion = InteractionRequest.CurrentSchemaVersion,
InteractableId = PrototypeInteractableRegistry.PrototypeResourceNodeAlphaId,
});
var inventoryAfter = await client.GetAsync("/game/players/dev-local-1/inventory");
var progressionAfter = await client.GetAsync("/game/players/dev-local-1/skill-progression");
// Assert
Assert.Equal(HttpStatusCode.OK, interact.StatusCode);
var envelope = await interact.Content.ReadFromJsonAsync<InteractionResponse>();
Assert.NotNull(envelope);
Assert.False(envelope!.Allowed);
Assert.Equal(GatherReasonCodes.NodeDepleted, envelope.ReasonCode);
var bagAfter = await inventoryAfter.Content.ReadFromJsonAsync<PlayerInventorySnapshotResponse>();
var skillsAfter = await progressionAfter.Content.ReadFromJsonAsync<SkillProgressionSnapshotResponse>();
Assert.Equal(scrapBefore, CountItem(bagAfter!, "scrap_metal_bulk"));
Assert.Equal(
salvageBefore,
skillsAfter!.Skills!.Single(static s => s.Id == "salvage").Xp);
}
[Fact]
public async Task PostInteract_PrototypeTerminal_ShouldNotGrantInventoryOrSalvageXp()
{
// Arrange
await using var factory = new InMemoryWebApplicationFactory();
var client = factory.CreateClient();
var move = new MoveCommandRequest
{
SchemaVersion = MoveCommandRequest.CurrentSchemaVersion,
Target = new PositionVector { X = 0.5, Y = 0.9, Z = 0.5 },
};
await client.PostAsJsonAsync("/game/players/dev-local-1/move", move);
// Act
var interact = await client.PostAsJsonAsync(
"/game/players/dev-local-1/interact",
new InteractionRequest
{
SchemaVersion = InteractionRequest.CurrentSchemaVersion,
InteractableId = PrototypeInteractableRegistry.PrototypeTerminalId,
});
var inventory = await client.GetAsync("/game/players/dev-local-1/inventory");
var progression = await client.GetAsync("/game/players/dev-local-1/skill-progression");
// Assert
Assert.Equal(HttpStatusCode.OK, interact.StatusCode);
var envelope = await interact.Content.ReadFromJsonAsync<InteractionResponse>();
Assert.NotNull(envelope);
Assert.True(envelope!.Allowed);
var bag = await inventory.Content.ReadFromJsonAsync<PlayerInventorySnapshotResponse>();
Assert.NotNull(bag);
Assert.Equal(0, CountItem(bag!, "scrap_metal_bulk"));
var skills = await progression.Content.ReadFromJsonAsync<SkillProgressionSnapshotResponse>();
Assert.NotNull(skills);
var salvage = skills!.Skills!.Single(static s => s.Id == "salvage");
Assert.Equal(0, salvage.Xp);
}
[Fact]
public async Task PostInteract_ResourceNode_WhenSalvageDisallowsActivity_ShouldDenyWithoutGrants()
{
// Arrange
await using var factory = new SalvageActivityDeniedRegistryWebApplicationFactory();
var client = factory.CreateClient();
await MoveNearAlphaAsync(client);
// Act
var interact = await client.PostAsJsonAsync(
"/game/players/dev-local-1/interact",
new InteractionRequest
{
SchemaVersion = InteractionRequest.CurrentSchemaVersion,
InteractableId = PrototypeInteractableRegistry.PrototypeResourceNodeAlphaId,
});
var inventory = await client.GetAsync("/game/players/dev-local-1/inventory");
var progression = await client.GetAsync("/game/players/dev-local-1/skill-progression");
// Assert
Assert.Equal(HttpStatusCode.OK, interact.StatusCode);
var envelope = await interact.Content.ReadFromJsonAsync<InteractionResponse>();
Assert.NotNull(envelope);
Assert.False(envelope!.Allowed);
Assert.False(string.IsNullOrEmpty(envelope.ReasonCode));
var bag = await inventory.Content.ReadFromJsonAsync<PlayerInventorySnapshotResponse>();
Assert.NotNull(bag);
Assert.Equal(0, CountItem(bag!, "scrap_metal_bulk"));
var skills = await progression.Content.ReadFromJsonAsync<SkillProgressionSnapshotResponse>();
Assert.NotNull(skills);
var salvage = skills!.Skills!.Single(static s => s.Id == "salvage");
Assert.Equal(0, salvage.Xp);
}
private static async Task MoveNearAlphaAsync(HttpClient client)
{
var move = new MoveCommandRequest
{
SchemaVersion = MoveCommandRequest.CurrentSchemaVersion,
Target = new PositionVector { X = 10, Y = 0.9, Z = -6 },
};
await client.PostAsJsonAsync("/game/players/dev-local-1/move", move);
}
private static int CountItem(PlayerInventorySnapshotResponse snapshot, string itemId)
{
var total = 0;
foreach (var slot in snapshot.BagSlots!)
{
if (string.Equals(slot.ItemId, itemId, StringComparison.Ordinal))
{
total += slot.Quantity;
}
}
return total;
}
}