Пять независимых 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>
105 lines
3.8 KiB
Go
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)
|
|
}
|
|
})
|
|
}
|
|
}
|