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>
92 lines
4.0 KiB
Go
92 lines
4.0 KiB
Go
package api
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/jackc/pgx/v5/pgtype"
|
|
|
|
"git.fabledsword.com/bvandeusen/minstrel/internal/db/dbq"
|
|
)
|
|
|
|
func dupUUID(b byte) pgtype.UUID {
|
|
var u pgtype.UUID
|
|
u.Bytes[15] = b
|
|
u.Valid = true
|
|
return u
|
|
}
|
|
|
|
func dupTS(t time.Time) pgtype.Timestamptz { return pgtype.Timestamptz{Time: t, Valid: true} }
|
|
|
|
// Rows arrive one per member, members of a group together. The fold must keep
|
|
// groups apart, keep the query's order, and propose each group's survivor from
|
|
// its own members only.
|
|
func TestFoldDuplicateGroups(t *testing.T) {
|
|
older := time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC)
|
|
newer := older.Add(48 * time.Hour)
|
|
ber := float32(0.04)
|
|
rows := []dbq.ListPendingDuplicateGroupMembersRow{
|
|
// Group 1: identical audio, sizes tie, the older copy should be kept.
|
|
{GroupID: dupUUID(1), Tier: "exact", DetectedAt: dupTS(newer), TrackID: dupUUID(10),
|
|
Title: "WWW", FileFormat: "mp3", FileSize: 6_900_000, DurationMs: 215_400, AddedAt: dupTS(newer), PlayCount: 3},
|
|
{GroupID: dupUUID(1), Tier: "exact", DetectedAt: dupTS(newer), TrackID: dupUUID(11),
|
|
Title: "WWW", FileFormat: "mp3", FileSize: 6_900_000, DurationMs: 215_400, AddedAt: dupTS(older), LikeCount: 1},
|
|
// Group 2: the same recording, FLAC against MP3.
|
|
{GroupID: dupUUID(2), Tier: "acoustic", WorstBitErrorRate: &ber, DetectedAt: dupTS(older), TrackID: dupUUID(20),
|
|
Title: "Lovesick", FileFormat: "mp3", FileSize: 9_000_000, DurationMs: 198_000, AddedAt: dupTS(older)},
|
|
{GroupID: dupUUID(2), Tier: "acoustic", WorstBitErrorRate: &ber, DetectedAt: dupTS(older), TrackID: dupUUID(21),
|
|
Title: "Lovesick", FileFormat: "flac", FileSize: 30_000_000, DurationMs: 198_000, AddedAt: dupTS(newer)},
|
|
}
|
|
|
|
got := foldDuplicateGroups(rows)
|
|
if len(got) != 2 {
|
|
t.Fatalf("folded %d groups, want 2", len(got))
|
|
}
|
|
|
|
g1, g2 := got[0], got[1]
|
|
if g1.ID != uuidToString(dupUUID(1)) || len(g1.Members) != 2 || g1.WorstBitErrorRate != nil {
|
|
t.Fatalf("group 1 = %+v, want the exact pair with no score", g1)
|
|
}
|
|
if g1.SurvivorTrackID != uuidToString(dupUUID(11)) || g1.SurvivorReason != "in the library longest" {
|
|
t.Errorf("group 1 survivor = (%s, %q), want the older copy", g1.SurvivorTrackID, g1.SurvivorReason)
|
|
}
|
|
if g1.Members[0].DurationSec != 215 || g1.Members[0].PlayCount != 3 || g1.Members[1].LikeCount != 1 {
|
|
t.Errorf("group 1 members lost their facts: %+v", g1.Members)
|
|
}
|
|
|
|
if g2.Tier != "acoustic" || g2.WorstBitErrorRate == nil || *g2.WorstBitErrorRate != ber {
|
|
t.Fatalf("group 2 = %+v, want the acoustic pair with its score", g2)
|
|
}
|
|
// Chosen from group 2's own members: a survivor leaking across groups is
|
|
// exactly what a wrong fold boundary would produce.
|
|
if g2.SurvivorTrackID != uuidToString(dupUUID(21)) || g2.SurvivorReason != "lossless (flac)" {
|
|
t.Errorf("group 2 survivor = (%s, %q), want the FLAC copy", g2.SurvivorTrackID, g2.SurvivorReason)
|
|
}
|
|
}
|
|
|
|
// M498: the proposal shown beside a group follows the resolver's rule. The copy
|
|
// whose tags fit the album is proposed even when the other is larger (only a
|
|
// lossless copy outranks it).
|
|
func TestFoldDuplicateGroups_ProposesTheCopyWhoseTagsFit(t *testing.T) {
|
|
at := time.Date(2025, 1, 1, 0, 0, 0, 0, time.UTC)
|
|
class := "same_release"
|
|
three := int32(3)
|
|
rows := []dbq.ListPendingDuplicateGroupMembersRow{
|
|
{GroupID: dupUUID(3), Tier: "acoustic", DetectedAt: dupTS(at), TrackID: dupUUID(30), Class: &class,
|
|
Title: "Song", FileFormat: "m4a", FileSize: 30_000_000, AddedAt: dupTS(at)},
|
|
{GroupID: dupUUID(3), Tier: "acoustic", DetectedAt: dupTS(at), TrackID: dupUUID(31), Class: &class,
|
|
Title: "Song", FileFormat: "mp3", FileSize: 9_000_000, AddedAt: dupTS(at), TrackNumber: &three, HasMbid: true},
|
|
}
|
|
got := foldDuplicateGroups(rows)
|
|
if len(got) != 1 {
|
|
t.Fatalf("folded %d groups, want 1", len(got))
|
|
}
|
|
g := got[0]
|
|
if g.SurvivorTrackID != uuidToString(dupUUID(31)) || g.SurvivorReason != "tags fit the album" {
|
|
t.Errorf("survivor = (%s, %q), want the tagged mp3", g.SurvivorTrackID, g.SurvivorReason)
|
|
}
|
|
if g.Class == nil || *g.Class != "same_release" {
|
|
t.Errorf("class not carried: %+v", g)
|
|
}
|
|
}
|