Files
GmRelayBot/tests/GmRelay.Bot.Tests/Features/Sessions/CreateSession/Wizard/CreateSessionHandlerSubmitMissingFieldsTests.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

79 lines
3.0 KiB
C#

using System;
using System.Threading;
using System.Threading.Tasks;
using GmRelay.Bot.Features.Sessions.CreateSession;
using GmRelay.Shared.Features.Sessions.CreateSession.Wizard;
using Microsoft.Extensions.Logging.Abstractions;
using static GmRelay.Bot.Tests.Features.Sessions.CreateSession.Wizard.WizardTestFakes;
namespace GmRelay.Bot.Tests.Features.Sessions.CreateSession.Wizard;
/// <summary>
/// Verifies that <see cref="CreateSessionHandler.SubmitDraftAsync"/> bails
/// out gracefully when the wizard payload is missing required fields. The
/// missing-fields path returns before the shared handler is ever called,
/// so we pass <c>null!</c> for the shared dependency — a NRE on that
/// branch would itself prove the validation did not fire.
/// </summary>
public sealed class CreateSessionHandlerSubmitMissingFieldsTests
{
[Fact]
public async Task SubmitDraftAsync_EmptyPayload_EditsMessageWithMissingFields()
{
var drafts = new FakeWizardDraftRepository();
var messenger = new FakeWizardMessenger();
var sut = new CreateSessionHandler(
drafts,
shared: null!, // missing-fields path returns before touching the shared handler
messenger,
NullLogger<CreateSessionHandler>.Instance);
// Empty payload → every required field is missing.
var draft = NewDraft(WizardStepNames.Confirm, new WizardPayload());
drafts.Seed(draft);
await sut.SubmitDraftAsync(draft, CancellationToken.None);
// The wizard message is edited to surface the missing-field error.
Assert.Single(messenger.Edits);
var edit = messenger.Edits[0];
Assert.Equal(long.Parse(draft.ChatId, System.Globalization.CultureInfo.InvariantCulture), edit.ChatId);
Assert.Contains("Не заполнены", edit.Text, StringComparison.OrdinalIgnoreCase);
}
[Fact]
public async Task SubmitDraftAsync_MissingTitleOnly_EditsMessageNamingTitle()
{
var drafts = new FakeWizardDraftRepository();
var messenger = new FakeWizardMessenger();
var sut = new CreateSessionHandler(
drafts,
shared: null!,
messenger,
NullLogger<CreateSessionHandler>.Instance);
// All required fields set except Title.
var payload = new WizardPayload
{
Type = WizardCreationType.Single,
System = "Dnd5e",
DurationMinutes = 240,
Visibility = WizardVisibility.Public,
Single = new WizardSingleInput
{
ScheduledAt = DateTimeOffset.UtcNow.AddDays(7),
MaxPlayers = 4,
},
};
var draft = NewDraft(WizardStepNames.Confirm, payload);
drafts.Seed(draft);
await sut.SubmitDraftAsync(draft, CancellationToken.None);
Assert.Single(messenger.Edits);
Assert.Contains("название", messenger.Edits[0].Text, StringComparison.OrdinalIgnoreCase);
}
}