package metadata import ( "context" "encoding/json" "net/http" "net/http/httptest" "sync/atomic" "testing" ) // fakeTVDB — стенд v4: /login выдаёт токен, остальное требует Bearer. func fakeTVDB(t *testing.T, logins *atomic.Int32) *httptest.Server { t.Helper() mux := http.NewServeMux() mux.HandleFunc("/login", func(w http.ResponseWriter, r *http.Request) { var body map[string]string _ = json.NewDecoder(r.Body).Decode(&body) if body["apikey"] != "k" { w.WriteHeader(http.StatusUnauthorized) return } if logins != nil { logins.Add(1) } _, _ = w.Write([]byte(`{"status":"success","data":{"token":"tok"}}`)) }) authed := func(next http.HandlerFunc) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { if r.Header.Get("Authorization") != "Bearer tok" { w.WriteHeader(http.StatusUnauthorized) return } next(w, r) } } mux.HandleFunc("/search", authed(func(w http.ResponseWriter, r *http.Request) { if r.URL.Query().Get("type") != "series" || r.URL.Query().Get("query") != "Fargo" { t.Errorf("query = %v", r.URL.Query()) } _, _ = w.Write([]byte(`{"data":[{"tvdb_id":"269613","name":"Fargo","year":"2014"}]}`)) })) mux.HandleFunc("/series/269613/extended", authed(func(w http.ResponseWriter, _ *http.Request) { _, _ = w.Write([]byte(`{"data":{"episodes":[ {"seasonNumber":1},{"seasonNumber":1},{"seasonNumber":2} ]}}`)) })) srv := httptest.NewServer(mux) t.Cleanup(srv.Close) return srv } func newTVDB(t *testing.T, url string) *TVDB { t.Helper() c, err := NewTVDB(TVDBConfig{APIKey: "k", BaseURL: url}) if err != nil { t.Fatalf("NewTVDB: %v", err) } return c } func TestTVDB_SearchAndLoginCached(t *testing.T) { var logins atomic.Int32 srv := fakeTVDB(t, &logins) c := newTVDB(t, srv.URL) got, err := c.Search(context.Background(), Query{Type: Series, Title: "Fargo", Year: 2014}) if err != nil { t.Fatalf("Search: %v", err) } if len(got) != 1 || got[0].ID != "269613" || got[0].Provider != "tvdb" || got[0].Year != 2014 { t.Fatalf("candidate = %+v", got) } // Второй запрос переиспользует токен — повторного логина нет. if _, err := c.Search(context.Background(), Query{Type: Series, Title: "Fargo"}); err != nil { t.Fatal(err) } if logins.Load() != 1 { t.Errorf("logins = %d, want 1 (token cached)", logins.Load()) } } func TestTVDB_SeasonEpisodeCounts(t *testing.T) { srv := fakeTVDB(t, nil) counts, err := newTVDB(t, srv.URL).SeasonEpisodeCounts(context.Background(), "269613") if err != nil { t.Fatalf("SeasonEpisodeCounts: %v", err) } if counts[1] != 2 || counts[2] != 1 { t.Errorf("counts = %v", counts) } } func TestTVDB_ReloginOn401(t *testing.T) { var logins atomic.Int32 var token atomic.Value token.Store("tok") mux := http.NewServeMux() mux.HandleFunc("/login", func(w http.ResponseWriter, _ *http.Request) { logins.Add(1) _, _ = w.Write([]byte(`{"data":{"token":"tok"}}`)) }) var firstCall atomic.Bool mux.HandleFunc("/search", func(w http.ResponseWriter, _ *http.Request) { // Первый авторизованный запрос отдаёт 401 (токен «протух»). if firstCall.CompareAndSwap(false, true) { w.WriteHeader(http.StatusUnauthorized) return } _, _ = w.Write([]byte(`{"data":[{"tvdb_id":"1","name":"X","year":"2000"}]}`)) }) srv := httptest.NewServer(mux) defer srv.Close() c := newTVDB(t, srv.URL) got, err := c.Search(context.Background(), Query{Type: Series, Title: "X"}) if err != nil { t.Fatalf("Search: %v", err) } if len(got) != 1 { t.Fatalf("got %d", len(got)) } if logins.Load() != 2 { t.Errorf("logins = %d, want 2 (initial + relogin)", logins.Load()) } } func TestNewTVDB_RequiresKey(t *testing.T) { if _, err := NewTVDB(TVDBConfig{}); err == nil { t.Fatal("want error without api_key") } }