mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-09-03 08:40:44 +02:00
198 lines
6.0 KiB
Go
198 lines
6.0 KiB
Go
package gobackend
|
|
|
|
import (
|
|
"context"
|
|
"io"
|
|
"net/http"
|
|
"sync"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
type roundTripFunc func(*http.Request) (*http.Response, error)
|
|
|
|
func (fn roundTripFunc) RoundTrip(req *http.Request) (*http.Response, error) {
|
|
return fn(req)
|
|
}
|
|
|
|
type blockingPlatformResolver struct {
|
|
calls atomic.Int32
|
|
started chan struct{}
|
|
release chan struct{}
|
|
result resolverResult
|
|
}
|
|
|
|
func (r *blockingPlatformResolver) Resolve(context.Context, string, resolverMetadata) (resolverResult, error) {
|
|
if r.calls.Add(1) == 1 {
|
|
close(r.started)
|
|
}
|
|
<-r.release
|
|
return r.result, nil
|
|
}
|
|
|
|
func TestIdenticalPlatformResolverRequestsAreCoalesced(t *testing.T) {
|
|
resolver := &blockingPlatformResolver{
|
|
started: make(chan struct{}),
|
|
release: make(chan struct{}),
|
|
result: resolverResult{Links: map[string]songLinkPlatformLink{
|
|
"deezer": {URL: "https://www.deezer.com/track/123"},
|
|
}},
|
|
}
|
|
client := &SongLinkClient{fallbackResolver: resolver}
|
|
|
|
const workers = 16
|
|
start := make(chan struct{})
|
|
errs := make(chan error, workers)
|
|
var wg sync.WaitGroup
|
|
wg.Add(workers)
|
|
for range workers {
|
|
go func() {
|
|
defer wg.Done()
|
|
<-start
|
|
links, err := client.resolveTrackPlatforms("https://open.spotify.com/track/coalesced")
|
|
if err == nil && links["deezer"].URL == "" {
|
|
err = io.ErrUnexpectedEOF
|
|
}
|
|
errs <- err
|
|
}()
|
|
}
|
|
close(start)
|
|
|
|
select {
|
|
case <-resolver.started:
|
|
case <-time.After(time.Second):
|
|
t.Fatal("resolver did not start")
|
|
}
|
|
time.Sleep(20 * time.Millisecond)
|
|
close(resolver.release)
|
|
wg.Wait()
|
|
close(errs)
|
|
for err := range errs {
|
|
if err != nil {
|
|
t.Fatalf("coalesced resolution failed: %v", err)
|
|
}
|
|
}
|
|
if got := resolver.calls.Load(); got != 1 {
|
|
t.Fatalf("resolver calls = %d, want 1", got)
|
|
}
|
|
}
|
|
|
|
func TestGetRetryAfterDurationMissingHeaderReturnsZero(t *testing.T) {
|
|
resp := &http.Response{Header: make(http.Header)}
|
|
if got := getRetryAfterDuration(resp); got != 0 {
|
|
t.Fatalf("getRetryAfterDuration() = %v, want 0", got)
|
|
}
|
|
}
|
|
|
|
func resetTrackAvailabilityCache() {
|
|
trackAvailabilityCacheMu.Lock()
|
|
trackAvailabilityCache = map[string]trackAvailabilityCacheEntry{}
|
|
trackAvailabilityCacheMu.Unlock()
|
|
}
|
|
|
|
// testResolverResult is an in-memory fixture; none of these URLs are fetched.
|
|
func testResolverResult() resolverResult {
|
|
return resolverResult{Links: map[string]songLinkPlatformLink{
|
|
"spotify": {URL: "https://open.spotify.com/track/testspotifyid"},
|
|
"deezer": {URL: "https://www.deezer.com/track/101"},
|
|
"amazonMusic": {URL: "https://music.amazon.com/tracks/TESTASIN"},
|
|
"tidal": {URL: "https://listen.tidal.com/track/202"},
|
|
"qobuz": {URL: "https://open.qobuz.com/track/303"},
|
|
"youtubeMusic": {URL: "https://music.youtube.com/watch?v=testvideoid1"},
|
|
}}
|
|
}
|
|
|
|
func TestCheckTrackAvailabilityFromSpotifyUsesActiveResolverChain(t *testing.T) {
|
|
resetTrackAvailabilityCache()
|
|
client := &SongLinkClient{fallbackResolver: &stubPlatformResolver{result: testResolverResult()}}
|
|
|
|
availability, err := client.CheckTrackAvailability("testspotifyid", "")
|
|
if err != nil {
|
|
t.Fatalf("CheckTrackAvailability() error = %v", err)
|
|
}
|
|
if availability.SpotifyID != "testspotifyid" || availability.DeezerID != "101" {
|
|
t.Fatalf("availability IDs = %+v", availability)
|
|
}
|
|
if !availability.Deezer || !availability.Amazon || !availability.Tidal || !availability.Qobuz || !availability.YouTube {
|
|
t.Fatalf("availability flags = %+v", availability)
|
|
}
|
|
if availability.YouTubeID != "testvideoid1" {
|
|
t.Fatalf("YouTubeID = %q", availability.YouTubeID)
|
|
}
|
|
}
|
|
|
|
type inputCapturingResolver struct {
|
|
input string
|
|
result resolverResult
|
|
}
|
|
|
|
func (r *inputCapturingResolver) Resolve(_ context.Context, input string, _ resolverMetadata) (resolverResult, error) {
|
|
r.input = input
|
|
return r.result, nil
|
|
}
|
|
|
|
func TestResolveTrackPlatformsByPlatformBuildsDirectURL(t *testing.T) {
|
|
resolver := &inputCapturingResolver{result: testResolverResult()}
|
|
client := &SongLinkClient{fallbackResolver: resolver}
|
|
|
|
links, err := client.resolveTrackPlatformsByPlatform("spotify", "song", "testspotifyid")
|
|
if err != nil {
|
|
t.Fatalf("resolveTrackPlatformsByPlatform() error = %v", err)
|
|
}
|
|
if resolver.input != "https://open.spotify.com/track/testspotifyid" {
|
|
t.Fatalf("resolver input = %q", resolver.input)
|
|
}
|
|
if links["deezer"].URL == "" {
|
|
t.Fatalf("resolver links = %#v", links)
|
|
}
|
|
}
|
|
|
|
func TestCheckTrackAvailabilityCachesResult(t *testing.T) {
|
|
resetTrackAvailabilityCache()
|
|
resolver := &stubPlatformResolver{result: resolverResult{Links: map[string]songLinkPlatformLink{
|
|
"spotify": {URL: "https://open.spotify.com/track/cachedid"},
|
|
"deezer": {URL: "https://www.deezer.com/track/111"},
|
|
}}}
|
|
client := &SongLinkClient{fallbackResolver: resolver}
|
|
|
|
first, err := client.CheckTrackAvailability("cachedid", "")
|
|
if err != nil {
|
|
t.Fatalf("first CheckTrackAvailability() error = %v", err)
|
|
}
|
|
second, err := client.CheckTrackAvailability("cachedid", "")
|
|
if err != nil {
|
|
t.Fatalf("second CheckTrackAvailability() error = %v", err)
|
|
}
|
|
if resolver.calls != 1 {
|
|
t.Fatalf("resolver calls = %d, want 1", resolver.calls)
|
|
}
|
|
if first == second || second.DeezerID != "111" {
|
|
t.Fatalf("cached result = %+v", second)
|
|
}
|
|
}
|
|
|
|
func TestCheckTrackAvailabilityNegativeCacheTTL(t *testing.T) {
|
|
resetTrackAvailabilityCache()
|
|
entry := trackAvailabilityCacheEntry{err: true, expiresAt: time.Now().Add(-time.Second)}
|
|
key := GetSongLinkRegion() + "|spotify:expiredneg"
|
|
trackAvailabilityCacheMu.Lock()
|
|
trackAvailabilityCache[key] = entry
|
|
trackAvailabilityCacheMu.Unlock()
|
|
|
|
if _, hit, _ := trackAvailabilityCacheLookup(key); hit {
|
|
t.Fatal("expired negative entry should not be a cache hit")
|
|
}
|
|
}
|
|
|
|
func TestCheckAvailabilityFromDeezerUsesActiveResolverChain(t *testing.T) {
|
|
client := &SongLinkClient{fallbackResolver: &stubPlatformResolver{result: testResolverResult()}}
|
|
availability, err := client.CheckAvailabilityFromDeezer("908604612")
|
|
if err != nil {
|
|
t.Fatalf("CheckAvailabilityFromDeezer() error = %v", err)
|
|
}
|
|
if !availability.Deezer || availability.DeezerID != "908604612" || availability.SpotifyID != "testspotifyid" {
|
|
t.Fatalf("availability = %+v", availability)
|
|
}
|
|
}
|