review fixes: complete success path, pool capacity navigation, time parser tests
PR Checks / test-and-build (pull_request) Successful in 24m27s

- DiscordWizardSubmitter.SubmitAsync: confirm success, delete draft, clear context.
- GameCreationWizard: pool free-text duration now advances to Capacity.
- PreviousStep(Capacity) returns PoolSystemDuration for pools.
- Remove unused optional IPlatformMessenger/NpgsqlDataSource from submitter.
- Add DiscordTimeParserTests preserving ParseTimeInput coverage.
This commit is contained in:
2026-06-15 18:37:23 +03:00
parent 9709d09b15
commit e0602052ea
4 changed files with 131 additions and 10 deletions
@@ -0,0 +1,77 @@
using GmRelay.DiscordBot.Features.Sessions;
namespace GmRelay.Bot.Tests.Discord;
public sealed class DiscordTimeParserTests
{
[Fact]
public void ParseTimeInput_ShouldTreatInputAsMoscowTime()
{
var future = DateTimeOffset.UtcNow.AddDays(7);
var result = DiscordTimeParser.ParseTimeInput(
future.ToString("yyyy-MM-dd '15:00'", System.Globalization.CultureInfo.InvariantCulture));
Assert.True(result.IsSuccess);
// 15:00 MSK = 12:00 UTC
Assert.Equal(12, result.Value.Hour);
Assert.Equal(0, result.Value.Minute);
Assert.Equal(TimeSpan.Zero, result.Value.Offset);
}
[Fact]
public void ParseTimeInput_ShouldParseDiscordDateFormat()
{
var expected = FutureDateAt1930();
var result = DiscordTimeParser.ParseTimeInput(
expected.ToString("yyyy-MM-dd HH:mm", System.Globalization.CultureInfo.InvariantCulture));
Assert.True(result.IsSuccess);
Assert.Equal(expected.Year, result.Value.Year);
Assert.Equal(expected.Month, result.Value.Month);
Assert.Equal(expected.Day, result.Value.Day);
// Input is treated as Moscow time; 19:30 MSK = 16:30 UTC
Assert.Equal(16, result.Value.Hour);
Assert.Equal(30, result.Value.Minute);
}
[Fact]
public void ParseTimeInput_ShouldRejectPastDate()
{
var result = DiscordTimeParser.ParseTimeInput("2020-01-01 00:00");
Assert.False(result.IsSuccess);
}
[Fact]
public void ParseTimeInput_ShouldParseRussianDateFormat()
{
var expected = FutureDateAt1930();
var result = DiscordTimeParser.ParseTimeInput(
expected.ToString("dd.MM.yyyy HH:mm", System.Globalization.CultureInfo.InvariantCulture));
Assert.True(result.IsSuccess);
Assert.Equal(expected.Year, result.Value.Year);
Assert.Equal(expected.Month, result.Value.Month);
Assert.Equal(expected.Day, result.Value.Day);
}
[Fact]
public void ParseTimeInput_ShouldRejectInvalidFormat()
{
var result = DiscordTimeParser.ParseTimeInput("not-a-date");
Assert.False(result.IsSuccess);
Assert.NotNull(result.Error);
}
private static DateTimeOffset FutureDateAt1930()
{
var future = DateTimeOffset.UtcNow.AddDays(7);
return new DateTimeOffset(
future.Year,
future.Month,
future.Day,
19,
30,
0,
TimeSpan.Zero);
}
}
@@ -93,6 +93,48 @@ public sealed class GameCreationWizardStepTransitionsTests
Assert.Equal(10, maxPlayers.GetInt32());
}
[Fact]
public async Task PoolSystemDuration_FreeTextDuration_AdvancesToCapacity()
{
var wizard = BuildWizard(out var drafts, out _);
var payload = new WizardPayload
{
Type = WizardCreationType.Pool,
Title = "Pool",
System = "Dnd5e",
};
var draft = NewDraft(WizardStepNames.PoolSystemDuration, payload);
drafts.Seed(draft);
await wizard.HandleInteractionAsync(TextInteraction("4", ownerId: draft.OwnerId), draft, CancellationToken.None);
Assert.Equal(WizardStepNames.Capacity, draft.Step);
using var doc = JsonDocument.Parse(draft.PayloadJson);
var root = doc.RootElement;
Assert.True(root.TryGetProperty("durationMinutes", out var dur));
Assert.Equal(240, dur.GetInt32());
}
[Fact]
public async Task Back_FromPoolCapacity_GoesToPoolSystemDuration()
{
var wizard = BuildWizard(out var drafts, out _);
var payload = new WizardPayload
{
Type = WizardCreationType.Pool,
Title = "Pool",
System = "Dnd5e",
DurationMinutes = 240,
};
var draft = NewDraft(WizardStepNames.Capacity, payload);
drafts.Seed(draft);
var data = WizardCallbackData.Back();
await wizard.HandleInteractionAsync(CallbackInteraction(data, ownerId: draft.OwnerId), draft, CancellationToken.None);
Assert.Equal(WizardStepNames.PoolSystemDuration, draft.Step);
}
[Theory]
[InlineData("waitlist:on")]
[InlineData("waitlist:off")]