mirror of
https://git.sr.ht/~joren/streamrip-go
synced 2026-06-17 15:05:39 +02:00
Merge remote-tracking branch 'origin/master' into fix/resolved-audio-folder-naming
This commit is contained in:
@@ -105,6 +105,23 @@ func TestGetDownloadableNativeCipher(t *testing.T) {
|
||||
case "/track/42":
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{"id": 42, "title": "X", "track_token": "tt"})
|
||||
case "/media":
|
||||
if got := strings.TrimSpace(r.Header.Get("Accept-Charset")); got != "UTF-8" {
|
||||
t.Fatalf("accept-charset = %q, want UTF-8", got)
|
||||
}
|
||||
var payload map[string]any
|
||||
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
media, _ := payload["media"].([]any)
|
||||
if len(media) != 1 {
|
||||
t.Fatalf("media length = %d, want 1", len(media))
|
||||
}
|
||||
entry, _ := media[0].(map[string]any)
|
||||
formats, _ := entry["formats"].([]any)
|
||||
if len(formats) != 6 {
|
||||
t.Fatalf("formats length = %d, want 6", len(formats))
|
||||
}
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{"data": []any{map[string]any{"errors": []any{}, "media": []any{map[string]any{"cipher": map[string]any{"type": "BF_CBC_STRIPE"}, "format": "FLAC", "sources": []any{map[string]any{"url": "https://cdn.example/file"}}}}}}})
|
||||
default:
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
@@ -144,6 +161,66 @@ func TestGetDownloadableNativeCipher(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoginPrefersARLFlowOverRefreshShortcut(t *testing.T) {
|
||||
mobileToken := testMobileToken(t)
|
||||
refreshCalled := false
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case "/web":
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{"results": map[string]any{"USER_ID": "42", "JWT": "jwt-from-web", "refresh_token": "refresh-from-web", "license_token": "license-from-web"}})
|
||||
case "/gateway":
|
||||
switch r.URL.Query().Get("method") {
|
||||
case "mobile_auth":
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{"results": map[string]any{"TOKEN": mobileToken}})
|
||||
case "api_checkToken":
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{"results": "sid123"})
|
||||
case "mobile_userAutolog":
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{"results": map[string]any{"JWT": "jwt-from-autolog", "license_token": "license-from-autolog", "refresh_token": "refresh-from-autolog"}})
|
||||
default:
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
case "/pipe":
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{"data": map[string]any{"tokens": map[string]any{"mediaServiceLicenseToken": map[string]any{"token": "license-from-pipe"}}}})
|
||||
case "/renew":
|
||||
refreshCalled = true
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
default:
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
cfgData := config.DefaultConfigData()
|
||||
cfgData.Deezer.ARL = "arl"
|
||||
cfgData.Deezer.RefreshToken = "refresh-token"
|
||||
c := New(&config.Config{File: cfgData, Session: cfgData})
|
||||
|
||||
origGateway := gatewayURL
|
||||
origWeb := webGWLight
|
||||
origPipe := pipeURL
|
||||
origAuth := authURL
|
||||
gatewayURL = ts.URL + "/gateway"
|
||||
webGWLight = ts.URL + "/web"
|
||||
pipeURL = ts.URL + "/pipe"
|
||||
authURL = ts.URL + "/renew"
|
||||
defer func() {
|
||||
gatewayURL = origGateway
|
||||
webGWLight = origWeb
|
||||
pipeURL = origPipe
|
||||
authURL = origAuth
|
||||
}()
|
||||
|
||||
if err := c.Login(context.Background()); err != nil {
|
||||
t.Fatalf("Login() error = %v", err)
|
||||
}
|
||||
if refreshCalled {
|
||||
t.Fatalf("expected ARL launch flow without refresh shortcut")
|
||||
}
|
||||
if c.license != "license-from-pipe" {
|
||||
t.Fatalf("license = %q, want license-from-pipe", c.license)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetDownloadableRequiresARL(t *testing.T) {
|
||||
cfgData := config.DefaultConfigData()
|
||||
cfgData.Deezer.ARL = ""
|
||||
@@ -194,6 +271,96 @@ func TestGetDownloadableDRMError(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetTrackTokenPrefersPipeToken(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case "/pipe":
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{"data": map[string]any{"track": map[string]any{"media": map[string]any{"token": map[string]any{"payload": "pipe-track-token"}}}}})
|
||||
case "/track/42":
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{"id": 42, "track_token": "api-track-token"})
|
||||
default:
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
cfgData := config.DefaultConfigData()
|
||||
c := New(&config.Config{File: cfgData, Session: cfgData})
|
||||
c.loggedIn = true
|
||||
c.jwt = "jwt-token"
|
||||
|
||||
origBase := baseURL
|
||||
origPipe := pipeURL
|
||||
baseURL = ts.URL
|
||||
pipeURL = ts.URL + "/pipe"
|
||||
defer func() {
|
||||
baseURL = origBase
|
||||
pipeURL = origPipe
|
||||
}()
|
||||
|
||||
token, err := c.getTrackToken(context.Background(), "42")
|
||||
if err != nil {
|
||||
t.Fatalf("getTrackToken() error = %v", err)
|
||||
}
|
||||
if token != "pipe-track-token" {
|
||||
t.Fatalf("token = %q, want pipe-track-token", token)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetDownloadableUsesPipeTrackToken(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
case "/track/42":
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{"id": 42, "title": "X", "track_token": "api-track-token"})
|
||||
case "/pipe":
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{"data": map[string]any{"track": map[string]any{"media": map[string]any{"token": map[string]any{"payload": "pipe-track-token"}}}}})
|
||||
case "/media":
|
||||
var payload map[string]any
|
||||
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
tokens, _ := payload["track_tokens"].([]any)
|
||||
if len(tokens) == 0 || strings.TrimSpace(jsonutil.StringFromAny(tokens[0])) != "pipe-track-token" {
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{"data": []any{map[string]any{"errors": []any{map[string]any{"code": 2004, "message": "The track country differs from the license."}}, "media": []any{}}}})
|
||||
return
|
||||
}
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{"data": []any{map[string]any{"errors": []any{}, "media": []any{map[string]any{"cipher": map[string]any{"type": "BF_CBC_STRIPE"}, "format": "FLAC", "sources": []any{map[string]any{"url": "https://cdn.example/file"}}}}}}})
|
||||
default:
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
}
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
cfgData := config.DefaultConfigData()
|
||||
cfgData.Deezer.ARL = "arl"
|
||||
c := New(&config.Config{File: cfgData, Session: cfgData})
|
||||
c.loggedIn = true
|
||||
c.arl = "arl"
|
||||
c.license = "license"
|
||||
c.jwt = "jwt"
|
||||
|
||||
origBase := baseURL
|
||||
origMedia := mediaURL
|
||||
origPipe := pipeURL
|
||||
baseURL = ts.URL
|
||||
mediaURL = ts.URL + "/media"
|
||||
pipeURL = ts.URL + "/pipe"
|
||||
defer func() {
|
||||
baseURL = origBase
|
||||
mediaURL = origMedia
|
||||
pipeURL = origPipe
|
||||
}()
|
||||
|
||||
d, err := c.GetDownloadable(context.Background(), "42", 2)
|
||||
if err != nil {
|
||||
t.Fatalf("GetDownloadable() error = %v", err)
|
||||
}
|
||||
if d.URL != "https://cdn.example/file" || d.Extension != "flac" {
|
||||
t.Fatalf("unexpected downloadable: %+v", d)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetMetadataAddsLyricsFromPipe(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
switch r.URL.Path {
|
||||
@@ -295,6 +462,102 @@ func TestLoginWithCredentials(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestMobileAuthIncludesAppContextParams(t *testing.T) {
|
||||
mobileToken := testMobileToken(t)
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/gateway" {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
if r.URL.Query().Get("method") != "mobile_auth" {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if got := strings.TrimSpace(r.URL.Query().Get("version")); got != deezerAppVersion {
|
||||
t.Fatalf("version = %q, want %q", got, deezerAppVersion)
|
||||
}
|
||||
if got := strings.TrimSpace(r.URL.Query().Get("lang")); got != deezerAppLang {
|
||||
t.Fatalf("lang = %q, want %q", got, deezerAppLang)
|
||||
}
|
||||
if got := strings.TrimSpace(r.URL.Query().Get("buildId")); got != deezerBuildID {
|
||||
t.Fatalf("buildId = %q, want %q", got, deezerBuildID)
|
||||
}
|
||||
if got := strings.TrimSpace(r.URL.Query().Get("screenWidth")); got != deezerScreenW {
|
||||
t.Fatalf("screenWidth = %q, want %q", got, deezerScreenW)
|
||||
}
|
||||
if got := strings.TrimSpace(r.URL.Query().Get("screenHeight")); got != deezerScreenH {
|
||||
t.Fatalf("screenHeight = %q, want %q", got, deezerScreenH)
|
||||
}
|
||||
if got := strings.TrimSpace(r.URL.Query().Get("uniq_id")); got == "" {
|
||||
t.Fatalf("uniq_id is empty")
|
||||
}
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{"results": map[string]any{"TOKEN": mobileToken}})
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
cfgData := config.DefaultConfigData()
|
||||
c := New(&config.Config{File: cfgData, Session: cfgData})
|
||||
|
||||
origGateway := gatewayURL
|
||||
gatewayURL = ts.URL + "/gateway"
|
||||
defer func() { gatewayURL = origGateway }()
|
||||
|
||||
token, err := c.mobileAuth(context.Background())
|
||||
if err != nil {
|
||||
t.Fatalf("mobileAuth() error = %v", err)
|
||||
}
|
||||
if token != mobileToken {
|
||||
t.Fatalf("token = %q, want %q", token, mobileToken)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMobileUserAutologIncludesRefreshToken(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.URL.Path != "/gateway" {
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
if method := r.URL.Query().Get("method"); method != "mobile_userAutolog" {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if got := strings.TrimSpace(r.URL.Query().Get("arl")); got != "" {
|
||||
t.Fatalf("unexpected arl query parameter: %q", got)
|
||||
}
|
||||
var payload map[string]any
|
||||
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
if got := strings.TrimSpace(jsonutil.StringFromAny(payload["refresh_token"])); got != "refresh-token" {
|
||||
t.Fatalf("refresh_token payload = %q, want refresh-token", got)
|
||||
}
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{"results": map[string]any{"JWT": "jwt-token", "license_token": "license-token"}})
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
cfgData := config.DefaultConfigData()
|
||||
c := New(&config.Config{File: cfgData, Session: cfgData})
|
||||
c.sid = "sid123"
|
||||
c.userID = "42"
|
||||
c.arl = "arl-token"
|
||||
c.refresh = "refresh-token"
|
||||
|
||||
origGateway := gatewayURL
|
||||
gatewayURL = ts.URL + "/gateway"
|
||||
defer func() { gatewayURL = origGateway }()
|
||||
|
||||
if err := c.mobileUserAutolog(context.Background()); err != nil {
|
||||
t.Fatalf("mobileUserAutolog() error = %v", err)
|
||||
}
|
||||
if c.jwt != "jwt-token" {
|
||||
t.Fatalf("jwt = %q, want jwt-token", c.jwt)
|
||||
}
|
||||
if c.license != "license-token" {
|
||||
t.Fatalf("license = %q, want license-token", c.license)
|
||||
}
|
||||
}
|
||||
|
||||
func testMobileToken(t *testing.T) string {
|
||||
t.Helper()
|
||||
plain := []byte(strings.Repeat("A", 64) + strings.Repeat("B", 16) + strings.Repeat("C", 16))
|
||||
@@ -385,3 +648,51 @@ func TestRefreshLicenseFromPipeGraphQLError(t *testing.T) {
|
||||
t.Fatalf("expected graphql error, got %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRefreshLicenseFromPipeUsesMobileGraphQLShape(t *testing.T) {
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if got := strings.TrimSpace(r.Header.Get("Authorization")); got != "Bearer jwt-token" {
|
||||
t.Fatalf("authorization = %q, want Bearer jwt-token", got)
|
||||
}
|
||||
if got := r.Header.Get("Accept"); !strings.Contains(got, "application/graphql-response+json") {
|
||||
t.Fatalf("accept header = %q", got)
|
||||
}
|
||||
if got := strings.TrimSpace(r.Header.Get("Accept-Language")); got != "en-US" {
|
||||
t.Fatalf("accept-language = %q, want en-US", got)
|
||||
}
|
||||
var payload map[string]any
|
||||
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
op := strings.TrimSpace(jsonutil.StringFromAny(payload["operationName"]))
|
||||
if op != "KmpMpMediaServiceLicenseToken" {
|
||||
t.Fatalf("operationName = %q, want KmpMpMediaServiceLicenseToken", op)
|
||||
}
|
||||
ext, _ := payload["extensions"].(map[string]any)
|
||||
clientLib, _ := ext["clientLibrary"].(map[string]any)
|
||||
if got := strings.TrimSpace(jsonutil.StringFromAny(clientLib["name"])); got != "apollo-kotlin" {
|
||||
t.Fatalf("clientLibrary.name = %q, want apollo-kotlin", got)
|
||||
}
|
||||
if got := strings.TrimSpace(jsonutil.StringFromAny(clientLib["version"])); got != "4.4.2" {
|
||||
t.Fatalf("clientLibrary.version = %q, want 4.4.2", got)
|
||||
}
|
||||
_ = json.NewEncoder(w).Encode(map[string]any{"data": map[string]any{"tokens": map[string]any{"mediaServiceLicenseToken": map[string]any{"token": "license-token"}}}})
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
cfgData := config.DefaultConfigData()
|
||||
c := New(&config.Config{File: cfgData, Session: cfgData})
|
||||
c.jwt = "jwt-token"
|
||||
|
||||
origPipe := pipeURL
|
||||
pipeURL = ts.URL
|
||||
defer func() { pipeURL = origPipe }()
|
||||
|
||||
if err := c.refreshLicenseFromPipe(context.Background()); err != nil {
|
||||
t.Fatalf("refreshLicenseFromPipe() error = %v", err)
|
||||
}
|
||||
if c.license != "license-token" {
|
||||
t.Fatalf("license = %q, want license-token", c.license)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user