VanguardReMaster/server/tests/Vanguard.Server.Tests/ExternalStorageTests.cs

138 lines
4.8 KiB
C#

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;
/// <summary>
/// Проверки настоящих PostgreSQL и Redis. Запускаются только при заданных
/// переменных окружения:
/// VANGUARD_TEST_POSTGRES — connection string к отдельной тестовой БД;
/// VANGUARD_TEST_REDIS — configuration Redis (лучше отдельный DB).
///
/// Postgres-тест делает EnsureDeleted/EnsureCreated, поэтому БД должна быть
/// заведомо тестовой.
/// </summary>
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<MatchDbContext>()
.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<MatchDbContext>
{
private readonly DbContextOptions<MatchDbContext> _options;
public TestDbContextFactory(DbContextOptions<MatchDbContext> options) => _options = options;
public MatchDbContext CreateDbContext() => new(_options);
}
}