From 320ec18ab07d3665f8251a9efc30de3c21be3b2a Mon Sep 17 00:00:00 2001 From: Toutsu Date: Tue, 9 Jun 2026 13:15:15 +0300 Subject: [PATCH 1/2] fix(bot): IsComplete must not flag null MaxPlayers as missing (no-limit) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit After 3.9.6 fixed long-polling, the bot finally reaches the final '✅ Создать' step. Users pressing '♾ Без лимита' on the Capacity step get a valid payload where Single.MaxPlayers = null (the legitimate no-limit choice from GameCreationWizard.ApplyCapacityChoice 'no_limit'), but CreateSessionHandler.IsComplete then reports 'лимит мест' as missing, blocking session creation. This regression existed since 3.9.3 (when 'no_limit' was added) but stayed invisible because 3.9.4 and 3.9.5 never reached SubmitDraft (libgssapi-krb5 missing → long-polling hung). Once 3.9.6 restored polling, the bug surfaced immediately. Fix: drop the null-MaxPlayers check from IsComplete for Single type. Null is a valid 'no limit' state and must pass through to BuildCommands → shared handler, which already accepts null MaxPlayers correctly. Closes #131. Bump version 3.9.6 -> 3.9.7 --- .gitea/workflows/deploy.yml | 2 +- Directory.Build.props | 2 +- compose.yaml | 6 +-- .../CreateSession/CreateSessionHandler.cs | 6 ++- .../Components/Layout/NavMenu.razor | 2 +- ...eateSessionHandlerSubmitValidationTests.cs | 43 +++++++++++++++++++ 6 files changed, 54 insertions(+), 7 deletions(-) diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index a30a680..eaa389a 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -6,7 +6,7 @@ on: - main env: - VERSION: 3.9.6 + VERSION: 3.9.7 jobs: # ЧАСТЬ 1: Собираем образы и кладем в Gitea (чтобы делиться с ребятами) diff --git a/Directory.Build.props b/Directory.Build.props index 02dc18a..da1a835 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,6 @@ - 3.9.6 + 3.9.7 net10.0 preview enable diff --git a/compose.yaml b/compose.yaml index b8cecdd..a790505 100644 --- a/compose.yaml +++ b/compose.yaml @@ -49,7 +49,7 @@ services: crond -f bot: - image: git.codeanddice.ru/toutsu/gmrelay-bot:3.9.6 + image: git.codeanddice.ru/toutsu/gmrelay-bot:3.9.7 restart: always depends_on: db: @@ -67,7 +67,7 @@ services: retries: 3 discord: - image: git.codeanddice.ru/toutsu/gmrelay-discord-bot:3.9.6 + image: git.codeanddice.ru/toutsu/gmrelay-discord-bot:3.9.7 restart: always depends_on: db: @@ -86,7 +86,7 @@ services: retries: 3 web: - image: git.codeanddice.ru/toutsu/gmrelay-web:3.9.6 + image: git.codeanddice.ru/toutsu/gmrelay-web:3.9.7 restart: always depends_on: db: diff --git a/src/GmRelay.Bot/Features/Sessions/CreateSession/CreateSessionHandler.cs b/src/GmRelay.Bot/Features/Sessions/CreateSession/CreateSessionHandler.cs index d16db2f..70cf0c6 100644 --- a/src/GmRelay.Bot/Features/Sessions/CreateSession/CreateSessionHandler.cs +++ b/src/GmRelay.Bot/Features/Sessions/CreateSession/CreateSessionHandler.cs @@ -229,7 +229,11 @@ public sealed class CreateSessionHandler if (p.Type == WizardCreationType.Single) { if (p.Single?.ScheduledAt is null) missingFields.Add("дата/время"); - if (p.Single?.MaxPlayers is null) missingFields.Add("лимит мест"); + // MaxPlayers = null is a valid "♾ Без лимита" choice + // (see GameCreationWizard.ApplyCapacityChoice "no_limit"). Only a + // missing wizard step on the numeric-input path is a real defect; + // a non-null 0 also blocks (zero players is never what the user + // asked for). Treat null as "no limit" and accept it. } else { diff --git a/src/GmRelay.Web/Components/Layout/NavMenu.razor b/src/GmRelay.Web/Components/Layout/NavMenu.razor index d765c6c..b7b076d 100644 --- a/src/GmRelay.Web/Components/Layout/NavMenu.razor +++ b/src/GmRelay.Web/Components/Layout/NavMenu.razor @@ -82,7 +82,7 @@ - + diff --git a/tests/GmRelay.Bot.Tests/Features/Sessions/CreateSession/Wizard/CreateSessionHandlerSubmitValidationTests.cs b/tests/GmRelay.Bot.Tests/Features/Sessions/CreateSession/Wizard/CreateSessionHandlerSubmitValidationTests.cs index 134a935..81fb996 100644 --- a/tests/GmRelay.Bot.Tests/Features/Sessions/CreateSession/Wizard/CreateSessionHandlerSubmitValidationTests.cs +++ b/tests/GmRelay.Bot.Tests/Features/Sessions/CreateSession/Wizard/CreateSessionHandlerSubmitValidationTests.cs @@ -146,4 +146,47 @@ public sealed class CreateSessionHandlerSubmitValidationTests 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.Instance); + + var payload = new WizardPayload + { + Type = WizardCreationType.Single, + Title = "T", + System = "Dnd5e", + DurationMinutes = 240, + 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); + } } From 37ed697696ed21b8bb8fbc0c537c74a49a22f06a Mon Sep 17 00:00:00 2001 From: Toutsu Date: Tue, 9 Jun 2026 13:33:53 +0300 Subject: [PATCH 2/2] fix(bot): trim misleading comment in IsComplete The previous comment claimed a 0 MaxPlayers would also be blocked, but the code never actually enforced that. The wizard's text-input path already guarantees cap >= MinCapacity, so 0 is unreachable. Drop the inaccurate half-sentence to keep the comment faithful to the code. --- .../Features/Sessions/CreateSession/CreateSessionHandler.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/GmRelay.Bot/Features/Sessions/CreateSession/CreateSessionHandler.cs b/src/GmRelay.Bot/Features/Sessions/CreateSession/CreateSessionHandler.cs index 70cf0c6..e3190f8 100644 --- a/src/GmRelay.Bot/Features/Sessions/CreateSession/CreateSessionHandler.cs +++ b/src/GmRelay.Bot/Features/Sessions/CreateSession/CreateSessionHandler.cs @@ -230,10 +230,7 @@ public sealed class CreateSessionHandler { if (p.Single?.ScheduledAt is null) missingFields.Add("дата/время"); // MaxPlayers = null is a valid "♾ Без лимита" choice - // (see GameCreationWizard.ApplyCapacityChoice "no_limit"). Only a - // missing wizard step on the numeric-input path is a real defect; - // a non-null 0 also blocks (zero players is never what the user - // asked for). Treat null as "no limit" and accept it. + // (see GameCreationWizard.ApplyCapacityChoice "no_limit"). } else {