From 023fdacda54eb304ef9307147cda9b7025605df1 Mon Sep 17 00:00:00 2001 From: Anton Vakhrushev Date: Sat, 18 Jul 2026 16:09:48 +0300 Subject: [PATCH] =?UTF-8?q?tgbot:=20=D1=82=D0=B5=D1=81=D1=82=D1=8B=20?= =?UTF-8?q?=D0=BD=D0=B0=20=D1=8D=D0=BA=D1=80=D0=B0=D0=BD=D0=B8=D1=80=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=20=D0=BF=D1=83=D1=82=D0=B8/?= =?UTF-8?q?=D0=BF=D1=80=D0=B8=D1=87=D0=B8=D0=BD=20=D0=B8=20=D1=84=D0=BE?= =?UTF-8?q?=D1=80=D0=BC=D0=B0=D1=82=20download=5Fid=20=D0=B2=20opErr?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Финальная сверка спек notifications отметила два сценария спеки без прицельного теста (спецсимволы в пути раскладки/причинах, формат id в opErr) — поведение верное, но регресс не ловился. Закрываю тестами. Co-Authored-By: Claude Opus 4.8 (1M context) --- internal/tgbot/bot_test.go | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/internal/tgbot/bot_test.go b/internal/tgbot/bot_test.go index d57a737..8d5ab63 100644 --- a/internal/tgbot/bot_test.go +++ b/internal/tgbot/bot_test.go @@ -413,6 +413,39 @@ func TestBot_NotifyFailedEscapesExternalText(t *testing.T) { } } +// Карточка ревью: спецсимволы во внешнем пути раскладки и в причинах +// распознавания экранируются (esc после tailPath/join), сырая разметка не +// просачивается. Закрывает сценарий «Внешний путь и причины экранируются». +func TestReviewCard_EscapesPathAndReasons(t *testing.T) { + b, _, _, _ := newTestBot(t, []int64{7}) + rd := reviewData(store.StateReview) + rd.Recognition.Reasons = `["Tom & Jerry x"]` + rd.Preview[0].Dst = "/srv/media/series/A & B /file .mkv" + text, _ := b.renderCard(rd) + + if !strings.Contains(text, "Tom & Jerry <b>x</b>") { + t.Errorf("причины не экранированы:\n%s", text) + } + if !strings.Contains(text, "A & B <x>") || !strings.Contains(text, "file <y>.mkv") { + t.Errorf("путь раскладки не экранирован:\n%s", text) + } + if strings.Contains(text, "") || strings.Contains(text, "") { + t.Errorf("сырая разметка просочилась:\n%s", text) + } +} + +// opErr: при наличии download id он уходит моноширинным (tap-to-copy) под +// ключом download_id=; без id — дружелюбный текст без ключа. Закрывает сценарий +// «Id в сообщении об ошибке копируется». +func TestOpErr_IDMonospace(t *testing.T) { + if got := opErr("Не удалось выполнить действие", tid); !strings.Contains(got, "download_id="+idCode(tid)) { + t.Errorf("id не моноширинным под ключом download_id: %q", got) + } + if got := opErr("Готово", ""); strings.Contains(got, "download_id") { + t.Errorf("без id ключа download_id быть не должно: %q", got) + } +} + func TestBot_CallbackRetry(t *testing.T) { b, _, _, rev := newTestBot(t, []int64{7}) rev.data = reviewData(store.StateFailed)