Files
AIC-Project/Server/Server.Host.Tests/RpsModuleLoadTests.cs
T
2026-07-10 10:24:29 +08:00

89 lines
3.5 KiB
C#

using System.Collections.Generic;
using Xunit;
using XWorld.Framework;
using XWorld.Server.Host;
using RPS.Core;
namespace XWorld.Server.Host.Tests
{
/// <summary>
/// Proves "framework zero-change loads a new game via ALC": loads RPS through the existing ALC contract.
/// Loads RPS from TestGames/rps/1, drives it with 1 human + 1 AI seat, advances past 60s,
/// asserts broadcasts decode and game progresses to Finished.
/// </summary>
public class RpsModuleLoadTests
{
private sealed class FakeOutput : IRoomOutput
{
public readonly List<NetMessage> Broadcasts = new List<NetMessage>();
public void Broadcast(NetMessage m) => Broadcasts.Add(m);
public void SendTo(int playerId, NetMessage m) { }
}
private static readonly IReadOnlyList<PlayerInfo> Players = new[]
{
new PlayerInfo { PlayerId = 1, Name = "Human", IsAI = false },
new PlayerInfo { PlayerId = -1, Name = "AI", IsAI = true },
};
[Fact]
public void Load_Rps_v1_Via_ALC_ProducesRoom_ThatImplementsSharedInterface()
{
var module = new GameModuleLoader().Load(TestGames.Root, "rps", 1);
Assert.Equal("rps", module.GameId);
Assert.Equal(1, module.Version);
IGameServerRoom room = module.CreateRoom();
Assert.NotNull(room);
// Verify ALC isolation: the implementation type lives in the game ALC,
// not the default context where IGameServerRoom is defined.
Assert.NotEqual(typeof(IGameServerRoom).Assembly, room.GetType().Assembly);
}
[Fact]
public void Load_Rps_v1_Via_ALC_BroadcastsSnapshots_AndProgressesToFinished()
{
var module = new GameModuleLoader().Load(TestGames.Root, "rps", 1);
IGameServerRoom room = module.CreateRoom();
var output = new FakeOutput();
bool ended = false;
var ctx = new RoomCtx(
new DeterministicRandom(99),
new ManualClock(),
new ConsoleLogger(),
new InMemoryStorage(),
output,
_ => ended = true);
room.OnRoomStart(Players, ctx);
// Initial broadcast on start
Assert.True(output.Broadcasts.Count >= 1);
// Decode the first snapshot to confirm Round=1, Phase=Choosing
var logic = new RpsLogic();
var first = logic.Decode(output.Broadcasts[0].Payload);
Assert.Equal(1, first.Round);
Assert.Equal(RpsPhase.Choosing, first.Phase);
// Drive with large dt to tick through all rounds within 60s total:
// Each tick dt=10f: 10+10+10+10+10+10 = 60s → should finish by tick 6
for (int i = 0; i < 10 && !ended; i++)
room.OnTick(10f);
Assert.True(ended, "EndRoom must have been called (game finished within 10 large ticks)");
Assert.True(output.Broadcasts.Count > 1, "Should have multiple broadcasts");
// Decode the last snapshot — must be Finished
var last = output.Broadcasts[output.Broadcasts.Count - 1];
var finalState = logic.Decode(last.Payload);
Assert.Equal(RpsPhase.Finished, finalState.Phase);
// Winner is 0, 1, or 2 (draw) — must be a valid value
Assert.True(finalState.Winner >= 0 && finalState.Winner <= 2,
$"Winner should be 0, 1, or 2 but was {finalState.Winner}");
}
}
}