Something went wrong. Try again.
Self-hosted web interface and downloader for Qobuz.
downloader self-hosted music qobuz
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594package api_test
import ( "context" "encoding/json" "fmt" "io" "net/http" "net/http/httptest" "strings" "testing"
"gobuz/internal/api")
func TestAuthWithToken_FreeAccount(t *testing.T) { srv := newMockServer(func(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(map[string]interface{}{ "user": map[string]interface{}{ "credential": map[string]interface{}{ "parameters": nil, }, }, }) }) defer srv.Close()
c := clientForServer(t, srv) err := c.AuthWithToken(context.Background(), "42", "tok") if err == nil { t.Fatal("expected IneligibleError for free account") } if _, ok := err.(*api.IneligibleError); !ok { t.Errorf("expected IneligibleError, got %T: %v", err, err) }}
func TestAuthWithToken_BadShape(t *testing.T) { srv := newMockServer(func(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(map[string]interface{}{ "user": "notamap", }) }) defer srv.Close()
c := clientForServer(t, srv) err := c.AuthWithToken(context.Background(), "42", "tok") if err == nil { t.Fatal("expected error for bad response shape") } if _, ok := err.(*api.AuthenticationError); !ok { t.Errorf("expected AuthenticationError, got %T: %v", err, err) }}
func TestDoGet_401_ReturnsAuthError(t *testing.T) { srv := newMockServer(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(401) json.NewEncoder(w).Encode(map[string]interface{}{ "code": 401, "message": "User authentication is required.", }) }) defer srv.Close()
c := clientForServer(t, srv) err := c.AuthWithToken(context.Background(), "99", "tok") if err == nil { t.Fatal("expected error, got nil") } if _, ok := err.(*api.AuthenticationError); !ok { t.Errorf("expected AuthenticationError, got %T: %v", err, err) }}
func TestErrorTypes(t *testing.T) { errs := []error{ &api.AuthenticationError{}, &api.IneligibleError{}, &api.InvalidAppIDError{}, &api.InvalidAppSecretError{}, &api.InvalidQualityError{}, } for _, err := range errs { if err.Error() == "" { t.Errorf("%T.Error() returned empty string", err) } }}
func TestGetAlbum_MockServer(t *testing.T) { srv := newMockServer(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/api.json/0.2/album/get" { t.Errorf("unexpected path: %s", r.URL.Path) } if r.URL.Query().Get("album_id") != "abc123" { t.Errorf("unexpected album_id: %s", r.URL.Query().Get("album_id")) } json.NewEncoder(w).Encode(map[string]interface{}{ "id": "abc123", "title": "Test Album", }) }) defer srv.Close()
c := clientForServer(t, srv) meta, err := c.GetAlbum(context.Background(), "abc123") if err != nil { t.Fatalf("GetAlbum: %v", err) } if meta.Title != "Test Album" { t.Errorf("title = %q", meta.Title) } if meta.ID != "abc123" { t.Errorf("id = %q", meta.ID) }}
func TestGetTrack_MockServer(t *testing.T) { srv := newMockServer(func(w http.ResponseWriter, r *http.Request) { json.NewEncoder(w).Encode(map[string]interface{}{ "id": "t1", "title": "My Track", }) }) defer srv.Close()
c := clientForServer(t, srv) meta, err := c.GetTrack(context.Background(), "t1") if err != nil { t.Fatalf("GetTrack: %v", err) } if meta.Title != "My Track" { t.Errorf("title = %q", meta.Title) } if meta.ID != "t1" { t.Errorf("id = %q", meta.ID) }}
func TestAuthWithToken_MockServer(t *testing.T) { srv := newMockServer(func(w http.ResponseWriter, r *http.Request) { q := r.URL.Query() if q.Get("user_id") != "99" || q.Get("user_auth_token") != "mytoken" { w.WriteHeader(401) json.NewEncoder(w).Encode(map[string]interface{}{"code": 401}) return } json.NewEncoder(w).Encode(map[string]interface{}{ "user_auth_token": "mytoken", "user": map[string]interface{}{ "id": "99", "credential": map[string]interface{}{ "parameters": map[string]interface{}{ "short_label": "Sublime", }, }, }, }) }) defer srv.Close()
c := clientForServer(t, srv) if err := c.AuthWithToken(context.Background(), "99", "mytoken"); err != nil { t.Fatalf("AuthWithToken: %v", err) } if c.Label != "Sublime" { t.Errorf("Label = %q", c.Label) } if c.UAT != "mytoken" { t.Errorf("UAT = %q", c.UAT) }}
func TestAuthWithToken_WrongToken_Returns401(t *testing.T) { srv := newMockServer(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(401) json.NewEncoder(w).Encode(map[string]interface{}{"code": 401}) }) defer srv.Close()
c := clientForServer(t, srv) err := c.AuthWithToken(context.Background(), "1", "badtoken") if err == nil { t.Fatal("expected error for 401") } if _, ok := err.(*api.AuthenticationError); !ok { t.Errorf("expected AuthenticationError, got %T", err) }}
func TestSearchAlbums_MockServer(t *testing.T) { srv := newMockServer(func(w http.ResponseWriter, r *http.Request) { if r.URL.Query().Get("query") != "radiohead" { t.Errorf("unexpected query: %s", r.URL.Query().Get("query")) } json.NewEncoder(w).Encode(map[string]interface{}{ "albums": map[string]interface{}{ "total": 1, "items": []interface{}{ map[string]interface{}{"id": "alb1", "title": "OK Computer"}, }, }, }) }) defer srv.Close()
c := clientForServer(t, srv) results, err := c.SearchAlbums(context.Background(), "radiohead", 5) if err != nil { t.Fatalf("SearchAlbums: %v", err) } if len(results.Items) != 1 { t.Errorf("expected 1 result, got %d", len(results.Items)) } if results.Items[0].Title != "OK Computer" { t.Errorf("title = %q, want %q", results.Items[0].Title, "OK Computer") }}
func TestSearchArtists_MockServer(t *testing.T) { srv := newMockServer(func(w http.ResponseWriter, r *http.Request) { if r.URL.Query().Get("query") != "radiohead" { t.Errorf("unexpected query: %s", r.URL.Query().Get("query")) } json.NewEncoder(w).Encode(map[string]interface{}{ "artists": map[string]interface{}{ "total": 1, "items": []interface{}{ map[string]interface{}{"id": 123, "name": "Radiohead"}, }, }, }) }) defer srv.Close()
c := clientForServer(t, srv) results, err := c.SearchArtists(context.Background(), "radiohead", 5) if err != nil { t.Fatalf("SearchArtists: %v", err) } if len(results.Items) != 1 { t.Errorf("expected 1 result, got %d", len(results.Items)) } if results.Items[0].Name != "Radiohead" { t.Errorf("name = %q, want %q", results.Items[0].Name, "Radiohead") }}
func TestGetTrackURL_InvalidQuality(t *testing.T) { c := api.New("123", nil) _, err := c.GetTrackURL(context.Background(), "t1", 99) if err == nil { t.Fatal("expected error for invalid quality") } if _, ok := err.(*api.InvalidQualityError); !ok { t.Errorf("expected InvalidQualityError, got %T", err) }}
func TestGetTrackURL_MockServer(t *testing.T) { srv := newMockServer(func(w http.ResponseWriter, r *http.Request) { q := r.URL.Query() if q.Get("track_id") == "" || q.Get("format_id") == "" { t.Error("missing required params") } if q.Get("request_sig") == "" { t.Error("missing request_sig") } json.NewEncoder(w).Encode(map[string]interface{}{ "url": "https://cdn.example.com/track.flac", }) }) defer srv.Close()
c := clientForServer(t, srv) c.Secret = "mysecret" result, err := c.GetTrackURL(context.Background(), "t1", 6) if err != nil { t.Fatalf("GetTrackURL: %v", err) } if result.URL != "https://cdn.example.com/track.flac" { t.Errorf("url = %q", result.URL) }}
func TestHTTP500_ReturnsError(t *testing.T) { srv := newMockServer(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(500) w.Write([]byte("internal error")) }) defer srv.Close()
c := clientForServer(t, srv) _, err := c.GetAlbum(context.Background(), "any") if err == nil { t.Fatal("expected error for 500") }}
type roundTripFunc func(req *http.Request) (*http.Response, error)
func (f roundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) { return f(req)}
func TestGetArtistPage_Signing(t *testing.T) { var gotPath, gotArtistID, gotSort, gotTS, gotSig string hc := &http.Client{ Transport: roundTripFunc(func(r *http.Request) (*http.Response, error) { gotPath = r.URL.Path gotArtistID = r.URL.Query().Get("artist_id") gotSort = r.URL.Query().Get("sort") gotTS = r.URL.Query().Get("request_ts") gotSig = r.URL.Query().Get("request_sig")
body := `{"id": 12345, "name": {"display": "Radiohead"}}` return &http.Response{ StatusCode: 200, Body: io.NopCloser(strings.NewReader(body)), Header: make(http.Header), }, nil }), }
c := api.NewWithHTTP("123456789", []string{"testsecret"}, hc)
res, err := c.GetArtistPage(context.Background(), "12345", "release_date") if err != nil { t.Fatalf("GetArtistPage failed: %v", err) }
if !strings.HasSuffix(gotPath, "/artist/page") { t.Errorf("expected path to end in /artist/page, got %s", gotPath) } if gotArtistID != "12345" { t.Errorf("expected artist_id 12345, got %s", gotArtistID) } if gotSort != "release_date" { t.Errorf("expected sort release_date, got %s", gotSort) } if gotTS == "" { t.Errorf("expected non-empty request_ts") } if gotSig == "" { t.Errorf("expected non-empty request_sig") } if res.ID != "12345" { t.Errorf("expected response to have id 12345, got %s", res.ID) } if res.Name.Display != "Radiohead" { t.Errorf("expected response to have name Radiohead, got %s", res.Name.Display) }}
func TestGetArtistReleasesGrid_Signing(t *testing.T) { var gotPath, gotArtistID, gotType, gotLimit, gotOffset, gotSig string hc := &http.Client{ Transport: roundTripFunc(func(r *http.Request) (*http.Response, error) { gotPath = r.URL.Path gotArtistID = r.URL.Query().Get("artist_id") gotType = r.URL.Query().Get("release_type") gotLimit = r.URL.Query().Get("limit") gotOffset = r.URL.Query().Get("offset") gotSig = r.URL.Query().Get("request_sig")
body := `{"has_more": false, "items": []}` return &http.Response{ StatusCode: 200, Body: io.NopCloser(strings.NewReader(body)), Header: make(http.Header), }, nil }), }
c := api.NewWithHTTP("123456789", []string{"testsecret"}, hc)
res, err := c.GetArtistReleasesGrid(context.Background(), "12345", "album", 20, 0, "release_date") if err != nil { t.Fatalf("GetArtistReleasesGrid failed: %v", err) }
if !strings.HasSuffix(gotPath, "/artist/getReleasesGrid") { t.Errorf("expected path to end in /artist/getReleasesGrid, got %s", gotPath) } if gotArtistID != "12345" { t.Errorf("expected artist_id 12345, got %s", gotArtistID) } if gotType != "album" { t.Errorf("expected release_type album, got %s", gotType) } if gotLimit != "20" { t.Errorf("expected limit 20, got %s", gotLimit) } if gotOffset != "0" { t.Errorf("expected offset 0, got %s", gotOffset) } if gotSig == "" { t.Errorf("expected non-empty request_sig") } if res.HasMore != false { t.Errorf("expected has_more false, got %v", res.HasMore) }}
func TestGetArtist_MockServer(t *testing.T) { srv := newMockServer(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/api.json/0.2/artist/get" { t.Errorf("unexpected path: %s", r.URL.Path) } if r.URL.Query().Get("artist_id") != "123" { t.Errorf("unexpected artist_id: %s", r.URL.Query().Get("artist_id")) } json.NewEncoder(w).Encode(map[string]interface{}{ "id": 123, "name": "Radiohead", "albums_count": 9, }) }) defer srv.Close()
c := clientForServer(t, srv) artist, err := c.GetArtist(context.Background(), "123") if err != nil { t.Fatalf("GetArtist: %v", err) } if artist.Name != "Radiohead" { t.Errorf("artist.Name = %q, want Radiohead", artist.Name) } if artist.AlbumsCount != 9 { t.Errorf("artist.AlbumsCount = %d, want 9", artist.AlbumsCount) }}
func TestCatalogSearch_MockServer(t *testing.T) { srv := newMockServer(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/api.json/0.2/catalog/search" { t.Errorf("unexpected path: %s", r.URL.Path) } if r.URL.Query().Get("query") != "radiohead" { t.Errorf("unexpected query: %s", r.URL.Query().Get("query")) } json.NewEncoder(w).Encode(map[string]interface{}{ "albums": map[string]interface{}{ "total": 1, "items": []interface{}{ map[string]interface{}{"id": "alb1", "title": "OK Computer"}, }, }, "tracks": map[string]interface{}{ "total": 1, "items": []interface{}{ map[string]interface{}{"id": 101, "title": "Karma Police"}, }, }, }) }) defer srv.Close()
c := clientForServer(t, srv) res, err := c.CatalogSearch(context.Background(), "radiohead", 10, 0) if err != nil { t.Fatalf("CatalogSearch: %v", err) } if res.Albums == nil || len(res.Albums.Items) != 1 || res.Albums.Items[0].Title != "OK Computer" { t.Errorf("unexpected albums: %v", res.Albums) } if res.Tracks == nil || len(res.Tracks.Items) != 1 || res.Tracks.Items[0].Title != "Karma Police" { t.Errorf("unexpected tracks: %v", res.Tracks) }}
func TestGetRaw_MockServer(t *testing.T) { expected := `{"custom_field": "verbatim", "number": 42}` srv := newMockServer(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") w.Write([]byte(expected)) }) defer srv.Close()
c := clientForServer(t, srv) raw, err := c.GetRaw(context.Background(), "test/endpoint", nil) if err != nil { t.Fatalf("GetRaw failed: %v", err) } if string(raw) != expected { t.Errorf("got %s, want %s", string(raw), expected) }}
func TestGetAlbumRaw_MockServer(t *testing.T) { expected := `{"id": "alb999", "title": "Verbatim Album", "extra_prop": true}` srv := newMockServer(func(w http.ResponseWriter, r *http.Request) { if r.URL.Query().Get("album_id") != "alb999" { t.Errorf("unexpected album_id: %s", r.URL.Query().Get("album_id")) } w.Header().Set("Content-Type", "application/json") w.Write([]byte(expected)) }) defer srv.Close()
c := clientForServer(t, srv) raw, err := c.GetAlbumRaw(context.Background(), "alb999") if err != nil { t.Fatalf("GetAlbumRaw failed: %v", err) } if string(raw) != expected { t.Errorf("got %s, want %s", string(raw), expected) }}
func TestGetArtistPageRaw_MockServer(t *testing.T) { expected := `{"id": 12345, "name": {"display": "Radiohead"}, "arbitrary": 123}` srv := newMockServer(func(w http.ResponseWriter, r *http.Request) { if r.URL.Query().Get("artist_id") != "12345" { t.Errorf("unexpected artist_id: %s", r.URL.Query().Get("artist_id")) } if r.URL.Query().Get("sort") != "release_date" { t.Errorf("unexpected sort: %s", r.URL.Query().Get("sort")) } if r.URL.Query().Get("request_sig") == "" { t.Errorf("expected request_sig") } w.Header().Set("Content-Type", "application/json") w.Write([]byte(expected)) }) defer srv.Close()
c := clientForServer(t, srv) raw, err := c.GetArtistPageRaw(context.Background(), "12345", "release_date") if err != nil { t.Fatalf("GetArtistPageRaw failed: %v", err) } if string(raw) != expected { t.Errorf("got %s, want %s", string(raw), expected) }}
func TestSearchArtistsRaw_MockServer(t *testing.T) { expected := `{"items": [{"id": 1, "name": "Radiohead"}]}` srv := newMockServer(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") fmt.Fprintf(w, `{"query": "radiohead", "artists": %s}`, expected) }) defer srv.Close()
c := clientForServer(t, srv) raw, err := c.SearchArtistsRaw(context.Background(), "radiohead", 5) if err != nil { t.Fatalf("SearchArtistsRaw failed: %v", err) } if string(raw) != expected { t.Errorf("got %s, want %s", string(raw), expected) }}
func TestSearchAlbumsRaw_MockServer(t *testing.T) { expected := `{"items": [{"id": "alb1", "title": "OK Computer"}]}` srv := newMockServer(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") fmt.Fprintf(w, `{"query": "radiohead", "albums": %s}`, expected) }) defer srv.Close()
c := clientForServer(t, srv) raw, err := c.SearchAlbumsRaw(context.Background(), "radiohead", 5) if err != nil { t.Fatalf("SearchAlbumsRaw failed: %v", err) } if string(raw) != expected { t.Errorf("got %s, want %s", string(raw), expected) }}
type mockServer struct { handler http.HandlerFunc}
func (s *mockServer) Close() {}
func newMockServer(h http.HandlerFunc) *mockServer { return &mockServer{handler: h}}
type handlerTransport struct { handler http.HandlerFunc}
func (h *handlerTransport) RoundTrip(req *http.Request) (*http.Response, error) { rec := httptest.NewRecorder() h.handler(rec, req) return rec.Result(), nil}
func clientForServer(t *testing.T, srv *mockServer) *api.Client { t.Helper() return api.NewWithHTTP("123456789", []string{"testsecret"}, &http.Client{Transport: &handlerTransport{handler: srv.handler}})}