package httpapi_test import ( "bytes" "encoding/json" "errors" "fmt" "mime/multipart" "net/http" "net/url" "strings" "testing" "git.vakhrushev.me/av/jellybit/internal/httpapi" "git.vakhrushev.me/av/jellybit/internal/ingest" "git.vakhrushev.me/av/jellybit/internal/store" ) // Веб-форма с выбранным .torrent-файлом → приём по байтам (TorrentData). func TestUIAddTorrentFile(t *testing.T) { ing := &fakeIngestor{res: ingest.Result{DownloadID: tid, State: store.StateCatched}} srv := newServer(t, httpapi.Deps{Ingestor: ing, Commander: &fakeCommander{}, Reader: &fakeReader{}}) var body bytes.Buffer mw := multipart.NewWriter(&body) _ = mw.WriteField("source", "") // источник пуст — используется файл fw, _ := mw.CreateFormFile("torrent", "dune.torrent") torrentBytes := []byte("d8:announce…bytes") _, _ = fw.Write(torrentBytes) _ = mw.Close() resp, err := noRedirectClient().Post(srv.URL+"/ui/downloads", mw.FormDataContentType(), &body) if err != nil { t.Fatal(err) } defer resp.Body.Close() if resp.StatusCode != http.StatusSeeOther { t.Fatalf("status = %d, want 303", resp.StatusCode) } if !bytes.Equal(ing.lastReq.TorrentData, torrentBytes) { t.Errorf("TorrentData не проброшен в ingest: %q", ing.lastReq.TorrentData) } } // Без файла — прежний текстовый путь (Source), TorrentData пуст. func TestUIAddTextNoFile(t *testing.T) { ing := &fakeIngestor{res: ingest.Result{DownloadID: tid, State: store.StateCatched}} srv := newServer(t, httpapi.Deps{Ingestor: ing, Commander: &fakeCommander{}, Reader: &fakeReader{}}) var body bytes.Buffer mw := multipart.NewWriter(&body) _ = mw.WriteField("source", "magnet:?xt=urn:btih:abc") _ = mw.Close() resp, err := noRedirectClient().Post(srv.URL+"/ui/downloads", mw.FormDataContentType(), &body) if err != nil { t.Fatal(err) } defer resp.Body.Close() if resp.StatusCode != http.StatusSeeOther { t.Fatalf("status = %d, want 303", resp.StatusCode) } if len(ing.lastReq.TorrentData) != 0 { t.Errorf("TorrentData должен быть пуст без файла") } if !strings.HasPrefix(ing.lastReq.Source, "magnet:") { t.Errorf("source не проброшен: %q", ing.lastReq.Source) } } // Обычная (не-multipart) urlencoded-отправка тоже работает (ErrNotMultipart // глотается, поля уже в PostForm) — устойчивость к curl/старой странице. func TestUIAddUrlencoded(t *testing.T) { ing := &fakeIngestor{res: ingest.Result{DownloadID: tid, State: store.StateCatched}} 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() if resp.StatusCode != http.StatusSeeOther { t.Fatalf("status = %d, want 303", resp.StatusCode) } if !strings.HasPrefix(ing.lastReq.Source, "magnet:") { 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) } }) }