package tidal import ( "context" "encoding/base64" "encoding/json" "net/http" "net/http/httptest" "strconv" "testing" "streamrip-go/internal/config" ) func TestLoginMissingToken(t *testing.T) { cfgData := config.DefaultConfigData() cfgData.Tidal.AccessToken = "" c := New(&config.Config{File: cfgData, Session: cfgData}) err := c.Login(context.Background()) if err == nil { t.Fatalf("expected error") } } func TestSearch(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case "/v1/sessions": _ = json.NewEncoder(w).Encode(map[string]any{"countryCode": "US", "userId": 123}) case "/v1/search/albums": _ = json.NewEncoder(w).Encode(map[string]any{"items": []any{map[string]any{"id": 1, "title": "x"}}}) default: w.WriteHeader(http.StatusNotFound) } })) defer ts.Close() cfgData := config.DefaultConfigData() cfgData.Tidal.AccessToken = "token" cfgData.Tidal.CountryCode = "US" c := New(&config.Config{File: cfgData, Session: cfgData}) c.baseURL = ts.URL + "/v1" if err := c.Login(context.Background()); err != nil { t.Fatalf("login err = %v", err) } pages, err := c.Search(context.Background(), "album", "x", 10) if err != nil { t.Fatalf("search err = %v", err) } if len(pages) != 1 { t.Fatalf("pages = %d", len(pages)) } } func TestGetVideoDownloadable(t *testing.T) { var server *httptest.Server server = httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case "/v1/sessions": _ = json.NewEncoder(w).Encode(map[string]any{"countryCode": "US", "userId": 123}) case "/v1/videos/42/playbackinfopostpaywall": manifest := map[string]any{"urls": []string{server.URL + "/master.m3u8"}} b, _ := json.Marshal(manifest) _ = json.NewEncoder(w).Encode(map[string]any{"manifest": base64.StdEncoding.EncodeToString(b)}) case "/master.m3u8": _, _ = w.Write([]byte("#EXTM3U\n#EXT-X-STREAM-INF:BANDWIDTH=1000,CODECS=\"avc1.42E01E,mp4a.40.2\",RESOLUTION=640x360\nlow/stream.m3u8\n#EXT-X-STREAM-INF:BANDWIDTH=2000,CODECS=\"avc1.4D401F,mp4a.40.2\",RESOLUTION=1280x720\nhi/stream.m3u8\n")) default: w.WriteHeader(http.StatusNotFound) } })) defer server.Close() cfgData := config.DefaultConfigData() cfgData.Tidal.AccessToken = "token" cfgData.Tidal.CountryCode = "US" c := New(&config.Config{File: cfgData, Session: cfgData}) c.baseURL = server.URL + "/v1" if err := c.Login(context.Background()); err != nil { t.Fatalf("login err = %v", err) } d, err := c.GetVideoDownloadable(context.Background(), "42") if err != nil { t.Fatalf("GetVideoDownloadable() err = %v", err) } if d.Extension != "mp4" { t.Fatalf("extension = %q, want mp4", d.Extension) } if d.URL != server.URL+"/hi/stream.m3u8" { t.Fatalf("url = %q, want %q", d.URL, server.URL+"/hi/stream.m3u8") } } func TestBestHLSVariantURLFallsBackToMaster(t *testing.T) { master := "https://example.com/master.m3u8" got := bestHLSVariantURL(master, "#EXTM3U\n#comment") if got != master { t.Fatalf("url = %q, want %q", got, master) } } func TestGetMetadataArtistPaginatesAlbums(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { switch r.URL.Path { case "/v1/sessions": _ = json.NewEncoder(w).Encode(map[string]any{"countryCode": "US", "userId": 123}) case "/v1/artists/9": _ = json.NewEncoder(w).Encode(map[string]any{"id": 9, "name": "Artist X"}) case "/v1/artists/9/albums": offset, _ := strconv.Atoi(r.URL.Query().Get("offset")) filter := r.URL.Query().Get("filter") if filter == "" { if offset == 0 { items := make([]any, 0, 100) for i := 0; i < 100; i++ { items = append(items, map[string]any{"id": i + 1}) } _ = json.NewEncoder(w).Encode(map[string]any{"items": items}) return } if offset == 100 { _ = json.NewEncoder(w).Encode(map[string]any{"items": []any{map[string]any{"id": 101}}}) return } _ = json.NewEncoder(w).Encode(map[string]any{"items": []any{}}) return } if filter == "EPSANDSINGLES" { if offset == 0 { _ = json.NewEncoder(w).Encode(map[string]any{"items": []any{map[string]any{"id": 101}, map[string]any{"id": 102}}}) return } _ = json.NewEncoder(w).Encode(map[string]any{"items": []any{}}) return } w.WriteHeader(http.StatusBadRequest) default: w.WriteHeader(http.StatusNotFound) } })) defer ts.Close() cfgData := config.DefaultConfigData() cfgData.Tidal.AccessToken = "token" cfgData.Tidal.CountryCode = "US" c := New(&config.Config{File: cfgData, Session: cfgData}) c.baseURL = ts.URL + "/v1" if err := c.Login(context.Background()); err != nil { t.Fatalf("login err = %v", err) } meta, err := c.GetMetadata(context.Background(), "9", "artist") if err != nil { t.Fatalf("GetMetadata() err = %v", err) } albumsObj, _ := meta["albums"].(map[string]any) items, _ := albumsObj["items"].([]map[string]any) if len(items) != 102 { t.Fatalf("albums len = %d, want 102", len(items)) } }