mirror of https://github.com/gabehf/Koito.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
893 lines
28 KiB
893 lines
28 KiB
package engine_test
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"mime/multipart"
|
|
"net/http"
|
|
"net/url"
|
|
"os"
|
|
"path"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/gabehf/koito/engine/handlers"
|
|
"github.com/gabehf/koito/internal/cfg"
|
|
"github.com/gabehf/koito/internal/db"
|
|
"github.com/gabehf/koito/internal/models"
|
|
"github.com/google/uuid"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
var session string
|
|
var apikey string
|
|
var loginOnce sync.Once
|
|
var apikeyOnce sync.Once
|
|
|
|
func login(t *testing.T) {
|
|
loginOnce.Do(func() {
|
|
formdata := url.Values{}
|
|
formdata.Set("username", cfg.DefaultUsername())
|
|
formdata.Set("password", cfg.DefaultPassword())
|
|
encoded := formdata.Encode()
|
|
resp, err := http.DefaultClient.Post(host()+"/apis/web/v1/login", "application/x-www-form-urlencoded", strings.NewReader(encoded))
|
|
respBytes, _ := io.ReadAll(resp.Body)
|
|
t.Logf("Login request response: %s - %s", resp.Status, respBytes)
|
|
require.NoError(t, err)
|
|
require.Len(t, resp.Cookies(), 1)
|
|
session = resp.Cookies()[0].Value
|
|
require.NotEmpty(t, session)
|
|
})
|
|
}
|
|
|
|
func makeAuthRequest(t *testing.T, session, method, endpoint string, body io.Reader) (*http.Response, error) {
|
|
req, err := http.NewRequest(method, host()+endpoint, body)
|
|
require.NoError(t, err)
|
|
req.AddCookie(&http.Cookie{
|
|
Name: "koito_session",
|
|
Value: session,
|
|
})
|
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
|
t.Logf("Making request to %s with session: %s", endpoint, session)
|
|
return http.DefaultClient.Do(req)
|
|
}
|
|
|
|
// Expects a valid session
|
|
func getApiKey(t *testing.T, session string) {
|
|
apikeyOnce.Do(func() {
|
|
resp, err := makeAuthRequest(t, session, "GET", "/apis/web/v1/user/apikeys", nil)
|
|
require.NoError(t, err)
|
|
var keys []models.ApiKey
|
|
err = json.NewDecoder(resp.Body).Decode(&keys)
|
|
require.NoError(t, err)
|
|
require.GreaterOrEqual(t, len(keys), 1)
|
|
apikey = keys[0].Key
|
|
})
|
|
}
|
|
|
|
func truncateTestData(t *testing.T) {
|
|
err := store.Exec(context.Background(),
|
|
`TRUNCATE
|
|
artists,
|
|
artist_aliases,
|
|
tracks,
|
|
artist_tracks,
|
|
releases,
|
|
artist_releases,
|
|
release_aliases,
|
|
listens
|
|
RESTART IDENTITY CASCADE`)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
func doSubmitListens(t *testing.T) {
|
|
login(t)
|
|
getApiKey(t, session)
|
|
truncateTestData(t)
|
|
bodies := []string{fmt.Sprintf(`{
|
|
"listen_type": "single",
|
|
"payload": [
|
|
{
|
|
"listened_at": %d,
|
|
"track_metadata": {
|
|
"additional_info": {
|
|
"artist_mbids": [
|
|
"efc787f0-046f-4a60-beff-77b398c8cdf4"
|
|
],
|
|
"artist_names": [
|
|
"さユり"
|
|
],
|
|
"duration_ms": 275960,
|
|
"recording_mbid": "21524d55-b1f8-45d1-b172-976cba447199",
|
|
"release_group_mbid": "3281e0d9-fa44-4337-a8ce-6f264beeae16",
|
|
"release_mbid": "eb790e90-0065-4852-b47d-bbeede4aa9fc",
|
|
"submission_client": "navidrome",
|
|
"submission_client_version": "0.56.1 (fa2cf362)"
|
|
},
|
|
"artist_name": "さユり",
|
|
"release_name": "酸欠少女",
|
|
"track_name": "花の塔"
|
|
}
|
|
}
|
|
]
|
|
}`, time.Now().Add(-2*time.Hour).Unix()), // yesterday
|
|
fmt.Sprintf(`{
|
|
"listen_type": "single",
|
|
"payload": [
|
|
{
|
|
"listened_at": %d,
|
|
"track_metadata": {
|
|
"additional_info": {
|
|
"artist_mbids": [
|
|
"80b3cb83-b7a3-4f79-ad42-8325cefb3626"
|
|
],
|
|
"artist_names": [
|
|
"キタニタツヤ"
|
|
],
|
|
"duration_ms": 197270,
|
|
"recording_mbid": "4e909c21-e7a8-404d-b75a-0c8c2926efb0",
|
|
"release_group_mbid": "89069d92-e495-462c-b189-3431551868ed",
|
|
"release_mbid": "e16a49d6-77f3-4d73-b93c-cac855ce6ad5",
|
|
"submission_client": "navidrome",
|
|
"submission_client_version": "0.56.1 (fa2cf362)"
|
|
},
|
|
"artist_name": "キタニタツヤ",
|
|
"release_name": "Where Our Blue Is",
|
|
"track_name": "Where Our Blue Is"
|
|
}
|
|
}
|
|
]
|
|
}`, time.Now().Unix()),
|
|
fmt.Sprintf(`{
|
|
"listen_type": "single",
|
|
"payload": [
|
|
{
|
|
"listened_at": %d,
|
|
"track_metadata": {
|
|
"additional_info": {
|
|
"artist_mbids": [
|
|
"1262ab85-308b-46e7-b0b5-91fef8e46b62"
|
|
],
|
|
"artist_names": [
|
|
"ネクライトーキー"
|
|
],
|
|
"duration_ms": 241560,
|
|
"recording_mbid": "8eec4f3f-a059-4217-aad1-fbf82e33e756",
|
|
"release_group_mbid": "14f1aff0-dd19-4b42-82dd-720386b6d4c1",
|
|
"release_mbid": "7762d7af-7b6c-454f-977e-1b261743e265",
|
|
"submission_client": "navidrome",
|
|
"submission_client_version": "0.56.1 (fa2cf362)"
|
|
},
|
|
"artist_name": "ネクライトーキー",
|
|
"release_name": "ONE!",
|
|
"track_name": "こんがらがった!"
|
|
}
|
|
}
|
|
]
|
|
}`, time.Now().Add(-1*time.Hour).Unix())}
|
|
for _, body := range bodies {
|
|
req, err := http.NewRequest("POST", host()+"/apis/listenbrainz/1/submit-listens", strings.NewReader(body))
|
|
require.NoError(t, err)
|
|
req.Header.Add("Authorization", fmt.Sprintf("Token %s", apikey))
|
|
resp, err := http.DefaultClient.Do(req)
|
|
require.NoError(t, err)
|
|
respBytes, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, `{"status": "ok"}`, string(respBytes))
|
|
}
|
|
}
|
|
|
|
func TestGetters(t *testing.T) {
|
|
t.Run("Submit Listens", doSubmitListens)
|
|
// Artist was saved
|
|
resp, err := http.DefaultClient.Get(host() + "/apis/web/v1/artist?id=1")
|
|
assert.NoError(t, err)
|
|
var artist models.Artist
|
|
err = json.NewDecoder(resp.Body).Decode(&artist)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "さユり", artist.Name)
|
|
|
|
// Album was saved
|
|
resp, err = http.DefaultClient.Get(host() + "/apis/web/v1/album?id=1")
|
|
assert.NoError(t, err)
|
|
var album models.Album
|
|
err = json.NewDecoder(resp.Body).Decode(&album)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "酸欠少女", album.Title)
|
|
|
|
// Track was saved
|
|
resp, err = http.DefaultClient.Get(host() + "/apis/web/v1/track?id=1")
|
|
assert.NoError(t, err)
|
|
var track models.Track
|
|
err = json.NewDecoder(resp.Body).Decode(&track)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, "花の塔", track.Title)
|
|
|
|
// Listen was saved
|
|
resp, err = http.DefaultClient.Get(host() + "/apis/web/v1/listens")
|
|
assert.NoError(t, err)
|
|
var listens db.PaginatedResponse[models.Listen]
|
|
err = json.NewDecoder(resp.Body).Decode(&listens)
|
|
require.NoError(t, err)
|
|
require.Len(t, listens.Items, 3)
|
|
assert.EqualValues(t, 2, listens.Items[0].Track.ID)
|
|
assert.Equal(t, "Where Our Blue Is", listens.Items[0].Track.Title)
|
|
|
|
resp, err = http.DefaultClient.Get(host() + "/apis/web/v1/top-artists")
|
|
assert.NoError(t, err)
|
|
var artists db.PaginatedResponse[models.Artist]
|
|
err = json.NewDecoder(resp.Body).Decode(&artists)
|
|
require.NoError(t, err)
|
|
require.Len(t, artists.Items, 3)
|
|
|
|
resp, err = http.DefaultClient.Get(host() + "/apis/web/v1/top-albums")
|
|
assert.NoError(t, err)
|
|
var albums db.PaginatedResponse[models.Album]
|
|
err = json.NewDecoder(resp.Body).Decode(&albums)
|
|
require.NoError(t, err)
|
|
require.Len(t, albums.Items, 3)
|
|
|
|
resp, err = http.DefaultClient.Get(host() + "/apis/web/v1/top-tracks")
|
|
assert.NoError(t, err)
|
|
var tracks db.PaginatedResponse[models.Track]
|
|
err = json.NewDecoder(resp.Body).Decode(&tracks)
|
|
require.NoError(t, err)
|
|
require.Len(t, tracks.Items, 3)
|
|
|
|
truncateTestData(t)
|
|
}
|
|
|
|
func TestMerge(t *testing.T) {
|
|
|
|
t.Run("Submit Listens", doSubmitListens)
|
|
|
|
resp, err := makeAuthRequest(t, session, "POST", "/apis/web/v1/merge/tracks?from_id=1&to_id=2", nil)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 204, resp.StatusCode)
|
|
|
|
resp, err = http.DefaultClient.Get(host() + "/apis/web/v1/track?id=2")
|
|
require.NoError(t, err)
|
|
var track models.Track
|
|
err = json.NewDecoder(resp.Body).Decode(&track)
|
|
require.NoError(t, err)
|
|
assert.EqualValues(t, 2, track.ListenCount)
|
|
|
|
truncateTestData(t)
|
|
|
|
t.Run("Submit Listens", doSubmitListens)
|
|
|
|
resp, err = makeAuthRequest(t, session, "POST", "/apis/web/v1/merge/artists?from_id=1&to_id=2", nil)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 204, resp.StatusCode)
|
|
|
|
resp, err = http.DefaultClient.Get(host() + "/apis/web/v1/artist?id=2")
|
|
require.NoError(t, err)
|
|
var artist models.Artist
|
|
err = json.NewDecoder(resp.Body).Decode(&artist)
|
|
require.NoError(t, err)
|
|
assert.EqualValues(t, 2, artist.ListenCount)
|
|
|
|
truncateTestData(t)
|
|
|
|
t.Run("Submit Listens", doSubmitListens)
|
|
|
|
resp, err = makeAuthRequest(t, session, "POST", "/apis/web/v1/merge/albums?from_id=1&to_id=2", nil)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 204, resp.StatusCode)
|
|
|
|
resp, err = http.DefaultClient.Get(host() + "/apis/web/v1/album?id=2")
|
|
require.NoError(t, err)
|
|
var album models.Album
|
|
err = json.NewDecoder(resp.Body).Decode(&album)
|
|
require.NoError(t, err)
|
|
assert.EqualValues(t, 2, album.ListenCount)
|
|
|
|
truncateTestData(t)
|
|
}
|
|
|
|
func TestValidateToken(t *testing.T) {
|
|
login(t)
|
|
getApiKey(t, session)
|
|
|
|
req, err := http.NewRequest("GET", host()+"/apis/listenbrainz/1/validate-token", nil)
|
|
require.NoError(t, err)
|
|
req.Header.Add("Authorization", fmt.Sprintf("Token %s", apikey))
|
|
resp, err := http.DefaultClient.Do(req)
|
|
require.NoError(t, err)
|
|
var actual handlers.LbzValidateResponse
|
|
require.NoError(t, json.NewDecoder(resp.Body).Decode(&actual))
|
|
t.Log(actual)
|
|
var expected handlers.LbzValidateResponse
|
|
expected.Code = 200
|
|
expected.Message = "Token valid."
|
|
expected.Valid = true
|
|
expected.UserName = "test"
|
|
assert.True(t, assert.ObjectsAreEqual(expected, actual))
|
|
|
|
req, err = http.NewRequest("GET", host()+"/apis/listenbrainz/1/validate-token", nil)
|
|
require.NoError(t, err)
|
|
req.Header.Add("Authorization", "Token thisisasuperinvalidtoken")
|
|
resp, err = http.DefaultClient.Do(req)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 401, resp.StatusCode)
|
|
|
|
req, err = http.NewRequest("GET", host()+"/apis/listenbrainz/1/validate-token", nil)
|
|
require.NoError(t, err)
|
|
resp, err = http.DefaultClient.Do(req)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 401, resp.StatusCode)
|
|
}
|
|
|
|
func TestDelete(t *testing.T) {
|
|
|
|
t.Run("Submit Listens", doSubmitListens)
|
|
|
|
resp, err := makeAuthRequest(t, session, "DELETE", "/apis/web/v1/artist?id=1", nil)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 204, resp.StatusCode)
|
|
|
|
resp, err = http.DefaultClient.Get(host() + "/apis/web/v1/artist?id=1")
|
|
require.NoError(t, err)
|
|
require.Equal(t, 404, resp.StatusCode)
|
|
|
|
resp, err = makeAuthRequest(t, session, "DELETE", "/apis/web/v1/album?id=1", nil)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 204, resp.StatusCode)
|
|
|
|
resp, err = http.DefaultClient.Get(host() + "/apis/web/v1/album?id=1")
|
|
require.NoError(t, err)
|
|
require.Equal(t, 404, resp.StatusCode)
|
|
|
|
resp, err = makeAuthRequest(t, session, "DELETE", "/apis/web/v1/track?id=1", nil)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 204, resp.StatusCode)
|
|
|
|
resp, err = http.DefaultClient.Get(host() + "/apis/web/v1/track?id=1")
|
|
require.NoError(t, err)
|
|
require.Equal(t, 404, resp.StatusCode)
|
|
|
|
truncateTestData(t)
|
|
}
|
|
|
|
func TestAliasesAndSearch(t *testing.T) {
|
|
|
|
t.Run("Submit Listens", doSubmitListens)
|
|
|
|
resp, err := makeAuthRequest(t, session, "POST", "/apis/web/v1/aliases?artist_id=1&alias=Sayuri", nil)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 201, resp.StatusCode)
|
|
|
|
resp, err = http.DefaultClient.Get(host() + "/apis/web/v1/aliases?artist_id=1")
|
|
require.NoError(t, err)
|
|
require.Equal(t, 200, resp.StatusCode)
|
|
var actual []models.Alias
|
|
require.NoError(t, json.NewDecoder(resp.Body).Decode(&actual))
|
|
require.Len(t, actual, 2)
|
|
assert.Equal(t, actual[0].Alias, "さユり")
|
|
assert.Equal(t, actual[0].Source, "Canonical")
|
|
assert.Equal(t, actual[1].Alias, "Sayuri")
|
|
assert.Equal(t, actual[1].Source, "Manual")
|
|
|
|
resp, err = makeAuthRequest(t, session, "POST", "/apis/web/v1/aliases?album_id=1&alias=Sanketsu+Girl", nil)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 201, resp.StatusCode)
|
|
|
|
resp, err = http.DefaultClient.Get(host() + "/apis/web/v1/aliases?album_id=1")
|
|
require.NoError(t, err)
|
|
require.Equal(t, 200, resp.StatusCode)
|
|
actual = nil
|
|
require.NoError(t, json.NewDecoder(resp.Body).Decode(&actual))
|
|
require.Len(t, actual, 2)
|
|
assert.Equal(t, actual[0].Alias, "酸欠少女")
|
|
assert.Equal(t, actual[0].Source, "Canonical")
|
|
assert.Equal(t, actual[1].Alias, "Sanketsu Girl")
|
|
assert.Equal(t, actual[1].Source, "Manual")
|
|
|
|
resp, err = makeAuthRequest(t, session, "POST", "/apis/web/v1/aliases?track_id=1&alias=Tower+of+Flower", nil)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 201, resp.StatusCode)
|
|
|
|
resp, err = makeAuthRequest(t, session, "POST", "/apis/web/v1/aliases/primary?track_id=1&alias=Tower+of+Flower", nil)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 204, resp.StatusCode)
|
|
|
|
resp, err = http.DefaultClient.Get(host() + "/apis/web/v1/track?id=1")
|
|
require.NoError(t, err)
|
|
require.Equal(t, 200, resp.StatusCode)
|
|
var track models.Track
|
|
require.NoError(t, json.NewDecoder(resp.Body).Decode(&track))
|
|
require.Len(t, actual, 2)
|
|
assert.Equal(t, track.Title, "Tower of Flower")
|
|
|
|
resp, err = makeAuthRequest(t, session, "POST", "/apis/web/v1/aliases/primary?artist_id=1&alias=Sayuri", nil)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 204, resp.StatusCode)
|
|
|
|
// make sure searching works with aliases
|
|
|
|
resp, err = http.DefaultClient.Get(host() + "/apis/web/v1/search?q=Sanketsu")
|
|
require.NoError(t, err)
|
|
require.Equal(t, 200, resp.StatusCode)
|
|
var results handlers.SearchResults
|
|
require.NoError(t, json.NewDecoder(resp.Body).Decode(&results))
|
|
require.Len(t, results.Albums, 1)
|
|
assert.Equal(t, results.Albums[0].Title, "酸欠少女")
|
|
|
|
resp, err = http.DefaultClient.Get(host() + "/apis/web/v1/search?q=Sayuri")
|
|
require.NoError(t, err)
|
|
require.Equal(t, 200, resp.StatusCode)
|
|
results = handlers.SearchResults{}
|
|
require.NoError(t, json.NewDecoder(resp.Body).Decode(&results))
|
|
require.Len(t, results.Artists, 1)
|
|
assert.Equal(t, results.Artists[0].Name, "Sayuri") // reflects the new primary alias
|
|
|
|
truncateTestData(t)
|
|
}
|
|
|
|
func TestStats(t *testing.T) {
|
|
// zeroes
|
|
resp, err := http.DefaultClient.Get(host() + "/apis/web/v1/stats")
|
|
t.Log(resp)
|
|
require.NoError(t, err)
|
|
|
|
t.Run("Submit Listens", doSubmitListens)
|
|
|
|
resp, err = http.DefaultClient.Get(host() + "/apis/web/v1/stats")
|
|
t.Log(resp)
|
|
require.NoError(t, err)
|
|
var actual handlers.StatsResponse
|
|
require.NoError(t, json.NewDecoder(resp.Body).Decode(&actual))
|
|
assert.EqualValues(t, 3, actual.ListenCount)
|
|
assert.EqualValues(t, 3, actual.TrackCount)
|
|
assert.EqualValues(t, 3, actual.AlbumCount)
|
|
assert.EqualValues(t, 3, actual.ArtistCount)
|
|
assert.EqualValues(t, 11, actual.MinutesListened)
|
|
}
|
|
|
|
func TestListenActivity(t *testing.T) {
|
|
|
|
// this test fails when run a bit after midnight
|
|
// i'll figure out a better test later
|
|
|
|
// t.Run("Submit Listens", doSubmitListens)
|
|
|
|
// resp, err := http.DefaultClient.Get(host() + "/apis/web/v1/listen-activity?range=3")
|
|
// t.Log(resp)
|
|
// require.NoError(t, err)
|
|
// var actual []db.ListenActivityItem
|
|
// require.NoError(t, json.NewDecoder(resp.Body).Decode(&actual))
|
|
// t.Log(actual)
|
|
// require.Len(t, actual, 3)
|
|
// assert.EqualValues(t, 3, actual[2].Listens)
|
|
}
|
|
|
|
func TestAuth(t *testing.T) {
|
|
// logs in a new session
|
|
formdata := url.Values{}
|
|
formdata.Set("username", cfg.DefaultUsername())
|
|
formdata.Set("password", cfg.DefaultPassword())
|
|
encoded := formdata.Encode()
|
|
resp, err := http.DefaultClient.Post(host()+"/apis/web/v1/login", "application/x-www-form-urlencoded", strings.NewReader(encoded))
|
|
respBytes, _ := io.ReadAll(resp.Body)
|
|
t.Logf("Login request response: %s - %s", resp.Status, respBytes)
|
|
require.NoError(t, err)
|
|
require.Len(t, resp.Cookies(), 1)
|
|
s := resp.Cookies()[0].Value
|
|
require.NotEmpty(t, s)
|
|
|
|
// test update user
|
|
req, err := http.NewRequest("PATCH", host()+"/apis/web/v1/user?username=new&password=supersecret", nil)
|
|
require.NoError(t, err)
|
|
req.AddCookie(&http.Cookie{
|
|
Name: "koito_session",
|
|
Value: s,
|
|
})
|
|
resp, err = http.DefaultClient.Do(req)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 204, resp.StatusCode)
|
|
|
|
// test /me with updated info
|
|
req, err = http.NewRequest("GET", host()+"/apis/web/v1/user/me", nil)
|
|
require.NoError(t, err)
|
|
req.AddCookie(&http.Cookie{
|
|
Name: "koito_session",
|
|
Value: s,
|
|
})
|
|
resp, err = http.DefaultClient.Do(req)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 200, resp.StatusCode)
|
|
var me models.User
|
|
require.NoError(t, json.NewDecoder(resp.Body).Decode(&me))
|
|
require.Equal(t, "new", me.Username)
|
|
|
|
// login with old password fails
|
|
formdata = url.Values{}
|
|
formdata.Set("username", cfg.DefaultUsername())
|
|
formdata.Set("password", cfg.DefaultPassword())
|
|
encoded = formdata.Encode()
|
|
resp, err = http.DefaultClient.Post(host()+"/apis/web/v1/login", "application/x-www-form-urlencoded", strings.NewReader(encoded))
|
|
require.NoError(t, err)
|
|
require.Equal(t, 401, resp.StatusCode)
|
|
|
|
// reset update so other tests dont fail
|
|
req, err = http.NewRequest("PATCH", host()+fmt.Sprintf("/apis/web/v1/user?username=%s&password=%s", cfg.DefaultUsername(), cfg.DefaultPassword()), nil)
|
|
require.NoError(t, err)
|
|
req.AddCookie(&http.Cookie{
|
|
Name: "koito_session",
|
|
Value: s,
|
|
})
|
|
resp, err = http.DefaultClient.Do(req)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 204, resp.StatusCode)
|
|
|
|
// creates api key
|
|
req, err = http.NewRequest("POST", host()+"/apis/web/v1/user/apikeys?label=testing", nil)
|
|
require.NoError(t, err)
|
|
req.AddCookie(&http.Cookie{
|
|
Name: "koito_session",
|
|
Value: s,
|
|
})
|
|
resp, err = http.DefaultClient.Do(req)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 201, resp.StatusCode)
|
|
var response struct {
|
|
Key string `json:"key"`
|
|
}
|
|
require.NoError(t, json.NewDecoder(resp.Body).Decode(&response))
|
|
require.NotEmpty(t, response.Key)
|
|
|
|
// validates api key
|
|
req, err = http.NewRequest("GET", host()+"/apis/listenbrainz/1/validate-token", nil)
|
|
require.NoError(t, err)
|
|
req.Header.Add("Authorization", fmt.Sprintf("Token %s", response.Key))
|
|
resp, err = http.DefaultClient.Do(req)
|
|
require.NoError(t, err)
|
|
var actual handlers.LbzValidateResponse
|
|
require.NoError(t, json.NewDecoder(resp.Body).Decode(&actual))
|
|
var expected handlers.LbzValidateResponse
|
|
expected.Code = 200
|
|
expected.Message = "Token valid."
|
|
expected.Valid = true
|
|
expected.UserName = "test"
|
|
assert.True(t, assert.ObjectsAreEqual(expected, actual))
|
|
|
|
// changes api key label
|
|
login(t) // i dont care about using the new session anymore
|
|
resp, err = makeAuthRequest(t, s, "PATCH", "/apis/web/v1/user/apikeys?id=2&label=well+tested", nil)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, 200, resp.StatusCode)
|
|
resp, err = makeAuthRequest(t, s, "GET", "/apis/web/v1/user/apikeys", nil)
|
|
require.NoError(t, err)
|
|
var keys []models.ApiKey
|
|
err = json.NewDecoder(resp.Body).Decode(&keys)
|
|
require.NoError(t, err)
|
|
require.GreaterOrEqual(t, len(keys), 2)
|
|
require.NotNil(t, keys[1].Label)
|
|
assert.Equal(t, "well tested", keys[1].Label)
|
|
|
|
// logs out
|
|
req, err = http.NewRequest("POST", host()+"/apis/web/v1/logout", nil)
|
|
require.NoError(t, err)
|
|
req.AddCookie(&http.Cookie{
|
|
Name: "koito_session",
|
|
Value: s,
|
|
})
|
|
resp, err = http.DefaultClient.Do(req)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 204, resp.StatusCode)
|
|
|
|
// attempts to create an api key - unauthorized
|
|
formdata = url.Values{}
|
|
formdata.Set("label", "testing")
|
|
encoded = formdata.Encode()
|
|
req, err = http.NewRequest("POST", host()+"/apis/web/v1/user/apikeys", strings.NewReader(encoded))
|
|
require.NoError(t, err)
|
|
req.AddCookie(&http.Cookie{
|
|
Name: "koito_session",
|
|
Value: s,
|
|
})
|
|
resp, err = http.DefaultClient.Do(req)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 401, resp.StatusCode)
|
|
}
|
|
|
|
func TestDeleteListen(t *testing.T) {
|
|
login(t)
|
|
getApiKey(t, session)
|
|
|
|
truncateTestData(t)
|
|
|
|
body := `{
|
|
"listen_type": "single",
|
|
"payload": [
|
|
{
|
|
"listened_at": 1749475719,
|
|
"track_metadata": {
|
|
"additional_info": {
|
|
"artist_mbids": [
|
|
"80b3cb83-b7a3-4f79-ad42-8325cefb3626"
|
|
],
|
|
"artist_names": [
|
|
"キタニタツヤ"
|
|
],
|
|
"duration_ms": 197270,
|
|
"recording_mbid": "4e909c21-e7a8-404d-b75a-0c8c2926efb0",
|
|
"release_group_mbid": "89069d92-e495-462c-b189-3431551868ed",
|
|
"release_mbid": "e16a49d6-77f3-4d73-b93c-cac855ce6ad5",
|
|
"submission_client": "navidrome",
|
|
"submission_client_version": "0.56.1 (fa2cf362)"
|
|
},
|
|
"artist_name": "キタニタツヤ",
|
|
"release_name": "Where Our Blue Is",
|
|
"track_name": "Where Our Blue Is"
|
|
}
|
|
}
|
|
]
|
|
}`
|
|
|
|
req, err := http.NewRequest("POST", host()+"/apis/listenbrainz/1/submit-listens", strings.NewReader(body))
|
|
require.NoError(t, err)
|
|
req.Header.Add("Authorization", fmt.Sprintf("Token %s", apikey))
|
|
resp, err := http.DefaultClient.Do(req)
|
|
require.NoError(t, err)
|
|
respBytes, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, `{"status": "ok"}`, string(respBytes))
|
|
|
|
resp, err = makeAuthRequest(t, session, "DELETE", "/apis/web/v1/listen?track_id=1&unix=1749475719", nil)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 204, resp.StatusCode)
|
|
|
|
// deletes are idempotent
|
|
resp, err = makeAuthRequest(t, session, "DELETE", "/apis/web/v1/listen?track_id=1&unix=1749475719", nil)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 204, resp.StatusCode)
|
|
|
|
// listen is deleted
|
|
resp, err = http.DefaultClient.Get(host() + "/apis/web/v1/track?id=1")
|
|
require.NoError(t, err)
|
|
var track models.Track
|
|
err = json.NewDecoder(resp.Body).Decode(&track)
|
|
require.NoError(t, err)
|
|
assert.EqualValues(t, 0, track.ListenCount)
|
|
}
|
|
|
|
func TestArtistReplaceImage(t *testing.T) {
|
|
|
|
t.Run("Submit Listens", doSubmitListens)
|
|
|
|
buf := &bytes.Buffer{}
|
|
mpw := multipart.NewWriter(buf)
|
|
mpw.WriteField("artist_id", "1")
|
|
w, err := mpw.CreateFormFile("image", path.Join("..", "test_assets", "yuu.jpg"))
|
|
require.NoError(t, err)
|
|
f, err := os.Open(path.Join("..", "test_assets", "yuu.jpg"))
|
|
require.NoError(t, err)
|
|
defer f.Close()
|
|
_, err = io.Copy(w, f)
|
|
require.NoError(t, err)
|
|
require.NoError(t, mpw.Close())
|
|
|
|
req, err := http.NewRequest("POST", host()+"/apis/web/v1/replace-image", buf)
|
|
require.NoError(t, err)
|
|
req.AddCookie(&http.Cookie{
|
|
Name: "koito_session",
|
|
Value: session,
|
|
})
|
|
req.Header.Add("Content-Type", mpw.FormDataContentType())
|
|
resp, err := http.DefaultClient.Do(req)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 200, resp.StatusCode)
|
|
response := new(handlers.ReplaceImageResponse)
|
|
require.NoError(t, json.NewDecoder(resp.Body).Decode(response))
|
|
require.NotEmpty(t, response.Image)
|
|
newid, err := uuid.Parse(response.Image)
|
|
require.NoError(t, err)
|
|
|
|
a, err := store.GetArtist(context.Background(), db.GetArtistOpts{ID: 1})
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, a.Image)
|
|
assert.Equal(t, newid, *a.Image)
|
|
}
|
|
|
|
func TestAlbumReplaceImage(t *testing.T) {
|
|
|
|
t.Run("Submit Listens", doSubmitListens)
|
|
|
|
buf := &bytes.Buffer{}
|
|
mpw := multipart.NewWriter(buf)
|
|
mpw.WriteField("album_id", "1")
|
|
w, err := mpw.CreateFormFile("image", path.Join("..", "test_assets", "yuu.jpg"))
|
|
require.NoError(t, err)
|
|
f, err := os.Open(path.Join("..", "test_assets", "yuu.jpg"))
|
|
require.NoError(t, err)
|
|
defer f.Close()
|
|
_, err = io.Copy(w, f)
|
|
require.NoError(t, err)
|
|
require.NoError(t, mpw.Close())
|
|
|
|
req, err := http.NewRequest("POST", host()+"/apis/web/v1/replace-image", buf)
|
|
require.NoError(t, err)
|
|
req.AddCookie(&http.Cookie{
|
|
Name: "koito_session",
|
|
Value: session,
|
|
})
|
|
req.Header.Add("Content-Type", mpw.FormDataContentType())
|
|
resp, err := http.DefaultClient.Do(req)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 200, resp.StatusCode)
|
|
response := new(handlers.ReplaceImageResponse)
|
|
require.NoError(t, json.NewDecoder(resp.Body).Decode(response))
|
|
require.NotEmpty(t, response.Image)
|
|
newid, err := uuid.Parse(response.Image)
|
|
require.NoError(t, err)
|
|
|
|
a, err := store.GetAlbum(context.Background(), db.GetAlbumOpts{ID: 1})
|
|
require.NoError(t, err)
|
|
assert.NotNil(t, a.Image)
|
|
assert.Equal(t, newid, *a.Image)
|
|
}
|
|
|
|
func TestSetPrimaryArtist(t *testing.T) {
|
|
|
|
t.Run("Submit Listens", doSubmitListens)
|
|
|
|
ctx := context.Background()
|
|
|
|
// set and unset track primary artist
|
|
|
|
formdata := url.Values{}
|
|
formdata.Set("artist_id", "1")
|
|
formdata.Set("track_id", "1")
|
|
formdata.Set("is_primary", "false")
|
|
body := formdata.Encode()
|
|
resp, err := makeAuthRequest(t, session, "POST", "/apis/web/v1/artists/primary", strings.NewReader(body))
|
|
require.NoError(t, err)
|
|
require.Equal(t, 204, resp.StatusCode)
|
|
|
|
exists, err := store.RowExists(ctx, `
|
|
SELECT EXISTS (
|
|
SELECT 1 FROM artist_tracks
|
|
WHERE track_id = $1 AND artist_id = $2 AND is_primary = $3
|
|
)`, 1, 1, false)
|
|
require.NoError(t, err)
|
|
assert.True(t, exists, "expected artist is_primary to be false")
|
|
|
|
formdata = url.Values{}
|
|
formdata.Set("artist_id", "1")
|
|
formdata.Set("track_id", "1")
|
|
formdata.Set("is_primary", "true")
|
|
body = formdata.Encode()
|
|
resp, err = makeAuthRequest(t, session, "POST", "/apis/web/v1/artists/primary", strings.NewReader(body))
|
|
require.NoError(t, err)
|
|
require.Equal(t, 204, resp.StatusCode)
|
|
|
|
exists, err = store.RowExists(ctx, `
|
|
SELECT EXISTS (
|
|
SELECT 1 FROM artist_tracks
|
|
WHERE track_id = $1 AND artist_id = $2 AND is_primary = $3
|
|
)`, 1, 1, true)
|
|
require.NoError(t, err)
|
|
assert.True(t, exists, "expected artist is_primary to be true")
|
|
|
|
// set and unset album primary artist
|
|
|
|
formdata = url.Values{}
|
|
formdata.Set("artist_id", "1")
|
|
formdata.Set("album_id", "1")
|
|
formdata.Set("is_primary", "false")
|
|
body = formdata.Encode()
|
|
resp, err = makeAuthRequest(t, session, "POST", "/apis/web/v1/artists/primary", strings.NewReader(body))
|
|
require.NoError(t, err)
|
|
require.Equal(t, 204, resp.StatusCode)
|
|
|
|
exists, err = store.RowExists(ctx, `
|
|
SELECT EXISTS (
|
|
SELECT 1 FROM artist_releases
|
|
WHERE release_id = $1 AND artist_id = $2 AND is_primary = $3
|
|
)`, 1, 1, false)
|
|
require.NoError(t, err)
|
|
assert.True(t, exists, "expected artist is_primary to be false")
|
|
|
|
formdata = url.Values{}
|
|
formdata.Set("artist_id", "1")
|
|
formdata.Set("album_id", "1")
|
|
formdata.Set("is_primary", "true")
|
|
body = formdata.Encode()
|
|
resp, err = makeAuthRequest(t, session, "POST", "/apis/web/v1/artists/primary", strings.NewReader(body))
|
|
require.NoError(t, err)
|
|
require.Equal(t, 204, resp.StatusCode)
|
|
|
|
exists, err = store.RowExists(ctx, `
|
|
SELECT EXISTS (
|
|
SELECT 1 FROM artist_releases
|
|
WHERE release_id = $1 AND artist_id = $2 AND is_primary = $3
|
|
)`, 1, 1, true)
|
|
require.NoError(t, err)
|
|
assert.True(t, exists, "expected artist is_primary to be true")
|
|
|
|
// create a new track with multiple artists to make sure only one is primary at a time
|
|
|
|
listenBody := `{
|
|
"listen_type": "single",
|
|
"payload": [
|
|
{
|
|
"listened_at": 1749475719,
|
|
"track_metadata": {
|
|
"additional_info": {
|
|
"artist_names": [
|
|
"Rat Tally",
|
|
"Madeline Kenney"
|
|
],
|
|
"duration_ms": 197270,
|
|
"submission_client": "navidrome",
|
|
"submission_client_version": "0.56.1 (fa2cf362)"
|
|
},
|
|
"artist_name": "Rat Tally feat. Madeline Kenney",
|
|
"release_name": "In My Car",
|
|
"track_name": "In My Car"
|
|
}
|
|
}
|
|
]
|
|
}`
|
|
|
|
req, err := http.NewRequest("POST", host()+"/apis/listenbrainz/1/submit-listens", strings.NewReader(listenBody))
|
|
require.NoError(t, err)
|
|
req.Header.Add("Authorization", fmt.Sprintf("Token %s", apikey))
|
|
req.Header.Add("Content-Type", "application/json")
|
|
resp, err = http.DefaultClient.Do(req)
|
|
require.NoError(t, err)
|
|
respBytes, err := io.ReadAll(resp.Body)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, `{"status": "ok"}`, string(respBytes))
|
|
|
|
// set both artists as primary
|
|
|
|
formdata = url.Values{}
|
|
formdata.Set("artist_id", "4")
|
|
formdata.Set("album_id", "4")
|
|
formdata.Set("is_primary", "true")
|
|
body = formdata.Encode()
|
|
resp, err = makeAuthRequest(t, session, "POST", "/apis/web/v1/artists/primary", strings.NewReader(body))
|
|
require.NoError(t, err)
|
|
require.Equal(t, 204, resp.StatusCode)
|
|
formdata = url.Values{}
|
|
formdata.Set("artist_id", "5")
|
|
formdata.Set("album_id", "4")
|
|
formdata.Set("is_primary", "true")
|
|
body = formdata.Encode()
|
|
resp, err = makeAuthRequest(t, session, "POST", "/apis/web/v1/artists/primary", strings.NewReader(body))
|
|
require.NoError(t, err)
|
|
require.Equal(t, 204, resp.StatusCode)
|
|
|
|
formdata = url.Values{}
|
|
formdata.Set("artist_id", "4")
|
|
formdata.Set("track_id", "4")
|
|
formdata.Set("is_primary", "true")
|
|
body = formdata.Encode()
|
|
resp, err = makeAuthRequest(t, session, "POST", "/apis/web/v1/artists/primary", strings.NewReader(body))
|
|
require.NoError(t, err)
|
|
require.Equal(t, 204, resp.StatusCode)
|
|
formdata = url.Values{}
|
|
formdata.Set("artist_id", "5")
|
|
formdata.Set("track_id", "4")
|
|
formdata.Set("is_primary", "true")
|
|
body = formdata.Encode()
|
|
resp, err = makeAuthRequest(t, session, "POST", "/apis/web/v1/artists/primary", strings.NewReader(body))
|
|
require.NoError(t, err)
|
|
require.Equal(t, 204, resp.StatusCode)
|
|
|
|
count, err := store.Count(ctx, `SELECT COUNT(*) FROM artist_releases WHERE release_id = $1 AND is_primary = $2`, 4, true)
|
|
require.NoError(t, err)
|
|
assert.EqualValues(t, 1, count, "expected only one primary artist for release")
|
|
count, err = store.Count(ctx, `SELECT COUNT(*) FROM artist_tracks WHERE track_id = $1 AND is_primary = $2`, 4, true)
|
|
require.NoError(t, err)
|
|
assert.EqualValues(t, 1, count, "expected only one primary artist for track")
|
|
}
|