release / govulncheck (push) Successful in 14s
release / web (push) Successful in 1m17s
release / go (push) Successful in 1m35s
release / integration (push) Successful in 5m9s
release / android (push) Successful in 5m15s
release / Build signed APK (releases and dev) (push) Successful in 5m20s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 24s
release / Verify release artifacts (tag releases only) (push) Skipped
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
168 lines
6.7 KiB
Go
168 lines
6.7 KiB
Go
package wanted
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
|
|
"github.com/jackc/pgx/v5/pgtype"
|
|
|
|
"git.fabledsword.com/bvandeusen/minstrel/internal/db/dbq"
|
|
"git.fabledsword.com/bvandeusen/minstrel/internal/tags"
|
|
)
|
|
|
|
func TestParseMusicBrainzRef(t *testing.T) {
|
|
const id = "11111111-0000-4000-8000-000000000003"
|
|
for ref, want := range map[string]string{
|
|
"https://musicbrainz.org/release-group/" + id: "release-group " + id,
|
|
"https://musicbrainz.org/release/" + id + "?tab=x": "release " + id,
|
|
" " + id + " ": " " + id,
|
|
"https://MusicBrainz.org/Release-Group/" + "11111111-0000-4000-8000-00000000000A": "release-group 11111111-0000-4000-8000-00000000000a",
|
|
} {
|
|
kind, got, ok := ParseMusicBrainzRef(ref)
|
|
if !ok || kind+" "+got != want {
|
|
t.Errorf("ParseMusicBrainzRef(%q) = %q %q %v, want %q", ref, kind, got, ok, want)
|
|
}
|
|
}
|
|
for _, bad := range []string{"", "Dial Tone", "https://musicbrainz.org/artist/nope"} {
|
|
if _, _, ok := ParseMusicBrainzRef(bad); ok {
|
|
t.Errorf("ParseMusicBrainzRef(%q) read an id", bad)
|
|
}
|
|
}
|
|
}
|
|
|
|
// A person names the MusicBrainz album an unmatched album is, and it goes
|
|
// on the list as held; or leaves it off, and matching never tries it again
|
|
// (#5579).
|
|
func TestMatchAlbumTo_ByLinkOrLeaveItOff_Integration(t *testing.T) {
|
|
pool := newPool(t)
|
|
ctx := context.Background()
|
|
q := dbq.New(pool)
|
|
s := newService(t, pool, newFakeMB(t))
|
|
s.releaseGroup = func(context.Context, string) (string, error) { return "", tags.ErrNotFound }
|
|
split := seedAlbum(t, q, blur, "Blur + Friends", "", []string{"The Ballad", "St. Charles Square"})
|
|
local := seedAlbum(t, q, blur, "Dial Tone", "", []string{"Intro"})
|
|
for _, a := range []pgtype.UUID{split.ID, local.ID} {
|
|
if err := q.RecordAlbumMatch(ctx, dbq.RecordAlbumMatchParams{
|
|
AlbumID: a, Outcome: "unmatched", Detail: "no match", Matcher: libraryMatcher}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
if _, err := pool.Exec(ctx, `UPDATE acquisition_settings
|
|
SET library_listing_started_at = now(), library_listed_at = now(), library_unlisted = 2`); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
for ref, want := range map[string]error{
|
|
"not a link": ErrMatchRef,
|
|
"https://musicbrainz.org/release-group/99999999-0000-4000-8000-000000000000": ErrMatchNoGroup,
|
|
"99999999-0000-4000-8000-000000000000": ErrMatchNoGroup,
|
|
} {
|
|
if _, err := s.MatchAlbumTo(ctx, split.ID, ref); !errors.Is(err, want) {
|
|
t.Errorf("MatchAlbumTo(%q) = %v, want %v", ref, err, want)
|
|
}
|
|
}
|
|
|
|
m, err := s.MatchAlbumTo(ctx, split.ID, "https://musicbrainz.org/release-group/"+theBallad)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if m.Group != theBallad || m.Title != "The Ballad of Darren" || m.Agree != 2 || m.Tracks != 2 {
|
|
t.Errorf("match = %+v", m)
|
|
}
|
|
w, err := q.GetWantByReleaseGroup(ctx, theBallad)
|
|
if err != nil || w.Reason != ReasonLibrary || !w.FulfilledAt.Valid || w.AlbumID != split.ID || w.ArtistMbid != blur {
|
|
t.Fatalf("want = %+v, %v: held, about the album", w, err)
|
|
}
|
|
if outcome, method, group, _ := matchOf(t, pool, split.ID); outcome != "listed" || method != matchManual || group != theBallad {
|
|
t.Errorf("decided %s by %s as %s", outcome, method, group)
|
|
}
|
|
if state, _ := q.GetLibraryListing(ctx); state.LibraryUnlisted != 1 {
|
|
t.Errorf("unlisted = %d, want the local album alone", state.LibraryUnlisted)
|
|
}
|
|
|
|
// The local band's album: left off, it is not counted, and a newer
|
|
// matcher does not try it again.
|
|
if err := s.LeaveAlbumOff(ctx, local.ID); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if outcome, _, _, _ := matchOf(t, pool, local.ID); outcome != "dismissed" {
|
|
t.Errorf("left off = %s", outcome)
|
|
}
|
|
if state, _ := q.GetLibraryListing(ctx); state.LibraryUnlisted != 0 {
|
|
t.Errorf("unlisted = %d after leaving it off", state.LibraryUnlisted)
|
|
}
|
|
if _, err := pool.Exec(ctx, `UPDATE library_album_matches SET matcher = 0 WHERE album_id = $1`, local.ID); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := s.listLibrary(ctx, &PassResult{}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if outcome, _, _, _ := matchOf(t, pool, local.ID); outcome != "dismissed" {
|
|
t.Errorf("an album left off was matched again: %s", outcome)
|
|
}
|
|
rows, err := q.ListUnmatchedLibraryAlbums(ctx, 10)
|
|
if err != nil || len(rows) != 1 || rows[0].Outcome != "dismissed" || rows[0].Tracks != 1 {
|
|
t.Errorf("unmatched section = %+v, %v: the left-off album alone", rows, err)
|
|
}
|
|
|
|
// Put back, it is unmatched again; putting it back twice is not.
|
|
if err := s.PutAlbumBack(ctx, local.ID); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if outcome, _, _, _ := matchOf(t, pool, local.ID); outcome != "unmatched" {
|
|
t.Errorf("put back = %s", outcome)
|
|
}
|
|
if err := s.PutAlbumBack(ctx, local.ID); !errors.Is(err, ErrAlbumNotUnmatched) {
|
|
t.Errorf("put back twice: %v", err)
|
|
}
|
|
}
|
|
|
|
// A re-tag files a library track under a new album row. That album is the
|
|
// library's, not one dropped in by hand, so the listing matches it; the
|
|
// old row, its tracks gone, is no longer unmatched (#5579).
|
|
func TestListLibrary_MatchesARetaggedAlbum_Integration(t *testing.T) {
|
|
pool := newPool(t)
|
|
ctx := context.Background()
|
|
q := dbq.New(pool)
|
|
s := newService(t, pool, newFakeMB(t))
|
|
untagged := seedAlbum(t, q, blur, "Unknown Album", "", []string{"The Ballad", "St. Charles Square"})
|
|
if err := q.RecordAlbumMatch(ctx, dbq.RecordAlbumMatchParams{
|
|
AlbumID: untagged.ID, Outcome: "unmatched", Detail: "no match", Matcher: libraryMatcher}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, stmt := range []string{
|
|
`UPDATE albums SET created_at = now() - interval '2 hours' WHERE id = $1`,
|
|
`UPDATE tracks SET added_at = now() - interval '2 hours' WHERE album_id = $1`,
|
|
} {
|
|
if _, err := pool.Exec(ctx, stmt, untagged.ID); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
if _, err := pool.Exec(ctx, `UPDATE acquisition_settings SET library_listing_started_at = now() - interval '1 hour',
|
|
library_listed_at = now() - interval '1 hour', library_unlisted = 1`); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
// The re-tag: the same tracks, under the album their new tags name.
|
|
retagged, err := q.UpsertAlbum(ctx, dbq.UpsertAlbumParams{Title: "The Ballad of Darren", SortTitle: "The Ballad of Darren", ArtistID: untagged.ArtistID})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := pool.Exec(ctx, `UPDATE tracks SET album_id = $1, updated_at = now() WHERE album_id = $2`, retagged.ID, untagged.ID); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
if err := s.listLibrary(ctx, &PassResult{}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if outcome, method, group, _ := matchOf(t, pool, retagged.ID); outcome != "listed" || method != matchDiscography || group != theBallad {
|
|
t.Errorf("the re-tagged album was decided %s by %s as %s", outcome, method, group)
|
|
}
|
|
if rows, _ := q.ListUnmatchedLibraryAlbums(ctx, 10); len(rows) != 0 {
|
|
t.Errorf("unmatched = %+v: the old row has no tracks left", rows)
|
|
}
|
|
if state, _ := q.GetLibraryListing(ctx); state.LibraryUnlisted != 0 {
|
|
t.Errorf("unlisted = %d", state.LibraryUnlisted)
|
|
}
|
|
}
|