Files
jellybit/internal/tgbot/parse_test.go
T
avandClaude Opus 4.8 b8017d65eb Приём/UI: пачка фиксов границ и парсинга (F7–F10, N2)
Пять независимых bugfix'ов из ревью приёма (docs/backlog/review-f7-f10-ingest-ui-fixes.md):

- F7: oversized .torrent через веб отдавал 500. Введён sentinel
  ingest.ErrTorrentTooLarge, classifyErr транслирует его в 400.
- F8: гонка fast-path attach с cancel. Пред-рид
  FindReingestBlockingByInfohash больше не короткозамыкает активную
  запись — авторитетное дедуп-решение принимает CreateDownloadIfNoActive
  под BEGIN IMMEDIATE; короткозамыкание оставлено только для терминальных
  desync-записей (target_missing/orphaned). F6-апгрейд сохранён.
- F9: magnet — регистронезависимый URN-префикс xt (RFC 2141);
  tgbot.ParseMessage срезает хвостовую пунктуацию, приклеенную жадным
  matchем.
- F10: cap контекста до 16 KiB в ingest.Ingest (единственное место
  слияния — покрывает все транспорты), рунобезопасная обрезка + маркер.
- N2: httpapi.shorten режет по рунам, не байтам — кириллица не рвётся в
  U+FFFD.

Добавлены юнит-тесты на каждое исправленное поведение.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-17 20:35:14 +03:00

105 lines
3.8 KiB
Go

package tgbot
import (
"strings"
"testing"
)
// botMessage — реальный блок из tmp/examples.md (Аватар, Книга 2).
const botMessage = `[3] #3117703 [rutracker], 2020-01-30 (https://hashurl.ru/eyJhbGc.abc):
Аватар: Легенда об Аанге / Avatar: The Last Airbender / Книга 2: Земля / Серии: 1-20 из 20 (Майкл Данте ДиМартино / Michael Dante DiMartino, Брайан Кониецко / Bryan Konietzko) [2006, США, приключения, фэнтези, DVDRip-AVC] Dub + Original
✅ (проверено) | 4 GB
сохранённая копия описания раздачи (https://hashurl.ru/eyJ.def)
magnet:?xt=urn:btih:7931AA3ED6666746012F5739D099B5BC64D72A16&tr=http%3A%2F%2Fbt2.t-ru.org%2Fann%3Fmagnet&dn=rutracker-topic-3117703
Открыть magnet в вашем клиенте (https://hashurl.ru/eyJ.ghi)
или получить .torrent: /tr_bc8ea
Оценить:
👍: /g_ad035f или 👎🏿: /r_ad035f
[список файлов] (https://download.exfreedomist.com/files/7931AA3E)
Следить: /us_bc8ea
В закладки: /mka_46d1e
cправка: /help, настройки: /settings`
func TestParseMessage_BotForward(t *testing.T) {
src, ctx, ok := ParseMessage(botMessage)
if !ok {
t.Fatal("magnet не найден")
}
if !strings.HasPrefix(src, "magnet:?xt=urn:btih:7931AA3E") {
t.Errorf("magnet = %q", src)
}
// Контекст несёт заголовок релиза и метаданные.
for _, want := range []string{"Avatar: The Last Airbender", "Книга 2", "2006", "DVDRip-AVC", "✅ (проверено) | 4 GB"} {
if !strings.Contains(ctx, want) {
t.Errorf("контекст без %q:\n%s", want, ctx)
}
}
// Мусор и ссылки вычищены.
for _, bad := range []string{"magnet:", "hashurl.ru", "/tr_", "/g_ad035f", "Следить", "cправка", "список файлов"} {
if strings.Contains(ctx, bad) {
t.Errorf("контекст содержит мусор %q:\n%s", bad, ctx)
}
}
}
func TestParseMessage_PlainMagnet(t *testing.T) {
src, ctx, ok := ParseMessage("magnet:?xt=urn:btih:ABC123&dn=x")
if !ok || src != "magnet:?xt=urn:btih:ABC123&dn=x" {
t.Errorf("src = %q, ok = %v", src, ok)
}
if ctx != "" {
t.Errorf("ожидался пустой контекст, got %q", ctx)
}
}
func TestParseMessage_MagnetWithUserText(t *testing.T) {
text := "вот сериал, второй сезон\nmagnet:?xt=urn:btih:DEF456"
src, ctx, ok := ParseMessage(text)
if !ok || !strings.HasPrefix(src, "magnet:?xt=urn:btih:DEF456") {
t.Errorf("src = %q", src)
}
if ctx != "вот сериал, второй сезон" {
t.Errorf("context = %q", ctx)
}
}
func TestParseMessage_NoMagnet(t *testing.T) {
if _, _, ok := ParseMessage("просто текст без ссылки"); ok {
t.Error("ожидалось ok=false без magnet")
}
}
// F9: жадный magnetRe приклеивает хвостовую пунктуацию (magnet в конце
// предложения/в скобках) — ParseMessage её срезает, иначе последний xt сдвигается
// и длина btih ломается.
func TestParseMessage_TrimsTrailingPunct(t *testing.T) {
const want = "magnet:?xt=urn:btih:7931AA3ED6666746012F5739D099B5BC64D72A16"
cases := []struct {
name string
text string
}{
{"точка", "Смотри: " + want + "."},
{"скобка", "(" + want + ")"},
{"кавычка-ёлочка", "«" + want + "»"},
{"несколько", want + ").,"},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
src, _, ok := ParseMessage(tc.text)
if !ok {
t.Fatal("magnet не найден")
}
if src != want {
t.Errorf("src = %q, want %q", src, want)
}
})
}
}