refactor: add platform messenger contracts
PR Checks / test-and-build (pull_request) Successful in 12m35s
PR Checks / test-and-build (pull_request) Successful in 12m35s
Introduce platform-neutral PlatformKind, PlatformUser, PlatformGroup, and IPlatformMessenger contracts in GmRelay.Shared. Route Telegram session schedule updates, direct notifications, interaction replies, and calendar export through TelegramPlatformMessenger while preserving existing Telegram behavior. Bump version -> 2.0.1
This commit is contained in:
@@ -0,0 +1,560 @@
|
||||
# Platform Messenger Contracts Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Implement issue #24 by adding platform-neutral platform identity and messaging contracts, then routing the Telegram session flows through a Telegram adapter without changing Telegram behavior.
|
||||
|
||||
**Architecture:** Keep update routing and Telegram update parsing at the `GmRelay.Bot.Infrastructure.Telegram` boundary, but move outbound messaging decisions behind `GmRelay.Shared.Platform.IPlatformMessenger`. `GmRelay.Shared` owns platform-neutral DTOs and contracts; `GmRelay.Bot` owns `TelegramPlatformMessenger`, which translates neutral requests into `Telegram.Bot` calls and reuses the existing Telegram renderers/editing rules.
|
||||
|
||||
**Tech Stack:** .NET 10, C# preview, xUnit, Dapper.AOT constraints, Telegram.Bot in `GmRelay.Bot` only, platform-neutral shared contracts in `GmRelay.Shared`.
|
||||
|
||||
---
|
||||
|
||||
## Issue Context
|
||||
|
||||
- Gitea issue: #24, `refactor: ввести PlatformKind, PlatformUser, PlatformGroup и IPlatformMessenger`
|
||||
- Labels: `area:bot`, `area:platform`, `area:shared`, `platform:multi`, `type:refactor`, `pending-approval`
|
||||
- Acceptance criteria:
|
||||
- New contracts live in a platform-neutral layer.
|
||||
- Telegram flow goes through the adapter without behavior changes.
|
||||
- A future DiscordBot can reference the contract without depending on Telegram assemblies.
|
||||
|
||||
## Proposed Version Bump
|
||||
|
||||
Current version is `2.0.0` in:
|
||||
|
||||
- `Directory.Build.props`
|
||||
- `compose.yaml`
|
||||
- `.gitea/workflows/deploy.yml`
|
||||
- `src/GmRelay.Web/Components/Layout/NavMenu.razor`
|
||||
|
||||
Issue label is `type:refactor`; per workflow rules this is not a major bump and has no user-facing feature label. Proposed bump: `2.0.0` -> `2.0.1`.
|
||||
|
||||
## Files
|
||||
|
||||
- Create: `src/GmRelay.Shared/Platform/PlatformKind.cs`
|
||||
- Create: `src/GmRelay.Shared/Platform/PlatformUser.cs`
|
||||
- Create: `src/GmRelay.Shared/Platform/PlatformGroup.cs`
|
||||
- Create: `src/GmRelay.Shared/Platform/PlatformMessageContracts.cs`
|
||||
- Create: `src/GmRelay.Shared/Platform/IPlatformMessenger.cs`
|
||||
- Create: `src/GmRelay.Bot/Infrastructure/Telegram/TelegramPlatformMessenger.cs`
|
||||
- Create: `tests/GmRelay.Bot.Tests/Platform/PlatformContractsTests.cs`
|
||||
- Create: `tests/GmRelay.Bot.Tests/Infrastructure/Telegram/TelegramPlatformMessengerSourceTests.cs`
|
||||
- Modify: `src/GmRelay.Bot/Program.cs`
|
||||
- Modify: `src/GmRelay.Bot/Features/Notifications/DirectSessionNotificationSender.cs`
|
||||
- Modify: `src/GmRelay.Bot/Features/Sessions/CreateSession/JoinSessionHandler.cs`
|
||||
- Modify: `src/GmRelay.Bot/Features/Sessions/CreateSession/LeaveSessionHandler.cs`
|
||||
- Modify: `src/GmRelay.Bot/Features/Sessions/CreateSession/CancelSessionHandler.cs`
|
||||
- Modify: `src/GmRelay.Bot/Features/Sessions/CreateSession/PromoteWaitlistedPlayerHandler.cs`
|
||||
- Modify: `src/GmRelay.Bot/Features/Sessions/RescheduleSession/InitiateRescheduleHandler.cs`
|
||||
- Modify: `src/GmRelay.Bot/Features/Sessions/RescheduleSession/HandleRescheduleTimeInputHandler.cs`
|
||||
- Modify: `src/GmRelay.Bot/Features/Sessions/RescheduleSession/HandleRescheduleVoteHandler.cs`
|
||||
- Modify: `src/GmRelay.Bot/Features/Sessions/RescheduleSession/RescheduleVotingDeadlineService.cs`
|
||||
- Modify: `src/GmRelay.Bot/Features/Sessions/ExportCalendar/ExportCalendarHandler.cs`
|
||||
- Modify: version files listed above
|
||||
|
||||
## Design
|
||||
|
||||
### Shared Contracts
|
||||
|
||||
`PlatformKind` is a sentinel enum where `Max` is not a sendable platform:
|
||||
|
||||
```csharp
|
||||
namespace GmRelay.Shared.Platform;
|
||||
|
||||
public enum PlatformKind
|
||||
{
|
||||
Telegram = 0,
|
||||
Discord = 1,
|
||||
Max = 2
|
||||
}
|
||||
```
|
||||
|
||||
`PlatformUser` and `PlatformGroup` carry external platform identity while keeping current Telegram IDs representable as strings:
|
||||
|
||||
```csharp
|
||||
namespace GmRelay.Shared.Platform;
|
||||
|
||||
public sealed record PlatformUser(
|
||||
PlatformKind Platform,
|
||||
string ExternalUserId,
|
||||
string DisplayName,
|
||||
string? ExternalUsername);
|
||||
|
||||
public sealed record PlatformGroup(
|
||||
PlatformKind Platform,
|
||||
string ExternalGroupId,
|
||||
string DisplayName,
|
||||
string? ExternalChannelId = null,
|
||||
string? ExternalThreadId = null);
|
||||
```
|
||||
|
||||
Outbound message contracts stay independent of Telegram/Discord SDK types:
|
||||
|
||||
```csharp
|
||||
using GmRelay.Shared.Rendering;
|
||||
|
||||
namespace GmRelay.Shared.Platform;
|
||||
|
||||
public sealed record PlatformMessageRef(
|
||||
PlatformKind Platform,
|
||||
string ExternalGroupId,
|
||||
string? ExternalThreadId,
|
||||
string ExternalMessageId);
|
||||
|
||||
public sealed record PlatformMessageAction(
|
||||
string Key,
|
||||
string Label,
|
||||
string Payload);
|
||||
|
||||
public sealed record PlatformScheduleMessage(
|
||||
PlatformGroup Group,
|
||||
SessionBatchViewModel View,
|
||||
PlatformMessageRef? ExistingMessage,
|
||||
string? ImageReference = null);
|
||||
|
||||
public sealed record PlatformPrivateMessage(
|
||||
PlatformUser Recipient,
|
||||
string HtmlText);
|
||||
|
||||
public sealed record PlatformInteractionReply(
|
||||
string InteractionId,
|
||||
string Text,
|
||||
bool ShowAlert = false);
|
||||
|
||||
public sealed record PlatformCalendarFile(
|
||||
PlatformGroup Group,
|
||||
string FileName,
|
||||
byte[] Content,
|
||||
string CaptionHtml,
|
||||
IReadOnlyList<PlatformMessageAction> Actions);
|
||||
```
|
||||
|
||||
`IPlatformMessenger` exposes the required outward operations:
|
||||
|
||||
```csharp
|
||||
namespace GmRelay.Shared.Platform;
|
||||
|
||||
public interface IPlatformMessenger
|
||||
{
|
||||
Task<PlatformMessageRef> SendScheduleAsync(PlatformScheduleMessage message, CancellationToken ct);
|
||||
Task UpdateScheduleAsync(PlatformScheduleMessage message, CancellationToken ct);
|
||||
Task SendGroupMessageAsync(PlatformGroup group, string htmlText, CancellationToken ct);
|
||||
Task SendPrivateMessageAsync(PlatformPrivateMessage message, CancellationToken ct);
|
||||
Task AnswerInteractionAsync(PlatformInteractionReply reply, CancellationToken ct);
|
||||
Task SendCalendarFileAsync(PlatformCalendarFile file, CancellationToken ct);
|
||||
}
|
||||
```
|
||||
|
||||
### Telegram Adapter
|
||||
|
||||
`TelegramPlatformMessenger` lives in `GmRelay.Bot.Infrastructure.Telegram`, depends on `ITelegramBotClient`, and translates neutral DTOs to existing Telegram calls:
|
||||
|
||||
- `SendScheduleAsync` renders `SessionBatchViewModel` with `TelegramSessionBatchRenderer.Render`.
|
||||
- `UpdateScheduleAsync` calls `BatchMessageEditor.EditBatchMessageAsync`.
|
||||
- `SendGroupMessageAsync` calls `SendMessage` with `ParseMode.Html` and optional `messageThreadId`.
|
||||
- `SendPrivateMessageAsync` calls `SendMessage` to `PlatformUser.ExternalUserId`.
|
||||
- `AnswerInteractionAsync` calls `AnswerCallbackQuery`.
|
||||
- `SendCalendarFileAsync` calls `SendDocument` and maps URL actions to inline keyboard buttons.
|
||||
|
||||
### Handler Scope
|
||||
|
||||
Refactor outbound Telegram calls in these flows to `IPlatformMessenger`:
|
||||
|
||||
- Join/leave/promote waitlist schedule updates and callback replies.
|
||||
- Cancel schedule update, group cancellation message, direct notification and callback reply.
|
||||
- Reschedule initiation, voting message updates, immediate reschedule schedule update, direct notifications and callback replies.
|
||||
- Export calendar file sending.
|
||||
|
||||
Keep Telegram inbound DTOs at the boundary for now:
|
||||
|
||||
- `UpdateRouter` still receives `Telegram.Bot.Types.Update`.
|
||||
- Text message parsing in reschedule input still receives `Telegram.Bot.Types.Message`.
|
||||
- `CreateSessionHandler` can keep photo/topic creation via `ITelegramBotClient` because issue #24 targets outbound schedule/interaction/private/calendar contract, not replacing all Telegram update primitives in one PR.
|
||||
|
||||
## Tasks
|
||||
|
||||
### Task 1: RED - Shared Contract Tests
|
||||
|
||||
**Files:**
|
||||
- Create: `tests/GmRelay.Bot.Tests/Platform/PlatformContractsTests.cs`
|
||||
|
||||
- [ ] **Step 1: Write failing tests for neutral contracts**
|
||||
|
||||
```csharp
|
||||
using GmRelay.Shared.Platform;
|
||||
using GmRelay.Shared.Rendering;
|
||||
|
||||
namespace GmRelay.Bot.Tests.Platform;
|
||||
|
||||
public sealed class PlatformContractsTests
|
||||
{
|
||||
[Fact]
|
||||
public void PlatformKind_ShouldDefineTelegramDiscordAndMaxSentinel()
|
||||
{
|
||||
Assert.Equal(0, (int)PlatformKind.Telegram);
|
||||
Assert.Equal(1, (int)PlatformKind.Discord);
|
||||
Assert.Equal(2, (int)PlatformKind.Max);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PlatformContracts_ShouldBeTelegramAssemblyFree()
|
||||
{
|
||||
var contractTypes = new[]
|
||||
{
|
||||
typeof(PlatformUser),
|
||||
typeof(PlatformGroup),
|
||||
typeof(PlatformMessageRef),
|
||||
typeof(PlatformMessageAction),
|
||||
typeof(PlatformScheduleMessage),
|
||||
typeof(PlatformPrivateMessage),
|
||||
typeof(PlatformInteractionReply),
|
||||
typeof(PlatformCalendarFile),
|
||||
typeof(IPlatformMessenger)
|
||||
};
|
||||
|
||||
Assert.All(contractTypes, type =>
|
||||
Assert.DoesNotContain(
|
||||
"Telegram",
|
||||
string.Join(" ", type.Assembly.GetReferencedAssemblies().Select(value => value.Name)),
|
||||
StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PlatformScheduleMessage_ShouldCarrySharedViewModelWithoutPlatformTypes()
|
||||
{
|
||||
var sessionId = Guid.Parse("aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa");
|
||||
var view = SessionBatchViewBuilder.Build(
|
||||
"Campaign",
|
||||
[new SessionBatchDto(sessionId, new DateTime(2026, 5, 15, 16, 0, 0, DateTimeKind.Utc), "Planned", 4, "https://example.test/game")],
|
||||
[]);
|
||||
var group = new PlatformGroup(PlatformKind.Discord, "guild-1", "Guild", "channel-1", "thread-1");
|
||||
|
||||
var message = new PlatformScheduleMessage(group, view, ExistingMessage: null);
|
||||
|
||||
Assert.Equal(PlatformKind.Discord, message.Group.Platform);
|
||||
Assert.Same(view, message.View);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run tests and verify RED**
|
||||
|
||||
Run:
|
||||
|
||||
```powershell
|
||||
dotnet test tests/GmRelay.Bot.Tests/GmRelay.Bot.Tests.csproj --filter PlatformContractsTests
|
||||
```
|
||||
|
||||
Expected: compile failure because `GmRelay.Shared.Platform` types do not exist.
|
||||
|
||||
### Task 2: GREEN - Add Shared Contracts
|
||||
|
||||
**Files:**
|
||||
- Create: `src/GmRelay.Shared/Platform/PlatformKind.cs`
|
||||
- Create: `src/GmRelay.Shared/Platform/PlatformUser.cs`
|
||||
- Create: `src/GmRelay.Shared/Platform/PlatformGroup.cs`
|
||||
- Create: `src/GmRelay.Shared/Platform/PlatformMessageContracts.cs`
|
||||
- Create: `src/GmRelay.Shared/Platform/IPlatformMessenger.cs`
|
||||
|
||||
- [ ] **Step 1: Add the contract files exactly as described in the Design section**
|
||||
- [ ] **Step 2: Run PlatformContractsTests and verify GREEN**
|
||||
|
||||
Run:
|
||||
|
||||
```powershell
|
||||
dotnet test tests/GmRelay.Bot.Tests/GmRelay.Bot.Tests.csproj --filter PlatformContractsTests
|
||||
```
|
||||
|
||||
Expected: `Passed`.
|
||||
|
||||
### Task 3: RED - Adapter and Flow Source Tests
|
||||
|
||||
**Files:**
|
||||
- Create: `tests/GmRelay.Bot.Tests/Infrastructure/Telegram/TelegramPlatformMessengerSourceTests.cs`
|
||||
|
||||
- [ ] **Step 1: Write source tests for adapter wiring and target flows**
|
||||
|
||||
```csharp
|
||||
namespace GmRelay.Bot.Tests.Infrastructure.Telegram;
|
||||
|
||||
public sealed class TelegramPlatformMessengerSourceTests
|
||||
{
|
||||
[Fact]
|
||||
public async Task Program_ShouldRegisterTelegramPlatformMessenger()
|
||||
{
|
||||
var program = await ReadRepositoryFileAsync("src/GmRelay.Bot/Program.cs");
|
||||
|
||||
Assert.Contains("IPlatformMessenger", program, StringComparison.Ordinal);
|
||||
Assert.Contains("TelegramPlatformMessenger", program, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("src/GmRelay.Bot/Features/Sessions/CreateSession/JoinSessionHandler.cs")]
|
||||
[InlineData("src/GmRelay.Bot/Features/Sessions/CreateSession/LeaveSessionHandler.cs")]
|
||||
[InlineData("src/GmRelay.Bot/Features/Sessions/CreateSession/CancelSessionHandler.cs")]
|
||||
[InlineData("src/GmRelay.Bot/Features/Sessions/CreateSession/PromoteWaitlistedPlayerHandler.cs")]
|
||||
[InlineData("src/GmRelay.Bot/Features/Sessions/RescheduleSession/InitiateRescheduleHandler.cs")]
|
||||
[InlineData("src/GmRelay.Bot/Features/Sessions/RescheduleSession/HandleRescheduleTimeInputHandler.cs")]
|
||||
[InlineData("src/GmRelay.Bot/Features/Sessions/RescheduleSession/HandleRescheduleVoteHandler.cs")]
|
||||
[InlineData("src/GmRelay.Bot/Features/Sessions/RescheduleSession/RescheduleVotingDeadlineService.cs")]
|
||||
[InlineData("src/GmRelay.Bot/Features/Sessions/ExportCalendar/ExportCalendarHandler.cs")]
|
||||
public async Task SessionFlows_ShouldUsePlatformMessengerForOutboundTelegramWork(string relativePath)
|
||||
{
|
||||
var source = await ReadRepositoryFileAsync(relativePath);
|
||||
|
||||
Assert.Contains("IPlatformMessenger", source, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain("BatchMessageEditor.EditBatchMessageAsync", source, StringComparison.Ordinal);
|
||||
Assert.DoesNotContain(".AnswerCallbackQuery(", source, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task TelegramPlatformMessenger_ShouldOwnTelegramBotClientCalls()
|
||||
{
|
||||
var source = await ReadRepositoryFileAsync("src/GmRelay.Bot/Infrastructure/Telegram/TelegramPlatformMessenger.cs");
|
||||
|
||||
Assert.Contains("ITelegramBotClient", source, StringComparison.Ordinal);
|
||||
Assert.Contains("BatchMessageEditor.EditBatchMessageAsync", source, StringComparison.Ordinal);
|
||||
Assert.Contains("AnswerCallbackQuery", source, StringComparison.Ordinal);
|
||||
Assert.Contains("SendDocument", source, StringComparison.Ordinal);
|
||||
}
|
||||
|
||||
private static async Task<string> ReadRepositoryFileAsync(string relativePath)
|
||||
{
|
||||
var directory = new DirectoryInfo(AppContext.BaseDirectory);
|
||||
while (directory is not null)
|
||||
{
|
||||
var candidate = Path.Combine(directory.FullName, relativePath);
|
||||
if (File.Exists(candidate))
|
||||
{
|
||||
return await File.ReadAllTextAsync(candidate);
|
||||
}
|
||||
|
||||
directory = directory.Parent;
|
||||
}
|
||||
|
||||
throw new FileNotFoundException($"Could not locate repository file '{relativePath}'.");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run tests and verify RED**
|
||||
|
||||
Run:
|
||||
|
||||
```powershell
|
||||
dotnet test tests/GmRelay.Bot.Tests/GmRelay.Bot.Tests.csproj --filter TelegramPlatformMessengerSourceTests
|
||||
```
|
||||
|
||||
Expected: failures because `TelegramPlatformMessenger` is missing and handlers still call Telegram APIs directly.
|
||||
|
||||
### Task 4: GREEN - Implement TelegramPlatformMessenger and Registration
|
||||
|
||||
**Files:**
|
||||
- Create: `src/GmRelay.Bot/Infrastructure/Telegram/TelegramPlatformMessenger.cs`
|
||||
- Modify: `src/GmRelay.Bot/Program.cs`
|
||||
|
||||
- [ ] **Step 1: Implement adapter**
|
||||
|
||||
Implementation notes:
|
||||
|
||||
- Parse Telegram chat/thread/message IDs from neutral string IDs with `long.Parse` and `int.Parse`.
|
||||
- Use `ParseMode.Html` for HTML text.
|
||||
- Map `PlatformMessageAction` URLs to `InlineKeyboardButton.WithUrl`.
|
||||
- Return a `PlatformMessageRef` with message IDs converted to strings.
|
||||
|
||||
- [ ] **Step 2: Register adapter**
|
||||
|
||||
Add `using GmRelay.Shared.Platform;` and register:
|
||||
|
||||
```csharp
|
||||
builder.Services.AddSingleton<IPlatformMessenger, TelegramPlatformMessenger>();
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Run adapter source tests**
|
||||
|
||||
Run:
|
||||
|
||||
```powershell
|
||||
dotnet test tests/GmRelay.Bot.Tests/GmRelay.Bot.Tests.csproj --filter TelegramPlatformMessengerSourceTests
|
||||
```
|
||||
|
||||
Expected: some handler source tests still fail until Task 5.
|
||||
|
||||
### Task 5: GREEN - Refactor Session Flows Through Adapter
|
||||
|
||||
**Files:**
|
||||
- Modify target handler files listed in Task 3
|
||||
- Modify: `src/GmRelay.Bot/Features/Notifications/DirectSessionNotificationSender.cs`
|
||||
|
||||
- [ ] **Step 1: Replace constructor dependencies**
|
||||
|
||||
Use `IPlatformMessenger messenger` in target handlers for outbound operations. Keep `ITelegramBotClient` only where the handler still performs inbound Telegram-specific work that is out of scope, such as message deletion or forum topic creation.
|
||||
|
||||
- [ ] **Step 2: Convert Telegram IDs to neutral platform objects**
|
||||
|
||||
Use helper code equivalent to:
|
||||
|
||||
```csharp
|
||||
private static PlatformGroup TelegramGroup(long chatId, string? title, int? threadId = null)
|
||||
=> new(
|
||||
PlatformKind.Telegram,
|
||||
chatId.ToString(System.Globalization.CultureInfo.InvariantCulture),
|
||||
title ?? "Telegram chat",
|
||||
ExternalChannelId: chatId.ToString(System.Globalization.CultureInfo.InvariantCulture),
|
||||
ExternalThreadId: threadId?.ToString(System.Globalization.CultureInfo.InvariantCulture));
|
||||
|
||||
private static PlatformUser TelegramUser(long telegramId, string displayName, string? username = null)
|
||||
=> new(
|
||||
PlatformKind.Telegram,
|
||||
telegramId.ToString(System.Globalization.CultureInfo.InvariantCulture),
|
||||
displayName,
|
||||
username);
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Replace schedule updates**
|
||||
|
||||
Build `SessionBatchViewModel` as before, then call:
|
||||
|
||||
```csharp
|
||||
await messenger.UpdateScheduleAsync(
|
||||
new PlatformScheduleMessage(
|
||||
group,
|
||||
view,
|
||||
new PlatformMessageRef(PlatformKind.Telegram, group.ExternalGroupId, group.ExternalThreadId, messageId.ToString(System.Globalization.CultureInfo.InvariantCulture))),
|
||||
ct);
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Replace interaction replies**
|
||||
|
||||
Use:
|
||||
|
||||
```csharp
|
||||
await messenger.AnswerInteractionAsync(
|
||||
new PlatformInteractionReply(command.CallbackQueryId, text, showAlert: false),
|
||||
ct);
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Replace direct notifications**
|
||||
|
||||
`DirectSessionNotificationSender` should become a small compatibility service over `IPlatformMessenger`:
|
||||
|
||||
```csharp
|
||||
await messenger.SendPrivateMessageAsync(
|
||||
new PlatformPrivateMessage(
|
||||
new PlatformUser(PlatformKind.Telegram, recipient.TelegramId.ToString(CultureInfo.InvariantCulture), recipient.DisplayName, null),
|
||||
htmlText),
|
||||
ct);
|
||||
```
|
||||
|
||||
- [ ] **Step 6: Replace calendar file sending**
|
||||
|
||||
`ExportCalendarHandler` builds the same ICS bytes and calls `SendCalendarFileAsync`, preserving the subscription URL button as a `PlatformMessageAction`.
|
||||
|
||||
- [ ] **Step 7: Run target source tests**
|
||||
|
||||
Run:
|
||||
|
||||
```powershell
|
||||
dotnet test tests/GmRelay.Bot.Tests/GmRelay.Bot.Tests.csproj --filter TelegramPlatformMessengerSourceTests
|
||||
```
|
||||
|
||||
Expected: `Passed`.
|
||||
|
||||
### Task 6: Regression Tests
|
||||
|
||||
**Files:**
|
||||
- Existing tests only unless a compiler failure exposes a missing using or changed behavior.
|
||||
|
||||
- [ ] **Step 1: Run rendering and routing tests**
|
||||
|
||||
Run:
|
||||
|
||||
```powershell
|
||||
dotnet test tests/GmRelay.Bot.Tests/GmRelay.Bot.Tests.csproj --filter "FullyQualifiedName~Rendering|FullyQualifiedName~Telegram|FullyQualifiedName~RescheduleSession"
|
||||
```
|
||||
|
||||
Expected: `Passed`.
|
||||
|
||||
- [ ] **Step 2: Run all tests**
|
||||
|
||||
Run:
|
||||
|
||||
```powershell
|
||||
dotnet test tests/GmRelay.Bot.Tests/GmRelay.Bot.Tests.csproj
|
||||
```
|
||||
|
||||
Expected: `Passed`.
|
||||
|
||||
- [ ] **Step 3: Build solution**
|
||||
|
||||
Run:
|
||||
|
||||
```powershell
|
||||
dotnet build GM-Relay.slnx
|
||||
```
|
||||
|
||||
Expected: `Build succeeded` with warnings treated as errors.
|
||||
|
||||
### Task 7: Version Bump
|
||||
|
||||
**Files:**
|
||||
- Modify: `Directory.Build.props`
|
||||
- Modify: `compose.yaml`
|
||||
- Modify: `.gitea/workflows/deploy.yml`
|
||||
- Modify: `src/GmRelay.Web/Components/Layout/NavMenu.razor`
|
||||
|
||||
- [ ] **Step 1: Update all four version locations to `2.0.1`**
|
||||
- [ ] **Step 2: Verify sync**
|
||||
|
||||
Run:
|
||||
|
||||
```powershell
|
||||
rg -n "2\\.0\\.0|2\\.0\\.1" Directory.Build.props compose.yaml .gitea/workflows/deploy.yml src/GmRelay.Web/Components/Layout/NavMenu.razor
|
||||
```
|
||||
|
||||
Expected: no `2.0.0` matches in these files and `2.0.1` appears in all required locations.
|
||||
|
||||
### Task 8: Documentation Review
|
||||
|
||||
**Files:**
|
||||
- Review: `README.md`
|
||||
- Review: `docs/adr/002-platform-neutral-batch-rendering.md`
|
||||
|
||||
- [ ] **Step 1: Check README and ADR for platform contract accuracy**
|
||||
- [ ] **Step 2: Update docs if they now misrepresent platform-neutral responsibilities**
|
||||
|
||||
Expected likely doc change: README currently lists current version as `v1.15.0`, which is already inconsistent with repo version `2.0.0`. If this PR bumps to `2.0.1`, update that line to `v2.0.1`.
|
||||
|
||||
### Task 9: Commit, PR, CI, Review, Merge, Deploy, Release
|
||||
|
||||
**Files:**
|
||||
- Stage only files intentionally changed for issue #24.
|
||||
|
||||
- [ ] **Step 1: Create branch**
|
||||
|
||||
```powershell
|
||||
git checkout -b codex/refactor/issue-24-platform-messenger
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Commit**
|
||||
|
||||
```powershell
|
||||
git add src/GmRelay.Shared/Platform src/GmRelay.Bot tests/GmRelay.Bot.Tests Directory.Build.props compose.yaml .gitea/workflows/deploy.yml src/GmRelay.Web/Components/Layout/NavMenu.razor README.md docs/adr/002-platform-neutral-batch-rendering.md
|
||||
git commit -m "refactor: add platform messenger contracts"
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Push and create PR via Gitea**
|
||||
- [ ] **Step 4: Wait for PR CI and fix failures if any**
|
||||
- [ ] **Step 5: Run code review subagent and address findings**
|
||||
- [ ] **Step 6: Merge PR after CI and review**
|
||||
- [ ] **Step 7: Monitor deploy workflow**
|
||||
- [ ] **Step 8: Create release `v2.0.1` with Russian release notes**
|
||||
- [ ] **Step 9: Close issue #24 with PR and release links**
|
||||
|
||||
## Self-Review
|
||||
|
||||
- Spec coverage: all issue acceptance criteria map to Shared contracts, Telegram adapter, handler source tests, and build/test verification.
|
||||
- Placeholder scan: no `TBD`, `TODO`, or "fill later" placeholders are left in this plan.
|
||||
- Type consistency: all snippets use `GmRelay.Shared.Platform`, `PlatformKind.Telegram`, `PlatformMessageRef`, and `IPlatformMessenger` consistently.
|
||||
- Scope control: inbound Telegram update parsing remains out of scope; outbound schedule/private/interaction/calendar operations are in scope.
|
||||
Reference in New Issue
Block a user