97 lines
3.9 KiB
C#
97 lines
3.9 KiB
C#
using System.Threading;
|
|
using System.Threading.Tasks;
|
|
using GmRelay.Bot.Features.Sessions.CreateSession.Wizard;
|
|
using GmRelay.Bot.Infrastructure.Telegram;
|
|
using Microsoft.Extensions.Configuration;
|
|
using Microsoft.Extensions.Logging.Abstractions;
|
|
using NSubstitute;
|
|
using Telegram.Bot;
|
|
using Telegram.Bot.Requests.Abstractions;
|
|
using Telegram.Bot.Types;
|
|
using BotCreateSessionHandler = GmRelay.Bot.Features.Sessions.CreateSession.CreateSessionHandler;
|
|
using static GmRelay.Bot.Tests.Features.Sessions.CreateSession.Wizard.WizardTestFakes;
|
|
|
|
namespace GmRelay.Bot.Tests.Features.Sessions.CreateSession.Wizard;
|
|
|
|
/// <summary>
|
|
/// When the user sends <c>/newsession</c> while a non-expired draft already
|
|
/// exists, the router delegates the update to the wizard (the wizard owns
|
|
/// every update while a draft is active). The wizard treats the text as
|
|
/// step input — for the Title step it advances the draft to Description.
|
|
/// This is the observable contract that this test pins down.
|
|
/// </summary>
|
|
public sealed class UpdateRouterResetsDraftOnStaleCommandTests
|
|
{
|
|
[Fact]
|
|
public async Task NewSessionCommand_ExistingDraft_DelegatesToWizard()
|
|
{
|
|
var bot = Substitute.For<ITelegramBotClient>();
|
|
var (sut, drafts, messenger) = BuildRouter(bot);
|
|
|
|
var draft = NewDraft(WizardStepNames.Title);
|
|
drafts.Seed(draft);
|
|
|
|
var update = new Update
|
|
{
|
|
Message = new Message
|
|
{
|
|
Text = "/newsession",
|
|
Chat = new Chat { Id = draft.ChatId },
|
|
From = new User { Id = draft.OwnerTelegramId, FirstName = "GM" },
|
|
},
|
|
};
|
|
|
|
await sut.RouteAsync(update, CancellationToken.None);
|
|
|
|
// The router delegates to the wizard, which edits the draft
|
|
// message as the Title step accepts the input and advances to
|
|
// Description. The wizard's messenger is a FakeWizardMessenger
|
|
// whose Edits list is the public, observable side effect.
|
|
Assert.NotEmpty(messenger.Edits);
|
|
// The bot.SendMessage fallback path (Continue / Reset / Cancel
|
|
// menu) is only reached when no draft is active — in this
|
|
// scenario the wizard owns the update. We assert it was NOT
|
|
// taken here.
|
|
await bot.DidNotReceiveWithAnyArgs().SendRequest(default(IRequest<Message>)!, default);
|
|
}
|
|
|
|
private static (UpdateRouter sut, FakeWizardDraftRepository drafts, FakeWizardMessenger messenger) BuildRouter(
|
|
ITelegramBotClient bot)
|
|
{
|
|
var drafts = new FakeWizardDraftRepository();
|
|
var messenger = new FakeWizardMessenger();
|
|
var wizard = new GameCreationWizard(drafts, messenger, NullLogger<GameCreationWizard>.Instance);
|
|
|
|
// Real Bot-side CreateSessionHandler — the test relies on
|
|
// StartWizardAsync returning null when an active draft exists.
|
|
// We pass null! for the shared handler since the active-draft
|
|
// path never touches it.
|
|
var createSessionHandler = new BotCreateSessionHandler(
|
|
drafts,
|
|
shared: null!,
|
|
messenger,
|
|
NullLogger<BotCreateSessionHandler>.Instance);
|
|
|
|
var sut = new UpdateRouter(
|
|
rsvpHandler: null!,
|
|
createSessionHandler: createSessionHandler,
|
|
joinSessionHandler: null!,
|
|
leaveSessionHandler: null!,
|
|
promoteWaitlistedPlayerHandler: null!,
|
|
cancelSessionHandler: null!,
|
|
deleteSessionHandler: null!,
|
|
listSessionsHandler: null!,
|
|
exportCalendarHandler: null!,
|
|
initiateRescheduleHandler: null!,
|
|
rescheduleTimeInputHandler: null!,
|
|
rescheduleVoteHandler: null!,
|
|
wizard: wizard,
|
|
drafts: drafts,
|
|
bot: bot,
|
|
configuration: Substitute.For<IConfiguration>(),
|
|
logger: NullLogger<UpdateRouter>.Instance);
|
|
|
|
return (sut, drafts, messenger);
|
|
}
|
|
}
|