138 lines
4.8 KiB
C#
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.MigrateAsync();
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|