From c0c8f852d2884046aeaa86a21c9d7aa2cc28f62a Mon Sep 17 00:00:00 2001 From: Toutsu Date: Sun, 10 May 2026 17:55:24 +0300 Subject: [PATCH] =?UTF-8?q?feat(#19):=20=D0=B4=D0=BE=D0=B1=D0=B0=D0=B2?= =?UTF-8?q?=D0=B8=D1=82=D1=8C=20=D1=81=D1=81=D1=8B=D0=BB=D0=BA=D1=83=20?= =?UTF-8?q?=D0=BD=D0=B0=20=D0=B8=D0=B3=D1=80=D1=83=20=D0=B2=20=D0=BA=D0=B0?= =?UTF-8?q?=D1=80=D1=82=D0=BE=D1=87=D0=BA=D1=83=20=D0=B1=D0=B0=D1=82=D1=87?= =?UTF-8?q?=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - SessionBatchDto: добавлено поле JoinLink - SessionViewItem: добавлено поле JoinLink - SessionBatchViewBuilder: прокидывание JoinLink из DTO в ViewModel - CreateSessionHandler, SessionService: обновлены все вызовы конструктора - TelegramSessionBatchRenderer (Bot + Web): рендеринг ссылки в карточке - Добавлены тесты на наличие ссылки в рендере - Все 7 SQL-запросов, загружающих SessionBatchDto, обновлены с join_link AS JoinLink - Бамп версии до 1.11.0 Co-Authored-By: Claude Opus 4.7 --- .gitea/workflows/deploy.yml | 2 +- Directory.Build.props | 2 +- compose.yaml | 4 ++-- .../CreateSession/CancelSessionHandler.cs | 2 +- .../CreateSession/CreateSessionHandler.cs | 2 +- .../Sessions/CreateSession/JoinSessionHandler.cs | 2 +- .../CreateSession/LeaveSessionHandler.cs | 3 ++- .../PromoteWaitlistedPlayerHandler.cs | 3 ++- .../HandleRescheduleTimeInputHandler.cs | 2 +- .../RescheduleVotingDeadlineService.cs | 2 +- .../Telegram/TelegramSessionBatchRenderer.cs | 5 +++++ src/GmRelay.Shared/Rendering/SessionBatchDto.cs | 2 +- .../Rendering/SessionBatchViewBuilder.cs | 1 + .../Rendering/SessionBatchViewModel.cs | 1 + src/GmRelay.Web/Components/Layout/NavMenu.razor | 2 +- src/GmRelay.Web/Services/SessionService.cs | 6 +++--- .../Services/TelegramSessionBatchRenderer.cs | 5 +++++ .../Landing/TelegramLandingPromisesSmokeTests.cs | 11 ++++++++--- .../Rendering/SessionBatchViewBuilderTests.cs | 16 ++++++++-------- .../TelegramSessionBatchRendererTests.cs | 13 ++++++++----- 20 files changed, 54 insertions(+), 32 deletions(-) diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index 0e048e7..68cf92a 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -6,7 +6,7 @@ on: - main env: - VERSION: 1.10.6 + VERSION: 1.11.0 jobs: # ЧАСТЬ 1: Собираем образы и кладем в Gitea (чтобы делиться с ребятами) diff --git a/Directory.Build.props b/Directory.Build.props index ec0b1ed..bee97ae 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,6 @@ - 1.10.6 + 1.11.0 net10.0 preview enable diff --git a/compose.yaml b/compose.yaml index fa30c1b..09c5c89 100644 --- a/compose.yaml +++ b/compose.yaml @@ -17,7 +17,7 @@ services: retries: 10 bot: - image: git.codeanddice.ru/toutsu/gmrelay-bot:1.10.6 + image: git.codeanddice.ru/toutsu/gmrelay-bot:1.11.0 restart: always depends_on: db: @@ -30,7 +30,7 @@ services: - gmrelay web: - image: git.codeanddice.ru/toutsu/gmrelay-web:1.10.6 + image: git.codeanddice.ru/toutsu/gmrelay-web:1.11.0 restart: always depends_on: db: diff --git a/src/GmRelay.Bot/Features/Sessions/CreateSession/CancelSessionHandler.cs b/src/GmRelay.Bot/Features/Sessions/CreateSession/CancelSessionHandler.cs index d0b40db..9d3e428 100644 --- a/src/GmRelay.Bot/Features/Sessions/CreateSession/CancelSessionHandler.cs +++ b/src/GmRelay.Bot/Features/Sessions/CreateSession/CancelSessionHandler.cs @@ -69,7 +69,7 @@ public sealed class CancelSessionHandler( // 3. Загружаем весь батч для перерисовки var batchSessions = await connection.QueryAsync( - @"SELECT id as SessionId, scheduled_at as ScheduledAt, status as Status, max_players as MaxPlayers + @"SELECT id as SessionId, scheduled_at as ScheduledAt, status as Status, max_players as MaxPlayers, join_link as JoinLink FROM sessions WHERE batch_id = @BatchId ORDER BY scheduled_at", diff --git a/src/GmRelay.Bot/Features/Sessions/CreateSession/CreateSessionHandler.cs b/src/GmRelay.Bot/Features/Sessions/CreateSession/CreateSessionHandler.cs index cf442b8..a55e2b5 100644 --- a/src/GmRelay.Bot/Features/Sessions/CreateSession/CreateSessionHandler.cs +++ b/src/GmRelay.Bot/Features/Sessions/CreateSession/CreateSessionHandler.cs @@ -178,7 +178,7 @@ public sealed class CreateSessionHandler( }, transaction); - sessions.Add(new SessionBatchDto(sessionId, scheduledAt.UtcDateTime, SessionStatus.Planned, parseResult.MaxPlayers)); + sessions.Add(new SessionBatchDto(sessionId, scheduledAt.UtcDateTime, SessionStatus.Planned, parseResult.MaxPlayers, link)); } await transaction.CommitAsync(cancellationToken); diff --git a/src/GmRelay.Bot/Features/Sessions/CreateSession/JoinSessionHandler.cs b/src/GmRelay.Bot/Features/Sessions/CreateSession/JoinSessionHandler.cs index 27aa360..22187f1 100644 --- a/src/GmRelay.Bot/Features/Sessions/CreateSession/JoinSessionHandler.cs +++ b/src/GmRelay.Bot/Features/Sessions/CreateSession/JoinSessionHandler.cs @@ -115,7 +115,7 @@ public sealed class JoinSessionHandler( // Загружаем весь батч для перерисовки var batchSessions = await connection.QueryAsync( - @"SELECT id as SessionId, scheduled_at as ScheduledAt, status as Status, max_players as MaxPlayers + @"SELECT id as SessionId, scheduled_at as ScheduledAt, status as Status, max_players as MaxPlayers, join_link as JoinLink FROM sessions WHERE batch_id = @BatchId ORDER BY scheduled_at", diff --git a/src/GmRelay.Bot/Features/Sessions/CreateSession/LeaveSessionHandler.cs b/src/GmRelay.Bot/Features/Sessions/CreateSession/LeaveSessionHandler.cs index d8c4258..e97024c 100644 --- a/src/GmRelay.Bot/Features/Sessions/CreateSession/LeaveSessionHandler.cs +++ b/src/GmRelay.Bot/Features/Sessions/CreateSession/LeaveSessionHandler.cs @@ -157,7 +157,8 @@ public sealed class LeaveSessionHandler( SELECT id AS SessionId, scheduled_at AS ScheduledAt, status AS Status, - max_players AS MaxPlayers + max_players AS MaxPlayers, + join_link AS JoinLink FROM sessions WHERE batch_id = @BatchId ORDER BY scheduled_at diff --git a/src/GmRelay.Bot/Features/Sessions/CreateSession/PromoteWaitlistedPlayerHandler.cs b/src/GmRelay.Bot/Features/Sessions/CreateSession/PromoteWaitlistedPlayerHandler.cs index 2850de6..f4f24b0 100644 --- a/src/GmRelay.Bot/Features/Sessions/CreateSession/PromoteWaitlistedPlayerHandler.cs +++ b/src/GmRelay.Bot/Features/Sessions/CreateSession/PromoteWaitlistedPlayerHandler.cs @@ -137,7 +137,8 @@ public sealed class PromoteWaitlistedPlayerHandler( SELECT id AS SessionId, scheduled_at AS ScheduledAt, status AS Status, - max_players AS MaxPlayers + max_players AS MaxPlayers, + join_link AS JoinLink FROM sessions WHERE batch_id = @BatchId ORDER BY scheduled_at diff --git a/src/GmRelay.Bot/Features/Sessions/RescheduleSession/HandleRescheduleTimeInputHandler.cs b/src/GmRelay.Bot/Features/Sessions/RescheduleSession/HandleRescheduleTimeInputHandler.cs index 9e12054..e66efb0 100644 --- a/src/GmRelay.Bot/Features/Sessions/RescheduleSession/HandleRescheduleTimeInputHandler.cs +++ b/src/GmRelay.Bot/Features/Sessions/RescheduleSession/HandleRescheduleTimeInputHandler.cs @@ -357,7 +357,7 @@ public sealed class HandleRescheduleTimeInputHandler( await using var conn = await dataSource.OpenConnectionAsync(ct); var batchSessions = (await conn.QueryAsync( - "SELECT id AS SessionId, scheduled_at AS ScheduledAt, status AS Status, max_players AS MaxPlayers FROM sessions WHERE batch_id = @BatchId ORDER BY scheduled_at", + "SELECT id AS SessionId, scheduled_at AS ScheduledAt, status AS Status, max_players AS MaxPlayers, join_link AS JoinLink FROM sessions WHERE batch_id = @BatchId ORDER BY scheduled_at", new { proposal.BatchId })).ToList(); var batchParticipants = (await conn.QueryAsync( diff --git a/src/GmRelay.Bot/Features/Sessions/RescheduleSession/RescheduleVotingDeadlineService.cs b/src/GmRelay.Bot/Features/Sessions/RescheduleSession/RescheduleVotingDeadlineService.cs index 3a4a847..1a498ce 100644 --- a/src/GmRelay.Bot/Features/Sessions/RescheduleSession/RescheduleVotingDeadlineService.cs +++ b/src/GmRelay.Bot/Features/Sessions/RescheduleSession/RescheduleVotingDeadlineService.cs @@ -286,7 +286,7 @@ public sealed class RescheduleVotingDeadlineService( await using var connection = await dataSource.OpenConnectionAsync(ct); var batchSessions = (await connection.QueryAsync( - "SELECT id AS SessionId, scheduled_at AS ScheduledAt, status AS Status, max_players AS MaxPlayers FROM sessions WHERE batch_id = @BatchId ORDER BY scheduled_at", + "SELECT id AS SessionId, scheduled_at AS ScheduledAt, status AS Status, max_players AS MaxPlayers, join_link AS JoinLink FROM sessions WHERE batch_id = @BatchId ORDER BY scheduled_at", new { proposal.BatchId })).ToList(); var batchParticipants = (await connection.QueryAsync( diff --git a/src/GmRelay.Bot/Infrastructure/Telegram/TelegramSessionBatchRenderer.cs b/src/GmRelay.Bot/Infrastructure/Telegram/TelegramSessionBatchRenderer.cs index 041902c..4089c0f 100644 --- a/src/GmRelay.Bot/Infrastructure/Telegram/TelegramSessionBatchRenderer.cs +++ b/src/GmRelay.Bot/Infrastructure/Telegram/TelegramSessionBatchRenderer.cs @@ -21,6 +21,11 @@ public static class TelegramSessionBatchRenderer ? $"👥 Места: {session.ActivePlayerCount}/{session.MaxPlayers.Value}\n" : $"👥 Игроки ({session.ActivePlayerCount}):\n"; + if (!string.IsNullOrEmpty(session.JoinLink)) + { + messageText += $"🔗 Ссылка на игру\n"; + } + if (session.ActivePlayers.Count > 0) { messageText += string.Join("\n", session.ActivePlayers.Select(p => diff --git a/src/GmRelay.Shared/Rendering/SessionBatchDto.cs b/src/GmRelay.Shared/Rendering/SessionBatchDto.cs index 658ed43..3ff4a2c 100644 --- a/src/GmRelay.Shared/Rendering/SessionBatchDto.cs +++ b/src/GmRelay.Shared/Rendering/SessionBatchDto.cs @@ -1,4 +1,4 @@ namespace GmRelay.Shared.Rendering; -public sealed record SessionBatchDto(Guid SessionId, DateTime ScheduledAt, string Status, int? MaxPlayers); +public sealed record SessionBatchDto(Guid SessionId, DateTime ScheduledAt, string Status, int? MaxPlayers, string JoinLink); public sealed record ParticipantBatchDto(Guid SessionId, string DisplayName, string? TelegramUsername, string RegistrationStatus); diff --git a/src/GmRelay.Shared/Rendering/SessionBatchViewBuilder.cs b/src/GmRelay.Shared/Rendering/SessionBatchViewBuilder.cs index f65f5df..2644428 100644 --- a/src/GmRelay.Shared/Rendering/SessionBatchViewBuilder.cs +++ b/src/GmRelay.Shared/Rendering/SessionBatchViewBuilder.cs @@ -38,6 +38,7 @@ public static class SessionBatchViewBuilder session.ScheduledAt, session.Status, session.MaxPlayers, + session.JoinLink, activePlayers.Count, activePlayers, waitlistedPlayers, diff --git a/src/GmRelay.Shared/Rendering/SessionBatchViewModel.cs b/src/GmRelay.Shared/Rendering/SessionBatchViewModel.cs index 95191d5..3782757 100644 --- a/src/GmRelay.Shared/Rendering/SessionBatchViewModel.cs +++ b/src/GmRelay.Shared/Rendering/SessionBatchViewModel.cs @@ -11,6 +11,7 @@ public sealed record SessionViewItem( DateTime ScheduledAt, string Status, int? MaxPlayers, + string JoinLink, int ActivePlayerCount, IReadOnlyList ActivePlayers, IReadOnlyList WaitlistedPlayers, diff --git a/src/GmRelay.Web/Components/Layout/NavMenu.razor b/src/GmRelay.Web/Components/Layout/NavMenu.razor index 7615e2c..8fa634d 100644 --- a/src/GmRelay.Web/Components/Layout/NavMenu.razor +++ b/src/GmRelay.Web/Components/Layout/NavMenu.razor @@ -56,7 +56,7 @@ - + diff --git a/src/GmRelay.Web/Services/SessionService.cs b/src/GmRelay.Web/Services/SessionService.cs index 1542d9e..1524c67 100644 --- a/src/GmRelay.Web/Services/SessionService.cs +++ b/src/GmRelay.Web/Services/SessionService.cs @@ -938,7 +938,7 @@ public sealed class SessionService( }, transaction); - renderedSessions.Add(new SessionBatchDto(sessionId, scheduledAt, SessionStatus.Planned, sourceSession.MaxPlayers)); + renderedSessions.Add(new SessionBatchDto(sessionId, scheduledAt, SessionStatus.Planned, sourceSession.MaxPlayers, batchJoinLink)); } await transaction.CommitAsync(); @@ -1147,7 +1147,7 @@ public sealed class SessionService( }, transaction); - renderedSessions.Add(new SessionBatchDto(sessionId, scheduledAt, SessionStatus.Planned, template.MaxPlayers)); + renderedSessions.Add(new SessionBatchDto(sessionId, scheduledAt, SessionStatus.Planned, template.MaxPlayers, template.JoinLink)); } await transaction.CommitAsync(); @@ -1245,7 +1245,7 @@ public sealed class SessionService( await using var conn = await dataSource.OpenConnectionAsync(); var sessions = (await conn.QueryAsync( - "SELECT id AS SessionId, scheduled_at AS ScheduledAt, status AS Status, max_players AS MaxPlayers FROM sessions WHERE batch_id = @BatchId ORDER BY scheduled_at", + "SELECT id AS SessionId, scheduled_at AS ScheduledAt, status AS Status, max_players AS MaxPlayers, join_link AS JoinLink FROM sessions WHERE batch_id = @BatchId ORDER BY scheduled_at", new { BatchId = batchId })).ToList(); var participants = (await conn.QueryAsync( diff --git a/src/GmRelay.Web/Services/TelegramSessionBatchRenderer.cs b/src/GmRelay.Web/Services/TelegramSessionBatchRenderer.cs index b49c376..6e41396 100644 --- a/src/GmRelay.Web/Services/TelegramSessionBatchRenderer.cs +++ b/src/GmRelay.Web/Services/TelegramSessionBatchRenderer.cs @@ -20,6 +20,11 @@ public static class TelegramSessionBatchRenderer ? $"👥 Места: {session.ActivePlayerCount}/{session.MaxPlayers.Value}\n" : $"👥 Игроки ({session.ActivePlayerCount}):\n"; + if (!string.IsNullOrEmpty(session.JoinLink)) + { + messageText += $"🔗 Ссылка на игру\n"; + } + if (session.ActivePlayers.Count > 0) { messageText += string.Join("\n", session.ActivePlayers.Select(p => diff --git a/tests/GmRelay.Bot.Tests/Features/Landing/TelegramLandingPromisesSmokeTests.cs b/tests/GmRelay.Bot.Tests/Features/Landing/TelegramLandingPromisesSmokeTests.cs index b4f91ad..ba1eb7f 100644 --- a/tests/GmRelay.Bot.Tests/Features/Landing/TelegramLandingPromisesSmokeTests.cs +++ b/tests/GmRelay.Bot.Tests/Features/Landing/TelegramLandingPromisesSmokeTests.cs @@ -147,6 +147,7 @@ public sealed class TelegramLandingPromisesSmokeTests string title, IReadOnlyList scheduledTimes, int? maxPlayers, + string joinLink, SessionNotificationMode notificationMode) { Title = title; @@ -156,7 +157,8 @@ public sealed class TelegramLandingPromisesSmokeTests Guid.NewGuid(), scheduledAt.UtcDateTime, SessionStatus.Planned, - maxPlayers)) + maxPlayers, + joinLink)) .ToList(); } @@ -173,6 +175,7 @@ public sealed class TelegramLandingPromisesSmokeTests parseResult.Title!, parseResult.ScheduledTimes, parseResult.MaxPlayers, + parseResult.Link!, notificationMode); scenario.RenderBatch(); @@ -318,7 +321,8 @@ public sealed class TelegramLandingPromisesSmokeTests session.Id, session.ScheduledAt, session.Status, - session.MaxPlayers)) + session.MaxPlayers, + session.JoinLink)) .ToList(), participants .Select(participant => new ParticipantBatchDto( @@ -371,7 +375,8 @@ public sealed class TelegramLandingPromisesSmokeTests Guid Id, DateTime ScheduledAt, string Status, - int? MaxPlayers) + int? MaxPlayers, + string JoinLink) { public DateTime ScheduledAt { get; set; } = ScheduledAt; } diff --git a/tests/GmRelay.Bot.Tests/Rendering/SessionBatchViewBuilderTests.cs b/tests/GmRelay.Bot.Tests/Rendering/SessionBatchViewBuilderTests.cs index 297fb6f..3c9eba4 100644 --- a/tests/GmRelay.Bot.Tests/Rendering/SessionBatchViewBuilderTests.cs +++ b/tests/GmRelay.Bot.Tests/Rendering/SessionBatchViewBuilderTests.cs @@ -14,9 +14,9 @@ public sealed class SessionBatchViewBuilderTests var sessions = new[] { - new SessionBatchDto(secondSessionId, new DateTime(2026, 4, 27, 18, 0, 0, DateTimeKind.Utc), SessionStatus.Planned, 4), - new SessionBatchDto(cancelledSessionId, new DateTime(2026, 4, 28, 18, 0, 0, DateTimeKind.Utc), SessionStatus.Cancelled, null), - new SessionBatchDto(firstSessionId, new DateTime(2026, 4, 26, 18, 0, 0, DateTimeKind.Utc), SessionStatus.Planned, 2) + new SessionBatchDto(secondSessionId, new DateTime(2026, 4, 27, 18, 0, 0, DateTimeKind.Utc), SessionStatus.Planned, 4, "https://example.com/game"), + new SessionBatchDto(cancelledSessionId, new DateTime(2026, 4, 28, 18, 0, 0, DateTimeKind.Utc), SessionStatus.Cancelled, null, ""), + new SessionBatchDto(firstSessionId, new DateTime(2026, 4, 26, 18, 0, 0, DateTimeKind.Utc), SessionStatus.Planned, 2, "https://example.com/game") }; var participants = new[] { @@ -38,7 +38,7 @@ public sealed class SessionBatchViewBuilderTests public void Build_ShouldCalculatePlayerCounts() { var sessionId = Guid.NewGuid(); - var sessions = new[] { new SessionBatchDto(sessionId, DateTime.UtcNow, SessionStatus.Planned, 4) }; + var sessions = new[] { new SessionBatchDto(sessionId, DateTime.UtcNow, SessionStatus.Planned, 4, "https://example.com/game") }; var participants = new[] { new ParticipantBatchDto(sessionId, "Alice", "alice", ParticipantRegistrationStatus.Active), @@ -59,7 +59,7 @@ public sealed class SessionBatchViewBuilderTests public void Build_ShouldIncludeActionsForActiveSessions() { var sessionId = Guid.NewGuid(); - var sessions = new[] { new SessionBatchDto(sessionId, DateTime.UtcNow, SessionStatus.Planned, 4) }; + var sessions = new[] { new SessionBatchDto(sessionId, DateTime.UtcNow, SessionStatus.Planned, 4, "https://example.com/game") }; var participants = Array.Empty(); var result = SessionBatchViewBuilder.Build("Test", sessions, participants); @@ -76,7 +76,7 @@ public sealed class SessionBatchViewBuilderTests public void Build_ShouldNotIncludeActionsForCancelledSessions() { var sessionId = Guid.NewGuid(); - var sessions = new[] { new SessionBatchDto(sessionId, DateTime.UtcNow, SessionStatus.Cancelled, null) }; + var sessions = new[] { new SessionBatchDto(sessionId, DateTime.UtcNow, SessionStatus.Cancelled, null, "") }; var participants = Array.Empty(); var result = SessionBatchViewBuilder.Build("Test", sessions, participants); @@ -88,7 +88,7 @@ public sealed class SessionBatchViewBuilderTests public void Build_ShouldMarkWaitlistActionWhenFull() { var sessionId = Guid.NewGuid(); - var sessions = new[] { new SessionBatchDto(sessionId, DateTime.UtcNow, SessionStatus.Planned, 1) }; + var sessions = new[] { new SessionBatchDto(sessionId, DateTime.UtcNow, SessionStatus.Planned, 1, "https://example.com/game") }; var participants = new[] { new ParticipantBatchDto(sessionId, "Alice", "alice", ParticipantRegistrationStatus.Active) }; var result = SessionBatchViewBuilder.Build("Test", sessions, participants); @@ -101,7 +101,7 @@ public sealed class SessionBatchViewBuilderTests public void Build_ShouldIncludePlayerUsernames() { var sessionId = Guid.NewGuid(); - var sessions = new[] { new SessionBatchDto(sessionId, DateTime.UtcNow, SessionStatus.Planned, null) }; + var sessions = new[] { new SessionBatchDto(sessionId, DateTime.UtcNow, SessionStatus.Planned, null, "https://example.com/game") }; var participants = new[] { new ParticipantBatchDto(sessionId, "Alice", "alice", ParticipantRegistrationStatus.Active) }; var result = SessionBatchViewBuilder.Build("Test", sessions, participants); diff --git a/tests/GmRelay.Bot.Tests/Rendering/TelegramSessionBatchRendererTests.cs b/tests/GmRelay.Bot.Tests/Rendering/TelegramSessionBatchRendererTests.cs index ff05840..8938d10 100644 --- a/tests/GmRelay.Bot.Tests/Rendering/TelegramSessionBatchRendererTests.cs +++ b/tests/GmRelay.Bot.Tests/Rendering/TelegramSessionBatchRendererTests.cs @@ -16,9 +16,9 @@ public sealed class TelegramSessionBatchRendererTests var sessions = new[] { - new SessionBatchDto(secondSessionId, new DateTime(2026, 4, 27, 18, 0, 0, DateTimeKind.Utc), SessionStatus.Planned, 4), - new SessionBatchDto(cancelledSessionId, new DateTime(2026, 4, 28, 18, 0, 0, DateTimeKind.Utc), SessionStatus.Cancelled, null), - new SessionBatchDto(firstSessionId, new DateTime(2026, 4, 26, 18, 0, 0, DateTimeKind.Utc), SessionStatus.Planned, 2) + new SessionBatchDto(secondSessionId, new DateTime(2026, 4, 27, 18, 0, 0, DateTimeKind.Utc), SessionStatus.Planned, 4, "https://example.com/game2"), + new SessionBatchDto(cancelledSessionId, new DateTime(2026, 4, 28, 18, 0, 0, DateTimeKind.Utc), SessionStatus.Cancelled, null, ""), + new SessionBatchDto(firstSessionId, new DateTime(2026, 4, 26, 18, 0, 0, DateTimeKind.Utc), SessionStatus.Planned, 2, "https://example.com/game1") }; var participants = new[] { @@ -35,6 +35,9 @@ public sealed class TelegramSessionBatchRendererTests Assert.Contains("Charlie", text); Assert.Contains("Bob", text); Assert.Contains("Сессия отменена", text); + Assert.Contains("Ссылка на игру", text); + Assert.Contains("https://example.com/game1", text); + Assert.Contains("https://example.com/game2", text); var buttons = markup.InlineKeyboard.SelectMany(row => row).ToList(); Assert.Equal(4, buttons.Count); // 2 sessions x 2 buttons each @@ -51,7 +54,7 @@ public sealed class TelegramSessionBatchRendererTests public void Render_ShouldSkipButtonsForCancelledSessions() { var cancelledSessionId = Guid.NewGuid(); - var sessions = new[] { new SessionBatchDto(cancelledSessionId, DateTime.UtcNow, SessionStatus.Cancelled, null) }; + var sessions = new[] { new SessionBatchDto(cancelledSessionId, DateTime.UtcNow, SessionStatus.Cancelled, null, "") }; var participants = Array.Empty(); var view = SessionBatchViewBuilder.Build("Test", sessions, participants); @@ -64,7 +67,7 @@ public sealed class TelegramSessionBatchRendererTests public void Render_ShouldShowWaitlistButtonWhenFull() { var sessionId = Guid.NewGuid(); - var sessions = new[] { new SessionBatchDto(sessionId, DateTime.UtcNow, SessionStatus.Planned, 1) }; + var sessions = new[] { new SessionBatchDto(sessionId, DateTime.UtcNow, SessionStatus.Planned, 1, "https://example.com/game") }; var participants = new[] { new ParticipantBatchDto(sessionId, "Alice", "alice", ParticipantRegistrationStatus.Active) }; var view = SessionBatchViewBuilder.Build("Test", sessions, participants);