Files
GmRelayBot/tests/GmRelay.Bot.Tests/Features/Sessions/CreateSession/Wizard/CreateSessionHandlerSubmitValidationTests.cs
T
Toutsu 014b5edd31
PR Checks / test-and-build (pull_request) Successful in 15m52s
feat(bot): add online/offline wizard locations
Add format and location steps to the Telegram /newsession wizard, persist offline addresses in sessions.location_address, and render online links/offline addresses in schedule messages.

Bump version to 3.10.0.
2026-06-10 11:29:25 +03:00

203 lines
7.2 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 the validation gates inside
/// <see cref="CreateSessionHandler.SubmitDraftAsync"/>. We never reach the
/// shared handler in any of these tests, so the shared dependency is
/// passed as <c>null!</c> — a NRE on that branch would itself prove the
/// validation did not fire.
/// </summary>
public sealed class CreateSessionHandlerSubmitValidationTests
{
[Fact]
public async Task SubmitDraftAsync_MissingVisibility_EditsMessageNamingVisibility()
{
var drafts = new FakeWizardDraftRepository();
var messenger = new FakeWizardMessenger();
var sut = new CreateSessionHandler(
drafts,
shared: null!,
messenger,
NullLogger<CreateSessionHandler>.Instance);
// All required fields set except Visibility.
var payload = new WizardPayload
{
Type = WizardCreationType.Single,
Title = "T",
System = "Dnd5e",
DurationMinutes = 240,
Format = WizardSessionFormat.Online,
JoinLink = "https://vtt.example/game",
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);
}
[Fact]
public async Task SubmitDraftAsync_MissingSystem_EditsMessageNamingSystem()
{
var drafts = new FakeWizardDraftRepository();
var messenger = new FakeWizardMessenger();
var sut = new CreateSessionHandler(
drafts,
shared: null!,
messenger,
NullLogger<CreateSessionHandler>.Instance);
// All required fields set except System.
var payload = new WizardPayload
{
Type = WizardCreationType.Single,
Title = "T",
DurationMinutes = 240,
Format = WizardSessionFormat.Online,
JoinLink = "https://vtt.example/game",
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);
}
[Fact]
public async Task SubmitDraftAsync_MissingDateTimeForSingleType_EditsMessageNamingDateTime()
{
var drafts = new FakeWizardDraftRepository();
var messenger = new FakeWizardMessenger();
var sut = new CreateSessionHandler(
drafts,
shared: null!,
messenger,
NullLogger<CreateSessionHandler>.Instance);
// All required fields set except ScheduledAt for Single type.
var payload = new WizardPayload
{
Type = WizardCreationType.Single,
Title = "T",
System = "Dnd5e",
DurationMinutes = 240,
Format = WizardSessionFormat.Online,
JoinLink = "https://vtt.example/game",
Visibility = WizardVisibility.Public,
Single = new WizardSingleInput { 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);
}
[Fact]
public async Task SubmitDraftAsync_EmptyPool_EditsMessageNamingSlots()
{
var drafts = new FakeWizardDraftRepository();
var messenger = new FakeWizardMessenger();
var sut = new CreateSessionHandler(
drafts,
shared: null!,
messenger,
NullLogger<CreateSessionHandler>.Instance);
// Pool type with no slots at all.
var payload = new WizardPayload
{
Type = WizardCreationType.Pool,
Title = "P",
System = "Dnd5e",
DurationMinutes = 240,
Format = WizardSessionFormat.Online,
JoinLink = "https://vtt.example/game",
Visibility = WizardVisibility.Public,
Pool = new WizardPoolInput(),
};
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);
}
[Fact]
public async Task SubmitDraftAsync_SingleWithNoLimit_DoesNotReportMaxPlayersAsMissing()
{
// Regression for #131: pressing "♾ Без лимита" sets MaxPlayers = null.
// IsComplete must NOT flag that as a missing field; null means
// "no player limit" and is a valid final state.
var drafts = new FakeWizardDraftRepository();
var messenger = new FakeWizardMessenger();
var sut = new CreateSessionHandler(
drafts,
shared: null!,
messenger,
NullLogger<CreateSessionHandler>.Instance);
var payload = new WizardPayload
{
Type = WizardCreationType.Single,
Title = "T",
System = "Dnd5e",
DurationMinutes = 240,
Format = WizardSessionFormat.Online,
JoinLink = "https://vtt.example/game",
Visibility = WizardVisibility.Public,
Single = new WizardSingleInput
{
ScheduledAt = DateTimeOffset.UtcNow.AddDays(7),
MaxPlayers = null,
},
};
var draft = NewDraft(WizardStepNames.Confirm, payload);
drafts.Seed(draft);
await sut.SubmitDraftAsync(draft, CancellationToken.None);
// Validation must let the no-limit payload through. The shared
// handler is null, so anything that reached the database call would
// throw a NullReferenceException — that is caught by the retry
// path and reported as a "💥 Ошибка:" edit, not a missing-fields
// edit. Therefore we assert that NO edit mentions a missing field.
Assert.NotEmpty(messenger.Edits);
var lastEdit = messenger.Edits[^1].Text;
Assert.DoesNotContain("Не заполнены", lastEdit, StringComparison.OrdinalIgnoreCase);
}
}