using Microsoft.Extensions.DependencyInjection; using Orleans.Hosting; using Orleans.TestingHost; using Vanguard.Rules.Cards; using Vanguard.Rules.Engine; using Vanguard.Server.Configuration; using Vanguard.Server.Matches; using Vanguard.Server.Matches.Orleans; using Vanguard.Server.Storage; using Vanguard.Shared.Protocol; namespace Vanguard.Server.Tests; public class OrleansMatchTests { private static readonly DateTimeOffset Start = new(2026, 10, 21, 12, 0, 0, TimeSpan.Zero); [Fact] public async Task Grain_Initializes_Attaches_And_Submits_Intent() { var time = new FakeTimeProvider(Start); var connections = new MatchConnectionRegistry(); var results = new RecordingResultSink(); var builder = new TestClusterBuilder(); builder.AddSiloBuilderConfigurator(new SiloConfigurator(time, connections, results)); var cluster = builder.Build(); try { await cluster.DeployAsync(); var actor = cluster.Client.GetGrain("orleans-match"); var setup = new MatchSetup( "orleans-match", GameMode.ClassicRanked, new MatchSeatSetup("p1", ServerTestData.BuildDeck(), 1000), new MatchSeatSetup("p2", ServerTestData.BuildDeck(), 1000)); await actor.InitializeAsync(MatchSetupData.From(setup)); var first = new FakeMatchConnection(); connections.Register("orleans-match", 0, first); Assert.True(await actor.AttachAsync(0, "p1", 0)); var hello = first.All().Single(); Assert.Equal(0, hello.PlayerIndex); Assert.False(hello.Resumed); var snapshot = first.All().Single().View; Assert.Equal(5, snapshot.Players[0].Hand.Count); Assert.Empty(snapshot.Players[1].Hand); var summary = await actor.GetSummaryAsync(); var intent = new ClientIntent { IntentId = "orleans-intent-1", BasedOnSeq = summary.EventSeq, Intent = new FinishMulliganIntent(), }; await actor.SubmitAsync(0, ProtocolSerializer.Encode(intent)); var accepted = first.Last(); Assert.True(accepted.Accepted); var after = await actor.GetSummaryAsync(); Assert.True(after.EventSeq >= summary.EventSeq); Assert.True(await actor.HasPlayerAsync("p1")); Assert.False(await actor.HasPlayerAsync("stranger")); } finally { await cluster.StopAsync(); cluster.Dispose(); } } private sealed class SiloConfigurator : ISiloConfigurator { private readonly FakeTimeProvider _time; private readonly IMatchConnectionRegistry _connections; private readonly IMatchResultSink _results; public SiloConfigurator( FakeTimeProvider time, IMatchConnectionRegistry connections, IMatchResultSink results) { _time = time; _connections = connections; _results = results; } public void Configure(ISiloBuilder siloBuilder) { siloBuilder.ConfigureServices(services => { var options = ServerTestData.SmallOptions(); services.AddSingleton(ServerTestData.Catalog); services.AddSingleton(options); services.AddSingleton(_time); services.AddSingleton(_results); services.AddSingleton(); services.AddSingleton(_connections); services.AddLogging(); }); } } }