using Microsoft.EntityFrameworkCore; using StackExchange.Redis; using Vanguard.Server.Configuration; using Vanguard.Server.Matches; using Vanguard.Server.Matchmaking; using Vanguard.Server.Storage; using Vanguard.Server.Tickets; using Vanguard.Shared.Protocol; namespace Vanguard.Server.Tests; /// /// Проверки настоящих PostgreSQL и Redis. Запускаются только при заданных /// переменных окружения: /// VANGUARD_TEST_POSTGRES — connection string к отдельной тестовой БД; /// VANGUARD_TEST_REDIS — configuration Redis (лучше отдельный DB). /// /// Postgres-тест делает EnsureDeleted/EnsureCreated, поэтому БД должна быть /// заведомо тестовой. /// public class ExternalStorageTests { private static readonly DateTimeOffset Start = new(2026, 10, 21, 12, 0, 0, TimeSpan.Zero); [Fact] public async Task Postgres_Store_RoundTrips_Header_And_Intents() { var connectionString = Environment.GetEnvironmentVariable("VANGUARD_TEST_POSTGRES"); if (string.IsNullOrWhiteSpace(connectionString)) return; var time = new FakeTimeProvider(Start); var options = new DbContextOptionsBuilder() .UseNpgsql(connectionString) .Options; await using (var db = new MatchDbContext(options)) { await db.Database.EnsureDeletedAsync(); await db.Database.EnsureCreatedAsync(); } var store = new EfMatchStore(new TestDbContextFactory(options), time); var deck = ServerTestData.BuildDeck(); var header = new MatchHeader( "pg-1", GameMode.ClassicRanked, "p1", "p2", deck, deck, 7, time.GetUtcNow(), Started: false, 300_000, 300_000, TurnDeadline: null, AbsentSince0: time.GetUtcNow(), AbsentSince1: time.GetUtcNow(), LastClockSent: time.GetUtcNow(), IsFinished: false, Winner: -1, FinishedAt: null, FinishReason: MatchFinishReason.Normal, EventSeq: 1); await store.CreateAsync(header); await store.AppendIntentAsync( header with { EventSeq = 2 }, new PersistedIntent("i1", 0, "finishMulligan", true, IntentRejectCode.None, null, 2)); var loaded = await store.GetAsync("pg-1"); Assert.NotNull(loaded); Assert.Equal(7UL, loaded!.Header.Seed); Assert.Equal(deck, loaded.Header.Deck0); Assert.Single(loaded.Intents); Assert.Equal("i1", loaded.Intents[0].IntentId); var unfinished = await store.LoadUnfinishedAsync(time.GetUtcNow().AddMinutes(-1)); Assert.Single(unfinished); } [Fact] public async Task Redis_Ticket_And_Queue_Stores_Work() { var configuration = Environment.GetEnvironmentVariable("VANGUARD_TEST_REDIS"); if (string.IsNullOrWhiteSpace(configuration)) return; await using var redis = await ConnectionMultiplexer.ConnectAsync(configuration); var options = new RedisOptions { Configuration = configuration, Prefix = $"vgtest:{Guid.NewGuid():N}:", }; var time = new FakeTimeProvider(Start); var game = ServerTestData.SmallOptions(); var tickets = new GameTicketService( new RedisTicketStore(redis, options, time), game, time); var ticket = await tickets.IssueAsync("p1", "m1", 0); var redeemed = await tickets.RedeemAsync(ticket.Token); Assert.NotNull(redeemed); Assert.Equal("p1", redeemed!.PlayerId); Assert.Equal(0, redeemed.Seat); Assert.Null(await tickets.RedeemAsync(ticket.Token)); var queue = new RedisQueueStore(redis, options, game); var entry = new QueueEntrySnapshot( Guid.NewGuid(), "p1", ServerTestData.BuildDeck(), GameMode.ClassicRanked, 1000, time.GetUtcNow(), 1, QueueEntryState.Queued, null, -1, null, null, null); await queue.SaveAsync(entry); var loaded = await queue.LoadQueuedAsync(); Assert.Single(loaded); Assert.Equal(entry.QueueId, loaded[0].QueueId); Assert.Equal(entry.PlayerId, loaded[0].PlayerId); await queue.RemoveAsync(entry.QueueId, entry.Mode); Assert.Empty(await queue.LoadQueuedAsync()); } private sealed class TestDbContextFactory : IDbContextFactory { private readonly DbContextOptions _options; public TestDbContextFactory(DbContextOptions options) => _options = options; public MatchDbContext CreateDbContext() => new(_options); } }