From aa01ae134210a0182a26fb3155b43ae542086f0c Mon Sep 17 00:00:00 2001 From: Philipp Wolfer Date: Sun, 12 Nov 2023 16:28:23 +0100 Subject: [PATCH] Unified code for backend clients and tests --- backends/funkwhale/client_test.go | 2 +- backends/listenbrainz/client.go | 15 +++-- backends/listenbrainz/client_test.go | 1 + backends/listenbrainz/listenbrainz.go | 4 +- backends/listenbrainz/listenbrainz_test.go | 24 ++++---- backends/maloja/client.go | 23 +++++--- backends/maloja/client_test.go | 68 ++++++++++++++++++++++ backends/maloja/maloja.go | 20 +++---- backends/maloja/maloja_test.go | 52 +++++++++++++++++ backends/maloja/models.go | 6 +- backends/maloja/testdata/scrobbles.json | 47 +++++++++++++++ 11 files changed, 220 insertions(+), 42 deletions(-) create mode 100644 backends/maloja/client_test.go create mode 100644 backends/maloja/maloja_test.go create mode 100644 backends/maloja/testdata/scrobbles.json diff --git a/backends/funkwhale/client_test.go b/backends/funkwhale/client_test.go index ee78d76..ff08871 100644 --- a/backends/funkwhale/client_test.go +++ b/backends/funkwhale/client_test.go @@ -42,8 +42,8 @@ func TestNewClient(t *testing.T) { func TestGetHistoryListenings(t *testing.T) { defer httpmock.DeactivateAndReset() - token := "thetoken" serverUrl := "https://funkwhale.example.com" + token := "thetoken" client := funkwhale.NewClient(serverUrl, token) setupHttpMock(t, client.HttpClient.GetClient(), "https://funkwhale.example.com/api/v1/history/listenings", diff --git a/backends/listenbrainz/client.go b/backends/listenbrainz/client.go index 8ab1c9b..32416c6 100644 --- a/backends/listenbrainz/client.go +++ b/backends/listenbrainz/client.go @@ -22,6 +22,7 @@ THE SOFTWARE. package listenbrainz import ( + "errors" "strconv" "time" @@ -52,17 +53,21 @@ func NewClient(token string) Client { return client } -func (c Client) GetListens(user string, maxTime time.Time, minTime time.Time) (GetListensResult, error) { +func (c Client) GetListens(user string, maxTime time.Time, minTime time.Time) (result GetListensResult, err error) { const path = "/user/{username}/listens" - result := &GetListensResult{} - _, err := c.HttpClient.R(). + response, err := c.HttpClient.R(). SetPathParam("username", user). SetQueryParams(map[string]string{ "max_ts": strconv.FormatInt(maxTime.Unix(), 10), "min_ts": strconv.FormatInt(minTime.Unix(), 10), "count": strconv.FormatInt(int64(c.MaxResults), 10), }). - SetResult(result). + SetResult(&result). Get(path) - return *result, err + + if response.StatusCode() != 200 { + err = errors.New(response.String()) + return + } + return } diff --git a/backends/listenbrainz/client_test.go b/backends/listenbrainz/client_test.go index c650830..54b130a 100644 --- a/backends/listenbrainz/client_test.go +++ b/backends/listenbrainz/client_test.go @@ -53,6 +53,7 @@ func TestGetListens(t *testing.T) { assert := assert.New(t) assert.Equal(2, result.Payload.Count) + require.Len(t, result.Payload.Listens, 2) assert.Equal("Shadowplay", result.Payload.Listens[0].TrackMetadata.TrackName) } diff --git a/backends/listenbrainz/listenbrainz.go b/backends/listenbrainz/listenbrainz.go index 369c80f..140f259 100644 --- a/backends/listenbrainz/listenbrainz.go +++ b/backends/listenbrainz/listenbrainz.go @@ -63,7 +63,7 @@ out: for _, listen := range result.Payload.Listens { if listen.ListenedAt > oldestTimestamp.Unix() { - listens = append(listens, ListenFromListenBrainz(listen)) + listens = append(listens, listen.ToListen()) } else { // result contains listens older then oldestTimestamp, // we can stop requesting more @@ -76,7 +76,7 @@ out: return listens, nil } -func ListenFromListenBrainz(lbListen Listen) models.Listen { +func (lbListen Listen) ToListen() models.Listen { track := lbListen.TrackMetadata listen := models.Listen{ ListenedAt: time.Unix(lbListen.ListenedAt, 0), diff --git a/backends/listenbrainz/listenbrainz_test.go b/backends/listenbrainz/listenbrainz_test.go index 81c236f..924a4d0 100644 --- a/backends/listenbrainz/listenbrainz_test.go +++ b/backends/listenbrainz/listenbrainz_test.go @@ -30,36 +30,36 @@ import ( "go.uploadedlobster.com/scotty/models" ) -func TestListenFromListenBrainz(t *testing.T) { +func TestListenBrainzListenToListen(t *testing.T) { lbListen := listenbrainz.Listen{ ListenedAt: 1699289873, UserName: "outsidecontext", TrackMetadata: listenbrainz.Track{ TrackName: "The Track", - ArtistName: "The Artist", - ReleaseName: "The Release", + ArtistName: "Dool", + ReleaseName: "Here Now, There Then", AdditionalInfo: map[string]any{ - "duration_ms": 528235, + "duration_ms": 413787, "foo": "bar", - "isrc": "DES561720901", - "tracknumber": 8, - "recording_mbid": "e225fb84-dc9a-419e-adcd-9890f59ec432", + "isrc": "DES561620801", + "tracknumber": 5, + "recording_mbid": "c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12", "release_group_mbid": "80aca1ee-aa51-41be-9f75-024710d92ff4", "release_mbid": "d7f22677-9803-4d21-ba42-081b633a6f68", }, }, } - listen := listenbrainz.ListenFromListenBrainz(lbListen) + listen := lbListen.ToListen() assert.Equal(t, time.Unix(1699289873, 0), listen.ListenedAt) assert.Equal(t, lbListen.UserName, listen.UserName) - assert.Equal(t, time.Duration(528235*time.Millisecond), listen.Duration) + assert.Equal(t, time.Duration(413787*time.Millisecond), listen.Duration) assert.Equal(t, lbListen.TrackMetadata.TrackName, listen.TrackName) assert.Equal(t, lbListen.TrackMetadata.ReleaseName, listen.ReleaseName) assert.Equal(t, []string{lbListen.TrackMetadata.ArtistName}, listen.ArtistNames) - assert.Equal(t, 8, listen.TrackNumber) - assert.Equal(t, models.MBID("e225fb84-dc9a-419e-adcd-9890f59ec432"), listen.RecordingMbid) + assert.Equal(t, 5, listen.TrackNumber) + assert.Equal(t, models.MBID("c0a1fc94-5f04-4a5f-bc09-e5de0c49cd12"), listen.RecordingMbid) assert.Equal(t, models.MBID("d7f22677-9803-4d21-ba42-081b633a6f68"), listen.ReleaseMbid) assert.Equal(t, models.MBID("80aca1ee-aa51-41be-9f75-024710d92ff4"), listen.ReleaseGroupMbid) - assert.Equal(t, "DES561720901", listen.Isrc) + assert.Equal(t, "DES561620801", listen.Isrc) assert.Equal(t, lbListen.TrackMetadata.AdditionalInfo["foo"], listen.AdditionalInfo["foo"]) } diff --git a/backends/maloja/client.go b/backends/maloja/client.go index c8a4df2..46020fe 100644 --- a/backends/maloja/client.go +++ b/backends/maloja/client.go @@ -22,14 +22,15 @@ THE SOFTWARE. package maloja import ( + "errors" "strconv" "github.com/go-resty/resty/v2" ) type Client struct { - resty *resty.Client - token string + HttpClient *resty.Client + token string } func NewClient(serverUrl string, token string) Client { @@ -37,22 +38,26 @@ func NewClient(serverUrl string, token string) Client { resty.SetBaseURL(serverUrl) resty.SetHeader("Accept", "application/json") client := Client{ - resty: resty, - token: token, + HttpClient: resty, + token: token, } return client } -func (c Client) GetListens(page int, perPage int) (GetListensResult, error) { +func (c Client) GetScrobbles(page int, perPage int) (result GetScrobblesResult, err error) { const path = "/apis/mlj_1/scrobbles" - result := &GetListensResult{} - _, err := c.resty.R(). + response, err := c.HttpClient.R(). SetQueryParams(map[string]string{ "page": strconv.Itoa(page), "perpage": strconv.Itoa(perPage), }). - SetResult(result). + SetResult(&result). Get(path) - return *result, err + + if response.StatusCode() != 200 { + err = errors.New(response.String()) + return + } + return } diff --git a/backends/maloja/client_test.go b/backends/maloja/client_test.go new file mode 100644 index 0000000..f7f7298 --- /dev/null +++ b/backends/maloja/client_test.go @@ -0,0 +1,68 @@ +/* +Copyright © 2023 Philipp Wolfer + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +package maloja_test + +import ( + "net/http" + "testing" + + "github.com/jarcoal/httpmock" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uploadedlobster.com/scotty/backends/maloja" +) + +func TestNewClient(t *testing.T) { + serverUrl := "https://maloja.example.com" + token := "foobar123" + client := maloja.NewClient(serverUrl, token) + assert.Equal(t, serverUrl, client.HttpClient.BaseURL) +} + +func TestGetScrobbles(t *testing.T) { + defer httpmock.DeactivateAndReset() + + serverUrl := "https://maloja.example.com" + token := "thetoken" + client := maloja.NewClient(serverUrl, token) + setupHttpMock(t, client.HttpClient.GetClient(), + "https://maloja.example.com/apis/mlj_1/scrobbles", + "testdata/scrobbles.json") + + result, err := client.GetScrobbles(0, 2) + require.NoError(t, err) + + assert := assert.New(t) + require.Len(t, result.List, 2) + assert.Equal("Way to Eden", result.List[0].Track.Title) + assert.Equal(int64(558), result.List[0].Duration) +} + +func setupHttpMock(t *testing.T, client *http.Client, url string, testDataPath string) { + httpmock.ActivateNonDefault(client) + + responder, err := httpmock.NewJsonResponder(200, httpmock.File(testDataPath)) + if err != nil { + t.Fatal(err) + } + httpmock.RegisterResponder("GET", url, responder) +} diff --git a/backends/maloja/maloja.go b/backends/maloja/maloja.go index 7f7f7b6..c42b765 100644 --- a/backends/maloja/maloja.go +++ b/backends/maloja/maloja.go @@ -50,19 +50,19 @@ func (b MalojaApiBackend) ExportListens(oldestTimestamp time.Time) ([]models.Lis out: for { - result, err := b.client.GetListens(page, perPage) + result, err := b.client.GetScrobbles(page, perPage) if err != nil { return nil, err } - count := len(result.Listens) + count := len(result.List) if count == 0 { break } - for _, listen := range result.Listens { - if listen.ListenedAt > oldestTimestamp.Unix() { - listens = append(listens, ListenFromMaloja(listen)) + for _, scrobble := range result.List { + if scrobble.ListenedAt > oldestTimestamp.Unix() { + listens = append(listens, scrobble.ToListen()) } else { break out } @@ -75,11 +75,11 @@ out: return listens, nil } -func ListenFromMaloja(mlListen Listen) models.Listen { - track := mlListen.Track +func (s Scrobble) ToListen() models.Listen { + track := s.Track listen := models.Listen{ - ListenedAt: time.Unix(mlListen.ListenedAt, 0), - PlaybackDuration: time.Duration(mlListen.Duration * int64(time.Second)), + ListenedAt: time.Unix(s.ListenedAt, 0), + PlaybackDuration: time.Duration(s.Duration * int64(time.Second)), Track: models.Track{ TrackName: track.Title, ReleaseName: track.Album.Title, @@ -89,7 +89,7 @@ func ListenFromMaloja(mlListen Listen) models.Listen { }, } - client, found := strings.CutPrefix(mlListen.Origin, "client:") + client, found := strings.CutPrefix(s.Origin, "client:") if found { listen.AdditionalInfo["media_player"] = client } diff --git a/backends/maloja/maloja_test.go b/backends/maloja/maloja_test.go new file mode 100644 index 0000000..1ae90d6 --- /dev/null +++ b/backends/maloja/maloja_test.go @@ -0,0 +1,52 @@ +/* +Copyright © 2023 Philipp Wolfer + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ +package maloja_test + +import ( + "testing" + "time" + + "github.com/stretchr/testify/assert" + "go.uploadedlobster.com/scotty/backends/maloja" +) + +func TestScrobbleToListen(t *testing.T) { + scrobble := maloja.Scrobble{ + ListenedAt: 1699289873, + Track: maloja.Track{ + Title: "Oweynagat", + Album: maloja.Album{ + Title: "Here Now, There Then", + }, + Artists: []string{"Dool"}, + Length: 414, + }, + Origin: "client:Funkwhale", + } + listen := scrobble.ToListen() + assert.Equal(t, time.Unix(1699289873, 0), listen.ListenedAt) + assert.Equal(t, time.Duration(414*time.Second), listen.Duration) + assert.Equal(t, scrobble.Track.Title, listen.TrackName) + assert.Equal(t, scrobble.Track.Album.Title, listen.ReleaseName) + assert.Equal(t, scrobble.Track.Artists, listen.ArtistNames) + assert.Equal(t, "Funkwhale", listen.AdditionalInfo["media_player"]) +} diff --git a/backends/maloja/models.go b/backends/maloja/models.go index 4a43c6c..e6da57b 100644 --- a/backends/maloja/models.go +++ b/backends/maloja/models.go @@ -21,13 +21,13 @@ THE SOFTWARE. */ package maloja -type GetListensResult struct { +type GetScrobblesResult struct { Status string `json:"status"` - Listens []Listen `json:"list"` + List []Scrobble `json:"list"` Pagination Pagination `json:"pagination"` } -type Listen struct { +type Scrobble struct { ListenedAt int64 `json:"time"` Duration int64 `json:"duration"` // Maloja sets Origin to the name of the API key diff --git a/backends/maloja/testdata/scrobbles.json b/backends/maloja/testdata/scrobbles.json new file mode 100644 index 0000000..bdd8597 --- /dev/null +++ b/backends/maloja/testdata/scrobbles.json @@ -0,0 +1,47 @@ +{ + "status": "ok", + "list": [ + { + "time": 1699574369, + "track": { + "artists": [ + "Hazeshuttle" + ], + "title": "Way to Eden", + "album": { + "artists": [ + "Hazeshuttle" + ], + "albumtitle": "Hazeshuttle" + }, + "length": 567 + }, + "duration": 558, + "origin": "client:Funkwhale" + }, + { + "time": 1699573362, + "track": { + "artists": [ + "Hazeshuttle" + ], + "title": "Homosativa", + "album": { + "artists": [ + "Hazeshuttle" + ], + "albumtitle": "Hazeshuttle" + }, + "length": 1007 + }, + "duration": null, + "origin": "client:Funkwhale" + } + ], + "pagination": { + "page": 0, + "perpage": 2, + "next_page": "/apis/mlj_1/scrobbles?page=1&perpage=2", + "prev_page": null + } +} \ No newline at end of file