release / govulncheck (push) Successful in 14s
release / web (push) Successful in 1m21s
release / go (push) Successful in 1m31s
release / android (push) Successful in 4m58s
release / integration (push) Successful in 5m0s
release / Build signed APK (releases and dev) (push) Successful in 5m7s
release / Attach APK to the Release (tag releases only) (push) Skipped
release / Build + push container image (push) Successful in 15s
release / Verify release artifacts (tag releases only) (push) Skipped
The first album-copy fold pass on 2026-10-20 removed five FLACs (Måneskin Rush!, Weezer 2016) in favour of mp3s whose only edge was a recording MBID, and an explicit "Suite-Pee" for its clean version. ProposeSurvivor now ranks lossless before tag fit; tag fit still outranks file size, which is what the Humanz rips needed. The fold keys copies by their version words (title and file name), so a clean, live or instrumental copy holding the same title tag is linked, not folded. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
163 lines
5.9 KiB
Go
163 lines
5.9 KiB
Go
package library
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func TestProposeSurvivor(t *testing.T) {
|
|
older := time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC)
|
|
newer := older.Add(24 * time.Hour)
|
|
cases := []struct {
|
|
name string
|
|
cands []SurvivorCandidate
|
|
wantID string
|
|
wantReason string
|
|
}{
|
|
{
|
|
// Lossless wins even against a much larger lossy file, and even
|
|
// when the lossy copy has been in the library longer.
|
|
name: "lossless beats larger and older",
|
|
cands: []SurvivorCandidate{
|
|
{TrackID: "mp3", FileFormat: "mp3", FileSize: 90_000_000, AddedAt: older},
|
|
{TrackID: "flac", FileFormat: "FLAC", FileSize: 30_000_000, AddedAt: newer},
|
|
},
|
|
wantID: "flac", wantReason: "lossless (flac)",
|
|
},
|
|
{
|
|
// m4a may be AAC; it must not outrank an mp3 just for being m4a.
|
|
name: "m4a is not treated as lossless",
|
|
cands: []SurvivorCandidate{
|
|
{TrackID: "m4a", FileFormat: "m4a", FileSize: 5_000_000, AddedAt: older},
|
|
{TrackID: "mp3", FileFormat: "mp3", FileSize: 9_000_000, AddedAt: newer},
|
|
},
|
|
wantID: "mp3", wantReason: "largest file",
|
|
},
|
|
{
|
|
name: "larger file wins among lossy copies",
|
|
cands: []SurvivorCandidate{
|
|
{TrackID: "128k", FileFormat: "mp3", FileSize: 3_400_000, AddedAt: older},
|
|
{TrackID: "320k", FileFormat: "mp3", FileSize: 8_600_000, AddedAt: newer},
|
|
},
|
|
wantID: "320k", wantReason: "largest file",
|
|
},
|
|
{
|
|
// The #3885 pair: identical audio, sizes equal but for the tags.
|
|
name: "the longest-standing copy wins when size ties",
|
|
cands: []SurvivorCandidate{
|
|
{TrackID: "www-02", FileFormat: "mp3", FileSize: 6_900_000, AddedAt: newer},
|
|
{TrackID: "www-01", FileFormat: "mp3", FileSize: 6_900_000, AddedAt: older},
|
|
},
|
|
wantID: "www-01", wantReason: "in the library longest",
|
|
},
|
|
{
|
|
name: "a full tie falls back to the lowest id, stably",
|
|
cands: []SurvivorCandidate{
|
|
{TrackID: "b", FileFormat: "mp3", FileSize: 1, AddedAt: older},
|
|
{TrackID: "a", FileFormat: "mp3", FileSize: 1, AddedAt: older},
|
|
},
|
|
wantID: "a", wantReason: "copies are otherwise identical",
|
|
},
|
|
{
|
|
name: "one copy",
|
|
cands: []SurvivorCandidate{{TrackID: "only", FileFormat: "mp3", FileSize: 1, AddedAt: older}},
|
|
wantID: "only", wantReason: "the only copy",
|
|
},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
id, reason := ProposeSurvivor(tc.cands)
|
|
if id != tc.wantID || reason != tc.wantReason {
|
|
t.Fatalf("ProposeSurvivor = (%q, %q), want (%q, %q)", id, reason, tc.wantID, tc.wantReason)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// The reason must name the rule that decided. Across three copies that is the
|
|
// comparison between first and second place, not the first rule any pair
|
|
// differs on: here the lossy copy differs from the others by format, but the
|
|
// two FLACs are separated by size.
|
|
func TestProposeSurvivor_ReasonIsTheDecidingRule(t *testing.T) {
|
|
at := time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC)
|
|
id, reason := ProposeSurvivor([]SurvivorCandidate{
|
|
{TrackID: "mp3", FileFormat: "mp3", FileSize: 99_000_000, AddedAt: at},
|
|
{TrackID: "flac-small", FileFormat: "flac", FileSize: 20_000_000, AddedAt: at},
|
|
{TrackID: "flac-big", FileFormat: "flac", FileSize: 40_000_000, AddedAt: at},
|
|
})
|
|
if id != "flac-big" || reason != "largest file" {
|
|
t.Fatalf("got (%q, %q), want (flac-big, largest file)", id, reason)
|
|
}
|
|
}
|
|
|
|
// M498 #5436: the copy whose tags fit the album outranks every quality rule but
|
|
// lossless, which decides first: a deleted FLAC cannot be recovered, and on
|
|
// 2026-10-20 five went for mp3s whose only edge was a recording MBID. Each tag
|
|
// fit case is built so file size would choose the other copy.
|
|
func TestProposeSurvivor_LosslessThenTagFit(t *testing.T) {
|
|
at := time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC)
|
|
cases := []struct {
|
|
name string
|
|
cands []SurvivorCandidate
|
|
wantID string
|
|
wantReason string
|
|
}{
|
|
{
|
|
name: "lossless beats tag fit",
|
|
cands: []SurvivorCandidate{
|
|
{TrackID: "fits", FileFormat: "mp3", FileSize: 5_000_000, AddedAt: at, TagFit: 3},
|
|
{TrackID: "flac", FileFormat: "flac", FileSize: 40_000_000, AddedAt: at, TagFit: 1},
|
|
},
|
|
wantID: "flac", wantReason: "lossless (flac)",
|
|
},
|
|
{
|
|
name: "tag fit beats a larger file",
|
|
cands: []SurvivorCandidate{
|
|
{TrackID: "big", FileFormat: "mp3", FileSize: 9_000_000, AddedAt: at, TagFit: 1},
|
|
{TrackID: "fits", FileFormat: "m4a", FileSize: 5_000_000, AddedAt: at, TagFit: 3},
|
|
},
|
|
wantID: "fits", wantReason: "tags fit the album",
|
|
},
|
|
{
|
|
// The Humanz shape: two rips a few bytes apart, one carrying a
|
|
// video marker and clashing with another track's position.
|
|
name: "tag fit beats a few bytes",
|
|
cands: []SurvivorCandidate{
|
|
{TrackID: "rip", FileFormat: "mp3", FileSize: 5_104_498, AddedAt: at, TagFit: 1},
|
|
{TrackID: "clean", FileFormat: "mp3", FileSize: 5_104_492, AddedAt: at, TagFit: 3},
|
|
},
|
|
wantID: "clean", wantReason: "tags fit the album",
|
|
},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
id, reason := ProposeSurvivor(tc.cands)
|
|
if id != tc.wantID || reason != tc.wantReason {
|
|
t.Fatalf("ProposeSurvivor = (%q, %q), want (%q, %q)", id, reason, tc.wantID, tc.wantReason)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestTagFitScore(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
hasTrackNumber bool
|
|
clash bool
|
|
path string
|
|
hasMBID bool
|
|
want int
|
|
}{
|
|
{"clean, numbered, identified", true, false, "/music/A/B/A - B - 01 - Song.flac", true, 3},
|
|
{"position taken by another track", true, true, "/music/A/B/A - B - 01 - Song.flac", true, 2},
|
|
{"no track number", false, false, "/music/A/B/Song.flac", true, 2},
|
|
{"video rip", true, false, "/music/A/B/A - B - 01 - Song (Official Video).mp3", true, 2},
|
|
{"rip with nothing else", false, false, "/music/A/B/Song (Official Video).mp3", false, 0},
|
|
}
|
|
for _, tc := range cases {
|
|
if got := TagFitScore(tc.hasTrackNumber, tc.clash, tc.path, tc.hasMBID); got != tc.want {
|
|
t.Errorf("%s: TagFitScore = %d, want %d", tc.name, got, tc.want)
|
|
}
|
|
}
|
|
}
|