🏡 index : ironmagma/quintodrome.git

package deezer

import (
	"bytes"
	"context"
	"fmt"
	"io"
	"net/http"
	"os"
	"time"

	"github.com/navidrome/navidrome/conf"
	"github.com/navidrome/navidrome/conf/configtest"
	"github.com/navidrome/navidrome/core/agents"
	"github.com/navidrome/navidrome/tests"
	. "github.com/onsi/ginkgo/v2"
	. "github.com/onsi/gomega"
)

var _ = Describe("deezerAgent", func() {
	var ctx context.Context

	BeforeEach(func() {
		ctx = context.Background()
		DeferCleanup(configtest.SetupConfig())
		conf.Server.Deezer.Enabled = true
	})

	Describe("deezerConstructor", func() {
		It("uses configured languages", func() {
			conf.Server.Deezer.Languages = []string{"pt", "en"}
			agent := deezerConstructor(&tests.MockDataStore{}).(*deezerAgent)
			Expect(agent.languages).To(Equal([]string{"pt", "en"}))
		})
	})

	Describe("GetArtistBiography - Language Fallback", func() {
		var agent *deezerAgent
		var httpClient *langAwareHttpClient

		BeforeEach(func() {
			httpClient = newLangAwareHttpClient()

			// Mock search artist (returns Michael Jackson)
			fSearch, _ := os.Open("tests/fixtures/deezer.search.artist.json")
			httpClient.searchResponse = &http.Response{Body: fSearch, StatusCode: 200}

			// Mock JWT token
			testJWT := createTestJWT(5 * time.Minute)
			httpClient.jwtResponse = &http.Response{
				StatusCode: 200,
				Body:       io.NopCloser(bytes.NewBufferString(fmt.Sprintf(`{"jwt":"%s","refresh_token":""}`, testJWT))),
			}
		})

		setupAgent := func(languages []string) {
			conf.Server.Deezer.Languages = languages
			agent = &deezerAgent{
				dataStore: &tests.MockDataStore{},
				client:    newClient(httpClient),
				languages: languages,
			}
		}

		It("returns content in first language when available (1 bio API call)", func() {
			setupAgent([]string{"fr", "en"})

			// French biography available
			fFr, _ := os.Open("tests/fixtures/deezer.artist.bio.fr.json")
			httpClient.bioResponses["fr"] = &http.Response{Body: fFr, StatusCode: 200}

			bio, err := agent.GetArtistBiography(ctx, "", "Michael Jackson", "")

			Expect(err).ToNot(HaveOccurred())
			Expect(bio).To(ContainSubstring("Guy-Manuel de Homem Christo et Thomas Bangalter"))
			Expect(httpClient.bioRequestCount).To(Equal(1))
			Expect(httpClient.bioRequests[0].Header.Get("Accept-Language")).To(Equal("fr"))
		})

		It("falls back to second language when first returns empty (2 bio API calls)", func() {
			setupAgent([]string{"ja", "en"})

			// Japanese returns empty biography
			fJa, _ := os.Open("tests/fixtures/deezer.artist.bio.empty.json")
			httpClient.bioResponses["ja"] = &http.Response{Body: fJa, StatusCode: 200}
			// English returns full biography
			fEn, _ := os.Open("tests/fixtures/deezer.artist.bio.en.json")
			httpClient.bioResponses["en"] = &http.Response{Body: fEn, StatusCode: 200}

			bio, err := agent.GetArtistBiography(ctx, "", "Michael Jackson", "")

			Expect(err).ToNot(HaveOccurred())
			Expect(bio).To(ContainSubstring("Schoolmates Thomas and Guy-Manuel"))
			Expect(httpClient.bioRequestCount).To(Equal(2))
			Expect(httpClient.bioRequests[0].Header.Get("Accept-Language")).To(Equal("ja"))
			Expect(httpClient.bioRequests[1].Header.Get("Accept-Language")).To(Equal("en"))
		})

		It("returns ErrNotFound when all languages return empty", func() {
			setupAgent([]string{"ja", "xx"})

			// Both languages return empty biography
			fJa, _ := os.Open("tests/fixtures/deezer.artist.bio.empty.json")
			httpClient.bioResponses["ja"] = &http.Response{Body: fJa, StatusCode: 200}
			fXx, _ := os.Open("tests/fixtures/deezer.artist.bio.empty.json")
			httpClient.bioResponses["xx"] = &http.Response{Body: fXx, StatusCode: 200}

			_, err := agent.GetArtistBiography(ctx, "", "Michael Jackson", "")

			Expect(err).To(MatchError(agents.ErrNotFound))
			Expect(httpClient.bioRequestCount).To(Equal(2))
		})
	})
})

// langAwareHttpClient is a mock HTTP client that returns different responses based on the Accept-Language header
type langAwareHttpClient struct {
	searchResponse  *http.Response
	jwtResponse     *http.Response
	bioResponses    map[string]*http.Response
	bioRequests     []*http.Request
	bioRequestCount int
}

func newLangAwareHttpClient() *langAwareHttpClient {
	return &langAwareHttpClient{
		bioResponses: make(map[string]*http.Response),
		bioRequests:  make([]*http.Request, 0),
	}
}

func (c *langAwareHttpClient) Do(req *http.Request) (*http.Response, error) {
	// Handle search artist request
	if req.URL.Host == "api.deezer.com" && req.URL.Path == "/search/artist" {
		if c.searchResponse != nil {
			return c.searchResponse, nil
		}
		return &http.Response{
			StatusCode: 200,
			Body:       io.NopCloser(bytes.NewBufferString(`{"data":[],"total":0}`)),
		}, nil
	}

	// Handle JWT token request
	if req.URL.Host == "auth.deezer.com" && req.URL.Path == "/login/anonymous" {
		if c.jwtResponse != nil {
			return c.jwtResponse, nil
		}
		return &http.Response{
			StatusCode: 500,
			Body:       io.NopCloser(bytes.NewBufferString(`{"error":"no mock"}`)),
		}, nil
	}

	// Handle bio request (GraphQL API)
	if req.URL.Host == "pipe.deezer.com" && req.URL.Path == "/api" {
		c.bioRequestCount++
		c.bioRequests = append(c.bioRequests, req)
		lang := req.Header.Get("Accept-Language")
		if resp, ok := c.bioResponses[lang]; ok {
			return resp, nil
		}
		// Return empty bio by default
		return &http.Response{
			StatusCode: 200,
			Body:       io.NopCloser(bytes.NewBufferString(`{"data":{"artist":{"bio":{"full":""}}}}`)),
		}, nil
	}

	panic("URL not mocked: " + req.URL.String())
}