Merge pull request 'fix(library): drop out-of-range release years instead of failing the album' (#12) from dev into main

This commit was merged in pull request #12.
This commit is contained in:
2026-04-20 04:22:18 +00:00
2 changed files with 48 additions and 5 deletions
+23 -5
View File
@@ -208,15 +208,33 @@ func (s *Scanner) resolveAlbum(ctx context.Context, q *dbq.Queries, artistID pgt
SortTitle: sortKey(title),
ArtistID: artistID,
}
if year > 0 {
params.ReleaseDate = pgtype.Date{
Time: time.Date(year, 1, 1, 0, 0, 0, 0, time.UTC),
Valid: true,
}
if d, ok := releaseDateFromYear(year); ok {
params.ReleaseDate = d
} else if year != 0 {
// year=0 is the no-tag case; anything else getting rejected is a tag
// we couldn't trust (typo, OOB number, …). Log it so users can chase
// down the file but don't fail the album insert over a soft field.
s.logger.Warn("library scan: dropping invalid release year",
"year", year, "album", title)
}
return q.UpsertAlbum(ctx, params)
}
// releaseDateFromYear converts a tag-supplied year into a Postgres date,
// returning ok=false if the year is outside what we'll accept. We're
// deliberately strict (1..9999): Postgres' date type goes much wider, but
// 5-digit years from ID3 tags are always garbage in practice and trigger
// SQLSTATE 22008 (datetime field overflow) at insert time.
func releaseDateFromYear(year int) (pgtype.Date, bool) {
if year < 1 || year > 9999 {
return pgtype.Date{}, false
}
return pgtype.Date{
Time: time.Date(year, 1, 1, 0, 0, 0, 0, time.UTC),
Valid: true,
}, true
}
// sortKey drops a leading "The " for sortable ordering. Non-English articles
// (Los, Die, Les) can be added if users ask — keeping the rule obvious for now.
func sortKey(s string) string {
+25
View File
@@ -16,6 +16,31 @@ import (
"git.fabledsword.com/bvandeusen/minstrel/internal/db/dbq"
)
func TestReleaseDateFromYear(t *testing.T) {
cases := []struct {
in int
ok bool
label string
}{
{0, false, "missing tag"},
{-1, false, "negative"},
{1, true, "lower bound"},
{1999, true, "ordinary"},
{9999, true, "upper bound"},
{10000, false, "five digits"},
{99999999, false, "tag corruption"},
}
for _, c := range cases {
got, ok := releaseDateFromYear(c.in)
if ok != c.ok {
t.Errorf("%s: ok = %v, want %v", c.label, ok, c.ok)
}
if ok && (!got.Valid || got.Time.Year() != c.in) {
t.Errorf("%s: date = %+v, want year=%d", c.label, got, c.in)
}
}
}
// TestScanner_Integration exercises walk → tag-parse → upsert → incremental
// skip against a real Postgres. Gated on MINSTREL_TEST_DATABASE_URL.
func TestScanner_Integration(t *testing.T) {