ingest: закрыты мелочи приёма — вырожденное имя, контракт Result, корреляция add
- имя раздачи нормализуется на границе разбора: вырожденное `-` (metainfo.NoName) даёт пустое имя, пробельное схлопывается — сентинел больше не доходит ни до контекста распознавания, ни до source_ref, ни до подсказки вывода имени - контракт «на любом пути ошибки приёма результат нулевой» объявлен в ingest и удерживается структурно; три транспорта перестали обещать идентификатор, которого нет, и коррелируют отказ по request_id - scoped-логгер загрузки ставится до вызова внешнего сервиса в семи командах воркера — записи об отказе qBittorrent и метабаз получили download_id и infohash; граница разбора bencode записана в docs/research
This commit is contained in:
@@ -432,7 +432,9 @@ func (s *server) handleUIAdd(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
res, err := s.deps.Ingestor.Ingest(r.Context(), req)
|
||||
if err != nil {
|
||||
redirectErr(w, r, userErr(r, err, res.DownloadID))
|
||||
// Нулевой Result на любом пути ошибки — контракт ingest.Ingest;
|
||||
// корреляционный ключ веб-формы, как и REST, — request_id.
|
||||
redirectErr(w, r, userErr(r, err, ""))
|
||||
return
|
||||
}
|
||||
if res.Deduplicated {
|
||||
@@ -585,10 +587,10 @@ func (s *server) handleAPIAdd(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
res, err := s.deps.Ingestor.Ingest(r.Context(), ingest.Request{Source: req.Source, Context: req.Context})
|
||||
if err != nil {
|
||||
// res.DownloadID непуст, если сбой после создания задачи (напр. qbit) —
|
||||
// тогда коррелируем по download_id, иначе (ранний разбор источника) по
|
||||
// request_id.
|
||||
s.apiErr(w, r, err, res.DownloadID)
|
||||
// Приём на любом пути ошибки возвращает нулевой Result (контракт
|
||||
// ingest.Ingest) — идентификатора загрузки тут нет и быть не может,
|
||||
// коррелируем по request_id.
|
||||
s.apiErr(w, r, err, "")
|
||||
return
|
||||
}
|
||||
status := http.StatusCreated
|
||||
|
||||
@@ -2,6 +2,9 @@ package httpapi_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/url"
|
||||
@@ -85,3 +88,50 @@ func TestUIAddUrlencoded(t *testing.T) {
|
||||
t.Errorf("urlencoded source не проброшен: %q", ing.lastReq.Source)
|
||||
}
|
||||
}
|
||||
|
||||
// Отказ приёма на HTTP-границе: идентификатора загрузки нет (контракт
|
||||
// ingest.Ingest — нулевой Result на любом пути ошибки), поэтому корреляционным
|
||||
// ключом остаётся request_id запроса. Проверяются оба HTTP-транспорта: REST
|
||||
// отдаёт ключ полем тела, веб-форма — текстом флеш-сообщения в редиректе.
|
||||
func TestIngestErrorCorrelatesByRequestID(t *testing.T) {
|
||||
t.Run("REST", func(t *testing.T) {
|
||||
ing := &fakeIngestor{err: fmt.Errorf("ingest: create download: %w", errors.New("boom"))}
|
||||
srv := newServer(t, httpapi.Deps{Ingestor: ing, Commander: &fakeCommander{}, Reader: &fakeReader{}})
|
||||
|
||||
resp, err := http.Post(srv.URL+"/api/downloads", "application/json",
|
||||
strings.NewReader(`{"source":"magnet:?xt=urn:btih:abc"}`))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
var body map[string]any
|
||||
if err := json.NewDecoder(resp.Body).Decode(&body); err != nil {
|
||||
t.Fatalf("decode: %v", err)
|
||||
}
|
||||
if s, _ := body["request_id"].(string); s == "" {
|
||||
t.Errorf("в теле отказа нет request_id: %v", body)
|
||||
}
|
||||
if _, ok := body["download_id"]; ok {
|
||||
t.Errorf("в теле отказа обещан download_id: %v", body)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("веб-форма", func(t *testing.T) {
|
||||
ing := &fakeIngestor{err: fmt.Errorf("ingest: create download: %w", errors.New("boom"))}
|
||||
srv := newServer(t, httpapi.Deps{Ingestor: ing, Commander: &fakeCommander{}, Reader: &fakeReader{}})
|
||||
|
||||
resp, err := noRedirectClient().PostForm(srv.URL+"/ui/downloads",
|
||||
url.Values{"source": {"magnet:?xt=urn:btih:abc"}})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
loc := resp.Header.Get("Location")
|
||||
if !strings.Contains(loc, "request_id%3D") && !strings.Contains(loc, "request_id=") {
|
||||
t.Errorf("в сообщении отказа нет request_id: %q", loc)
|
||||
}
|
||||
if strings.Contains(loc, "download_id") {
|
||||
t.Errorf("в сообщении отказа обещан download_id: %q", loc)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user