Minecraft-Console-Client/MinecraftClient.Tests/Pathing/Execution/PathPlanningContractTests.cs

289 lines
8.5 KiB
C#

using MinecraftClient.Pathing.Core;
using MinecraftClient.Pathing.Execution;
using MinecraftClient.Tests.Pathing.Execution.Contracts;
using Xunit;
namespace MinecraftClient.Tests.Pathing.Execution;
public sealed class PathPlanningContractTests
{
[Fact]
public void Get_ManagerAcceptedAscendChain_LoadsExactPlannerContract()
{
var store = PathingContractStore.LoadFromRepositoryRoot();
PathingPlannerContract contract = store.GetPlanner("manager-accepted-ascend-chain");
Assert.Equal("manager-accepted-ascend-chain", contract.ScenarioId);
Assert.Equal(PathStatus.Success, contract.ExpectedStatus);
Assert.Equal(6, contract.Segments.Count);
PathingPlannerSegmentContract firstSegment = contract.Segments[0];
Assert.Equal(MoveType.Diagonal, firstSegment.MoveType);
Assert.Equal(new PathingBlock(171, 80, 160), firstSegment.StartBlock);
Assert.Equal(new PathingBlock(172, 80, 161), firstSegment.EndBlock);
PathingPlannerSegmentContract lastSegment = contract.Segments[5];
Assert.Equal(MoveType.Ascend, lastSegment.MoveType);
Assert.Equal(new PathingBlock(176, 82, 162), lastSegment.StartBlock);
Assert.Equal(new PathingBlock(177, 83, 162), lastSegment.EndBlock);
}
[Fact]
public void Get_ManagerAcceptedAscendChain_LoadsTimingScaffoldShape()
{
var store = PathingContractStore.LoadFromRepositoryRoot();
PathingTimingBudget timing = store.GetTiming("manager-accepted-ascend-chain");
Assert.Equal("manager-accepted-ascend-chain", timing.ScenarioId);
Assert.Equal(6, timing.Segments.Count);
Assert.True(timing.ExpectedTotalTicks <= timing.MaxTotalTicks);
PathingSegmentTimingBudget firstSegment = timing.Segments[0];
Assert.Equal(MoveType.Diagonal, firstSegment.MoveType);
Assert.True(firstSegment.ExpectedTicks <= firstSegment.MaxTicks);
foreach (PathingSegmentTimingBudget segment in timing.Segments)
{
Assert.True(segment.ExpectedTicks <= segment.MaxTicks);
}
}
[Fact]
public void LoadFromJson_RejectsTimingSegment_WhenExpectedExceedsMax()
{
const string plannerJson = """
[
{
"scenarioId": "manager-accepted-ascend-chain",
"expectedStatus": "Success",
"segments": [
{
"moveType": "Diagonal",
"startBlock": { "x": 171, "y": 80, "z": 160 },
"endBlock": { "x": 172, "y": 80, "z": 161 }
}
]
}
]
""";
const string timingJson = """
[
{
"scenarioId": "manager-accepted-ascend-chain",
"expectedTotalTicks": 1,
"maxTotalTicks": 1,
"segments": [
{ "moveType": "Diagonal", "expectedTicks": 2, "maxTicks": 1 }
]
}
]
""";
InvalidDataException error = Assert.Throws<InvalidDataException>(
() => PathingContractStore.LoadFromJson(plannerJson, timingJson));
Assert.Contains("manager-accepted-ascend-chain", error.Message);
}
[Fact]
public void LoadFromJson_RejectsTimingBudget_WhenTotalsDoNotMatchSegments()
{
const string plannerJson = """
[
{
"scenarioId": "totals-mismatch",
"expectedStatus": "Success",
"segments": [
{
"moveType": "Traverse",
"startBlock": { "x": 0, "y": 80, "z": 0 },
"endBlock": { "x": 1, "y": 80, "z": 0 }
},
{
"moveType": "Ascend",
"startBlock": { "x": 1, "y": 80, "z": 0 },
"endBlock": { "x": 2, "y": 81, "z": 0 }
}
]
}
]
""";
const string timingJson = """
[
{
"scenarioId": "totals-mismatch",
"expectedTotalTicks": 1,
"maxTotalTicks": 2,
"segments": [
{ "moveType": "Traverse", "expectedTicks": 2, "maxTicks": 3 },
{ "moveType": "Ascend", "expectedTicks": 3, "maxTicks": 4 }
]
}
]
""";
InvalidDataException error = Assert.Throws<InvalidDataException>(
() => PathingContractStore.LoadFromJson(plannerJson, timingJson));
Assert.Contains("totals-mismatch", error.Message);
Assert.Contains("ExpectedTotalTicks mismatch", error.Message);
}
[Fact]
public void LoadFromJson_Rejects_WhenPlannerAndTimingScenarioSetsMismatch()
{
const string plannerJson = """
[
{
"scenarioId": "planner-only",
"expectedStatus": "Success",
"segments": [
{
"moveType": "Traverse",
"startBlock": { "x": 0, "y": 80, "z": 0 },
"endBlock": { "x": 1, "y": 80, "z": 0 }
}
]
}
]
""";
const string timingJson = """
[
{
"scenarioId": "timing-only",
"expectedTotalTicks": 1,
"maxTotalTicks": 1,
"segments": [
{ "moveType": "Traverse", "expectedTicks": 1, "maxTicks": 1 }
]
}
]
""";
InvalidDataException error = Assert.Throws<InvalidDataException>(
() => PathingContractStore.LoadFromJson(plannerJson, timingJson));
Assert.Contains("planner-only", error.Message);
Assert.Contains("timing-only", error.Message);
}
[Fact]
public void LoadFromJson_RejectsDuplicatePlannerScenarioId()
{
const string plannerJson = """
[
{
"scenarioId": "dup",
"expectedStatus": "Success",
"segments": [
{
"moveType": "Traverse",
"startBlock": { "x": 0, "y": 80, "z": 0 },
"endBlock": { "x": 1, "y": 80, "z": 0 }
}
]
},
{
"scenarioId": "dup",
"expectedStatus": "Success",
"segments": [
{
"moveType": "Traverse",
"startBlock": { "x": 1, "y": 80, "z": 0 },
"endBlock": { "x": 2, "y": 80, "z": 0 }
}
]
}
]
""";
const string timingJson = """
[
{
"scenarioId": "dup",
"expectedTotalTicks": 1,
"maxTotalTicks": 1,
"segments": [
{ "moveType": "Traverse", "expectedTicks": 1, "maxTicks": 1 }
]
}
]
""";
InvalidDataException error = Assert.Throws<InvalidDataException>(
() => PathingContractStore.LoadFromJson(plannerJson, timingJson));
Assert.Contains("Duplicate planner contract scenario id", error.Message);
}
[Fact]
public void LoadFromJson_Rejects_WhenPlannerAndTimingMoveSequenceDiffer()
{
const string plannerJson = """
[
{
"scenarioId": "sequence-mismatch",
"expectedStatus": "Success",
"segments": [
{
"moveType": "Traverse",
"startBlock": { "x": 0, "y": 80, "z": 0 },
"endBlock": { "x": 1, "y": 80, "z": 0 }
},
{
"moveType": "Ascend",
"startBlock": { "x": 1, "y": 80, "z": 0 },
"endBlock": { "x": 2, "y": 81, "z": 0 }
}
]
}
]
""";
const string timingJson = """
[
{
"scenarioId": "sequence-mismatch",
"expectedTotalTicks": 2,
"maxTotalTicks": 2,
"segments": [
{ "moveType": "Traverse", "expectedTicks": 1, "maxTicks": 1 },
{ "moveType": "Diagonal", "expectedTicks": 1, "maxTicks": 1 }
]
}
]
""";
InvalidDataException error = Assert.Throws<InvalidDataException>(
() => PathingContractStore.LoadFromJson(plannerJson, timingJson));
Assert.Contains("sequence-mismatch", error.Message);
Assert.Contains("segment 1", error.Message);
}
[Theory]
[InlineData("manager-accepted-ascend-chain")]
[InlineData("same-move-ascend-staircase")]
[InlineData("same-move-descend-staircase")]
[InlineData("rejected-3x1-invalid-goal")]
[InlineData("repeated-cardinal-parkour-chain")]
[InlineData("repeated-diagonal-parkour-chain")]
[InlineData("obstructed-parkour-l-turns")]
[InlineData("vertical-jump-mix")]
[InlineData("diagonal-vertical-mix")]
[InlineData("turn-density-alternating-traverse-diagonal-chain")]
[InlineData("mixed-traverse-ascend-parkour-descend")]
[InlineData("same-move-aligned-parkour-chain")]
[InlineData("mixed-diagonal-ascend-traverse-descend")]
[InlineData("speed-carry-repeated-traverse-ascend")]
[InlineData("speed-carry-repeated-traverse-descend")]
[InlineData("speed-carry-repeated-traverse-parkour")]
[InlineData("same-move-diagonal-chain")]
[InlineData("same-move-straight-traverse-chain")]
[InlineData("mixed-traverse-turn-parkour-turn-traverse")]
public void Scenario_PlannerMatchesContract(string scenarioId)
{
PathingExecutionScenario scenario = PathingExecutionScenarioCatalog.Get(scenarioId);
PathResult planResult = PathingScenarioRunner.PlanOnly(scenario);
PathingContractAssert.PlannerMatches(
PathingContractStore.LoadFromRepositoryRoot().GetPlanner(scenarioId),
PathSegmentBuilder.FromPath(planResult.Path),
planResult);
}
}