using System.Net.Http.Json; using System.Text.Json; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc.Testing; using Microsoft.Extensions.Configuration; using Vanguard.Server.Api; using Xunit.Sdk; namespace Vanguard.Server.Tests; /// /// Полный матч через HTTP + WebSocket с включённым Orleans: проверяет не /// только грану, но и связку registry → adapter → endpoint → silo. /// [Collection("Orleans")] public class OrleansServerE2ETests { [Fact] public async Task Two_Clients_Play_A_Full_Match_With_Orleans_Enabled() { using var factory = NewFactory(); using var http = factory.CreateClient(); var deck = ServerTestData.BuildDeck(); var firstQueue = await EnqueueAsync(http, "p1", deck); var secondQueue = await EnqueueAsync(http, "p2", deck); var firstStatus = await WaitForMatchAsync(http, firstQueue.QueueId, "p1"); var secondStatus = await WaitForMatchAsync(http, secondQueue.QueueId, "p2"); Assert.Equal(firstStatus.MatchId, secondStatus.MatchId); using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(120)); await using var first = await TestGameClient.ConnectAsync( factory, firstStatus.MatchId!, firstStatus.Ticket!, "p1", 0, cts.Token); await using var second = await TestGameClient.ConnectAsync( factory, secondStatus.MatchId!, secondStatus.Ticket!, "p2", 0, cts.Token); await Task.WhenAll( first.PlayRandomlyAsync(seed: 11, cts.Token), second.PlayRandomlyAsync(seed: 22, cts.Token)); Assert.True(first.View.IsFinished, "Первый клиент не увидел завершение партии."); Assert.True(second.View.IsFinished, "Второй клиент не увидел завершение партии."); Assert.Equal(first.View.Winner, second.View.Winner); var health = await http.GetFromJsonAsync("/health"); Assert.True(health.GetProperty("matches").GetInt32() >= 1); } private static WebApplicationFactory NewFactory() => new WebApplicationFactory().WithWebHostBuilder(builder => builder.ConfigureAppConfiguration((_, config) => config.AddInMemoryCollection(new Dictionary { ["Orleans:Enabled"] = "true", }))); private static async Task EnqueueAsync( HttpClient http, string playerId, List deck) { var response = await http.PostAsJsonAsync( "/api/game/queue", new QueueRequest(playerId, "classic-ranked", deck)); if (!response.IsSuccessStatusCode) throw new XunitException(await response.Content.ReadAsStringAsync()); return (await response.Content.ReadFromJsonAsync())!; } private static async Task WaitForMatchAsync( HttpClient http, Guid queueId, string playerId) { for (var attempt = 0; attempt < 200; attempt++) { var status = await http.GetFromJsonAsync( $"/api/game/queue/{queueId}?playerId={playerId}"); if (status is null) throw new XunitException("Очередь исчезла."); if (status.Status == "matched") return status; if (status.Status is "failed" or "expired" or "cancelled") throw new XunitException($"Очередь в состоянии {status.Status}: {status.Reason}"); await Task.Delay(50); } throw new XunitException("Матч не был найден за 10 секунд."); } }