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
parent
3dccab8edb
commit
71af7a5942
|
|
@ -52,7 +52,13 @@ class HoldFirstThenAutoTransport:
|
|||
) -> Error:
|
||||
request_count += 1
|
||||
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:
|
||||
_first = false
|
||||
call_deferred("_emit", body_bytes)
|
||||
|
|
@ -109,7 +115,10 @@ func test_bind_slot_posts_expected_payload() -> void:
|
|||
transport.enqueue(
|
||||
HTTPRequest.RESULT_SUCCESS,
|
||||
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)
|
||||
c.call("request_bind_slot", 3, "prototype_burst")
|
||||
|
|
@ -130,7 +139,10 @@ func test_denied_update_emits_reason_signal() -> void:
|
|||
transport.enqueue(
|
||||
HTTPRequest.RESULT_SUCCESS,
|
||||
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)
|
||||
monitor_signals(c)
|
||||
|
|
|
|||
|
|
@ -9,35 +9,50 @@ public class HorizontalReachTests
|
|||
public void HorizontalDistance_IgnoresY()
|
||||
{
|
||||
// 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);
|
||||
// Assert
|
||||
Assert.Equal(0, d);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsWithinHorizontalRadius_AtExactBoundary_ReturnsTrue()
|
||||
{
|
||||
{ // Assert
|
||||
// Act
|
||||
// Arrange
|
||||
|
||||
Assert.True(HorizontalReach.IsWithinHorizontalRadius(0, 0, 3, 0, 3));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsWithinHorizontalRadius_JustInside_ReturnsTrue()
|
||||
{
|
||||
{ // Act
|
||||
// Arrange
|
||||
|
||||
const double radius = 3.0;
|
||||
const double eps = 1e-10;
|
||||
// Assert
|
||||
Assert.True(HorizontalReach.IsWithinHorizontalRadius(0, 0, radius - eps, 0, radius));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsWithinHorizontalRadius_JustOutside_ReturnsFalse()
|
||||
{
|
||||
{ // Act
|
||||
// Arrange
|
||||
|
||||
const double radius = 3.0;
|
||||
const double eps = 1e-7;
|
||||
// Assert
|
||||
Assert.False(HorizontalReach.IsWithinHorizontalRadius(0, 0, radius + eps, 0, radius));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IsWithinHorizontalRadius_NegativeRadius_ThrowsArgumentOutOfRange()
|
||||
{
|
||||
{ // Assert
|
||||
// Act
|
||||
// Arrange
|
||||
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() =>
|
||||
HorizontalReach.IsWithinHorizontalRadius(0, 0, 0, 0, -1.0));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,11 +10,14 @@ public class InteractablesWorldApiTests
|
|||
{
|
||||
[Fact]
|
||||
public async Task GetInteractables_ShouldReturnOrderedDescriptors_WithSchemaV1()
|
||||
{
|
||||
{ // Arrange
|
||||
|
||||
await using var factory = new InMemoryWebApplicationFactory();
|
||||
var client = factory.CreateClient();
|
||||
// Act
|
||||
|
||||
var response = await client.GetAsync("/game/world/interactables");
|
||||
// Assert
|
||||
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
var body = await response.Content.ReadFromJsonAsync<InteractablesListResponse>();
|
||||
|
|
|
|||
|
|
@ -12,7 +12,9 @@ public class InteractionApiTests
|
|||
{
|
||||
[Fact]
|
||||
public async Task PostInteract_ShouldAllow_WhenExactlyAtHorizontalRadius()
|
||||
{
|
||||
{ // Act
|
||||
// Arrange
|
||||
|
||||
await using var factory = new InMemoryWebApplicationFactory();
|
||||
var client = factory.CreateClient();
|
||||
var move = new MoveCommandRequest
|
||||
|
|
@ -20,6 +22,7 @@ public class InteractionApiTests
|
|||
SchemaVersion = MoveCommandRequest.CurrentSchemaVersion,
|
||||
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);
|
||||
|
||||
var response = await client.PostAsJsonAsync(
|
||||
|
|
@ -38,7 +41,8 @@ public class InteractionApiTests
|
|||
|
||||
[Fact]
|
||||
public async Task PostInteract_ShouldAllow_WhenPlayerInHorizontalRange()
|
||||
{
|
||||
{ // Arrange
|
||||
|
||||
await using var factory = new InMemoryWebApplicationFactory();
|
||||
var client = factory.CreateClient();
|
||||
var move = new MoveCommandRequest
|
||||
|
|
@ -46,7 +50,9 @@ public class InteractionApiTests
|
|||
SchemaVersion = MoveCommandRequest.CurrentSchemaVersion,
|
||||
Target = new PositionVector { X = 0, Y = 0.9, Z = 0 },
|
||||
};
|
||||
// Act
|
||||
var moveResp = await client.PostAsJsonAsync("/game/players/dev-local-1/move", move);
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, moveResp.StatusCode);
|
||||
|
||||
var req = new InteractionRequest
|
||||
|
|
@ -69,7 +75,8 @@ public class InteractionApiTests
|
|||
|
||||
[Fact]
|
||||
public async Task PostInteract_ShouldDenyOutOfRange_WhenPlayerSeededFarFromTerminal()
|
||||
{
|
||||
{ // Arrange
|
||||
|
||||
await using var factory = new InMemoryWebApplicationFactory();
|
||||
var client = factory.CreateClient();
|
||||
var req = new InteractionRequest
|
||||
|
|
@ -77,8 +84,10 @@ public class InteractionApiTests
|
|||
SchemaVersion = InteractionRequest.CurrentSchemaVersion,
|
||||
InteractableId = PrototypeInteractableRegistry.PrototypeTerminalId,
|
||||
};
|
||||
// Act
|
||||
|
||||
var response = await client.PostAsJsonAsync("/game/players/dev-local-1/interact", req);
|
||||
// Assert
|
||||
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
var body = await response.Content.ReadFromJsonAsync<InteractionResponse>();
|
||||
|
|
@ -89,7 +98,8 @@ public class InteractionApiTests
|
|||
|
||||
[Fact]
|
||||
public async Task PostInteract_ShouldDenyUnknownInteractable_WhenIdNotInRegistry()
|
||||
{
|
||||
{ // Arrange
|
||||
|
||||
await using var factory = new InMemoryWebApplicationFactory();
|
||||
var client = factory.CreateClient();
|
||||
var req = new InteractionRequest
|
||||
|
|
@ -97,8 +107,10 @@ public class InteractionApiTests
|
|||
SchemaVersion = InteractionRequest.CurrentSchemaVersion,
|
||||
InteractableId = "no_such_thing",
|
||||
};
|
||||
// Act
|
||||
|
||||
var response = await client.PostAsJsonAsync("/game/players/dev-local-1/interact", req);
|
||||
// Assert
|
||||
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
var body = await response.Content.ReadFromJsonAsync<InteractionResponse>();
|
||||
|
|
@ -109,7 +121,9 @@ public class InteractionApiTests
|
|||
|
||||
[Fact]
|
||||
public async Task PostInteract_ShouldBeCaseInsensitive_ForInteractableId()
|
||||
{
|
||||
{ // Act
|
||||
// Arrange
|
||||
|
||||
await using var factory = new InMemoryWebApplicationFactory();
|
||||
var client = factory.CreateClient();
|
||||
var move = new MoveCommandRequest
|
||||
|
|
@ -117,6 +131,7 @@ public class InteractionApiTests
|
|||
SchemaVersion = MoveCommandRequest.CurrentSchemaVersion,
|
||||
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);
|
||||
|
||||
var req = new InteractionRequest
|
||||
|
|
@ -135,7 +150,8 @@ public class InteractionApiTests
|
|||
|
||||
[Fact]
|
||||
public async Task PostInteract_ShouldReturnBadRequest_WhenSchemaVersionWrong()
|
||||
{
|
||||
{ // Arrange
|
||||
|
||||
await using var factory = new InMemoryWebApplicationFactory();
|
||||
var client = factory.CreateClient();
|
||||
var req = new InteractionRequest
|
||||
|
|
@ -143,30 +159,36 @@ public class InteractionApiTests
|
|||
SchemaVersion = 999,
|
||||
InteractableId = PrototypeInteractableRegistry.PrototypeTerminalId,
|
||||
};
|
||||
// Act
|
||||
|
||||
var response = await client.PostAsJsonAsync("/game/players/dev-local-1/interact", req);
|
||||
// Assert
|
||||
|
||||
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PostInteract_ShouldReturnBadRequest_WhenInteractableIdMissing()
|
||||
{
|
||||
{ // Arrange
|
||||
|
||||
await using var factory = new InMemoryWebApplicationFactory();
|
||||
var client = factory.CreateClient();
|
||||
var content = new StringContent(
|
||||
$"{{\"schemaVersion\":{InteractionRequest.CurrentSchemaVersion}}}",
|
||||
Encoding.UTF8,
|
||||
"application/json");
|
||||
// Act
|
||||
|
||||
var response = await client.PostAsync("/game/players/dev-local-1/interact", content);
|
||||
// Assert
|
||||
|
||||
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PostInteract_ShouldReturnBadRequest_WhenInteractableIdWhitespaceOnly()
|
||||
{
|
||||
{ // Arrange
|
||||
|
||||
await using var factory = new InMemoryWebApplicationFactory();
|
||||
var client = factory.CreateClient();
|
||||
var req = new InteractionRequest
|
||||
|
|
@ -174,15 +196,18 @@ public class InteractionApiTests
|
|||
SchemaVersion = InteractionRequest.CurrentSchemaVersion,
|
||||
InteractableId = " \t ",
|
||||
};
|
||||
// Act
|
||||
|
||||
var response = await client.PostAsJsonAsync("/game/players/dev-local-1/interact", req);
|
||||
// Assert
|
||||
|
||||
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PostInteract_ShouldReturnNotFound_WhenPlayerUnknown()
|
||||
{
|
||||
{ // Arrange
|
||||
|
||||
await using var factory = new InMemoryWebApplicationFactory();
|
||||
var client = factory.CreateClient();
|
||||
var req = new InteractionRequest
|
||||
|
|
@ -190,17 +215,21 @@ public class InteractionApiTests
|
|||
SchemaVersion = InteractionRequest.CurrentSchemaVersion,
|
||||
InteractableId = PrototypeInteractableRegistry.PrototypeTerminalId,
|
||||
};
|
||||
// Act
|
||||
|
||||
var response = await client.PostAsJsonAsync("/game/players/nobody-here/interact", req);
|
||||
// Assert
|
||||
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PostInteract_ShouldReflectNewPosition_AfterMove()
|
||||
{
|
||||
{ // Arrange
|
||||
|
||||
await using var factory = new InMemoryWebApplicationFactory();
|
||||
var client = factory.CreateClient();
|
||||
// Act
|
||||
|
||||
var far = await client.PostAsJsonAsync(
|
||||
"/game/players/dev-local-1/interact",
|
||||
|
|
@ -210,6 +239,7 @@ public class InteractionApiTests
|
|||
InteractableId = PrototypeInteractableRegistry.PrototypeTerminalId,
|
||||
});
|
||||
var farBody = await far.Content.ReadFromJsonAsync<InteractionResponse>();
|
||||
// Assert
|
||||
Assert.False(farBody!.Allowed);
|
||||
Assert.Equal(InteractionApi.ReasonOutOfRange, farBody.ReasonCode);
|
||||
|
||||
|
|
@ -233,7 +263,9 @@ public class InteractionApiTests
|
|||
|
||||
[Fact]
|
||||
public async Task PostInteract_ShouldAllowResourceNode_WhenPlayerInRangeOfResourceAnchor()
|
||||
{
|
||||
{ // Act
|
||||
// Arrange
|
||||
|
||||
await using var factory = new InMemoryWebApplicationFactory();
|
||||
var client = factory.CreateClient();
|
||||
var move = new MoveCommandRequest
|
||||
|
|
@ -241,6 +273,7 @@ public class InteractionApiTests
|
|||
SchemaVersion = MoveCommandRequest.CurrentSchemaVersion,
|
||||
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);
|
||||
|
||||
var response = await client.PostAsJsonAsync(
|
||||
|
|
@ -260,9 +293,11 @@ public class InteractionApiTests
|
|||
|
||||
[Fact]
|
||||
public async Task PostInteract_ShouldDenyOutOfRange_ForResourceNode_WhenPlayerFar()
|
||||
{
|
||||
{ // Arrange
|
||||
|
||||
await using var factory = new InMemoryWebApplicationFactory();
|
||||
var client = factory.CreateClient();
|
||||
// Act
|
||||
var response = await client.PostAsJsonAsync(
|
||||
"/game/players/dev-local-1/interact",
|
||||
new InteractionRequest
|
||||
|
|
@ -270,6 +305,7 @@ public class InteractionApiTests
|
|||
SchemaVersion = InteractionRequest.CurrentSchemaVersion,
|
||||
InteractableId = PrototypeInteractableRegistry.PrototypeResourceNodeAlphaId,
|
||||
});
|
||||
// Assert
|
||||
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
var body = await response.Content.ReadFromJsonAsync<InteractionResponse>();
|
||||
|
|
|
|||
|
|
@ -110,7 +110,8 @@ public class MoveCommandApiTests
|
|||
|
||||
[Fact]
|
||||
public async Task PostMove_ShouldReturn400WithReason_WhenHorizontalStepTooLarge()
|
||||
{
|
||||
{ // Arrange
|
||||
|
||||
await using var factory = new InMemoryWebApplicationFactory().WithWebHostBuilder(b =>
|
||||
{
|
||||
b.ConfigureTestServices(services =>
|
||||
|
|
@ -128,8 +129,10 @@ public class MoveCommandApiTests
|
|||
SchemaVersion = MoveCommandRequest.CurrentSchemaVersion,
|
||||
Target = new PositionVector { X = 0, Y = 0.9, Z = 0 },
|
||||
};
|
||||
// Act
|
||||
|
||||
var response = await client.PostAsJsonAsync("/game/players/dev-local-1/move", cmd);
|
||||
// Assert
|
||||
|
||||
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||
var body = await response.Content.ReadFromJsonAsync<MoveCommandRejectedResponse>();
|
||||
|
|
@ -140,7 +143,8 @@ public class MoveCommandApiTests
|
|||
|
||||
[Fact]
|
||||
public async Task PostMove_ShouldReturn400WithReason_WhenVerticalStepTooLarge()
|
||||
{
|
||||
{ // Arrange
|
||||
|
||||
await using var factory = new InMemoryWebApplicationFactory().WithWebHostBuilder(b =>
|
||||
{
|
||||
b.ConfigureTestServices(services =>
|
||||
|
|
@ -154,8 +158,10 @@ public class MoveCommandApiTests
|
|||
SchemaVersion = MoveCommandRequest.CurrentSchemaVersion,
|
||||
Target = new PositionVector { X = -5, Y = 2.0, Z = -5 },
|
||||
};
|
||||
// Act
|
||||
|
||||
var response = await client.PostAsJsonAsync("/game/players/dev-local-1/move", cmd);
|
||||
// Assert
|
||||
|
||||
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||
var body = await response.Content.ReadFromJsonAsync<MoveCommandRejectedResponse>();
|
||||
|
|
@ -165,12 +171,15 @@ public class MoveCommandApiTests
|
|||
|
||||
[Fact]
|
||||
public async Task PostMove_ShouldReturn400WithoutReasonBody_WhenSchemaInvalid()
|
||||
{
|
||||
{ // Arrange
|
||||
|
||||
await using var factory = new InMemoryWebApplicationFactory();
|
||||
var client = factory.CreateClient();
|
||||
// Act
|
||||
var response = await client.PostAsync(
|
||||
"/game/players/dev-local-1/move",
|
||||
new StringContent("{\"schemaVersion\":999,\"target\":{\"x\":0,\"y\":0,\"z\":0}}", Encoding.UTF8, "application/json"));
|
||||
// Assert
|
||||
|
||||
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||
var text = await response.Content.ReadAsStringAsync();
|
||||
|
|
|
|||
|
|
@ -23,79 +23,106 @@ public class MoveCommandValidationTests
|
|||
|
||||
[Fact]
|
||||
public void TryValidate_ShouldAllow_WhenHorizontalExactlyAtMax()
|
||||
{
|
||||
{ // Act
|
||||
// Arrange
|
||||
|
||||
var from = new PositionSnapshot(0, 0, 0, 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.Equal("", code);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryValidate_ShouldRejectHorizontal_WhenJustOutsideMax()
|
||||
{
|
||||
{ // Act
|
||||
// Arrange
|
||||
|
||||
var from = new PositionSnapshot(0, 0, 0, 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.Equal(MoveCommandReasonCodes.HorizontalStepExceeded, code);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryValidate_ShouldAllow_WhenVerticalExactlyAtMax()
|
||||
{
|
||||
{ // Act
|
||||
// Arrange
|
||||
|
||||
var from = new PositionSnapshot(0, 1, 0, 0);
|
||||
var to = new PositionVector { X = 0, Y = 2.5, Z = 0 };
|
||||
// Assert
|
||||
Assert.True(MoveCommandValidation.TryValidate(from, to, Rules(10, 1.5), out _));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryValidate_ShouldRejectVertical_WhenJustOutsideMax()
|
||||
{
|
||||
{ // Act
|
||||
// Arrange
|
||||
|
||||
var from = new PositionSnapshot(0, 1, 0, 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.Equal(MoveCommandReasonCodes.VerticalStepExceeded, code);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryValidate_ShouldPreferHorizontalReason_WhenBothWouldFail()
|
||||
{
|
||||
{ // Act
|
||||
// Arrange
|
||||
|
||||
var from = new PositionSnapshot(0, 0, 0, 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.Equal(MoveCommandReasonCodes.HorizontalStepExceeded, code);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryValidate_ShouldAllow_WhenHorizontalDisabled()
|
||||
{
|
||||
{ // Act
|
||||
// Arrange
|
||||
|
||||
var from = new PositionSnapshot(0, 0, 0, 0);
|
||||
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.Equal("", code);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryValidate_ShouldIgnoreBounds_WhenDistrictDisabled()
|
||||
{
|
||||
{ // Act
|
||||
// Arrange
|
||||
|
||||
var from = new PositionSnapshot(0, 0, 0, 0);
|
||||
var to = new PositionVector { X = -50, Y = 0, Z = 0 };
|
||||
// Assert
|
||||
Assert.True(MoveCommandValidation.TryValidate(from, to, Rules(100, 100, bounds: false), out _));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryValidate_ShouldRejectOutOfBounds_WhenDistrictEnabled()
|
||||
{
|
||||
{ // Act
|
||||
// Arrange
|
||||
|
||||
var from = new PositionSnapshot(5, 5, 5, 0);
|
||||
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.Equal(MoveCommandReasonCodes.OutOfBounds, code);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TryValidate_ShouldAllowOnBoundsSurface_WhenDistrictEnabled()
|
||||
{
|
||||
{ // Act
|
||||
// Arrange
|
||||
|
||||
var from = new PositionSnapshot(5, 5, 5, 0);
|
||||
var to = new PositionVector { X = 10, Y = 10, Z = 10 };
|
||||
// Assert
|
||||
Assert.True(MoveCommandValidation.TryValidate(from, to, Rules(20, 20, bounds: true), out _));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@ public sealed class MoveStreamApiTests
|
|||
{
|
||||
[Fact]
|
||||
public async Task PostMoveStream_ShouldApplyChainAndIncrementSequence_WhenTwoSmallSteps()
|
||||
{
|
||||
{ // Arrange
|
||||
|
||||
await using var factory = new InMemoryWebApplicationFactory().WithWebHostBuilder(b =>
|
||||
{
|
||||
b.ConfigureTestServices(services =>
|
||||
|
|
@ -25,7 +26,9 @@ public sealed class MoveStreamApiTests
|
|||
});
|
||||
});
|
||||
var client = factory.CreateClient();
|
||||
// Act
|
||||
var before = await client.GetFromJsonAsync<PositionStateResponse>("/game/players/dev-local-1/position");
|
||||
// Assert
|
||||
Assert.NotNull(before);
|
||||
var seq0 = before!.Sequence;
|
||||
|
||||
|
|
@ -52,7 +55,8 @@ public sealed class MoveStreamApiTests
|
|||
|
||||
[Fact]
|
||||
public async Task PostMoveStream_ShouldReturnNotFound_WhenPlayerIsUnknown()
|
||||
{
|
||||
{ // Arrange
|
||||
|
||||
await using var factory = new InMemoryWebApplicationFactory();
|
||||
var client = factory.CreateClient();
|
||||
var body = new MoveStreamRequest
|
||||
|
|
@ -60,30 +64,36 @@ public sealed class MoveStreamApiTests
|
|||
SchemaVersion = MoveStreamRequest.CurrentSchemaVersion,
|
||||
Targets = [new PositionVector { X = 0, Y = 0.9, Z = 0 }],
|
||||
};
|
||||
// Act
|
||||
|
||||
var response = await client.PostAsJsonAsync("/game/players/unknown-player/move-stream", body);
|
||||
// Assert
|
||||
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PostMoveStream_ShouldReturn400_WhenTargetsEmpty()
|
||||
{
|
||||
{ // Arrange
|
||||
|
||||
await using var factory = new InMemoryWebApplicationFactory();
|
||||
var client = factory.CreateClient();
|
||||
var content = new StringContent(
|
||||
"{\"schemaVersion\":1,\"targets\":[]}",
|
||||
Encoding.UTF8,
|
||||
"application/json");
|
||||
// Act
|
||||
|
||||
var response = await client.PostAsync("/game/players/dev-local-1/move-stream", content);
|
||||
// Assert
|
||||
|
||||
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PostMoveStream_ShouldReturn400WithReason_WhenSecondStepExceedsHorizontalLimit()
|
||||
{
|
||||
{ // Arrange
|
||||
|
||||
await using var factory = new InMemoryWebApplicationFactory().WithWebHostBuilder(b =>
|
||||
{
|
||||
b.ConfigureTestServices(services =>
|
||||
|
|
@ -105,8 +115,10 @@ public sealed class MoveStreamApiTests
|
|||
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);
|
||||
// Assert
|
||||
|
||||
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||
var rej = await response.Content.ReadFromJsonAsync<MoveCommandRejectedResponse>();
|
||||
|
|
|
|||
|
|
@ -8,15 +8,21 @@ public class PlayerTargetStateReaderTests
|
|||
{
|
||||
[Fact]
|
||||
public void ComputeValidity_ShouldReturnInvalidTarget_WhenLockIdNotInRegistry()
|
||||
{
|
||||
{ // Act
|
||||
// Arrange
|
||||
|
||||
var snap = new PositionSnapshot(0, 0, 0, 0);
|
||||
// Assert
|
||||
Assert.Equal(TargetValidity.InvalidTarget, PlayerTargetStateReader.ComputeValidity("not_in_registry", in snap));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ComputeValidity_ShouldReturnNone_WhenLockNull()
|
||||
{
|
||||
{ // Act
|
||||
// Arrange
|
||||
|
||||
var snap = new PositionSnapshot(0, 0, 0, 0);
|
||||
// Assert
|
||||
Assert.Equal(TargetValidity.None, PlayerTargetStateReader.ComputeValidity(null, in snap));
|
||||
}
|
||||
|
||||
|
|
@ -24,7 +30,10 @@ public class PlayerTargetStateReaderTests
|
|||
public void ComputeValidity_AtExactLockRadius_ReturnsOk()
|
||||
{
|
||||
// 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);
|
||||
// Assert
|
||||
Assert.Equal(
|
||||
TargetValidity.Ok,
|
||||
PlayerTargetStateReader.ComputeValidity(PrototypeTargetRegistry.PrototypeTargetAlphaId, in snap));
|
||||
|
|
@ -32,9 +41,12 @@ public class PlayerTargetStateReaderTests
|
|||
|
||||
[Fact]
|
||||
public void ComputeValidity_JustOutsideLockRadius_ReturnsOutOfRange()
|
||||
{
|
||||
{ // Act
|
||||
// Arrange
|
||||
|
||||
const double eps = 1e-6;
|
||||
var snap = new PositionSnapshot(3 + eps, 0, -3, 0);
|
||||
// Assert
|
||||
Assert.Equal(
|
||||
TargetValidity.OutOfRange,
|
||||
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
|
||||
// flip without locomotion. Origin is ~4.24 m from each anchor (< 6 m).
|
||||
// Act
|
||||
// Arrange
|
||||
var snap = new PositionSnapshot(0, 0, 0, 0);
|
||||
// Assert
|
||||
Assert.Equal(
|
||||
TargetValidity.Ok,
|
||||
PlayerTargetStateReader.ComputeValidity(PrototypeTargetRegistry.PrototypeTargetAlphaId, in snap));
|
||||
|
|
|
|||
|
|
@ -12,11 +12,14 @@ public class TargetingApiTests
|
|||
{
|
||||
[Fact]
|
||||
public async Task GetTarget_ShouldReturnNone_WhenNoLock()
|
||||
{
|
||||
{ // Arrange
|
||||
|
||||
await using var factory = new InMemoryWebApplicationFactory();
|
||||
var client = factory.CreateClient();
|
||||
// Act
|
||||
|
||||
var response = await client.GetAsync("/game/players/dev-local-1/target");
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
var body = await response.Content.ReadFromJsonAsync<PlayerTargetStateResponse>();
|
||||
Assert.NotNull(body);
|
||||
|
|
@ -29,9 +32,11 @@ public class TargetingApiTests
|
|||
|
||||
[Fact]
|
||||
public async Task PostSelect_ShouldApplyLock_WhenInRange()
|
||||
{
|
||||
{ // Arrange
|
||||
|
||||
await using var factory = new InMemoryWebApplicationFactory();
|
||||
var client = factory.CreateClient();
|
||||
// Act
|
||||
|
||||
var response = await client.PostAsJsonAsync(
|
||||
"/game/players/dev-local-1/target/select",
|
||||
|
|
@ -40,6 +45,7 @@ public class TargetingApiTests
|
|||
SchemaVersion = TargetSelectRequest.CurrentSchemaVersion,
|
||||
TargetId = PrototypeTargetRegistry.PrototypeTargetAlphaId,
|
||||
});
|
||||
// Assert
|
||||
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
var body = await response.Content.ReadFromJsonAsync<TargetSelectResponse>();
|
||||
|
|
@ -53,9 +59,11 @@ public class TargetingApiTests
|
|||
|
||||
[Fact]
|
||||
public async Task PostSelect_ShouldDenyUnknownTarget_WithReasonCode()
|
||||
{
|
||||
{ // Arrange
|
||||
|
||||
await using var factory = new InMemoryWebApplicationFactory();
|
||||
var client = factory.CreateClient();
|
||||
// Act
|
||||
|
||||
var response = await client.PostAsJsonAsync(
|
||||
"/game/players/dev-local-1/target/select",
|
||||
|
|
@ -64,6 +72,7 @@ public class TargetingApiTests
|
|||
SchemaVersion = TargetSelectRequest.CurrentSchemaVersion,
|
||||
TargetId = "not_a_real_target",
|
||||
});
|
||||
// Assert
|
||||
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
var body = await response.Content.ReadFromJsonAsync<TargetSelectResponse>();
|
||||
|
|
@ -76,9 +85,11 @@ public class TargetingApiTests
|
|||
|
||||
[Fact]
|
||||
public async Task PostSelect_ShouldDenyOutOfRange_WhenBetaFarFromSpawn()
|
||||
{
|
||||
{ // Arrange
|
||||
|
||||
await using var factory = new InMemoryWebApplicationFactory();
|
||||
var client = factory.CreateClient();
|
||||
// Act
|
||||
|
||||
var response = await client.PostAsJsonAsync(
|
||||
"/game/players/dev-local-1/target/select",
|
||||
|
|
@ -87,6 +98,7 @@ public class TargetingApiTests
|
|||
SchemaVersion = TargetSelectRequest.CurrentSchemaVersion,
|
||||
TargetId = PrototypeTargetRegistry.PrototypeTargetBetaId,
|
||||
});
|
||||
// Assert
|
||||
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
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,
|
||||
// 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.
|
||||
// Arrange
|
||||
await using var factory = new InMemoryWebApplicationFactory();
|
||||
var client = factory.CreateClient();
|
||||
// Act
|
||||
|
||||
var response = await client.PostAsJsonAsync(
|
||||
"/game/players/dev-local-1/target/select",
|
||||
|
|
@ -113,6 +127,7 @@ public class TargetingApiTests
|
|||
TargetId = PrototypeTargetRegistry.PrototypeTargetBetaId,
|
||||
PositionHint = new PositionVector { X = 3.0, Y = 0.5, Z = 5.0 },
|
||||
});
|
||||
// Assert
|
||||
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
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;
|
||||
// it does not bypass the radius check.
|
||||
// Arrange
|
||||
await using var factory = new InMemoryWebApplicationFactory();
|
||||
var client = factory.CreateClient();
|
||||
// Act
|
||||
|
||||
var response = await client.PostAsJsonAsync(
|
||||
"/game/players/dev-local-1/target/select",
|
||||
|
|
@ -138,6 +155,7 @@ public class TargetingApiTests
|
|||
TargetId = PrototypeTargetRegistry.PrototypeTargetBetaId,
|
||||
PositionHint = new PositionVector { X = 50.0, Y = 0.0, Z = 50.0 },
|
||||
});
|
||||
// Assert
|
||||
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
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`
|
||||
// is untouched by a select-with-hint by reading it back through `GET /position`.
|
||||
// Arrange
|
||||
await using var factory = new InMemoryWebApplicationFactory();
|
||||
var client = factory.CreateClient();
|
||||
// Act
|
||||
|
||||
var before = await client.GetFromJsonAsync<PositionStateResponse>("/game/players/dev-local-1/position");
|
||||
// Assert
|
||||
Assert.NotNull(before);
|
||||
|
||||
Assert.Equal(
|
||||
|
|
@ -178,9 +199,12 @@ public class TargetingApiTests
|
|||
|
||||
[Fact]
|
||||
public async Task PostSelect_ShouldClearLock_WhenTargetIdOmitted()
|
||||
{
|
||||
{ // Act
|
||||
// Arrange
|
||||
|
||||
await using var factory = new InMemoryWebApplicationFactory();
|
||||
var client = factory.CreateClient();
|
||||
// Assert
|
||||
|
||||
Assert.Equal(
|
||||
HttpStatusCode.OK,
|
||||
|
|
@ -209,9 +233,12 @@ public class TargetingApiTests
|
|||
|
||||
[Fact]
|
||||
public async Task GetTarget_ShouldShowOutOfRange_AfterMoveWithoutSelect()
|
||||
{
|
||||
{ // Act
|
||||
// Arrange
|
||||
|
||||
await using var factory = new InMemoryWebApplicationFactory();
|
||||
var client = factory.CreateClient();
|
||||
// Assert
|
||||
|
||||
Assert.Equal(
|
||||
HttpStatusCode.OK,
|
||||
|
|
@ -240,19 +267,24 @@ public class TargetingApiTests
|
|||
|
||||
[Fact]
|
||||
public async Task GetTarget_ShouldReturnNotFound_WhenPlayerUnknown()
|
||||
{
|
||||
{ // Arrange
|
||||
|
||||
await using var factory = new InMemoryWebApplicationFactory();
|
||||
var client = factory.CreateClient();
|
||||
// Act
|
||||
|
||||
var response = await client.GetAsync("/game/players/missing-player/target");
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PostSelect_ShouldReturnNotFound_WhenPlayerUnknown()
|
||||
{
|
||||
{ // Arrange
|
||||
|
||||
await using var factory = new InMemoryWebApplicationFactory();
|
||||
var client = factory.CreateClient();
|
||||
// Act
|
||||
|
||||
var response = await client.PostAsJsonAsync(
|
||||
"/game/players/missing-player/target/select",
|
||||
|
|
@ -261,15 +293,18 @@ public class TargetingApiTests
|
|||
SchemaVersion = TargetSelectRequest.CurrentSchemaVersion,
|
||||
TargetId = PrototypeTargetRegistry.PrototypeTargetAlphaId,
|
||||
});
|
||||
// Assert
|
||||
|
||||
Assert.Equal(HttpStatusCode.NotFound, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PostSelect_ShouldReturnBadRequest_WhenSchemaVersionWrong()
|
||||
{
|
||||
{ // Arrange
|
||||
|
||||
await using var factory = new InMemoryWebApplicationFactory();
|
||||
var client = factory.CreateClient();
|
||||
// Act
|
||||
|
||||
var response = await client.PostAsJsonAsync(
|
||||
"/game/players/dev-local-1/target/select",
|
||||
|
|
@ -278,15 +313,18 @@ public class TargetingApiTests
|
|||
SchemaVersion = 999,
|
||||
TargetId = PrototypeTargetRegistry.PrototypeTargetAlphaId,
|
||||
});
|
||||
// Assert
|
||||
|
||||
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PostSelect_ShouldReturnBadRequest_WhenTargetIdWhitespaceOnly()
|
||||
{
|
||||
{ // Arrange
|
||||
|
||||
await using var factory = new InMemoryWebApplicationFactory();
|
||||
var client = factory.CreateClient();
|
||||
// Act
|
||||
|
||||
var response = await client.PostAsJsonAsync(
|
||||
"/game/players/dev-local-1/target/select",
|
||||
|
|
@ -295,15 +333,18 @@ public class TargetingApiTests
|
|||
SchemaVersion = TargetSelectRequest.CurrentSchemaVersion,
|
||||
TargetId = " \t ",
|
||||
});
|
||||
// Assert
|
||||
|
||||
Assert.Equal(HttpStatusCode.BadRequest, response.StatusCode);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task PostSelect_ShouldBeCaseInsensitive_ForTargetId()
|
||||
{
|
||||
{ // Arrange
|
||||
|
||||
await using var factory = new InMemoryWebApplicationFactory();
|
||||
var client = factory.CreateClient();
|
||||
// Act
|
||||
|
||||
var response = await client.PostAsJsonAsync(
|
||||
"/game/players/dev-local-1/target/select",
|
||||
|
|
@ -312,6 +353,7 @@ public class TargetingApiTests
|
|||
SchemaVersion = TargetSelectRequest.CurrentSchemaVersion,
|
||||
TargetId = " PROTOTYPE_TARGET_ALPHA ",
|
||||
});
|
||||
// Assert
|
||||
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
var body = await response.Content.ReadFromJsonAsync<TargetSelectResponse>();
|
||||
|
|
@ -322,7 +364,8 @@ public class TargetingApiTests
|
|||
|
||||
[Fact]
|
||||
public async Task PostSelect_ShouldStayIdempotent_WhenSameTargetReselect()
|
||||
{
|
||||
{ // Arrange
|
||||
|
||||
await using var factory = new InMemoryWebApplicationFactory();
|
||||
var client = factory.CreateClient();
|
||||
|
||||
|
|
@ -331,8 +374,10 @@ public class TargetingApiTests
|
|||
SchemaVersion = TargetSelectRequest.CurrentSchemaVersion,
|
||||
TargetId = PrototypeTargetRegistry.PrototypeTargetAlphaId,
|
||||
};
|
||||
// Act
|
||||
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);
|
||||
// Assert
|
||||
Assert.Equal(HttpStatusCode.OK, first.StatusCode);
|
||||
Assert.Equal(HttpStatusCode.OK, second.StatusCode);
|
||||
var b1 = await first.Content.ReadFromJsonAsync<TargetSelectResponse>();
|
||||
|
|
@ -342,9 +387,11 @@ public class TargetingApiTests
|
|||
|
||||
[Fact]
|
||||
public async Task PostSelect_ShouldDenyOutOfRange_WhenReselectingSameLockWhileOutOfRange()
|
||||
{
|
||||
{ // Arrange
|
||||
|
||||
await using var factory = new InMemoryWebApplicationFactory();
|
||||
var client = factory.CreateClient();
|
||||
// Act
|
||||
|
||||
await client.PostAsJsonAsync(
|
||||
"/game/players/dev-local-1/target/select",
|
||||
|
|
@ -368,6 +415,7 @@ public class TargetingApiTests
|
|||
SchemaVersion = TargetSelectRequest.CurrentSchemaVersion,
|
||||
TargetId = PrototypeTargetRegistry.PrototypeTargetAlphaId,
|
||||
});
|
||||
// Assert
|
||||
|
||||
Assert.Equal(HttpStatusCode.OK, response.StatusCode);
|
||||
var body = await response.Content.ReadFromJsonAsync<TargetSelectResponse>();
|
||||
|
|
|
|||
Loading…
Reference in New Issue