Реализация, фаза 1: добавление данных в qbittorrent
This commit is contained in:
@@ -0,0 +1,177 @@
|
||||
package httpapi_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.vakhrushev.me/av/jellybit/internal/httpapi"
|
||||
"git.vakhrushev.me/av/jellybit/internal/ingest"
|
||||
"git.vakhrushev.me/av/jellybit/internal/store"
|
||||
)
|
||||
|
||||
type fakeIngestor struct {
|
||||
res ingest.Result
|
||||
err error
|
||||
lastReq ingest.Request
|
||||
}
|
||||
|
||||
func (f *fakeIngestor) Ingest(_ context.Context, req ingest.Request) (ingest.Result, error) {
|
||||
f.lastReq = req
|
||||
return f.res, f.err
|
||||
}
|
||||
|
||||
type fakeCommander struct {
|
||||
cancelled []int64
|
||||
retried []int64
|
||||
err error
|
||||
}
|
||||
|
||||
func (f *fakeCommander) Cancel(_ context.Context, id int64) error {
|
||||
if f.err != nil {
|
||||
return f.err
|
||||
}
|
||||
f.cancelled = append(f.cancelled, id)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *fakeCommander) Retry(_ context.Context, id int64) error {
|
||||
if f.err != nil {
|
||||
return f.err
|
||||
}
|
||||
f.retried = append(f.retried, id)
|
||||
return nil
|
||||
}
|
||||
|
||||
type fakeReader struct {
|
||||
list []store.Download
|
||||
get *store.Download
|
||||
}
|
||||
|
||||
func (f *fakeReader) ListDownloads(_ context.Context) ([]store.Download, error) { return f.list, nil }
|
||||
|
||||
func (f *fakeReader) GetDownload(_ context.Context, id int64) (*store.Download, error) {
|
||||
if f.get != nil {
|
||||
return f.get, nil
|
||||
}
|
||||
return &store.Download{ID: id, State: store.StateCancelled}, nil
|
||||
}
|
||||
|
||||
func newServer(t *testing.T, d httpapi.Deps) *httptest.Server {
|
||||
t.Helper()
|
||||
if d.Logger == nil {
|
||||
d.Logger = slog.New(slog.NewTextHandler(io.Discard, nil))
|
||||
}
|
||||
h, err := httpapi.NewRouter(d)
|
||||
if err != nil {
|
||||
t.Fatalf("NewRouter: %v", err)
|
||||
}
|
||||
srv := httptest.NewServer(h)
|
||||
t.Cleanup(srv.Close)
|
||||
return srv
|
||||
}
|
||||
|
||||
func TestAPIAdd(t *testing.T) {
|
||||
ing := &fakeIngestor{res: ingest.Result{DownloadID: 1, Infohash: "abc", State: store.StateDownloading}}
|
||||
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","context":"Дюна"}`))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
t.Fatalf("status = %d, want 201", resp.StatusCode)
|
||||
}
|
||||
var got map[string]any
|
||||
_ = json.NewDecoder(resp.Body).Decode(&got)
|
||||
if got["id"].(float64) != 1 || got["state"] != "downloading" {
|
||||
t.Errorf("body = %v", got)
|
||||
}
|
||||
if ing.lastReq.Context != "Дюна" {
|
||||
t.Errorf("контекст не проброшен: %q", ing.lastReq.Context)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPIAddBadInput(t *testing.T) {
|
||||
ing := &fakeIngestor{err: ingestErr("bad magnet")}
|
||||
srv := newServer(t, httpapi.Deps{Ingestor: ing, Commander: &fakeCommander{}, Reader: &fakeReader{}})
|
||||
|
||||
resp, err := http.Post(srv.URL+"/api/downloads", "application/json", strings.NewReader(`{"source":"x"}`))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusBadRequest {
|
||||
t.Fatalf("status = %d, want 400", resp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPIList(t *testing.T) {
|
||||
reader := &fakeReader{list: []store.Download{
|
||||
{ID: 2, SourceType: store.SourceMagnet, State: store.StateCompleted, Infohash: store.NullString("abc")},
|
||||
{ID: 1, SourceType: store.SourceMagnet, State: store.StateDownloading},
|
||||
}}
|
||||
srv := newServer(t, httpapi.Deps{Ingestor: &fakeIngestor{}, Commander: &fakeCommander{}, Reader: reader})
|
||||
|
||||
resp, err := http.Get(srv.URL + "/api/downloads")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
var got []map[string]any
|
||||
_ = json.NewDecoder(resp.Body).Decode(&got)
|
||||
if len(got) != 2 {
|
||||
t.Fatalf("len = %d, want 2", len(got))
|
||||
}
|
||||
if got[0]["state"] != "completed" || got[0]["infohash"] != "abc" {
|
||||
t.Errorf("first = %v", got[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPICancel(t *testing.T) {
|
||||
cmd := &fakeCommander{}
|
||||
srv := newServer(t, httpapi.Deps{Ingestor: &fakeIngestor{}, Commander: cmd, Reader: &fakeReader{}})
|
||||
|
||||
resp, err := http.Post(srv.URL+"/api/downloads/5/cancel", "", nil)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("status = %d, want 200", resp.StatusCode)
|
||||
}
|
||||
if len(cmd.cancelled) != 1 || cmd.cancelled[0] != 5 {
|
||||
t.Errorf("cancel вызван неверно: %v", cmd.cancelled)
|
||||
}
|
||||
}
|
||||
|
||||
func TestIndexRenders(t *testing.T) {
|
||||
reader := &fakeReader{list: []store.Download{
|
||||
{ID: 1, SourceType: store.SourceMagnet, SourceRef: "magnet:?xt=urn:btih:abc", State: store.StateDownloading},
|
||||
}}
|
||||
srv := newServer(t, httpapi.Deps{Ingestor: &fakeIngestor{}, Commander: &fakeCommander{}, Reader: reader})
|
||||
|
||||
resp, err := http.Get(srv.URL + "/")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
t.Fatalf("status = %d", resp.StatusCode)
|
||||
}
|
||||
if !strings.Contains(string(body), "jellybit") || !strings.Contains(string(body), "downloading") {
|
||||
t.Error("страница не содержит ожидаемого контента")
|
||||
}
|
||||
}
|
||||
|
||||
type ingestErr string
|
||||
|
||||
func (e ingestErr) Error() string { return string(e) }
|
||||
Reference in New Issue
Block a user