refactor: update HTTP request handling to use host.HTTPSend for improved error management

This commit is contained in:
deluan
2026-02-24 13:46:56 -05:00
parent df3a426203
commit 375dd85b15
10 changed files with 119 additions and 117 deletions
+12 -8
View File
@@ -84,17 +84,21 @@ func uploadToUguu(imageData []byte, contentType string) (string, error) {
body = append(body, imageData...) body = append(body, imageData...)
body = append(body, []byte(fmt.Sprintf("\r\n--%s--\r\n", boundary))...) body = append(body, []byte(fmt.Sprintf("\r\n--%s--\r\n", boundary))...)
req := pdk.NewHTTPRequest(pdk.MethodPost, "https://uguu.se/upload") resp, err := host.HTTPSend(host.HTTPRequest{
req.SetHeader("Content-Type", fmt.Sprintf("multipart/form-data; boundary=%s", boundary)) Method: "POST",
req.SetBody(body) URL: "https://uguu.se/upload",
Headers: map[string]string{"Content-Type": fmt.Sprintf("multipart/form-data; boundary=%s", boundary)},
resp := req.Send() Body: body,
if resp.Status() >= 400 { })
return "", fmt.Errorf("uguu.se upload failed: HTTP %d", resp.Status()) if err != nil {
return "", fmt.Errorf("uguu.se upload failed: %w", err)
}
if resp.StatusCode >= 400 {
return "", fmt.Errorf("uguu.se upload failed: HTTP %d", resp.StatusCode)
} }
var result uguuResponse var result uguuResponse
if err := json.Unmarshal(resp.Body(), &result); err != nil { if err := json.Unmarshal(resp.Body, &result); err != nil {
return "", fmt.Errorf("failed to parse uguu.se response: %w", err) return "", fmt.Errorf("failed to parse uguu.se response: %w", err)
} }
+8 -7
View File
@@ -20,6 +20,8 @@ var _ = Describe("getImageURL", func() {
host.ArtworkMock.Calls = nil host.ArtworkMock.Calls = nil
host.SubsonicAPIMock.ExpectedCalls = nil host.SubsonicAPIMock.ExpectedCalls = nil
host.SubsonicAPIMock.Calls = nil host.SubsonicAPIMock.Calls = nil
host.HTTPMock.ExpectedCalls = nil
host.HTTPMock.Calls = nil
pdk.PDKMock.On("Log", mock.Anything, mock.Anything).Maybe() pdk.PDKMock.On("Log", mock.Anything, mock.Anything).Maybe()
}) })
@@ -71,10 +73,9 @@ var _ = Describe("getImageURL", func() {
Return("image/jpeg", imageData, nil) Return("image/jpeg", imageData, nil)
// Mock uguu.se HTTP upload // Mock uguu.se HTTP upload
uguuReq := &pdk.HTTPRequest{} host.HTTPMock.On("Send", mock.MatchedBy(func(req host.HTTPRequest) bool {
pdk.PDKMock.On("NewHTTPRequest", pdk.MethodPost, "https://uguu.se/upload").Return(uguuReq) return req.URL == "https://uguu.se/upload"
pdk.PDKMock.On("Send", uguuReq).Return(pdk.NewStubHTTPResponse(200, nil, })).Return(&host.HTTPResponse{StatusCode: 200, Body: []byte(`{"success":true,"files":[{"url":"https://a.uguu.se/uploaded.jpg"}]}`)}, nil)
[]byte(`{"success":true,"files":[{"url":"https://a.uguu.se/uploaded.jpg"}]}`)))
// Mock cache set // Mock cache set
host.CacheMock.On("SetString", "uguu.artwork.track1", "https://a.uguu.se/uploaded.jpg", int64(9000)).Return(nil) host.CacheMock.On("SetString", "uguu.artwork.track1", "https://a.uguu.se/uploaded.jpg", int64(9000)).Return(nil)
@@ -98,9 +99,9 @@ var _ = Describe("getImageURL", func() {
host.SubsonicAPIMock.On("CallRaw", "/getCoverArt?u=testuser&id=track1&size=300"). host.SubsonicAPIMock.On("CallRaw", "/getCoverArt?u=testuser&id=track1&size=300").
Return("image/jpeg", []byte("fake-image-data"), nil) Return("image/jpeg", []byte("fake-image-data"), nil)
uguuReq := &pdk.HTTPRequest{} host.HTTPMock.On("Send", mock.MatchedBy(func(req host.HTTPRequest) bool {
pdk.PDKMock.On("NewHTTPRequest", pdk.MethodPost, "https://uguu.se/upload").Return(uguuReq) return req.URL == "https://uguu.se/upload"
pdk.PDKMock.On("Send", uguuReq).Return(pdk.NewStubHTTPResponse(500, nil, []byte(`{"success":false}`))) })).Return(&host.HTTPResponse{StatusCode: 500, Body: []byte(`{"success":false}`)}, nil)
url := getImageURL("testuser", "track1") url := getImageURL("testuser", "track1")
Expect(url).To(BeEmpty()) Expect(url).To(BeEmpty())
+1 -1
View File
@@ -3,7 +3,7 @@ module discord-rich-presence
go 1.25 go 1.25
require ( require (
github.com/navidrome/navidrome/plugins/pdk/go v0.0.0-20260207182358-29f98b889b11 github.com/navidrome/navidrome/plugins/pdk/go v0.0.0-20260224182233-40719d928320
github.com/onsi/ginkgo/v2 v2.28.1 github.com/onsi/ginkgo/v2 v2.28.1
github.com/onsi/gomega v1.39.1 github.com/onsi/gomega v1.39.1
github.com/stretchr/testify v1.11.1 github.com/stretchr/testify v1.11.1
+2 -2
View File
@@ -30,8 +30,8 @@ github.com/maruel/natural v1.3.0 h1:VsmCsBmEyrR46RomtgHs5hbKADGRVtliHTyCOLFBpsg=
github.com/maruel/natural v1.3.0/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg= github.com/maruel/natural v1.3.0/go.mod h1:v+Rfd79xlw1AgVBjbO0BEQmptqb5HvL/k9GRHB7ZKEg=
github.com/mfridman/tparse v0.18.0 h1:wh6dzOKaIwkUGyKgOntDW4liXSo37qg5AXbIhkMV3vE= github.com/mfridman/tparse v0.18.0 h1:wh6dzOKaIwkUGyKgOntDW4liXSo37qg5AXbIhkMV3vE=
github.com/mfridman/tparse v0.18.0/go.mod h1:gEvqZTuCgEhPbYk/2lS3Kcxg1GmTxxU7kTC8DvP0i/A= github.com/mfridman/tparse v0.18.0/go.mod h1:gEvqZTuCgEhPbYk/2lS3Kcxg1GmTxxU7kTC8DvP0i/A=
github.com/navidrome/navidrome/plugins/pdk/go v0.0.0-20260207182358-29f98b889b11 h1:VE4bqzkS6apWDtco9hAGdThFttjbYoLR0DEILAGDyyc= github.com/navidrome/navidrome/plugins/pdk/go v0.0.0-20260224182233-40719d928320 h1:TVn0Jv9Xd4aoyTbBoSMAv38Mfh8lWX/kMP2au2KX1cQ=
github.com/navidrome/navidrome/plugins/pdk/go v0.0.0-20260207182358-29f98b889b11/go.mod h1:HijQ0Z0OeEa6LIwUJh6H9WqAptye096jHazmKXf+YV4= github.com/navidrome/navidrome/plugins/pdk/go v0.0.0-20260224182233-40719d928320/go.mod h1:HijQ0Z0OeEa6LIwUJh6H9WqAptye096jHazmKXf+YV4=
github.com/onsi/ginkgo/v2 v2.28.1 h1:S4hj+HbZp40fNKuLUQOYLDgZLwNUVn19N3Atb98NCyI= github.com/onsi/ginkgo/v2 v2.28.1 h1:S4hj+HbZp40fNKuLUQOYLDgZLwNUVn19N3Atb98NCyI=
github.com/onsi/ginkgo/v2 v2.28.1/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE= github.com/onsi/ginkgo/v2 v2.28.1/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE=
github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28= github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28=
+10 -12
View File
@@ -33,6 +33,8 @@ var _ = Describe("discordPlugin", func() {
host.ArtworkMock.Calls = nil host.ArtworkMock.Calls = nil
host.SubsonicAPIMock.ExpectedCalls = nil host.SubsonicAPIMock.ExpectedCalls = nil
host.SubsonicAPIMock.Calls = nil host.SubsonicAPIMock.Calls = nil
host.HTTPMock.ExpectedCalls = nil
host.HTTPMock.Calls = nil
}) })
Describe("getConfig", func() { Describe("getConfig", func() {
@@ -128,9 +130,9 @@ var _ = Describe("discordPlugin", func() {
// Mock HTTP GET request for gateway discovery // Mock HTTP GET request for gateway discovery
gatewayResp := []byte(`{"url":"wss://gateway.discord.gg"}`) gatewayResp := []byte(`{"url":"wss://gateway.discord.gg"}`)
gatewayReq := &pdk.HTTPRequest{} host.HTTPMock.On("Send", mock.MatchedBy(func(req host.HTTPRequest) bool {
pdk.PDKMock.On("NewHTTPRequest", pdk.MethodGet, "https://discord.com/api/gateway").Return(gatewayReq).Once() return req.Method == "GET" && req.URL == "https://discord.com/api/gateway"
pdk.PDKMock.On("Send", gatewayReq).Return(pdk.NewStubHTTPResponse(200, nil, gatewayResp)).Once() })).Return(&host.HTTPResponse{StatusCode: 200, Body: gatewayResp}, nil)
// Mock WebSocket connection // Mock WebSocket connection
host.WebSocketMock.On("Connect", mock.MatchedBy(func(url string) bool { host.WebSocketMock.On("Connect", mock.MatchedBy(func(url string) bool {
@@ -148,9 +150,7 @@ var _ = Describe("discordPlugin", func() {
host.ArtworkMock.On("GetTrackUrl", "track1", int32(300)).Return("https://example.com/art.jpg", nil) host.ArtworkMock.On("GetTrackUrl", "track1", int32(300)).Return("https://example.com/art.jpg", nil)
// Mock HTTP POST requests (Discord external assets API) // Mock HTTP POST requests (Discord external assets API)
postReq := &pdk.HTTPRequest{} host.HTTPMock.On("Send", externalAssetsReq).Return(&host.HTTPResponse{StatusCode: 200, Body: []byte(`{}`)}, nil)
pdk.PDKMock.On("NewHTTPRequest", pdk.MethodPost, externalAssetsURL).Return(postReq)
pdk.PDKMock.On("Send", postReq).Return(pdk.NewStubHTTPResponse(200, nil, []byte(`{}`)))
// Schedule clear activity callback // Schedule clear activity callback
host.SchedulerMock.On("ScheduleOneTime", mock.Anything, payloadClearActivity, "testuser-clear").Return("testuser-clear", nil) host.SchedulerMock.On("ScheduleOneTime", mock.Anything, payloadClearActivity, "testuser-clear").Return("testuser-clear", nil)
@@ -180,9 +180,9 @@ var _ = Describe("discordPlugin", func() {
// Connect mocks // Connect mocks
host.CacheMock.On("GetInt", "discord.seq.testuser").Return(int64(0), false, errors.New("not found")) host.CacheMock.On("GetInt", "discord.seq.testuser").Return(int64(0), false, errors.New("not found"))
gatewayResp := []byte(`{"url":"wss://gateway.discord.gg"}`) gatewayResp := []byte(`{"url":"wss://gateway.discord.gg"}`)
gatewayReq := &pdk.HTTPRequest{} host.HTTPMock.On("Send", mock.MatchedBy(func(req host.HTTPRequest) bool {
pdk.PDKMock.On("NewHTTPRequest", pdk.MethodGet, "https://discord.com/api/gateway").Return(gatewayReq).Once() return req.Method == "GET" && req.URL == "https://discord.com/api/gateway"
pdk.PDKMock.On("Send", gatewayReq).Return(pdk.NewStubHTTPResponse(200, nil, gatewayResp)).Once() })).Return(&host.HTTPResponse{StatusCode: 200, Body: gatewayResp}, nil)
host.WebSocketMock.On("Connect", mock.MatchedBy(func(url string) bool { host.WebSocketMock.On("Connect", mock.MatchedBy(func(url string) bool {
return strings.Contains(url, "gateway.discord.gg") return strings.Contains(url, "gateway.discord.gg")
}), mock.Anything, "testuser").Return("testuser", nil) }), mock.Anything, "testuser").Return("testuser", nil)
@@ -199,9 +199,7 @@ var _ = Describe("discordPlugin", func() {
host.CacheMock.On("GetString", discordImageKey).Return("", false, nil) host.CacheMock.On("GetString", discordImageKey).Return("", false, nil)
host.CacheMock.On("SetString", discordImageKey, mock.Anything, mock.Anything).Return(nil) host.CacheMock.On("SetString", discordImageKey, mock.Anything, mock.Anything).Return(nil)
host.ArtworkMock.On("GetTrackUrl", "track1", int32(300)).Return("https://example.com/art.jpg", nil) host.ArtworkMock.On("GetTrackUrl", "track1", int32(300)).Return("https://example.com/art.jpg", nil)
postReq := &pdk.HTTPRequest{} host.HTTPMock.On("Send", externalAssetsReq).Return(&host.HTTPResponse{StatusCode: 200, Body: []byte(`{}`)}, nil)
pdk.PDKMock.On("NewHTTPRequest", pdk.MethodPost, externalAssetsURL).Return(postReq)
pdk.PDKMock.On("Send", postReq).Return(pdk.NewStubHTTPResponse(200, nil, []byte(`{}`)))
host.SchedulerMock.On("ScheduleOneTime", mock.Anything, payloadClearActivity, "testuser-clear").Return("testuser-clear", nil) host.SchedulerMock.On("ScheduleOneTime", mock.Anything, payloadClearActivity, "testuser-clear").Return("testuser-clear", nil)
err := plugin.NowPlaying(scrobbler.NowPlayingRequest{ err := plugin.NowPlaying(scrobbler.NowPlayingRequest{
+2 -1
View File
@@ -4,6 +4,7 @@ import (
"strings" "strings"
"testing" "testing"
"github.com/navidrome/navidrome/plugins/pdk/go/host"
. "github.com/onsi/ginkgo/v2" . "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega" . "github.com/onsi/gomega"
"github.com/stretchr/testify/mock" "github.com/stretchr/testify/mock"
@@ -17,6 +18,6 @@ func TestDiscordPlugin(t *testing.T) {
// Shared matchers for tighter mock expectations across all test files. // Shared matchers for tighter mock expectations across all test files.
var ( var (
discordImageKey = mock.MatchedBy(func(key string) bool { return strings.HasPrefix(key, "discord.image.") }) discordImageKey = mock.MatchedBy(func(key string) bool { return strings.HasPrefix(key, "discord.image.") })
externalAssetsURL = mock.MatchedBy(func(url string) bool { return strings.Contains(url, "external-assets") }) externalAssetsReq = mock.MatchedBy(func(req host.HTTPRequest) bool { return strings.Contains(req.URL, "external-assets") })
spotifyURLKey = mock.MatchedBy(func(key string) bool { return strings.HasPrefix(key, "spotify.url.") }) spotifyURLKey = mock.MatchedBy(func(key string) bool { return strings.HasPrefix(key, "spotify.url.") })
) )
+24 -14
View File
@@ -147,18 +147,22 @@ func (r *discordRPC) processImage(imageURL, clientID, token string, ttl int64) (
// Process via Discord API // Process via Discord API
body := fmt.Sprintf(`{"urls":[%q]}`, imageURL) body := fmt.Sprintf(`{"urls":[%q]}`, imageURL)
req := pdk.NewHTTPRequest(pdk.MethodPost, fmt.Sprintf("https://discord.com/api/v9/applications/%s/external-assets", clientID)) resp, err := host.HTTPSend(host.HTTPRequest{
req.SetHeader("Authorization", token) Method: "POST",
req.SetHeader("Content-Type", "application/json") URL: fmt.Sprintf("https://discord.com/api/v9/applications/%s/external-assets", clientID),
req.SetBody([]byte(body)) Headers: map[string]string{"Authorization": token, "Content-Type": "application/json"},
Body: []byte(body),
resp := req.Send() })
if resp.Status() >= 400 { if err != nil {
return "", fmt.Errorf("failed to process image: HTTP %d", resp.Status()) pdk.Log(pdk.LogWarn, fmt.Sprintf("HTTP request failed for image processing: %v", err))
return "", fmt.Errorf("failed to process image: %w", err)
}
if resp.StatusCode >= 400 {
return "", fmt.Errorf("failed to process image: HTTP %d", resp.StatusCode)
} }
var data []map[string]string var data []map[string]string
if err := json.Unmarshal(resp.Body(), &data); err != nil { if err := json.Unmarshal(resp.Body, &data); err != nil {
return "", fmt.Errorf("failed to unmarshal image response: %w", err) return "", fmt.Errorf("failed to unmarshal image response: %w", err)
} }
@@ -257,14 +261,20 @@ func (r *discordRPC) sendMessage(username string, opCode int, payload any) error
// getDiscordGateway retrieves the Discord gateway URL. // getDiscordGateway retrieves the Discord gateway URL.
func (r *discordRPC) getDiscordGateway() (string, error) { func (r *discordRPC) getDiscordGateway() (string, error) {
req := pdk.NewHTTPRequest(pdk.MethodGet, "https://discord.com/api/gateway") resp, err := host.HTTPSend(host.HTTPRequest{
resp := req.Send() Method: "GET",
if resp.Status() != 200 { URL: "https://discord.com/api/gateway",
return "", fmt.Errorf("failed to get Discord gateway: HTTP %d", resp.Status()) })
if err != nil {
pdk.Log(pdk.LogWarn, fmt.Sprintf("HTTP request failed for Discord gateway: %v", err))
return "", fmt.Errorf("failed to get Discord gateway: %w", err)
}
if resp.StatusCode != 200 {
return "", fmt.Errorf("failed to get Discord gateway: HTTP %d", resp.StatusCode)
} }
var result map[string]string var result map[string]string
if err := json.Unmarshal(resp.Body(), &result); err != nil { if err := json.Unmarshal(resp.Body, &result); err != nil {
return "", fmt.Errorf("failed to parse Discord gateway response: %w", err) return "", fmt.Errorf("failed to parse Discord gateway response: %w", err)
} }
return result["url"], nil return result["url"], nil
+15 -35
View File
@@ -25,6 +25,8 @@ var _ = Describe("discordRPC", func() {
host.WebSocketMock.Calls = nil host.WebSocketMock.Calls = nil
host.SchedulerMock.ExpectedCalls = nil host.SchedulerMock.ExpectedCalls = nil
host.SchedulerMock.Calls = nil host.SchedulerMock.Calls = nil
host.HTTPMock.ExpectedCalls = nil
host.HTTPMock.Calls = nil
}) })
Describe("sendMessage", func() { Describe("sendMessage", func() {
@@ -81,9 +83,9 @@ var _ = Describe("discordRPC", func() {
// Mock HTTP GET request for gateway discovery // Mock HTTP GET request for gateway discovery
gatewayResp := []byte(`{"url":"wss://gateway.discord.gg"}`) gatewayResp := []byte(`{"url":"wss://gateway.discord.gg"}`)
httpReq := &pdk.HTTPRequest{} host.HTTPMock.On("Send", mock.MatchedBy(func(req host.HTTPRequest) bool {
pdk.PDKMock.On("NewHTTPRequest", pdk.MethodGet, "https://discord.com/api/gateway").Return(httpReq) return req.Method == "GET" && req.URL == "https://discord.com/api/gateway"
pdk.PDKMock.On("Send", mock.Anything).Return(pdk.NewStubHTTPResponse(200, nil, gatewayResp)) })).Return(&host.HTTPResponse{StatusCode: 200, Body: gatewayResp}, nil)
// Mock WebSocket connection // Mock WebSocket connection
host.WebSocketMock.On("Connect", mock.MatchedBy(func(url string) bool { host.WebSocketMock.On("Connect", mock.MatchedBy(func(url string) bool {
@@ -265,9 +267,7 @@ var _ = Describe("discordRPC", func() {
return val == "mp:external/new-asset" return val == "mp:external/new-asset"
}), int64(imageCacheTTL)).Return(nil) }), int64(imageCacheTTL)).Return(nil)
httpReq := &pdk.HTTPRequest{} host.HTTPMock.On("Send", externalAssetsReq).Return(&host.HTTPResponse{StatusCode: 200, Body: []byte(`[{"external_asset_path":"external/new-asset"}]`)}, nil)
pdk.PDKMock.On("NewHTTPRequest", pdk.MethodPost, externalAssetsURL).Return(httpReq)
pdk.PDKMock.On("Send", mock.Anything).Return(pdk.NewStubHTTPResponse(200, nil, []byte(`[{"external_asset_path":"external/new-asset"}]`)))
result, err := r.processImage("https://example.com/art.jpg", "client123", "token123", imageCacheTTL) result, err := r.processImage("https://example.com/art.jpg", "client123", "token123", imageCacheTTL)
Expect(err).ToNot(HaveOccurred()) Expect(err).ToNot(HaveOccurred())
@@ -277,9 +277,7 @@ var _ = Describe("discordRPC", func() {
It("returns error on HTTP failure", func() { It("returns error on HTTP failure", func() {
host.CacheMock.On("GetString", discordImageKey).Return("", false, nil) host.CacheMock.On("GetString", discordImageKey).Return("", false, nil)
httpReq := &pdk.HTTPRequest{} host.HTTPMock.On("Send", externalAssetsReq).Return(&host.HTTPResponse{StatusCode: 500, Body: []byte(`error`)}, nil)
pdk.PDKMock.On("NewHTTPRequest", pdk.MethodPost, externalAssetsURL).Return(httpReq)
pdk.PDKMock.On("Send", mock.Anything).Return(pdk.NewStubHTTPResponse(500, nil, []byte(`error`)))
_, err := r.processImage("https://example.com/art.jpg", "client123", "token123", imageCacheTTL) _, err := r.processImage("https://example.com/art.jpg", "client123", "token123", imageCacheTTL)
Expect(err).To(HaveOccurred()) Expect(err).To(HaveOccurred())
@@ -289,9 +287,7 @@ var _ = Describe("discordRPC", func() {
It("returns error on unmarshal failure", func() { It("returns error on unmarshal failure", func() {
host.CacheMock.On("GetString", discordImageKey).Return("", false, nil) host.CacheMock.On("GetString", discordImageKey).Return("", false, nil)
httpReq := &pdk.HTTPRequest{} host.HTTPMock.On("Send", externalAssetsReq).Return(&host.HTTPResponse{StatusCode: 200, Body: []byte(`{"not":"an-array"}`)}, nil)
pdk.PDKMock.On("NewHTTPRequest", pdk.MethodPost, externalAssetsURL).Return(httpReq)
pdk.PDKMock.On("Send", mock.Anything).Return(pdk.NewStubHTTPResponse(200, nil, []byte(`{"not":"an-array"}`)))
_, err := r.processImage("https://example.com/art.jpg", "client123", "token123", imageCacheTTL) _, err := r.processImage("https://example.com/art.jpg", "client123", "token123", imageCacheTTL)
Expect(err).To(HaveOccurred()) Expect(err).To(HaveOccurred())
@@ -301,9 +297,7 @@ var _ = Describe("discordRPC", func() {
It("returns error on empty response array", func() { It("returns error on empty response array", func() {
host.CacheMock.On("GetString", discordImageKey).Return("", false, nil) host.CacheMock.On("GetString", discordImageKey).Return("", false, nil)
httpReq := &pdk.HTTPRequest{} host.HTTPMock.On("Send", externalAssetsReq).Return(&host.HTTPResponse{StatusCode: 200, Body: []byte(`[]`)}, nil)
pdk.PDKMock.On("NewHTTPRequest", pdk.MethodPost, externalAssetsURL).Return(httpReq)
pdk.PDKMock.On("Send", mock.Anything).Return(pdk.NewStubHTTPResponse(200, nil, []byte(`[]`)))
_, err := r.processImage("https://example.com/art.jpg", "client123", "token123", imageCacheTTL) _, err := r.processImage("https://example.com/art.jpg", "client123", "token123", imageCacheTTL)
Expect(err).To(HaveOccurred()) Expect(err).To(HaveOccurred())
@@ -313,9 +307,7 @@ var _ = Describe("discordRPC", func() {
It("returns error on empty external_asset_path", func() { It("returns error on empty external_asset_path", func() {
host.CacheMock.On("GetString", discordImageKey).Return("", false, nil) host.CacheMock.On("GetString", discordImageKey).Return("", false, nil)
httpReq := &pdk.HTTPRequest{} host.HTTPMock.On("Send", externalAssetsReq).Return(&host.HTTPResponse{StatusCode: 200, Body: []byte(`[{"external_asset_path":""}]`)}, nil)
pdk.PDKMock.On("NewHTTPRequest", pdk.MethodPost, externalAssetsURL).Return(httpReq)
pdk.PDKMock.On("Send", mock.Anything).Return(pdk.NewStubHTTPResponse(200, nil, []byte(`[{"external_asset_path":""}]`)))
_, err := r.processImage("https://example.com/art.jpg", "client123", "token123", imageCacheTTL) _, err := r.processImage("https://example.com/art.jpg", "client123", "token123", imageCacheTTL)
Expect(err).To(HaveOccurred()) Expect(err).To(HaveOccurred())
@@ -332,9 +324,7 @@ var _ = Describe("discordRPC", func() {
host.CacheMock.On("GetString", discordImageKey).Return("", false, nil) host.CacheMock.On("GetString", discordImageKey).Return("", false, nil)
host.CacheMock.On("SetString", discordImageKey, mock.Anything, mock.Anything).Return(nil) host.CacheMock.On("SetString", discordImageKey, mock.Anything, mock.Anything).Return(nil)
httpReq := &pdk.HTTPRequest{} host.HTTPMock.On("Send", externalAssetsReq).Return(&host.HTTPResponse{StatusCode: 200, Body: []byte(`[{"external_asset_path":"external/art"}]`)}, nil)
pdk.PDKMock.On("NewHTTPRequest", pdk.MethodPost, externalAssetsURL).Return(httpReq)
pdk.PDKMock.On("Send", mock.Anything).Return(pdk.NewStubHTTPResponse(200, nil, []byte(`[{"external_asset_path":"external/art"}]`)))
host.WebSocketMock.On("SendText", "testuser", mock.MatchedBy(func(msg string) bool { host.WebSocketMock.On("SendText", "testuser", mock.MatchedBy(func(msg string) bool {
return strings.Contains(msg, `"op":3`) && return strings.Contains(msg, `"op":3`) &&
@@ -364,15 +354,9 @@ var _ = Describe("discordRPC", func() {
host.CacheMock.On("GetString", discordImageKey).Return("", false, nil) host.CacheMock.On("GetString", discordImageKey).Return("", false, nil)
host.CacheMock.On("SetString", discordImageKey, mock.Anything, mock.Anything).Return(nil) host.CacheMock.On("SetString", discordImageKey, mock.Anything, mock.Anything).Return(nil)
trackReq := &pdk.HTTPRequest{}
defaultReq := &pdk.HTTPRequest{}
// First call (track art) returns 500, second call (default) succeeds // First call (track art) returns 500, second call (default) succeeds
pdk.PDKMock.On("NewHTTPRequest", pdk.MethodPost, externalAssetsURL).Return(trackReq).Once() host.HTTPMock.On("Send", externalAssetsReq).Return(&host.HTTPResponse{StatusCode: 500, Body: []byte(`error`)}, nil).Once()
pdk.PDKMock.On("Send", trackReq).Return(pdk.NewStubHTTPResponse(500, nil, []byte(`error`))).Once() host.HTTPMock.On("Send", externalAssetsReq).Return(&host.HTTPResponse{StatusCode: 200, Body: []byte(`[{"external_asset_path":"external/logo"}]`)}, nil).Once()
pdk.PDKMock.On("NewHTTPRequest", pdk.MethodPost, externalAssetsURL).Return(defaultReq).Once()
pdk.PDKMock.On("Send", defaultReq).Return(pdk.NewStubHTTPResponse(200, nil, []byte(`[{"external_asset_path":"external/logo"}]`))).Once()
host.WebSocketMock.On("SendText", "testuser", mock.MatchedBy(func(msg string) bool { host.WebSocketMock.On("SendText", "testuser", mock.MatchedBy(func(msg string) bool {
return strings.Contains(msg, `"op":3`) && return strings.Contains(msg, `"op":3`) &&
@@ -400,9 +384,7 @@ var _ = Describe("discordRPC", func() {
It("clears all images when both track art and default fail", func() { It("clears all images when both track art and default fail", func() {
host.CacheMock.On("GetString", discordImageKey).Return("", false, nil) host.CacheMock.On("GetString", discordImageKey).Return("", false, nil)
httpReq := &pdk.HTTPRequest{} host.HTTPMock.On("Send", externalAssetsReq).Return(&host.HTTPResponse{StatusCode: 200, Body: []byte(`{"not":"array"}`)}, nil)
pdk.PDKMock.On("NewHTTPRequest", pdk.MethodPost, externalAssetsURL).Return(httpReq)
pdk.PDKMock.On("Send", mock.Anything).Return(pdk.NewStubHTTPResponse(200, nil, []byte(`{"not":"array"}`)))
host.WebSocketMock.On("SendText", "testuser", mock.MatchedBy(func(msg string) bool { host.WebSocketMock.On("SendText", "testuser", mock.MatchedBy(func(msg string) bool {
return strings.Contains(msg, `"op":3`) && return strings.Contains(msg, `"op":3`) &&
@@ -431,9 +413,7 @@ var _ = Describe("discordRPC", func() {
host.CacheMock.On("GetString", discordImageKey).Return("mp:cached/large", true, nil).Once() host.CacheMock.On("GetString", discordImageKey).Return("mp:cached/large", true, nil).Once()
host.CacheMock.On("GetString", discordImageKey).Return("", false, nil).Once() host.CacheMock.On("GetString", discordImageKey).Return("", false, nil).Once()
httpReq := &pdk.HTTPRequest{} host.HTTPMock.On("Send", externalAssetsReq).Return(&host.HTTPResponse{StatusCode: 500, Body: []byte(`error`)}, nil)
pdk.PDKMock.On("NewHTTPRequest", pdk.MethodPost, externalAssetsURL).Return(httpReq)
pdk.PDKMock.On("Send", mock.Anything).Return(pdk.NewStubHTTPResponse(500, nil, []byte(`error`)))
host.WebSocketMock.On("SendText", "testuser", mock.MatchedBy(func(msg string) bool { host.WebSocketMock.On("SendText", "testuser", mock.MatchedBy(func(msg string) bool {
return strings.Contains(msg, `"large_image":"mp:cached/large"`) && return strings.Contains(msg, `"large_image":"mp:cached/large"`) &&
+28 -19
View File
@@ -49,19 +49,23 @@ func spotifyCacheKey(artist, title, album string) string {
// trySpotifyFromMBID calls the ListenBrainz spotify-id-from-mbid endpoint. // trySpotifyFromMBID calls the ListenBrainz spotify-id-from-mbid endpoint.
func trySpotifyFromMBID(mbid string) string { func trySpotifyFromMBID(mbid string) string {
body := fmt.Sprintf(`[{"recording_mbid":%q}]`, mbid) body := fmt.Sprintf(`[{"recording_mbid":%q}]`, mbid)
req := pdk.NewHTTPRequest(pdk.MethodPost, "https://labs.api.listenbrainz.org/spotify-id-from-mbid/json") resp, err := host.HTTPSend(host.HTTPRequest{
req.SetHeader("Content-Type", "application/json") Method: "POST",
req.SetBody([]byte(body)) URL: "https://labs.api.listenbrainz.org/spotify-id-from-mbid/json",
Headers: map[string]string{"Content-Type": "application/json"},
resp := req.Send() Body: []byte(body),
status := resp.Status() })
if status < 200 || status >= 300 { if err != nil {
pdk.Log(pdk.LogDebug, fmt.Sprintf("ListenBrainz MBID lookup failed: HTTP %d, body=%s", status, string(resp.Body()))) pdk.Log(pdk.LogInfo, fmt.Sprintf("ListenBrainz MBID lookup request failed: %v", err))
return "" return ""
} }
id := parseSpotifyID(resp.Body()) if resp.StatusCode < 200 || resp.StatusCode >= 300 {
pdk.Log(pdk.LogDebug, fmt.Sprintf("ListenBrainz MBID lookup failed: HTTP %d, body=%s", resp.StatusCode, string(resp.Body)))
return ""
}
id := parseSpotifyID(resp.Body)
if id == "" { if id == "" {
pdk.Log(pdk.LogDebug, fmt.Sprintf("ListenBrainz MBID lookup returned no spotify_track_id for mbid=%s, body=%s", mbid, string(resp.Body()))) pdk.Log(pdk.LogDebug, fmt.Sprintf("ListenBrainz MBID lookup returned no spotify_track_id for mbid=%s, body=%s", mbid, string(resp.Body)))
} }
return id return id
} }
@@ -69,20 +73,25 @@ func trySpotifyFromMBID(mbid string) string {
// trySpotifyFromMetadata calls the ListenBrainz spotify-id-from-metadata endpoint. // trySpotifyFromMetadata calls the ListenBrainz spotify-id-from-metadata endpoint.
func trySpotifyFromMetadata(artist, title, album string) string { func trySpotifyFromMetadata(artist, title, album string) string {
payload := fmt.Sprintf(`[{"artist_name":%q,"track_name":%q,"release_name":%q}]`, artist, title, album) payload := fmt.Sprintf(`[{"artist_name":%q,"track_name":%q,"release_name":%q}]`, artist, title, album)
req := pdk.NewHTTPRequest(pdk.MethodPost, "https://labs.api.listenbrainz.org/spotify-id-from-metadata/json")
req.SetHeader("Content-Type", "application/json")
req.SetBody([]byte(payload))
pdk.Log(pdk.LogDebug, fmt.Sprintf("ListenBrainz metadata request: %s", payload)) pdk.Log(pdk.LogDebug, fmt.Sprintf("ListenBrainz metadata request: %s", payload))
resp := req.Send() resp, err := host.HTTPSend(host.HTTPRequest{
status := resp.Status() Method: "POST",
if status < 200 || status >= 300 { URL: "https://labs.api.listenbrainz.org/spotify-id-from-metadata/json",
pdk.Log(pdk.LogDebug, fmt.Sprintf("ListenBrainz metadata lookup failed: HTTP %d, body=%s", status, string(resp.Body()))) Headers: map[string]string{"Content-Type": "application/json"},
Body: []byte(payload),
})
if err != nil {
pdk.Log(pdk.LogInfo, fmt.Sprintf("ListenBrainz metadata lookup request failed: %v", err))
return "" return ""
} }
pdk.Log(pdk.LogDebug, fmt.Sprintf("ListenBrainz metadata response: HTTP %d, body=%s", status, string(resp.Body()))) if resp.StatusCode < 200 || resp.StatusCode >= 300 {
id := parseSpotifyID(resp.Body()) pdk.Log(pdk.LogDebug, fmt.Sprintf("ListenBrainz metadata lookup failed: HTTP %d, body=%s", resp.StatusCode, string(resp.Body)))
return ""
}
pdk.Log(pdk.LogDebug, fmt.Sprintf("ListenBrainz metadata response: HTTP %d, body=%s", resp.StatusCode, string(resp.Body)))
id := parseSpotifyID(resp.Body)
if id == "" { if id == "" {
pdk.Log(pdk.LogDebug, fmt.Sprintf("ListenBrainz metadata returned no spotify_track_id for %q - %q", artist, title)) pdk.Log(pdk.LogDebug, fmt.Sprintf("ListenBrainz metadata returned no spotify_track_id for %q - %q", artist, title))
} }
+17 -18
View File
@@ -118,6 +118,8 @@ var _ = Describe("Spotify", func() {
pdk.ResetMock() pdk.ResetMock()
host.CacheMock.ExpectedCalls = nil host.CacheMock.ExpectedCalls = nil
host.CacheMock.Calls = nil host.CacheMock.Calls = nil
host.HTTPMock.ExpectedCalls = nil
host.HTTPMock.Calls = nil
pdk.PDKMock.On("Log", mock.Anything, mock.Anything).Maybe() pdk.PDKMock.On("Log", mock.Anything, mock.Anything).Maybe()
}) })
@@ -138,10 +140,9 @@ var _ = Describe("Spotify", func() {
host.CacheMock.On("SetString", spotifyURLKey, mock.Anything, mock.Anything).Return(nil) host.CacheMock.On("SetString", spotifyURLKey, mock.Anything, mock.Anything).Return(nil)
// Mock the MBID HTTP request // Mock the MBID HTTP request
mbidReq := &pdk.HTTPRequest{} host.HTTPMock.On("Send", mock.MatchedBy(func(req host.HTTPRequest) bool {
pdk.PDKMock.On("NewHTTPRequest", pdk.MethodPost, "https://labs.api.listenbrainz.org/spotify-id-from-mbid/json").Return(mbidReq) return req.URL == "https://labs.api.listenbrainz.org/spotify-id-from-mbid/json"
pdk.PDKMock.On("Send", mbidReq).Return(pdk.NewStubHTTPResponse(200, nil, })).Return(&host.HTTPResponse{StatusCode: 200, Body: []byte(`[{"spotify_track_ids":["63OQupATfueTdZMWIV7nzz"]}]`)}, nil)
[]byte(`[{"spotify_track_ids":["63OQupATfueTdZMWIV7nzz"]}]`)))
url := resolveSpotifyURL(scrobbler.TrackInfo{ url := resolveSpotifyURL(scrobbler.TrackInfo{
Title: "Karma Police", Title: "Karma Police",
@@ -159,15 +160,14 @@ var _ = Describe("Spotify", func() {
host.CacheMock.On("SetString", spotifyURLKey, mock.Anything, mock.Anything).Return(nil) host.CacheMock.On("SetString", spotifyURLKey, mock.Anything, mock.Anything).Return(nil)
// MBID request fails // MBID request fails
mbidReq := &pdk.HTTPRequest{} host.HTTPMock.On("Send", mock.MatchedBy(func(req host.HTTPRequest) bool {
pdk.PDKMock.On("NewHTTPRequest", pdk.MethodPost, "https://labs.api.listenbrainz.org/spotify-id-from-mbid/json").Return(mbidReq) return req.URL == "https://labs.api.listenbrainz.org/spotify-id-from-mbid/json"
pdk.PDKMock.On("Send", mbidReq).Return(pdk.NewStubHTTPResponse(404, nil, []byte(`[]`))) })).Return(&host.HTTPResponse{StatusCode: 404, Body: []byte(`[]`)}, nil)
// Metadata request succeeds // Metadata request succeeds
metaReq := &pdk.HTTPRequest{} host.HTTPMock.On("Send", mock.MatchedBy(func(req host.HTTPRequest) bool {
pdk.PDKMock.On("NewHTTPRequest", pdk.MethodPost, "https://labs.api.listenbrainz.org/spotify-id-from-metadata/json").Return(metaReq) return req.URL == "https://labs.api.listenbrainz.org/spotify-id-from-metadata/json"
pdk.PDKMock.On("Send", metaReq).Return(pdk.NewStubHTTPResponse(200, nil, })).Return(&host.HTTPResponse{StatusCode: 200, Body: []byte(`[{"spotify_track_ids":["4wlLbLeDWbA6TzwZFp1UaK"]}]`)}, nil)
[]byte(`[{"spotify_track_ids":["4wlLbLeDWbA6TzwZFp1UaK"]}]`)))
url := resolveSpotifyURL(scrobbler.TrackInfo{ url := resolveSpotifyURL(scrobbler.TrackInfo{
Title: "Karma Police", Title: "Karma Police",
@@ -184,9 +184,9 @@ var _ = Describe("Spotify", func() {
host.CacheMock.On("SetString", spotifyURLKey, mock.Anything, mock.Anything).Return(nil) host.CacheMock.On("SetString", spotifyURLKey, mock.Anything, mock.Anything).Return(nil)
// No MBID, metadata request fails // No MBID, metadata request fails
metaReq := &pdk.HTTPRequest{} host.HTTPMock.On("Send", mock.MatchedBy(func(req host.HTTPRequest) bool {
pdk.PDKMock.On("NewHTTPRequest", pdk.MethodPost, "https://labs.api.listenbrainz.org/spotify-id-from-metadata/json").Return(metaReq) return req.URL == "https://labs.api.listenbrainz.org/spotify-id-from-metadata/json"
pdk.PDKMock.On("Send", metaReq).Return(pdk.NewStubHTTPResponse(500, nil, []byte(`error`))) })).Return(&host.HTTPResponse{StatusCode: 500, Body: []byte(`error`)}, nil)
url := resolveSpotifyURL(scrobbler.TrackInfo{ url := resolveSpotifyURL(scrobbler.TrackInfo{
Title: "Karma Police", Title: "Karma Police",
@@ -203,10 +203,9 @@ var _ = Describe("Spotify", func() {
host.CacheMock.On("GetString", spotifyURLKey).Return("", false, nil) host.CacheMock.On("GetString", spotifyURLKey).Return("", false, nil)
host.CacheMock.On("SetString", spotifyURLKey, mock.Anything, mock.Anything).Return(nil) host.CacheMock.On("SetString", spotifyURLKey, mock.Anything, mock.Anything).Return(nil)
metaReq := &pdk.HTTPRequest{} host.HTTPMock.On("Send", mock.MatchedBy(func(req host.HTTPRequest) bool {
pdk.PDKMock.On("NewHTTPRequest", pdk.MethodPost, "https://labs.api.listenbrainz.org/spotify-id-from-metadata/json").Return(metaReq) return req.URL == "https://labs.api.listenbrainz.org/spotify-id-from-metadata/json"
pdk.PDKMock.On("Send", metaReq).Return(pdk.NewStubHTTPResponse(200, nil, })).Return(&host.HTTPResponse{StatusCode: 200, Body: []byte(`[{"spotify_track_ids":["4tIGK5G9hNDA50ZdGioZRG"]}]`)}, nil)
[]byte(`[{"spotify_track_ids":["4tIGK5G9hNDA50ZdGioZRG"]}]`)))
url := resolveSpotifyURL(scrobbler.TrackInfo{ url := resolveSpotifyURL(scrobbler.TrackInfo{
Title: "Some Song", Title: "Some Song",