acdream/tests/AcDream.Headless.Tests/HeadlessBotPolicyTests.cs
Erik 000ea979d5 test: Campaign LA finish LA2 probe and idle gates
Prove idle play remains passive and live until cancellation, then converges through one truthful status teardown. Keep probe mode string-only so numeric enum aliases cannot expand the pinned v1 contract, and record the Windows/WSL gates.
2026-08-14 16:49:51 +02:00

77 lines
2.6 KiB
C#

using System.Reflection;
using AcDream.Headless.Policies;
using AcDream.Runtime;
namespace AcDream.Headless.Tests;
public sealed class HeadlessBotPolicyTests
{
/// <summary>
/// Campaign LA slice LA2: <c>idle</c> is a deliberately passive,
/// non-terminal policy. It must neither inspect Runtime state nor reach
/// any command surface, and no event can make it complete on its own.
/// The process scheduler therefore keeps the play session alive until
/// external cancellation/stop drives the host's ordinary teardown path.
/// </summary>
[Fact]
public void IdlePolicyIsPassiveAndNeverCompletesAutonomously()
{
var policy = new IdleHeadlessBotPolicy();
IGameRuntimeView view = CreateNoTouchProxy<IGameRuntimeView>(
out InvocationCountingProxy viewCalls);
IGameRuntimeCommands commands =
CreateNoTouchProxy<IGameRuntimeCommands>(
out InvocationCountingProxy commandCalls);
for (int index = 0; index < 3; index++)
policy.Tick(view, commands);
RuntimeLifecycleDelta lifecycle = default;
RuntimeCommandDelta command = default;
RuntimeEntityDelta entity = default;
RuntimeInventoryDelta inventory = default;
RuntimeChatDelta chat = default;
RuntimeMovementDelta movement = default;
RuntimePortalDelta portal = default;
RuntimeCombatDelta combat = default;
policy.OnLifecycle(in lifecycle);
policy.OnCommand(in command);
policy.OnEntity(in entity);
policy.OnInventory(in inventory);
policy.OnChat(in chat);
policy.OnMovement(in movement);
policy.OnPortal(in portal);
policy.OnCombat(in combat);
Assert.False(policy.IsComplete);
Assert.Equal(0, viewCalls.InvocationCount);
Assert.Equal(0, commandCalls.InvocationCount);
policy.Dispose();
policy.Dispose();
Assert.False(policy.IsComplete);
}
private static T CreateNoTouchProxy<T>(
out InvocationCountingProxy proxy)
where T : class
{
T value = DispatchProxy.Create<T, InvocationCountingProxy>();
proxy = (InvocationCountingProxy)(object)value;
return value;
}
public class InvocationCountingProxy : DispatchProxy
{
public int InvocationCount { get; private set; }
protected override object? Invoke(
MethodInfo? targetMethod,
object?[]? args)
{
InvocationCount++;
throw new InvalidOperationException(
$"Idle policy unexpectedly invoked {targetMethod?.Name}.");
}
}
}