Files
GmRelayBot/tests/GmRelay.Bot.Tests/Features/Sessions/CreateSession/Wizard/WizardDraftRepositoryTests.cs
T
Toutsu 8f0f2ef7e7 refactor(wizard): move core to Shared, add IWizardMessenger contract (issue #112)
Moves the game-creation wizard state machine, view builder, and
platform-neutral contracts (callback data, step names, storage
exception, club option, step limits) from GmRelay.Bot to GmRelay.Shared.
Telegram continues to work through a new TelegramWizardMessenger
implementing IWizardMessenger and a WizardInteractionMapper that
converts Update → WizardInteraction. Wires the new platform column on
wizard_drafts (V032 migration) and switches chat/owner/thread/message
ids to TEXT so the same table can hold Discord snowflakes later.

- GameCreationWizard: now in Shared, takes IWizardMessenger +
  IWizardDraftRepository, dispatches on WizardInteraction.
- New IWizardMessenger contract with Edit/Send/Answer/GetOwnerClubs
  (returns string ids so Telegram longs and Discord snowflakes both
  fit).
- New WizardStepViewBuilder in Shared returns
  (text, IReadOnlyList<WizardAction>); TelegramWizardMessenger
  renders actions into InlineKeyboardMarkup via a new Bot-side
  ToInlineKeyboard helper.
- New WizardInteractionMapper in Bot (5-case test) converts Telegram
  Update to WizardInteraction.
- WizardDraft gains a Platform column; ChatId/MessageThreadId/OwnerId/
  DraftMessageId switched to string. V032 migrates existing rows and
  rebuilds the owner lookup index on (platform, owner_id).
- All existing wizard / create-session tests updated to the new
  contract (HandleInteractionAsync + WizardInteraction). Wizard
  callback-data format preserved.
- dotnet build clean, dotnet format --verify-no-changes clean, all
  101 wizard tests pass.
2026-06-05 16:23:20 +03:00

95 lines
3.6 KiB
C#

using System;
using System.Threading;
using System.Threading.Tasks;
using GmRelay.Shared.Features.Sessions.CreateSession.Wizard;
using Npgsql;
namespace GmRelay.Bot.Tests.Features.Sessions.CreateSession.Wizard;
[Collection(WizardDraftRepositoryCollection.Name)]
public sealed class WizardDraftRepositoryTests(WizardDraftRepositoryFixture fixture)
{
[Fact]
public async Task UpsertAsync_InsertThenUpdate_PreservesSingleRow()
{
var connectionString = await fixture.CreateSchemaDatabaseAsync();
await using var dataSource = NpgsqlDataSource.Create(connectionString);
var sut = new WizardDraftRepository(dataSource);
var draft = NewDraft("Type", DateTimeOffset.UtcNow.AddHours(1));
await sut.UpsertAsync(draft, CancellationToken.None);
draft.Step = "Title";
draft.UpdatedAt = DateTimeOffset.UtcNow.AddSeconds(1);
await sut.UpsertAsync(draft, CancellationToken.None);
var loaded = await sut.GetActiveAsync(draft.Platform, draft.OwnerId, CancellationToken.None);
Assert.NotNull(loaded);
Assert.Equal("Title", loaded!.Step);
}
[Fact]
public async Task GetActiveAsync_ExpiredDraft_ReturnsNull()
{
var connectionString = await fixture.CreateSchemaDatabaseAsync();
await using var dataSource = NpgsqlDataSource.Create(connectionString);
var sut = new WizardDraftRepository(dataSource);
var draft = NewDraft("Type", DateTimeOffset.UtcNow.AddMinutes(-1));
await sut.UpsertAsync(draft, CancellationToken.None);
var loaded = await sut.GetActiveAsync(draft.Platform, draft.OwnerId, CancellationToken.None);
Assert.Null(loaded);
}
[Fact]
public async Task GetActiveAsync_DifferentOwner_ReturnsNull()
{
var connectionString = await fixture.CreateSchemaDatabaseAsync();
await using var dataSource = NpgsqlDataSource.Create(connectionString);
var sut = new WizardDraftRepository(dataSource);
var draft = NewDraft("Type", DateTimeOffset.UtcNow.AddHours(1));
await sut.UpsertAsync(draft, CancellationToken.None);
var otherOwner = (long.Parse(draft.OwnerId, System.Globalization.CultureInfo.InvariantCulture) + 1)
.ToString(System.Globalization.CultureInfo.InvariantCulture);
var loaded = await sut.GetActiveAsync(draft.Platform, otherOwner, CancellationToken.None);
Assert.Null(loaded);
}
[Fact]
public async Task DeleteExpiredAsync_DeletesOnlyExpired()
{
var connectionString = await fixture.CreateSchemaDatabaseAsync();
await using var dataSource = NpgsqlDataSource.Create(connectionString);
var sut = new WizardDraftRepository(dataSource);
var fresh = NewDraft("Type", DateTimeOffset.UtcNow.AddHours(1));
var stale = NewDraft("Type", DateTimeOffset.UtcNow.AddMinutes(-1));
stale.Id = Guid.NewGuid();
await sut.UpsertAsync(fresh, CancellationToken.None);
await sut.UpsertAsync(stale, CancellationToken.None);
var deleted = await sut.DeleteExpiredAsync(CancellationToken.None);
Assert.Equal(1, deleted);
var loadedFresh = await sut.GetActiveAsync(fresh.Platform, fresh.OwnerId, CancellationToken.None);
Assert.NotNull(loadedFresh);
}
private static WizardDraft NewDraft(string step, DateTimeOffset expiresAt) => new()
{
Id = Guid.NewGuid(),
ChatId = "42",
MessageThreadId = null,
OwnerId = "100",
Platform = "Telegram",
Step = step,
PayloadJson = "{}",
CreatedAt = DateTimeOffset.UtcNow,
UpdatedAt = DateTimeOffset.UtcNow,
ExpiresAt = expiresAt,
};
}