From 91e91ac3ce71aa8e2527d3eec5ca488bd5a84d2e Mon Sep 17 00:00:00 2001 From: Hermes Date: Sun, 16 Aug 2026 20:05:11 +0500 Subject: [PATCH] =?UTF-8?q?feat:=20=D0=BA=D0=BE=D0=BC=D0=B0=D0=BD=D0=B4?= =?UTF-8?q?=D0=B0=20/help=20=E2=80=94=20=D1=81=D0=BF=D1=80=D0=B0=D0=B2?= =?UTF-8?q?=D0=BA=D0=B0=20=D0=BF=D0=BE=20=D0=B2=D1=81=D0=B5=D0=BC=20=D0=BA?= =?UTF-8?q?=D0=BE=D0=BC=D0=B0=D0=BD=D0=B4=D0=B0=D0=BC=20=D0=B1=D0=BE=D1=82?= =?UTF-8?q?=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- internal/app/app.go | 32 ++++++++++++++++++++++++++++---- internal/app/app_test.go | 30 ++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 4 deletions(-) diff --git a/internal/app/app.go b/internal/app/app.go index bae665e..32cadb6 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -198,8 +198,11 @@ func (a *App) handleIncoming(inc chat.Incoming) { // update/status — команды бинаря вне машины состояний задач. // /status перехватываем только без числового аргумента (/status N — статус задачи). - if cmdName(text) == "/update" || (cmdName(text) == "/status" && !hasArg(text)) { - a.handleUpdateCommand(ctx, uid, text) + // /help — справка по всем командам (тоже команда бинаря, вне задач). + if cmdName(text) == "/help" || + cmdName(text) == "/update" || + (cmdName(text) == "/status" && !hasArg(text)) { + a.handleBinaryCommand(ctx, uid, text) return } @@ -342,16 +345,37 @@ func (a *App) autoCheckOnce(ctx context.Context) { a.send(ctx, uid, msg) } -// handleUpdateCommand — маршрутизация /update и /status. -func (a *App) handleUpdateCommand(ctx context.Context, uid chat.UserID, text string) { +// handleBinaryCommand — маршрутизация бинарных команд: /help, /update, /status. +func (a *App) handleBinaryCommand(ctx context.Context, uid chat.UserID, text string) { switch cmdName(text) { case "/status": a.handleStatusCmd(ctx, uid) case "/update": a.handleUpdateCmd(ctx, uid) + case "/help": + a.handleHelpCmd(ctx, uid) } } +// helpText — текст команды /help. +const helpText = "⚡ **Ratatoskr** — помогу продумать задачу и запущу агента.\n\n" + + "**Команды:**\n" + + "`/start` — новая задача\n" + + "`/cancel` — отменить текущую\n" + + "`/skip` — хватит вопросов, предложить черновик\n" + + "`/retry N` — перезапустить задачу `N`\n" + + "`/status N` — статус задачи `N`\n" + + "`/continue N` — продолжить задачу `N`\n" + + "`/status` — статус бинаря и обновления\n" + + "`/update` — применить обновление\n" + + "`/help` — эта справка\n\n" + + "Просто опишите задачу — я помогу её продумать." + +// handleHelpCmd отвечает списком команд. +func (a *App) handleHelpCmd(ctx context.Context, uid chat.UserID) { + a.send(ctx, uid, helpText) +} + // handleStatusCmd отвечает текущей версией и результатом последней проверки. func (a *App) handleStatusCmd(ctx context.Context, uid chat.UserID) { if a.Config.Update.Enabled && a.Updater != nil { diff --git a/internal/app/app_test.go b/internal/app/app_test.go index 8d3f2bf..2d7a8ba 100644 --- a/internal/app/app_test.go +++ b/internal/app/app_test.go @@ -162,4 +162,34 @@ func TestNew_UpdateWiring(t *testing.T) { if a2.Updater.Token != "embedded-update-token" { t.Errorf("Token = %q, want embedded-update-token (вшитый приоритетнее)", a2.Updater.Token) } +} + +// TestHelp_IsBinaryCommand проверяет, что /help распознаётся cmdName и, значит, +// в handleIncoming попадёт в бинарную ветку (не в машину состояний задач). +// Косвенно: /help не должен иметь аргумента-номера задачи (как /status N). +func TestHelp_IsBinaryCommand(t *testing.T) { + if got := cmdName("/help"); got != "/help" { + t.Errorf("cmdName(/help) = %q, want /help", got) + } + if hasArg("/help") { + t.Errorf("/help не должен требовать аргумент") + } + if got := cmdName("/help foo"); got != "/help" { + t.Errorf("cmdName(/help foo) = %q, want /help", got) + } +} + +// TestHelpText_ContainsCommands проверяет, что справка упоминает все команды. +func TestHelpText_ContainsCommands(t *testing.T) { + for _, cmd := range []string{ + "/start", "/cancel", "/skip", "/retry", "/status", "/continue", + "/update", "/help", + } { + if !strings.Contains(helpText, cmd) { + t.Errorf("helpText не упоминает команду %s", cmd) + } + } + if !strings.Contains(helpText, "Ratatoskr") { + t.Errorf("helpText не содержит имени бота") + } } \ No newline at end of file