navidrome/core/agents/listenbrainz/client_test.go

267 lines
9.5 KiB
Go

package listenbrainz
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"time"
"github.com/navidrome/navidrome/core/external_playlists"
"github.com/navidrome/navidrome/model"
"github.com/navidrome/navidrome/tests"
. "github.com/onsi/ginkgo/v2"
. "github.com/onsi/gomega"
)
var _ = Describe("client", func() {
var httpClient *tests.FakeHttpClient
var client *client
BeforeEach(func() {
httpClient = &tests.FakeHttpClient{}
client = newClient("BASE_URL/", httpClient)
})
Describe("listenBrainzResponse", func() {
It("parses a response properly", func() {
var response listenBrainzResponse
err := json.Unmarshal([]byte(`{"code": 200, "message": "Message", "user_name": "UserName", "valid": true, "status": "ok", "error": "Error"}`), &response)
Expect(err).ToNot(HaveOccurred())
Expect(response.Code).To(Equal(200))
Expect(response.Message).To(Equal("Message"))
Expect(response.UserName).To(Equal("UserName"))
Expect(response.Valid).To(BeTrue())
Expect(response.Status).To(Equal("ok"))
Expect(response.Error).To(Equal("Error"))
})
It("parses playlists response properly", func() {
var response listenBrainzResponse
f, _ := os.ReadFile("tests/fixtures/listenbrainz.playlists.json")
date, _ := time.Parse(time.RFC3339Nano, "2023-03-18T11:01:46.635538+00:00")
modified, _ := time.Parse(time.RFC3339Nano, "2023-03-18T11:01:46.635538+00:00")
err := json.Unmarshal(f, &response)
Expect(err).ToNot(HaveOccurred())
Expect(response.PlaylistCount).To(Equal(3))
Expect(response.Playlists).To(HaveLen(3))
Expect(response.Playlists[0]).To(Equal(overallPlaylist{
Playlist: lbPlaylist{
Annotation: "Daily jams playlist!",
Creator: "troi-bot",
Date: date,
Identifier: "https://listenbrainz.org/playlist/00000000-0000-0000-0000-000000000000",
Title: "Daily Jams for example, 2023-03-18 Sat",
Extension: plsExtension{
Extension: playlistExtension{
AdditionalMetadata: additionalMeta{
AlgorithmMetadata: algoMeta{
SourcePatch: "daily-jams",
},
},
Collaborators: []string{"example"},
CreatedFor: "example",
LastModified: modified,
Public: true,
},
},
Tracks: []lbTrack{},
},
}))
})
It("parses playlist response properly", func() {
var response listenBrainzResponse
f, _ := os.ReadFile("tests/fixtures/listenbrainz.playlist.json")
date, _ := time.Parse(time.RFC3339Nano, "2023-01-05T04:12:45.668903+00:00")
modified, _ := time.Parse(time.RFC3339Nano, "2023-01-05T04:12:45.668903+00:00")
err := json.Unmarshal(f, &response)
Expect(err).ToNot(HaveOccurred())
Expect(response.Playlist).To(Equal(lbPlaylist{
Annotation: "<p>\n This playlist contains the top tracks for example that were first listened to in 2022.\n </p>\n <p>\n For more information on how this playlist is generated, please see our\n <a href=\"https://musicbrainz.org/doc/YIM2022Playlists\">Year in Music 2022 Playlists</a> page.\n </p>\n ",
Creator: "troi-bot",
Date: date,
Identifier: "https://listenbrainz.org/playlist/00000000-0000-0000-0000-000000000004",
Title: "Top Discoveries of 2022 for example",
Extension: plsExtension{
Extension: playlistExtension{
AdditionalMetadata: additionalMeta{
AlgorithmMetadata: algoMeta{
SourcePatch: "top-discoveries-for-year",
},
},
Collaborators: []string{"example"},
CreatedFor: "example",
LastModified: modified,
Public: true,
},
},
Tracks: []lbTrack{
{
Creator: "Poets of the Fall",
Identifier: "https://musicbrainz.org/recording/684bedbb-78d7-4946-9038-5402d5fa83b0",
Title: "Requiem for My Harlequin",
},
{
Creator: "Old Gods of Asgard",
Identifier: "https://musicbrainz.org/recording/9f42783a-423b-4ed6-8a10-fdf4cb44456f",
Title: "Take Control",
},
},
}))
})
})
Describe("validateToken", func() {
BeforeEach(func() {
httpClient.Res = http.Response{
Body: io.NopCloser(bytes.NewBufferString(`{"code": 200, "message": "Token valid.", "user_name": "ListenBrainzUser", "valid": true}`)),
StatusCode: 200,
}
})
It("formats the request properly", func() {
_, err := client.validateToken(context.Background(), "LB-TOKEN")
Expect(err).ToNot(HaveOccurred())
Expect(httpClient.SavedRequest.Method).To(Equal(http.MethodGet))
Expect(httpClient.SavedRequest.URL.String()).To(Equal("BASE_URL/validate-token"))
Expect(httpClient.SavedRequest.Header.Get("Authorization")).To(Equal("Token LB-TOKEN"))
Expect(httpClient.SavedRequest.Header.Get("Content-Type")).To(Equal("application/json; charset=UTF-8"))
})
It("parses and returns the response", func() {
res, err := client.validateToken(context.Background(), "LB-TOKEN")
Expect(err).ToNot(HaveOccurred())
Expect(res.Valid).To(Equal(true))
Expect(res.UserName).To(Equal("ListenBrainzUser"))
})
})
Context("with listenInfo", func() {
var li listenInfo
BeforeEach(func() {
httpClient.Res = http.Response{
Body: io.NopCloser(bytes.NewBufferString(`{"status": "ok"}`)),
StatusCode: 200,
}
li = listenInfo{
TrackMetadata: trackMetadata{
ArtistName: "Track Artist",
TrackName: "Track Title",
ReleaseName: "Track Album",
AdditionalInfo: additionalInfo{
TrackNumber: 1,
RecordingMbzID: "mbz-123",
ArtistMbzIDs: []string{"mbz-789"},
ReleaseMbID: "mbz-456",
DurationMs: 142200,
},
},
}
})
Describe("updateNowPlaying", func() {
It("formats the request properly", func() {
Expect(client.updateNowPlaying(context.Background(), "LB-TOKEN", li)).To(Succeed())
Expect(httpClient.SavedRequest.Method).To(Equal(http.MethodPost))
Expect(httpClient.SavedRequest.URL.String()).To(Equal("BASE_URL/submit-listens"))
Expect(httpClient.SavedRequest.Header.Get("Authorization")).To(Equal("Token LB-TOKEN"))
Expect(httpClient.SavedRequest.Header.Get("Content-Type")).To(Equal("application/json; charset=UTF-8"))
body, _ := io.ReadAll(httpClient.SavedRequest.Body)
f, _ := os.ReadFile("tests/fixtures/listenbrainz.nowplaying.request.json")
Expect(body).To(MatchJSON(f))
})
})
Describe("scrobble", func() {
BeforeEach(func() {
li.ListenedAt = 1635000000
})
It("formats the request properly", func() {
Expect(client.scrobble(context.Background(), "LB-TOKEN", li)).To(Succeed())
Expect(httpClient.SavedRequest.Method).To(Equal(http.MethodPost))
Expect(httpClient.SavedRequest.URL.String()).To(Equal("BASE_URL/submit-listens"))
Expect(httpClient.SavedRequest.Header.Get("Authorization")).To(Equal("Token LB-TOKEN"))
Expect(httpClient.SavedRequest.Header.Get("Content-Type")).To(Equal("application/json; charset=UTF-8"))
body, _ := io.ReadAll(httpClient.SavedRequest.Body)
f, _ := os.ReadFile("tests/fixtures/listenbrainz.scrobble.request.json")
Expect(body).To(MatchJSON(f))
})
})
})
Describe("getPlaylists", func() {
Describe("successful responses", func() {
BeforeEach(func() {
f, _ := os.Open("tests/fixtures/listenbrainz.playlists.json")
httpClient.Res = http.Response{
Body: f,
StatusCode: 200,
}
})
test := func(offset, count int, plsType, extra string) {
It("Handles "+plsType+" correctly", func() {
resp, err := client.getPlaylists(context.Background(), offset, count, "LB-TOKEN", "example", plsType)
Expect(err).To(BeNil())
Expect(resp.Playlists).To(HaveLen(3))
Expect(httpClient.SavedRequest.URL.String()).To(Equal(fmt.Sprintf("BASE_URL/user/example/playlists%s?count=%d&offset=%d", extra, count, offset)))
Expect(httpClient.SavedRequest.Method).To(Equal(http.MethodGet))
Expect(httpClient.SavedRequest.Header.Get("Authorization")).To(Equal("Token LB-TOKEN"))
Expect(httpClient.SavedRequest.Header.Get("Content-Type")).To(Equal("application/json; charset=UTF-8"))
})
}
test(10, 30, "user", "")
test(11, 50, "created", "/createdfor")
test(0, 25, "collab", "/collaborator")
})
It("fails when provided an unsupported type", func() {
resp, err := client.getPlaylists(context.Background(), 0, 25, "LB-TOKEN", "example", "notatype")
Expect(resp).To(BeNil())
Expect(err).To(Equal(external_playlists.ErrorUnsupportedType))
})
})
Describe("getPlaylist", func() {
It("Should fetch playlist properly", func() {
f, _ := os.Open("tests/fixtures/listenbrainz.playlist.json")
httpClient.Res = http.Response{
Body: f,
StatusCode: 200,
}
list, err := client.getPlaylist(context.Background(), "LB-TOKEN", "00000000-0000-0000-0000-000000000004")
Expect(err).To(BeNil())
Expect(list.Playlist.Tracks).To(HaveLen(2))
Expect(httpClient.SavedRequest.URL.String()).To(Equal("BASE_URL/playlist/00000000-0000-0000-0000-000000000004"))
Expect(httpClient.SavedRequest.Method).To(Equal(http.MethodGet))
Expect(httpClient.SavedRequest.Header.Get("Authorization")).To(Equal("Token LB-TOKEN"))
Expect(httpClient.SavedRequest.Header.Get("Content-Type")).To(Equal("application/json; charset=UTF-8"))
})
It("Returns correct error on 404", func() {
httpClient.Res = http.Response{
Body: io.NopCloser(bytes.NewBufferString(`{"Code":404}`)),
StatusCode: 404,
}
resp, err := client.getPlaylist(context.Background(), "LB-TOKEN", "00000000-0000-0000-0000-000000000004")
Expect(resp).To(BeNil())
Expect(err).To(Equal(model.ErrNotFound))
})
})
})