NEO-29: normalize test formatting across server and client suites

Audit all test files and bring them in line with formatting/style expectations by adding explicit AAA markers across server test methods and fixing GDScript test lint/format issues.
pull/54/head
VinPropane 2026-04-25 22:57:05 -04:00
parent 3dccab8edb
commit 71af7a5942
9 changed files with 229 additions and 52 deletions

View File

@ -52,7 +52,13 @@ class HoldFirstThenAutoTransport:
) -> Error: ) -> Error:
request_count += 1 request_count += 1
last_body = request_data last_body = request_data
var body_bytes: PackedByteArray = '{"schemaVersion":1,"updated":true,"loadout":{"schemaVersion":1,"playerId":"dev-local-1","slotCount":8,"slots":[]}}'.to_utf8_buffer() var body_bytes: PackedByteArray = (
'{"schemaVersion":1,"updated":true,"loadout":'
+ (
'{"schemaVersion":1,"playerId":"dev-local-1","slotCount":8,"slots":[]}}'
. to_utf8_buffer()
)
)
if _first: if _first:
_first = false _first = false
call_deferred("_emit", body_bytes) call_deferred("_emit", body_bytes)
@ -109,7 +115,10 @@ func test_bind_slot_posts_expected_payload() -> void:
transport.enqueue( transport.enqueue(
HTTPRequest.RESULT_SUCCESS, HTTPRequest.RESULT_SUCCESS,
200, 200,
'{"schemaVersion":1,"updated":true,"loadout":{"schemaVersion":1,"playerId":"dev-local-1","slotCount":8,"slots":[]}}' (
'{"schemaVersion":1,"updated":true,"loadout":'
+ '{"schemaVersion":1,"playerId":"dev-local-1","slotCount":8,"slots":[]}}'
)
) )
var c := _make_client(transport) var c := _make_client(transport)
c.call("request_bind_slot", 3, "prototype_burst") c.call("request_bind_slot", 3, "prototype_burst")
@ -130,7 +139,10 @@ func test_denied_update_emits_reason_signal() -> void:
transport.enqueue( transport.enqueue(
HTTPRequest.RESULT_SUCCESS, HTTPRequest.RESULT_SUCCESS,
200, 200,
'{"schemaVersion":1,"updated":false,"reasonCode":"unknown_ability","loadout":{"schemaVersion":1,"playerId":"dev-local-1","slotCount":8,"slots":[]}}' (
'{"schemaVersion":1,"updated":false,"reasonCode":"unknown_ability","loadout":'
+ '{"schemaVersion":1,"playerId":"dev-local-1","slotCount":8,"slots":[]}}'
)
) )
var c := _make_client(transport) var c := _make_client(transport)
monitor_signals(c) monitor_signals(c)

View File

@ -9,35 +9,50 @@ public class HorizontalReachTests
public void HorizontalDistance_IgnoresY() public void HorizontalDistance_IgnoresY()
{ {
// Y is not a parameter; identical X/Z → zero horizontal separation regardless of implied height difference. // Y is not a parameter; identical X/Z → zero horizontal separation regardless of implied height difference.
// Act
// Arrange
var d = HorizontalReach.HorizontalDistance(1, 2, 1, 2); var d = HorizontalReach.HorizontalDistance(1, 2, 1, 2);
// Assert
Assert.Equal(0, d); Assert.Equal(0, d);
} }
[Fact] [Fact]
public void IsWithinHorizontalRadius_AtExactBoundary_ReturnsTrue() public void IsWithinHorizontalRadius_AtExactBoundary_ReturnsTrue()
{ { // Assert
// Act
// Arrange
Assert.True(HorizontalReach.IsWithinHorizontalRadius(0, 0, 3, 0, 3)); Assert.True(HorizontalReach.IsWithinHorizontalRadius(0, 0, 3, 0, 3));
} }
[Fact] [Fact]
public void IsWithinHorizontalRadius_JustInside_ReturnsTrue() public void IsWithinHorizontalRadius_JustInside_ReturnsTrue()
{ { // Act
// Arrange
const double radius = 3.0; const double radius = 3.0;
const double eps = 1e-10; const double eps = 1e-10;
// Assert
Assert.True(HorizontalReach.IsWithinHorizontalRadius(0, 0, radius - eps, 0, radius)); Assert.True(HorizontalReach.IsWithinHorizontalRadius(0, 0, radius - eps, 0, radius));
} }
[Fact] [Fact]
public void IsWithinHorizontalRadius_JustOutside_ReturnsFalse() public void IsWithinHorizontalRadius_JustOutside_ReturnsFalse()
{ { // Act
// Arrange
const double radius = 3.0; const double radius = 3.0;
const double eps = 1e-7; const double eps = 1e-7;
// Assert
Assert.False(HorizontalReach.IsWithinHorizontalRadius(0, 0, radius + eps, 0, radius)); Assert.False(HorizontalReach.IsWithinHorizontalRadius(0, 0, radius + eps, 0, radius));
} }
[Fact] [Fact]
public void IsWithinHorizontalRadius_NegativeRadius_ThrowsArgumentOutOfRange() public void IsWithinHorizontalRadius_NegativeRadius_ThrowsArgumentOutOfRange()
{ { // Assert
// Act
// Arrange
Assert.Throws<ArgumentOutOfRangeException>(() => Assert.Throws<ArgumentOutOfRangeException>(() =>
HorizontalReach.IsWithinHorizontalRadius(0, 0, 0, 0, -1.0)); HorizontalReach.IsWithinHorizontalRadius(0, 0, 0, 0, -1.0));
} }

View File

@ -10,11 +10,14 @@ public class InteractablesWorldApiTests
{ {
[Fact] [Fact]
public async Task GetInteractables_ShouldReturnOrderedDescriptors_WithSchemaV1() public async Task GetInteractables_ShouldReturnOrderedDescriptors_WithSchemaV1()
{ { // Arrange
await using var factory = new InMemoryWebApplicationFactory(); await using var factory = new InMemoryWebApplicationFactory();
var client = factory.CreateClient(); var client = factory.CreateClient();
// Act
var response = await client.GetAsync("/game/world/interactables"); var response = await client.GetAsync("/game/world/interactables");
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadFromJsonAsync<InteractablesListResponse>(); var body = await response.Content.ReadFromJsonAsync<InteractablesListResponse>();

View File

@ -12,7 +12,9 @@ public class InteractionApiTests
{ {
[Fact] [Fact]
public async Task PostInteract_ShouldAllow_WhenExactlyAtHorizontalRadius() public async Task PostInteract_ShouldAllow_WhenExactlyAtHorizontalRadius()
{ { // Act
// Arrange
await using var factory = new InMemoryWebApplicationFactory(); await using var factory = new InMemoryWebApplicationFactory();
var client = factory.CreateClient(); var client = factory.CreateClient();
var move = new MoveCommandRequest var move = new MoveCommandRequest
@ -20,6 +22,7 @@ public class InteractionApiTests
SchemaVersion = MoveCommandRequest.CurrentSchemaVersion, SchemaVersion = MoveCommandRequest.CurrentSchemaVersion,
Target = new PositionVector { X = 3, Y = 0, Z = 0 }, Target = new PositionVector { X = 3, Y = 0, Z = 0 },
}; };
// Assert
Assert.Equal(HttpStatusCode.OK, (await client.PostAsJsonAsync("/game/players/dev-local-1/move", move)).StatusCode); Assert.Equal(HttpStatusCode.OK, (await client.PostAsJsonAsync("/game/players/dev-local-1/move", move)).StatusCode);
var response = await client.PostAsJsonAsync( var response = await client.PostAsJsonAsync(
@ -38,7 +41,8 @@ public class InteractionApiTests
[Fact] [Fact]
public async Task PostInteract_ShouldAllow_WhenPlayerInHorizontalRange() public async Task PostInteract_ShouldAllow_WhenPlayerInHorizontalRange()
{ { // Arrange
await using var factory = new InMemoryWebApplicationFactory(); await using var factory = new InMemoryWebApplicationFactory();
var client = factory.CreateClient(); var client = factory.CreateClient();
var move = new MoveCommandRequest var move = new MoveCommandRequest
@ -46,7 +50,9 @@ public class InteractionApiTests
SchemaVersion = MoveCommandRequest.CurrentSchemaVersion, SchemaVersion = MoveCommandRequest.CurrentSchemaVersion,
Target = new PositionVector { X = 0, Y = 0.9, Z = 0 }, Target = new PositionVector { X = 0, Y = 0.9, Z = 0 },
}; };
// Act
var moveResp = await client.PostAsJsonAsync("/game/players/dev-local-1/move", move); var moveResp = await client.PostAsJsonAsync("/game/players/dev-local-1/move", move);
// Assert
Assert.Equal(HttpStatusCode.OK, moveResp.StatusCode); Assert.Equal(HttpStatusCode.OK, moveResp.StatusCode);
var req = new InteractionRequest var req = new InteractionRequest
@ -69,7 +75,8 @@ public class InteractionApiTests
[Fact] [Fact]
public async Task PostInteract_ShouldDenyOutOfRange_WhenPlayerSeededFarFromTerminal() public async Task PostInteract_ShouldDenyOutOfRange_WhenPlayerSeededFarFromTerminal()
{ { // Arrange
await using var factory = new InMemoryWebApplicationFactory(); await using var factory = new InMemoryWebApplicationFactory();
var client = factory.CreateClient(); var client = factory.CreateClient();
var req = new InteractionRequest var req = new InteractionRequest
@ -77,8 +84,10 @@ public class InteractionApiTests
SchemaVersion = InteractionRequest.CurrentSchemaVersion, SchemaVersion = InteractionRequest.CurrentSchemaVersion,
InteractableId = PrototypeInteractableRegistry.PrototypeTerminalId, InteractableId = PrototypeInteractableRegistry.PrototypeTerminalId,
}; };
// Act
var response = await client.PostAsJsonAsync("/game/players/dev-local-1/interact", req); var response = await client.PostAsJsonAsync("/game/players/dev-local-1/interact", req);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadFromJsonAsync<InteractionResponse>(); var body = await response.Content.ReadFromJsonAsync<InteractionResponse>();
@ -89,7 +98,8 @@ public class InteractionApiTests
[Fact] [Fact]
public async Task PostInteract_ShouldDenyUnknownInteractable_WhenIdNotInRegistry() public async Task PostInteract_ShouldDenyUnknownInteractable_WhenIdNotInRegistry()
{ { // Arrange
await using var factory = new InMemoryWebApplicationFactory(); await using var factory = new InMemoryWebApplicationFactory();
var client = factory.CreateClient(); var client = factory.CreateClient();
var req = new InteractionRequest var req = new InteractionRequest
@ -97,8 +107,10 @@ public class InteractionApiTests
SchemaVersion = InteractionRequest.CurrentSchemaVersion, SchemaVersion = InteractionRequest.CurrentSchemaVersion,
InteractableId = "no_such_thing", InteractableId = "no_such_thing",
}; };
// Act
var response = await client.PostAsJsonAsync("/game/players/dev-local-1/interact", req); var response = await client.PostAsJsonAsync("/game/players/dev-local-1/interact", req);
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadFromJsonAsync<InteractionResponse>(); var body = await response.Content.ReadFromJsonAsync<InteractionResponse>();
@ -109,7 +121,9 @@ public class InteractionApiTests
[Fact] [Fact]
public async Task PostInteract_ShouldBeCaseInsensitive_ForInteractableId() public async Task PostInteract_ShouldBeCaseInsensitive_ForInteractableId()
{ { // Act
// Arrange
await using var factory = new InMemoryWebApplicationFactory(); await using var factory = new InMemoryWebApplicationFactory();
var client = factory.CreateClient(); var client = factory.CreateClient();
var move = new MoveCommandRequest var move = new MoveCommandRequest
@ -117,6 +131,7 @@ public class InteractionApiTests
SchemaVersion = MoveCommandRequest.CurrentSchemaVersion, SchemaVersion = MoveCommandRequest.CurrentSchemaVersion,
Target = new PositionVector { X = 0, Y = 0, Z = 0 }, Target = new PositionVector { X = 0, Y = 0, Z = 0 },
}; };
// Assert
Assert.Equal(HttpStatusCode.OK, (await client.PostAsJsonAsync("/game/players/dev-local-1/move", move)).StatusCode); Assert.Equal(HttpStatusCode.OK, (await client.PostAsJsonAsync("/game/players/dev-local-1/move", move)).StatusCode);
var req = new InteractionRequest var req = new InteractionRequest
@ -135,7 +150,8 @@ public class InteractionApiTests
[Fact] [Fact]
public async Task PostInteract_ShouldReturnBadRequest_WhenSchemaVersionWrong() public async Task PostInteract_ShouldReturnBadRequest_WhenSchemaVersionWrong()
{ { // Arrange
await using var factory = new InMemoryWebApplicationFactory(); await using var factory = new InMemoryWebApplicationFactory();
var client = factory.CreateClient(); var client = factory.CreateClient();
var req = new InteractionRequest var req = new InteractionRequest
@ -143,30 +159,36 @@ public class InteractionApiTests
SchemaVersion = 999, SchemaVersion = 999,
InteractableId = PrototypeInteractableRegistry.PrototypeTerminalId, InteractableId = PrototypeInteractableRegistry.PrototypeTerminalId,
}; };
// Act
var response = await client.PostAsJsonAsync("/game/players/dev-local-1/interact", req); var response = await client.PostAsJsonAsync("/game/players/dev-local-1/interact", req);
// Assert
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
} }
[Fact] [Fact]
public async Task PostInteract_ShouldReturnBadRequest_WhenInteractableIdMissing() public async Task PostInteract_ShouldReturnBadRequest_WhenInteractableIdMissing()
{ { // Arrange
await using var factory = new InMemoryWebApplicationFactory(); await using var factory = new InMemoryWebApplicationFactory();
var client = factory.CreateClient(); var client = factory.CreateClient();
var content = new StringContent( var content = new StringContent(
$"{{\"schemaVersion\":{InteractionRequest.CurrentSchemaVersion}}}", $"{{\"schemaVersion\":{InteractionRequest.CurrentSchemaVersion}}}",
Encoding.UTF8, Encoding.UTF8,
"application/json"); "application/json");
// Act
var response = await client.PostAsync("/game/players/dev-local-1/interact", content); var response = await client.PostAsync("/game/players/dev-local-1/interact", content);
// Assert
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
} }
[Fact] [Fact]
public async Task PostInteract_ShouldReturnBadRequest_WhenInteractableIdWhitespaceOnly() public async Task PostInteract_ShouldReturnBadRequest_WhenInteractableIdWhitespaceOnly()
{ { // Arrange
await using var factory = new InMemoryWebApplicationFactory(); await using var factory = new InMemoryWebApplicationFactory();
var client = factory.CreateClient(); var client = factory.CreateClient();
var req = new InteractionRequest var req = new InteractionRequest
@ -174,15 +196,18 @@ public class InteractionApiTests
SchemaVersion = InteractionRequest.CurrentSchemaVersion, SchemaVersion = InteractionRequest.CurrentSchemaVersion,
InteractableId = " \t ", InteractableId = " \t ",
}; };
// Act
var response = await client.PostAsJsonAsync("/game/players/dev-local-1/interact", req); var response = await client.PostAsJsonAsync("/game/players/dev-local-1/interact", req);
// Assert
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
} }
[Fact] [Fact]
public async Task PostInteract_ShouldReturnNotFound_WhenPlayerUnknown() public async Task PostInteract_ShouldReturnNotFound_WhenPlayerUnknown()
{ { // Arrange
await using var factory = new InMemoryWebApplicationFactory(); await using var factory = new InMemoryWebApplicationFactory();
var client = factory.CreateClient(); var client = factory.CreateClient();
var req = new InteractionRequest var req = new InteractionRequest
@ -190,17 +215,21 @@ public class InteractionApiTests
SchemaVersion = InteractionRequest.CurrentSchemaVersion, SchemaVersion = InteractionRequest.CurrentSchemaVersion,
InteractableId = PrototypeInteractableRegistry.PrototypeTerminalId, InteractableId = PrototypeInteractableRegistry.PrototypeTerminalId,
}; };
// Act
var response = await client.PostAsJsonAsync("/game/players/nobody-here/interact", req); var response = await client.PostAsJsonAsync("/game/players/nobody-here/interact", req);
// Assert
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
} }
[Fact] [Fact]
public async Task PostInteract_ShouldReflectNewPosition_AfterMove() public async Task PostInteract_ShouldReflectNewPosition_AfterMove()
{ { // Arrange
await using var factory = new InMemoryWebApplicationFactory(); await using var factory = new InMemoryWebApplicationFactory();
var client = factory.CreateClient(); var client = factory.CreateClient();
// Act
var far = await client.PostAsJsonAsync( var far = await client.PostAsJsonAsync(
"/game/players/dev-local-1/interact", "/game/players/dev-local-1/interact",
@ -210,6 +239,7 @@ public class InteractionApiTests
InteractableId = PrototypeInteractableRegistry.PrototypeTerminalId, InteractableId = PrototypeInteractableRegistry.PrototypeTerminalId,
}); });
var farBody = await far.Content.ReadFromJsonAsync<InteractionResponse>(); var farBody = await far.Content.ReadFromJsonAsync<InteractionResponse>();
// Assert
Assert.False(farBody!.Allowed); Assert.False(farBody!.Allowed);
Assert.Equal(InteractionApi.ReasonOutOfRange, farBody.ReasonCode); Assert.Equal(InteractionApi.ReasonOutOfRange, farBody.ReasonCode);
@ -233,7 +263,9 @@ public class InteractionApiTests
[Fact] [Fact]
public async Task PostInteract_ShouldAllowResourceNode_WhenPlayerInRangeOfResourceAnchor() public async Task PostInteract_ShouldAllowResourceNode_WhenPlayerInRangeOfResourceAnchor()
{ { // Act
// Arrange
await using var factory = new InMemoryWebApplicationFactory(); await using var factory = new InMemoryWebApplicationFactory();
var client = factory.CreateClient(); var client = factory.CreateClient();
var move = new MoveCommandRequest var move = new MoveCommandRequest
@ -241,6 +273,7 @@ public class InteractionApiTests
SchemaVersion = MoveCommandRequest.CurrentSchemaVersion, SchemaVersion = MoveCommandRequest.CurrentSchemaVersion,
Target = new PositionVector { X = 12, Y = 0.9, Z = -6 }, Target = new PositionVector { X = 12, Y = 0.9, Z = -6 },
}; };
// Assert
Assert.Equal(HttpStatusCode.OK, (await client.PostAsJsonAsync("/game/players/dev-local-1/move", move)).StatusCode); Assert.Equal(HttpStatusCode.OK, (await client.PostAsJsonAsync("/game/players/dev-local-1/move", move)).StatusCode);
var response = await client.PostAsJsonAsync( var response = await client.PostAsJsonAsync(
@ -260,9 +293,11 @@ public class InteractionApiTests
[Fact] [Fact]
public async Task PostInteract_ShouldDenyOutOfRange_ForResourceNode_WhenPlayerFar() public async Task PostInteract_ShouldDenyOutOfRange_ForResourceNode_WhenPlayerFar()
{ { // Arrange
await using var factory = new InMemoryWebApplicationFactory(); await using var factory = new InMemoryWebApplicationFactory();
var client = factory.CreateClient(); var client = factory.CreateClient();
// Act
var response = await client.PostAsJsonAsync( var response = await client.PostAsJsonAsync(
"/game/players/dev-local-1/interact", "/game/players/dev-local-1/interact",
new InteractionRequest new InteractionRequest
@ -270,6 +305,7 @@ public class InteractionApiTests
SchemaVersion = InteractionRequest.CurrentSchemaVersion, SchemaVersion = InteractionRequest.CurrentSchemaVersion,
InteractableId = PrototypeInteractableRegistry.PrototypeResourceNodeAlphaId, InteractableId = PrototypeInteractableRegistry.PrototypeResourceNodeAlphaId,
}); });
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadFromJsonAsync<InteractionResponse>(); var body = await response.Content.ReadFromJsonAsync<InteractionResponse>();

View File

@ -110,7 +110,8 @@ public class MoveCommandApiTests
[Fact] [Fact]
public async Task PostMove_ShouldReturn400WithReason_WhenHorizontalStepTooLarge() public async Task PostMove_ShouldReturn400WithReason_WhenHorizontalStepTooLarge()
{ { // Arrange
await using var factory = new InMemoryWebApplicationFactory().WithWebHostBuilder(b => await using var factory = new InMemoryWebApplicationFactory().WithWebHostBuilder(b =>
{ {
b.ConfigureTestServices(services => b.ConfigureTestServices(services =>
@ -128,8 +129,10 @@ public class MoveCommandApiTests
SchemaVersion = MoveCommandRequest.CurrentSchemaVersion, SchemaVersion = MoveCommandRequest.CurrentSchemaVersion,
Target = new PositionVector { X = 0, Y = 0.9, Z = 0 }, Target = new PositionVector { X = 0, Y = 0.9, Z = 0 },
}; };
// Act
var response = await client.PostAsJsonAsync("/game/players/dev-local-1/move", cmd); var response = await client.PostAsJsonAsync("/game/players/dev-local-1/move", cmd);
// Assert
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
var body = await response.Content.ReadFromJsonAsync<MoveCommandRejectedResponse>(); var body = await response.Content.ReadFromJsonAsync<MoveCommandRejectedResponse>();
@ -140,7 +143,8 @@ public class MoveCommandApiTests
[Fact] [Fact]
public async Task PostMove_ShouldReturn400WithReason_WhenVerticalStepTooLarge() public async Task PostMove_ShouldReturn400WithReason_WhenVerticalStepTooLarge()
{ { // Arrange
await using var factory = new InMemoryWebApplicationFactory().WithWebHostBuilder(b => await using var factory = new InMemoryWebApplicationFactory().WithWebHostBuilder(b =>
{ {
b.ConfigureTestServices(services => b.ConfigureTestServices(services =>
@ -154,8 +158,10 @@ public class MoveCommandApiTests
SchemaVersion = MoveCommandRequest.CurrentSchemaVersion, SchemaVersion = MoveCommandRequest.CurrentSchemaVersion,
Target = new PositionVector { X = -5, Y = 2.0, Z = -5 }, Target = new PositionVector { X = -5, Y = 2.0, Z = -5 },
}; };
// Act
var response = await client.PostAsJsonAsync("/game/players/dev-local-1/move", cmd); var response = await client.PostAsJsonAsync("/game/players/dev-local-1/move", cmd);
// Assert
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
var body = await response.Content.ReadFromJsonAsync<MoveCommandRejectedResponse>(); var body = await response.Content.ReadFromJsonAsync<MoveCommandRejectedResponse>();
@ -165,12 +171,15 @@ public class MoveCommandApiTests
[Fact] [Fact]
public async Task PostMove_ShouldReturn400WithoutReasonBody_WhenSchemaInvalid() public async Task PostMove_ShouldReturn400WithoutReasonBody_WhenSchemaInvalid()
{ { // Arrange
await using var factory = new InMemoryWebApplicationFactory(); await using var factory = new InMemoryWebApplicationFactory();
var client = factory.CreateClient(); var client = factory.CreateClient();
// Act
var response = await client.PostAsync( var response = await client.PostAsync(
"/game/players/dev-local-1/move", "/game/players/dev-local-1/move",
new StringContent("{\"schemaVersion\":999,\"target\":{\"x\":0,\"y\":0,\"z\":0}}", Encoding.UTF8, "application/json")); new StringContent("{\"schemaVersion\":999,\"target\":{\"x\":0,\"y\":0,\"z\":0}}", Encoding.UTF8, "application/json"));
// Assert
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
var text = await response.Content.ReadAsStringAsync(); var text = await response.Content.ReadAsStringAsync();

View File

@ -23,79 +23,106 @@ public class MoveCommandValidationTests
[Fact] [Fact]
public void TryValidate_ShouldAllow_WhenHorizontalExactlyAtMax() public void TryValidate_ShouldAllow_WhenHorizontalExactlyAtMax()
{ { // Act
// Arrange
var from = new PositionSnapshot(0, 0, 0, 0); var from = new PositionSnapshot(0, 0, 0, 0);
var to = new PositionVector { X = 5, Y = 0, Z = 0 }; var to = new PositionVector { X = 5, Y = 0, Z = 0 };
// Assert
Assert.True(MoveCommandValidation.TryValidate(from, to, Rules(5, 10), out var code)); Assert.True(MoveCommandValidation.TryValidate(from, to, Rules(5, 10), out var code));
Assert.Equal("", code); Assert.Equal("", code);
} }
[Fact] [Fact]
public void TryValidate_ShouldRejectHorizontal_WhenJustOutsideMax() public void TryValidate_ShouldRejectHorizontal_WhenJustOutsideMax()
{ { // Act
// Arrange
var from = new PositionSnapshot(0, 0, 0, 0); var from = new PositionSnapshot(0, 0, 0, 0);
var to = new PositionVector { X = 5 + 1e-6, Y = 0, Z = 0 }; var to = new PositionVector { X = 5 + 1e-6, Y = 0, Z = 0 };
// Assert
Assert.False(MoveCommandValidation.TryValidate(from, to, Rules(5, 10), out var code)); Assert.False(MoveCommandValidation.TryValidate(from, to, Rules(5, 10), out var code));
Assert.Equal(MoveCommandReasonCodes.HorizontalStepExceeded, code); Assert.Equal(MoveCommandReasonCodes.HorizontalStepExceeded, code);
} }
[Fact] [Fact]
public void TryValidate_ShouldAllow_WhenVerticalExactlyAtMax() public void TryValidate_ShouldAllow_WhenVerticalExactlyAtMax()
{ { // Act
// Arrange
var from = new PositionSnapshot(0, 1, 0, 0); var from = new PositionSnapshot(0, 1, 0, 0);
var to = new PositionVector { X = 0, Y = 2.5, Z = 0 }; var to = new PositionVector { X = 0, Y = 2.5, Z = 0 };
// Assert
Assert.True(MoveCommandValidation.TryValidate(from, to, Rules(10, 1.5), out _)); Assert.True(MoveCommandValidation.TryValidate(from, to, Rules(10, 1.5), out _));
} }
[Fact] [Fact]
public void TryValidate_ShouldRejectVertical_WhenJustOutsideMax() public void TryValidate_ShouldRejectVertical_WhenJustOutsideMax()
{ { // Act
// Arrange
var from = new PositionSnapshot(0, 1, 0, 0); var from = new PositionSnapshot(0, 1, 0, 0);
var to = new PositionVector { X = 0, Y = 2.5 + 1e-9, Z = 0 }; var to = new PositionVector { X = 0, Y = 2.5 + 1e-9, Z = 0 };
// Assert
Assert.False(MoveCommandValidation.TryValidate(from, to, Rules(10, 1.5), out var code)); Assert.False(MoveCommandValidation.TryValidate(from, to, Rules(10, 1.5), out var code));
Assert.Equal(MoveCommandReasonCodes.VerticalStepExceeded, code); Assert.Equal(MoveCommandReasonCodes.VerticalStepExceeded, code);
} }
[Fact] [Fact]
public void TryValidate_ShouldPreferHorizontalReason_WhenBothWouldFail() public void TryValidate_ShouldPreferHorizontalReason_WhenBothWouldFail()
{ { // Act
// Arrange
var from = new PositionSnapshot(0, 0, 0, 0); var from = new PositionSnapshot(0, 0, 0, 0);
var to = new PositionVector { X = 100, Y = 100, Z = 0 }; var to = new PositionVector { X = 100, Y = 100, Z = 0 };
// Assert
Assert.False(MoveCommandValidation.TryValidate(from, to, Rules(1, 1), out var code)); Assert.False(MoveCommandValidation.TryValidate(from, to, Rules(1, 1), out var code));
Assert.Equal(MoveCommandReasonCodes.HorizontalStepExceeded, code); Assert.Equal(MoveCommandReasonCodes.HorizontalStepExceeded, code);
} }
[Fact] [Fact]
public void TryValidate_ShouldAllow_WhenHorizontalDisabled() public void TryValidate_ShouldAllow_WhenHorizontalDisabled()
{ { // Act
// Arrange
var from = new PositionSnapshot(0, 0, 0, 0); var from = new PositionSnapshot(0, 0, 0, 0);
var to = new PositionVector { X = 1000, Y = 0, Z = 1000 }; var to = new PositionVector { X = 1000, Y = 0, Z = 1000 };
// Assert
Assert.True(MoveCommandValidation.TryValidate(from, to, Rules(1, 10, horizontalEnabled: false), out var code)); Assert.True(MoveCommandValidation.TryValidate(from, to, Rules(1, 10, horizontalEnabled: false), out var code));
Assert.Equal("", code); Assert.Equal("", code);
} }
[Fact] [Fact]
public void TryValidate_ShouldIgnoreBounds_WhenDistrictDisabled() public void TryValidate_ShouldIgnoreBounds_WhenDistrictDisabled()
{ { // Act
// Arrange
var from = new PositionSnapshot(0, 0, 0, 0); var from = new PositionSnapshot(0, 0, 0, 0);
var to = new PositionVector { X = -50, Y = 0, Z = 0 }; var to = new PositionVector { X = -50, Y = 0, Z = 0 };
// Assert
Assert.True(MoveCommandValidation.TryValidate(from, to, Rules(100, 100, bounds: false), out _)); Assert.True(MoveCommandValidation.TryValidate(from, to, Rules(100, 100, bounds: false), out _));
} }
[Fact] [Fact]
public void TryValidate_ShouldRejectOutOfBounds_WhenDistrictEnabled() public void TryValidate_ShouldRejectOutOfBounds_WhenDistrictEnabled()
{ { // Act
// Arrange
var from = new PositionSnapshot(5, 5, 5, 0); var from = new PositionSnapshot(5, 5, 5, 0);
var to = new PositionVector { X = 5, Y = 5, Z = 11 }; var to = new PositionVector { X = 5, Y = 5, Z = 11 };
// Assert
Assert.False(MoveCommandValidation.TryValidate(from, to, Rules(20, 20, bounds: true), out var code)); Assert.False(MoveCommandValidation.TryValidate(from, to, Rules(20, 20, bounds: true), out var code));
Assert.Equal(MoveCommandReasonCodes.OutOfBounds, code); Assert.Equal(MoveCommandReasonCodes.OutOfBounds, code);
} }
[Fact] [Fact]
public void TryValidate_ShouldAllowOnBoundsSurface_WhenDistrictEnabled() public void TryValidate_ShouldAllowOnBoundsSurface_WhenDistrictEnabled()
{ { // Act
// Arrange
var from = new PositionSnapshot(5, 5, 5, 0); var from = new PositionSnapshot(5, 5, 5, 0);
var to = new PositionVector { X = 10, Y = 10, Z = 10 }; var to = new PositionVector { X = 10, Y = 10, Z = 10 };
// Assert
Assert.True(MoveCommandValidation.TryValidate(from, to, Rules(20, 20, bounds: true), out _)); Assert.True(MoveCommandValidation.TryValidate(from, to, Rules(20, 20, bounds: true), out _));
} }
} }

View File

@ -12,7 +12,8 @@ public sealed class MoveStreamApiTests
{ {
[Fact] [Fact]
public async Task PostMoveStream_ShouldApplyChainAndIncrementSequence_WhenTwoSmallSteps() public async Task PostMoveStream_ShouldApplyChainAndIncrementSequence_WhenTwoSmallSteps()
{ { // Arrange
await using var factory = new InMemoryWebApplicationFactory().WithWebHostBuilder(b => await using var factory = new InMemoryWebApplicationFactory().WithWebHostBuilder(b =>
{ {
b.ConfigureTestServices(services => b.ConfigureTestServices(services =>
@ -25,7 +26,9 @@ public sealed class MoveStreamApiTests
}); });
}); });
var client = factory.CreateClient(); var client = factory.CreateClient();
// Act
var before = await client.GetFromJsonAsync<PositionStateResponse>("/game/players/dev-local-1/position"); var before = await client.GetFromJsonAsync<PositionStateResponse>("/game/players/dev-local-1/position");
// Assert
Assert.NotNull(before); Assert.NotNull(before);
var seq0 = before!.Sequence; var seq0 = before!.Sequence;
@ -52,7 +55,8 @@ public sealed class MoveStreamApiTests
[Fact] [Fact]
public async Task PostMoveStream_ShouldReturnNotFound_WhenPlayerIsUnknown() public async Task PostMoveStream_ShouldReturnNotFound_WhenPlayerIsUnknown()
{ { // Arrange
await using var factory = new InMemoryWebApplicationFactory(); await using var factory = new InMemoryWebApplicationFactory();
var client = factory.CreateClient(); var client = factory.CreateClient();
var body = new MoveStreamRequest var body = new MoveStreamRequest
@ -60,30 +64,36 @@ public sealed class MoveStreamApiTests
SchemaVersion = MoveStreamRequest.CurrentSchemaVersion, SchemaVersion = MoveStreamRequest.CurrentSchemaVersion,
Targets = [new PositionVector { X = 0, Y = 0.9, Z = 0 }], Targets = [new PositionVector { X = 0, Y = 0.9, Z = 0 }],
}; };
// Act
var response = await client.PostAsJsonAsync("/game/players/unknown-player/move-stream", body); var response = await client.PostAsJsonAsync("/game/players/unknown-player/move-stream", body);
// Assert
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
} }
[Fact] [Fact]
public async Task PostMoveStream_ShouldReturn400_WhenTargetsEmpty() public async Task PostMoveStream_ShouldReturn400_WhenTargetsEmpty()
{ { // Arrange
await using var factory = new InMemoryWebApplicationFactory(); await using var factory = new InMemoryWebApplicationFactory();
var client = factory.CreateClient(); var client = factory.CreateClient();
var content = new StringContent( var content = new StringContent(
"{\"schemaVersion\":1,\"targets\":[]}", "{\"schemaVersion\":1,\"targets\":[]}",
Encoding.UTF8, Encoding.UTF8,
"application/json"); "application/json");
// Act
var response = await client.PostAsync("/game/players/dev-local-1/move-stream", content); var response = await client.PostAsync("/game/players/dev-local-1/move-stream", content);
// Assert
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
} }
[Fact] [Fact]
public async Task PostMoveStream_ShouldReturn400WithReason_WhenSecondStepExceedsHorizontalLimit() public async Task PostMoveStream_ShouldReturn400WithReason_WhenSecondStepExceedsHorizontalLimit()
{ { // Arrange
await using var factory = new InMemoryWebApplicationFactory().WithWebHostBuilder(b => await using var factory = new InMemoryWebApplicationFactory().WithWebHostBuilder(b =>
{ {
b.ConfigureTestServices(services => b.ConfigureTestServices(services =>
@ -105,8 +115,10 @@ public sealed class MoveStreamApiTests
new PositionVector { X = -4.0, Y = 0.9, Z = -5.0 }, new PositionVector { X = -4.0, Y = 0.9, Z = -5.0 },
], ],
}; };
// Act
var response = await client.PostAsJsonAsync("/game/players/dev-local-1/move-stream", body); var response = await client.PostAsJsonAsync("/game/players/dev-local-1/move-stream", body);
// Assert
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
var rej = await response.Content.ReadFromJsonAsync<MoveCommandRejectedResponse>(); var rej = await response.Content.ReadFromJsonAsync<MoveCommandRejectedResponse>();

View File

@ -8,15 +8,21 @@ public class PlayerTargetStateReaderTests
{ {
[Fact] [Fact]
public void ComputeValidity_ShouldReturnInvalidTarget_WhenLockIdNotInRegistry() public void ComputeValidity_ShouldReturnInvalidTarget_WhenLockIdNotInRegistry()
{ { // Act
// Arrange
var snap = new PositionSnapshot(0, 0, 0, 0); var snap = new PositionSnapshot(0, 0, 0, 0);
// Assert
Assert.Equal(TargetValidity.InvalidTarget, PlayerTargetStateReader.ComputeValidity("not_in_registry", in snap)); Assert.Equal(TargetValidity.InvalidTarget, PlayerTargetStateReader.ComputeValidity("not_in_registry", in snap));
} }
[Fact] [Fact]
public void ComputeValidity_ShouldReturnNone_WhenLockNull() public void ComputeValidity_ShouldReturnNone_WhenLockNull()
{ { // Act
// Arrange
var snap = new PositionSnapshot(0, 0, 0, 0); var snap = new PositionSnapshot(0, 0, 0, 0);
// Assert
Assert.Equal(TargetValidity.None, PlayerTargetStateReader.ComputeValidity(null, in snap)); Assert.Equal(TargetValidity.None, PlayerTargetStateReader.ComputeValidity(null, in snap));
} }
@ -24,7 +30,10 @@ public class PlayerTargetStateReaderTests
public void ComputeValidity_AtExactLockRadius_ReturnsOk() public void ComputeValidity_AtExactLockRadius_ReturnsOk()
{ {
// Alpha anchor XZ (-3, -3), lockRadius 6 → horizontal distance 6 from (3, -3) is on boundary (inclusive). // Alpha anchor XZ (-3, -3), lockRadius 6 → horizontal distance 6 from (3, -3) is on boundary (inclusive).
// Act
// Arrange
var snap = new PositionSnapshot(3, 0, -3, 0); var snap = new PositionSnapshot(3, 0, -3, 0);
// Assert
Assert.Equal( Assert.Equal(
TargetValidity.Ok, TargetValidity.Ok,
PlayerTargetStateReader.ComputeValidity(PrototypeTargetRegistry.PrototypeTargetAlphaId, in snap)); PlayerTargetStateReader.ComputeValidity(PrototypeTargetRegistry.PrototypeTargetAlphaId, in snap));
@ -32,9 +41,12 @@ public class PlayerTargetStateReaderTests
[Fact] [Fact]
public void ComputeValidity_JustOutsideLockRadius_ReturnsOutOfRange() public void ComputeValidity_JustOutsideLockRadius_ReturnsOutOfRange()
{ { // Act
// Arrange
const double eps = 1e-6; const double eps = 1e-6;
var snap = new PositionSnapshot(3 + eps, 0, -3, 0); var snap = new PositionSnapshot(3 + eps, 0, -3, 0);
// Assert
Assert.Equal( Assert.Equal(
TargetValidity.OutOfRange, TargetValidity.OutOfRange,
PlayerTargetStateReader.ComputeValidity(PrototypeTargetRegistry.PrototypeTargetAlphaId, in snap)); PlayerTargetStateReader.ComputeValidity(PrototypeTargetRegistry.PrototypeTargetAlphaId, in snap));
@ -45,7 +57,10 @@ public class PlayerTargetStateReaderTests
{ {
// NEO-24 post-merge: alpha (-3,-3) r=6 and beta (3,3) r=6 overlap at origin so Tab can // NEO-24 post-merge: alpha (-3,-3) r=6 and beta (3,3) r=6 overlap at origin so Tab can
// flip without locomotion. Origin is ~4.24 m from each anchor (< 6 m). // flip without locomotion. Origin is ~4.24 m from each anchor (< 6 m).
// Act
// Arrange
var snap = new PositionSnapshot(0, 0, 0, 0); var snap = new PositionSnapshot(0, 0, 0, 0);
// Assert
Assert.Equal( Assert.Equal(
TargetValidity.Ok, TargetValidity.Ok,
PlayerTargetStateReader.ComputeValidity(PrototypeTargetRegistry.PrototypeTargetAlphaId, in snap)); PlayerTargetStateReader.ComputeValidity(PrototypeTargetRegistry.PrototypeTargetAlphaId, in snap));

View File

@ -12,11 +12,14 @@ public class TargetingApiTests
{ {
[Fact] [Fact]
public async Task GetTarget_ShouldReturnNone_WhenNoLock() public async Task GetTarget_ShouldReturnNone_WhenNoLock()
{ { // Arrange
await using var factory = new InMemoryWebApplicationFactory(); await using var factory = new InMemoryWebApplicationFactory();
var client = factory.CreateClient(); var client = factory.CreateClient();
// Act
var response = await client.GetAsync("/game/players/dev-local-1/target"); var response = await client.GetAsync("/game/players/dev-local-1/target");
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadFromJsonAsync<PlayerTargetStateResponse>(); var body = await response.Content.ReadFromJsonAsync<PlayerTargetStateResponse>();
Assert.NotNull(body); Assert.NotNull(body);
@ -29,9 +32,11 @@ public class TargetingApiTests
[Fact] [Fact]
public async Task PostSelect_ShouldApplyLock_WhenInRange() public async Task PostSelect_ShouldApplyLock_WhenInRange()
{ { // Arrange
await using var factory = new InMemoryWebApplicationFactory(); await using var factory = new InMemoryWebApplicationFactory();
var client = factory.CreateClient(); var client = factory.CreateClient();
// Act
var response = await client.PostAsJsonAsync( var response = await client.PostAsJsonAsync(
"/game/players/dev-local-1/target/select", "/game/players/dev-local-1/target/select",
@ -40,6 +45,7 @@ public class TargetingApiTests
SchemaVersion = TargetSelectRequest.CurrentSchemaVersion, SchemaVersion = TargetSelectRequest.CurrentSchemaVersion,
TargetId = PrototypeTargetRegistry.PrototypeTargetAlphaId, TargetId = PrototypeTargetRegistry.PrototypeTargetAlphaId,
}); });
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadFromJsonAsync<TargetSelectResponse>(); var body = await response.Content.ReadFromJsonAsync<TargetSelectResponse>();
@ -53,9 +59,11 @@ public class TargetingApiTests
[Fact] [Fact]
public async Task PostSelect_ShouldDenyUnknownTarget_WithReasonCode() public async Task PostSelect_ShouldDenyUnknownTarget_WithReasonCode()
{ { // Arrange
await using var factory = new InMemoryWebApplicationFactory(); await using var factory = new InMemoryWebApplicationFactory();
var client = factory.CreateClient(); var client = factory.CreateClient();
// Act
var response = await client.PostAsJsonAsync( var response = await client.PostAsJsonAsync(
"/game/players/dev-local-1/target/select", "/game/players/dev-local-1/target/select",
@ -64,6 +72,7 @@ public class TargetingApiTests
SchemaVersion = TargetSelectRequest.CurrentSchemaVersion, SchemaVersion = TargetSelectRequest.CurrentSchemaVersion,
TargetId = "not_a_real_target", TargetId = "not_a_real_target",
}); });
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadFromJsonAsync<TargetSelectResponse>(); var body = await response.Content.ReadFromJsonAsync<TargetSelectResponse>();
@ -76,9 +85,11 @@ public class TargetingApiTests
[Fact] [Fact]
public async Task PostSelect_ShouldDenyOutOfRange_WhenBetaFarFromSpawn() public async Task PostSelect_ShouldDenyOutOfRange_WhenBetaFarFromSpawn()
{ { // Arrange
await using var factory = new InMemoryWebApplicationFactory(); await using var factory = new InMemoryWebApplicationFactory();
var client = factory.CreateClient(); var client = factory.CreateClient();
// Act
var response = await client.PostAsJsonAsync( var response = await client.PostAsJsonAsync(
"/game/players/dev-local-1/target/select", "/game/players/dev-local-1/target/select",
@ -87,6 +98,7 @@ public class TargetingApiTests
SchemaVersion = TargetSelectRequest.CurrentSchemaVersion, SchemaVersion = TargetSelectRequest.CurrentSchemaVersion,
TargetId = PrototypeTargetRegistry.PrototypeTargetBetaId, TargetId = PrototypeTargetRegistry.PrototypeTargetBetaId,
}); });
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadFromJsonAsync<TargetSelectResponse>(); var body = await response.Content.ReadFromJsonAsync<TargetSelectResponse>();
@ -102,8 +114,10 @@ public class TargetingApiTests
// NEO-24 follow-up #5: without the hint, spawn's stored position (-5, -5) denies beta (r=6, // NEO-24 follow-up #5: without the hint, spawn's stored position (-5, -5) denies beta (r=6,
// anchor (3, 3)) — ~11.3 m out. With an advisory `positionHint` pinning the capsule inside // anchor (3, 3)) — ~11.3 m out. With an advisory `positionHint` pinning the capsule inside
// beta's ring, the server must accept. This is exactly the race-free path the client uses. // beta's ring, the server must accept. This is exactly the race-free path the client uses.
// Arrange
await using var factory = new InMemoryWebApplicationFactory(); await using var factory = new InMemoryWebApplicationFactory();
var client = factory.CreateClient(); var client = factory.CreateClient();
// Act
var response = await client.PostAsJsonAsync( var response = await client.PostAsJsonAsync(
"/game/players/dev-local-1/target/select", "/game/players/dev-local-1/target/select",
@ -113,6 +127,7 @@ public class TargetingApiTests
TargetId = PrototypeTargetRegistry.PrototypeTargetBetaId, TargetId = PrototypeTargetRegistry.PrototypeTargetBetaId,
PositionHint = new PositionVector { X = 3.0, Y = 0.5, Z = 5.0 }, PositionHint = new PositionVector { X = 3.0, Y = 0.5, Z = 5.0 },
}); });
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadFromJsonAsync<TargetSelectResponse>(); var body = await response.Content.ReadFromJsonAsync<TargetSelectResponse>();
@ -127,8 +142,10 @@ public class TargetingApiTests
{ {
// Hint that places the capsule outside beta's ring must deny — hint is advisory only; // Hint that places the capsule outside beta's ring must deny — hint is advisory only;
// it does not bypass the radius check. // it does not bypass the radius check.
// Arrange
await using var factory = new InMemoryWebApplicationFactory(); await using var factory = new InMemoryWebApplicationFactory();
var client = factory.CreateClient(); var client = factory.CreateClient();
// Act
var response = await client.PostAsJsonAsync( var response = await client.PostAsJsonAsync(
"/game/players/dev-local-1/target/select", "/game/players/dev-local-1/target/select",
@ -138,6 +155,7 @@ public class TargetingApiTests
TargetId = PrototypeTargetRegistry.PrototypeTargetBetaId, TargetId = PrototypeTargetRegistry.PrototypeTargetBetaId,
PositionHint = new PositionVector { X = 50.0, Y = 0.0, Z = 50.0 }, PositionHint = new PositionVector { X = 50.0, Y = 0.0, Z = 50.0 },
}); });
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadFromJsonAsync<TargetSelectResponse>(); var body = await response.Content.ReadFromJsonAsync<TargetSelectResponse>();
@ -151,10 +169,13 @@ public class TargetingApiTests
{ {
// Hint must be purely advisory for the range check. Confirm the stored `PositionState` // Hint must be purely advisory for the range check. Confirm the stored `PositionState`
// is untouched by a select-with-hint by reading it back through `GET /position`. // is untouched by a select-with-hint by reading it back through `GET /position`.
// Arrange
await using var factory = new InMemoryWebApplicationFactory(); await using var factory = new InMemoryWebApplicationFactory();
var client = factory.CreateClient(); var client = factory.CreateClient();
// Act
var before = await client.GetFromJsonAsync<PositionStateResponse>("/game/players/dev-local-1/position"); var before = await client.GetFromJsonAsync<PositionStateResponse>("/game/players/dev-local-1/position");
// Assert
Assert.NotNull(before); Assert.NotNull(before);
Assert.Equal( Assert.Equal(
@ -178,9 +199,12 @@ public class TargetingApiTests
[Fact] [Fact]
public async Task PostSelect_ShouldClearLock_WhenTargetIdOmitted() public async Task PostSelect_ShouldClearLock_WhenTargetIdOmitted()
{ { // Act
// Arrange
await using var factory = new InMemoryWebApplicationFactory(); await using var factory = new InMemoryWebApplicationFactory();
var client = factory.CreateClient(); var client = factory.CreateClient();
// Assert
Assert.Equal( Assert.Equal(
HttpStatusCode.OK, HttpStatusCode.OK,
@ -209,9 +233,12 @@ public class TargetingApiTests
[Fact] [Fact]
public async Task GetTarget_ShouldShowOutOfRange_AfterMoveWithoutSelect() public async Task GetTarget_ShouldShowOutOfRange_AfterMoveWithoutSelect()
{ { // Act
// Arrange
await using var factory = new InMemoryWebApplicationFactory(); await using var factory = new InMemoryWebApplicationFactory();
var client = factory.CreateClient(); var client = factory.CreateClient();
// Assert
Assert.Equal( Assert.Equal(
HttpStatusCode.OK, HttpStatusCode.OK,
@ -240,19 +267,24 @@ public class TargetingApiTests
[Fact] [Fact]
public async Task GetTarget_ShouldReturnNotFound_WhenPlayerUnknown() public async Task GetTarget_ShouldReturnNotFound_WhenPlayerUnknown()
{ { // Arrange
await using var factory = new InMemoryWebApplicationFactory(); await using var factory = new InMemoryWebApplicationFactory();
var client = factory.CreateClient(); var client = factory.CreateClient();
// Act
var response = await client.GetAsync("/game/players/missing-player/target"); var response = await client.GetAsync("/game/players/missing-player/target");
// Assert
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
} }
[Fact] [Fact]
public async Task PostSelect_ShouldReturnNotFound_WhenPlayerUnknown() public async Task PostSelect_ShouldReturnNotFound_WhenPlayerUnknown()
{ { // Arrange
await using var factory = new InMemoryWebApplicationFactory(); await using var factory = new InMemoryWebApplicationFactory();
var client = factory.CreateClient(); var client = factory.CreateClient();
// Act
var response = await client.PostAsJsonAsync( var response = await client.PostAsJsonAsync(
"/game/players/missing-player/target/select", "/game/players/missing-player/target/select",
@ -261,15 +293,18 @@ public class TargetingApiTests
SchemaVersion = TargetSelectRequest.CurrentSchemaVersion, SchemaVersion = TargetSelectRequest.CurrentSchemaVersion,
TargetId = PrototypeTargetRegistry.PrototypeTargetAlphaId, TargetId = PrototypeTargetRegistry.PrototypeTargetAlphaId,
}); });
// Assert
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode); Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
} }
[Fact] [Fact]
public async Task PostSelect_ShouldReturnBadRequest_WhenSchemaVersionWrong() public async Task PostSelect_ShouldReturnBadRequest_WhenSchemaVersionWrong()
{ { // Arrange
await using var factory = new InMemoryWebApplicationFactory(); await using var factory = new InMemoryWebApplicationFactory();
var client = factory.CreateClient(); var client = factory.CreateClient();
// Act
var response = await client.PostAsJsonAsync( var response = await client.PostAsJsonAsync(
"/game/players/dev-local-1/target/select", "/game/players/dev-local-1/target/select",
@ -278,15 +313,18 @@ public class TargetingApiTests
SchemaVersion = 999, SchemaVersion = 999,
TargetId = PrototypeTargetRegistry.PrototypeTargetAlphaId, TargetId = PrototypeTargetRegistry.PrototypeTargetAlphaId,
}); });
// Assert
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
} }
[Fact] [Fact]
public async Task PostSelect_ShouldReturnBadRequest_WhenTargetIdWhitespaceOnly() public async Task PostSelect_ShouldReturnBadRequest_WhenTargetIdWhitespaceOnly()
{ { // Arrange
await using var factory = new InMemoryWebApplicationFactory(); await using var factory = new InMemoryWebApplicationFactory();
var client = factory.CreateClient(); var client = factory.CreateClient();
// Act
var response = await client.PostAsJsonAsync( var response = await client.PostAsJsonAsync(
"/game/players/dev-local-1/target/select", "/game/players/dev-local-1/target/select",
@ -295,15 +333,18 @@ public class TargetingApiTests
SchemaVersion = TargetSelectRequest.CurrentSchemaVersion, SchemaVersion = TargetSelectRequest.CurrentSchemaVersion,
TargetId = " \t ", TargetId = " \t ",
}); });
// Assert
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode); Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
} }
[Fact] [Fact]
public async Task PostSelect_ShouldBeCaseInsensitive_ForTargetId() public async Task PostSelect_ShouldBeCaseInsensitive_ForTargetId()
{ { // Arrange
await using var factory = new InMemoryWebApplicationFactory(); await using var factory = new InMemoryWebApplicationFactory();
var client = factory.CreateClient(); var client = factory.CreateClient();
// Act
var response = await client.PostAsJsonAsync( var response = await client.PostAsJsonAsync(
"/game/players/dev-local-1/target/select", "/game/players/dev-local-1/target/select",
@ -312,6 +353,7 @@ public class TargetingApiTests
SchemaVersion = TargetSelectRequest.CurrentSchemaVersion, SchemaVersion = TargetSelectRequest.CurrentSchemaVersion,
TargetId = " PROTOTYPE_TARGET_ALPHA ", TargetId = " PROTOTYPE_TARGET_ALPHA ",
}); });
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadFromJsonAsync<TargetSelectResponse>(); var body = await response.Content.ReadFromJsonAsync<TargetSelectResponse>();
@ -322,7 +364,8 @@ public class TargetingApiTests
[Fact] [Fact]
public async Task PostSelect_ShouldStayIdempotent_WhenSameTargetReselect() public async Task PostSelect_ShouldStayIdempotent_WhenSameTargetReselect()
{ { // Arrange
await using var factory = new InMemoryWebApplicationFactory(); await using var factory = new InMemoryWebApplicationFactory();
var client = factory.CreateClient(); var client = factory.CreateClient();
@ -331,8 +374,10 @@ public class TargetingApiTests
SchemaVersion = TargetSelectRequest.CurrentSchemaVersion, SchemaVersion = TargetSelectRequest.CurrentSchemaVersion,
TargetId = PrototypeTargetRegistry.PrototypeTargetAlphaId, TargetId = PrototypeTargetRegistry.PrototypeTargetAlphaId,
}; };
// Act
var first = await client.PostAsJsonAsync("/game/players/dev-local-1/target/select", req); var first = await client.PostAsJsonAsync("/game/players/dev-local-1/target/select", req);
var second = await client.PostAsJsonAsync("/game/players/dev-local-1/target/select", req); var second = await client.PostAsJsonAsync("/game/players/dev-local-1/target/select", req);
// Assert
Assert.Equal(HttpStatusCode.OK, first.StatusCode); Assert.Equal(HttpStatusCode.OK, first.StatusCode);
Assert.Equal(HttpStatusCode.OK, second.StatusCode); Assert.Equal(HttpStatusCode.OK, second.StatusCode);
var b1 = await first.Content.ReadFromJsonAsync<TargetSelectResponse>(); var b1 = await first.Content.ReadFromJsonAsync<TargetSelectResponse>();
@ -342,9 +387,11 @@ public class TargetingApiTests
[Fact] [Fact]
public async Task PostSelect_ShouldDenyOutOfRange_WhenReselectingSameLockWhileOutOfRange() public async Task PostSelect_ShouldDenyOutOfRange_WhenReselectingSameLockWhileOutOfRange()
{ { // Arrange
await using var factory = new InMemoryWebApplicationFactory(); await using var factory = new InMemoryWebApplicationFactory();
var client = factory.CreateClient(); var client = factory.CreateClient();
// Act
await client.PostAsJsonAsync( await client.PostAsJsonAsync(
"/game/players/dev-local-1/target/select", "/game/players/dev-local-1/target/select",
@ -368,6 +415,7 @@ public class TargetingApiTests
SchemaVersion = TargetSelectRequest.CurrentSchemaVersion, SchemaVersion = TargetSelectRequest.CurrentSchemaVersion,
TargetId = PrototypeTargetRegistry.PrototypeTargetAlphaId, TargetId = PrototypeTargetRegistry.PrototypeTargetAlphaId,
}); });
// Assert
Assert.Equal(HttpStatusCode.OK, response.StatusCode); Assert.Equal(HttpStatusCode.OK, response.StatusCode);
var body = await response.Content.ReadFromJsonAsync<TargetSelectResponse>(); var body = await response.Content.ReadFromJsonAsync<TargetSelectResponse>();