Alternative names (#32): - PUT /api/v1/soundtracks/{uuid}/alternative-names replaces a soundtrack's alternative names (trimmed, deduplicated, without the soundtrack's own name). - alternative_names is in the soundtrack and statistics responses. - /find matches them too and shows which name matched. Covers (#12), stored in COVERS_PATH and off when it is not set: - GET, PUT (upload) and DELETE /api/v1/soundtracks/{uuid}/cover, and PUT .../cover/source to pick a SteamGridDB game or AniList anime by ID. Uploaded and picked covers are never replaced automatically. - With STEAMGRIDDB_API_KEY, a background job searches SteamGridDB, then AniList, by name, alternative names and the name without bracketed parts, at startup, after a sync and after names change. Search results are ranked by matching words, so "Civilization IV" gets "Sid Meier's Civilization IV". - cover_modified (Unix seconds) is in the soundtrack and statistics responses, so clients can cache covers. The new columns are added to migration 000006, which is not released yet. Closes #12, closes #32 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
99 lines
3.4 KiB
Go
99 lines
3.4 KiB
Go
//go:build integration
|
|
|
|
package backend
|
|
|
|
import (
|
|
"context"
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"music-server/internal/db/repository"
|
|
"music-server/internal/testutil/pgtest"
|
|
|
|
"github.com/jackc/pgx/v5/pgtype"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// TestAlternativeNamesAndCoversWithPostgres checks the alternative name and cover queries
|
|
// against a real database.
|
|
func TestAlternativeNamesAndCoversWithPostgres(t *testing.T) {
|
|
ctx := context.Background()
|
|
q := repository.New(pgtest.NewMigratedDatabase(t).Pool)
|
|
s := NewSyncService(q, copyTestMusic(t), nil)
|
|
require.True(t, s.Start(false))
|
|
s.Wait()
|
|
|
|
library := NewLibraryService(q)
|
|
soundtracks, err := library.Soundtracks(ctx)
|
|
require.NoError(t, err)
|
|
require.GreaterOrEqual(t, len(soundtracks), 2)
|
|
first, second := soundtracks[0], soundtracks[1]
|
|
assert.Equal(t, []string{}, first.AlternativeNames)
|
|
assert.Nil(t, first.CoverModified)
|
|
|
|
// Alternative names, in every soundtrack response.
|
|
updated, err := library.SetAlternativeNames(ctx, first.Uuid, []string{" Alt ", "alt", first.Name, "Другое"})
|
|
require.NoError(t, err)
|
|
assert.Equal(t, []string{"Alt", "Другое"}, updated.AlternativeNames)
|
|
stats, err := NewStatisticsService(q).AllSoundtracks(ctx)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, []string{"Alt", "Другое"}, stats[0].AlternativeNames)
|
|
never, err := NewStatisticsService(q).NeverPlayedSoundtracks(ctx)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, []string{"Alt", "Другое"}, never[0].AlternativeNames)
|
|
search, err := NewMusicService(q, t.TempDir()).SearchSoundtracks(ctx)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, []string{"Alt", "Другое"}, search[0].AlternativeNames)
|
|
|
|
// Covers.
|
|
c, err := NewCoverService(q, filepath.Join(t.TempDir(), "covers"), CoverOptions{})
|
|
require.NoError(t, err)
|
|
require.NoError(t, c.Upload(ctx, first.Uuid, pngBytes(t)))
|
|
withCover, err := library.Soundtrack(ctx, first.Uuid)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, withCover.CoverModified)
|
|
stats, err = NewStatisticsService(q).AllSoundtracks(ctx)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, withCover.CoverModified, stats[0].CoverModified)
|
|
|
|
needing, err := q.ListSoundtracksNeedingCover(ctx, nowPlus(0))
|
|
require.NoError(t, err)
|
|
for _, row := range needing {
|
|
assert.NotEqual(t, first.Uuid, row.Uuid, "a soundtrack with a cover is not searched")
|
|
}
|
|
require.NoError(t, q.MarkCoverChecked(ctx, second.Uuid))
|
|
needing, err = q.ListSoundtracksNeedingCover(ctx, nowPlus(-60))
|
|
require.NoError(t, err)
|
|
for _, row := range needing {
|
|
assert.NotEqual(t, second.Uuid, row.Uuid, "searched recently")
|
|
}
|
|
|
|
// New names make a soundtrack without a cover searched again.
|
|
_, err = library.SetAlternativeNames(ctx, second.Uuid, []string{"Other"})
|
|
require.NoError(t, err)
|
|
needing, err = q.ListSoundtracksNeedingCover(ctx, nowPlus(-60))
|
|
require.NoError(t, err)
|
|
var found bool
|
|
for _, row := range needing {
|
|
if row.Uuid == second.Uuid {
|
|
found = true
|
|
assert.Equal(t, []string{"Other"}, row.AlternativeNames)
|
|
}
|
|
}
|
|
assert.True(t, found)
|
|
|
|
files, err := q.ListCoverFiles(ctx)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, []string{first.Uuid.String() + ".png"}, files)
|
|
require.NoError(t, c.Remove(ctx, first.Uuid))
|
|
files, err = q.ListCoverFiles(ctx)
|
|
require.NoError(t, err)
|
|
assert.Empty(t, files)
|
|
}
|
|
|
|
func nowPlus(seconds int) pgtype.Timestamptz {
|
|
return pgtype.Timestamptz{Time: time.Now().Add(time.Duration(seconds) * time.Second), Valid: true}
|
|
}
|