release / govulncheck (push) Successful in 37s
release / web (push) Successful in 1m13s
release / go (push) Successful in 1m29s
release / integration (push) Successful in 4m46s
release / android (push) Successful in 5m36s
release / Build signed APK (releases and dev) (push) Successful in 5m54s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 6m48s
release / Verify release artifacts (tag releases only) (push) Skipped
Lidarr is off, and with it the resolver had stopped merging: it only merged when Lidarr had said which copies it mapped. Minstrel is now the only program writing the library, so the rule is about the copies: - A song held twice on one album: the extra copies merge into the one whose tags fit best (then lossless, larger, older). - Across releases or under another title: when every copy but one is a video rip (held back, or named like one and not marked fine), the rips merge into the one that is not. - Clean copies on different releases are linked as one song, as before. - Anything else waits for the operator. Gone with Lidarr: release switches and their settle window, the unmapped-file read, the rip search, the merge guard and its 409/503, lidarr.Client's release methods, and duplicate_group_members.lidarr_state (migration 0078). The duplicates page drops the per-copy Lidarr badge and the blocked-merge notice; past release changes stay in its history. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
62 lines
2.7 KiB
Go
62 lines
2.7 KiB
Go
package library
|
|
|
|
import "testing"
|
|
|
|
// The shapes measured on the operator's library (milestone 498's body).
|
|
func TestClassifyDuplicateGroup(t *testing.T) {
|
|
m := func(album, title string) ClassifyMember {
|
|
return ClassifyMember{AlbumID: album, Title: title, ArtistName: "Gorillaz"}
|
|
}
|
|
cases := []struct {
|
|
name string
|
|
tier string
|
|
members []ClassifyMember
|
|
want DuplicateClass
|
|
}{
|
|
{"Humanz: one album, one song twice", "exact",
|
|
[]ClassifyMember{m("humanz", "Saturnz Barz"), m("humanz", "Saturnz Barz (Official Video)")}, ClassSameRelease},
|
|
{"one album, video upload title with artist prefix", "acoustic",
|
|
[]ClassifyMember{m("humanz", "Andromeda"), m("humanz", "Gorillaz - Andromeda (Official Audio)")}, ClassSameRelease},
|
|
{"one album, identical bytes under different titles", "exact",
|
|
[]ClassifyMember{m("www", "WWW"), m("www", "WWW (instrumental)")}, ClassSameRelease},
|
|
{"one album, acoustic match, different recordings named", "acoustic",
|
|
[]ClassifyMember{m("www", "WWW"), m("www", "WWW (instrumental)")}, ClassReview},
|
|
{"Big Me: the single and the album", "exact",
|
|
[]ClassifyMember{m("single", "Big Me"), m("album", "Big Me")}, ClassCrossRelease},
|
|
{"remaster and featuring credits are one song", "acoustic",
|
|
[]ClassifyMember{m("a", "Feel Good Inc. (2017 Remaster)"), m("b", "Feel Good Inc feat. De La Soul")}, ClassCrossRelease},
|
|
{"Nervosa / Anorexia: identical bytes, different songs", "exact",
|
|
[]ClassifyMember{m("a", "Nervosa"), m("b", "Anorexia")}, ClassMismatch},
|
|
{"studio and live across albums", "acoustic",
|
|
[]ClassifyMember{m("studio", "Charger"), m("live", "Charger (live)")}, ClassReview},
|
|
{"a lone copy", "exact", []ClassifyMember{m("a", "Song")}, ClassReview},
|
|
}
|
|
for _, tc := range cases {
|
|
if got := ClassifyDuplicateGroup(tc.tier, tc.members); got != tc.want {
|
|
t.Errorf("%s: got %s, want %s", tc.name, got, tc.want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestMatchTitleKey(t *testing.T) {
|
|
cases := []struct{ title, artist, want string }{
|
|
{"Saturnz Barz (Official Video)", "Gorillaz", "saturnzbarz"},
|
|
{"Gorillaz - Saturnz Barz", "Gorillaz", "saturnzbarz"},
|
|
{"Doin' It Right (Music Video) ft. Panda Bear", "Daft Punk", "doinitright"},
|
|
{"Song [feat. X]", "", "song"},
|
|
{"Song - Remastered 2009", "", "song"},
|
|
{"Rock & Roll", "", "rockandroll"},
|
|
// What names a different recording stays.
|
|
{"Song (live)", "", "songlive"},
|
|
{"Song (demo)", "", "songdemo"},
|
|
{"Song (Remix)", "", "songremix"},
|
|
// A title that is nothing but a marker keeps its words rather than vanishing.
|
|
{"(Official Video)", "", "officialvideo"},
|
|
}
|
|
for _, tc := range cases {
|
|
if got := MatchTitleKey(tc.title, tc.artist); got != tc.want {
|
|
t.Errorf("MatchTitleKey(%q, %q) = %q, want %q", tc.title, tc.artist, got, tc.want)
|
|
}
|
|
}
|
|
}
|