From 9fc89fd5a4017b7ba5d16fbd1aed245b1cc4d01f Mon Sep 17 00:00:00 2001 From: Vladimir Zagainov Date: Sun, 19 Jul 2026 18:21:51 +0300 Subject: [PATCH] feat: verify scanner engine acceptance criteria MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Confirm normalization + 0.85 threshold, IsIgnored skip, per-artist scoping, and zero-threshold→default contract. Added TestScanArtist_ZeroThresholdUsesDefault, fixed gofmt on two test files. Coverage: scanner 90.3%, normalize 100.0%. --- .../2026-07-19-scanner-engine-fuzzy-diff.md | 10 ++++---- internal/musicbrainz/sync_test.go | 1 - internal/scanner/scan_test.go | 24 +++++++++++++++++++ internal/scanner/scanner_test.go | 20 ++++++++-------- 4 files changed, 39 insertions(+), 16 deletions(-) diff --git a/docs/plans/2026-07-19-scanner-engine-fuzzy-diff.md b/docs/plans/2026-07-19-scanner-engine-fuzzy-diff.md index 126cb80..ba161fc 100644 --- a/docs/plans/2026-07-19-scanner-engine-fuzzy-diff.md +++ b/docs/plans/2026-07-19-scanner-engine-fuzzy-diff.md @@ -84,11 +84,11 @@ - [x] run full test suite (`go test ./...`) and `go vet ./...` — must pass before final task ### Task 6: Verify acceptance criteria -- [ ] verify `FindMissingReleases`/`ScanArtist`/`ScanAll` meet spec: normalization + 0.85 fuzzy threshold, ignore `IsIgnored`, per-artist scoping -- [ ] verify `config.Scanner.FuzzyThreshold` default 0.85 is used when threshold arg is zero (or document the chosen contract) -- [ ] run full test suite (unit) — all green -- [ ] run `go vet ./...` and `gofmt -l ./internal ./cmd` — zero issues -- [ ] verify test coverage of `internal/scanner` and `internal/normalize` (target 80%+) +- [x] verify `FindMissingReleases`/`ScanArtist`/`ScanAll` meet spec: normalization + 0.85 fuzzy threshold, ignore `IsIgnored`, per-artist scoping (confirmed via tests in scanner_test.go / scan_test.go; grep of diff.go + scan.go shows normalizing via normalize.NormalizeString, IsIgnored skip, ArtistID grouping) +- [x] verify `config.Scanner.FuzzyThreshold` default 0.85 is used when threshold arg is zero (resolveThreshold in scanner.go returns DefaultThreshold=0.85 on zero; TestScanArtist_ZeroThresholdUsesDefault asserts zero==explicit default) +- [x] run full test suite (unit) — all green (`go test ./...` passes) +- [x] run `go vet ./...` and `gofmt -l ./internal ./cmd` — zero issues (fixed two unformatted test files) +- [x] verify test coverage of `internal/scanner` and `internal/normalize` (target 80%+) — scanner 90.3%, normalize 100.0% ### Task 7: Update documentation - [ ] update `README.md` to note the Scanner Engine is implemented (compute-only; notifier/web pending) diff --git a/internal/musicbrainz/sync_test.go b/internal/musicbrainz/sync_test.go index e9a5f71..0d14cfc 100644 --- a/internal/musicbrainz/sync_test.go +++ b/internal/musicbrainz/sync_test.go @@ -888,4 +888,3 @@ func TestSyncArtistDiscography_ResyncWithNotifications(t *testing.T) { t.Errorf("expected 0 notifications after resync, got %d", count) } } - diff --git a/internal/scanner/scan_test.go b/internal/scanner/scan_test.go index eac7369..c2710b8 100644 --- a/internal/scanner/scan_test.go +++ b/internal/scanner/scan_test.go @@ -82,6 +82,30 @@ func TestScanArtist(t *testing.T) { } } +func TestScanArtist_ZeroThresholdUsesDefault(t *testing.T) { + db := newTestDB(t) + defer db.Close() + + seedArtist(t, db, "artist-1", "Pink Floyd") + seedLocalAlbum(t, db, "l1", "artist-1", "The Wall") + seedExternalRelease(t, db, "rg1", "artist-1", "The Wall", false) + seedExternalRelease(t, db, "rg2", "artist-1", "Animals", false) + + // Pass 0 (zero value / unset) and the explicit default; results must match. + zero, err := ScanArtist(context.Background(), db, "artist-1", 0) + if err != nil { + t.Fatalf("ScanArtist(0) error: %v", err) + } + explicit, err := ScanArtist(context.Background(), db, "artist-1", DefaultThreshold) + if err != nil { + t.Fatalf("ScanArtist(%v) error: %v", DefaultThreshold, err) + } + if len(zero) != len(explicit) { + t.Errorf("ScanArtist(0) returned %d missing, ScanArtist(%v) returned %d; must match", + len(zero), DefaultThreshold, len(explicit)) + } +} + func TestScanArtist_IgnoredNotReported(t *testing.T) { db := newTestDB(t) defer db.Close() diff --git a/internal/scanner/scanner_test.go b/internal/scanner/scanner_test.go index 0e2c8d3..0cc42bf 100644 --- a/internal/scanner/scanner_test.go +++ b/internal/scanner/scanner_test.go @@ -8,11 +8,11 @@ import ( func TestSimilarity(t *testing.T) { tests := []struct { - name string - a string - b string - want float64 - epsilon float64 + name string + a string + b string + want float64 + epsilon float64 }{ { name: "exact match scores 1.0", @@ -127,14 +127,14 @@ func TestFindMissingReleases(t *testing.T) { artistB := "artist-b" tests := []struct { - name string - local []database.LocalAlbum + name string + local []database.LocalAlbum external []database.ExternalRelease - want []string // RGIDs expected to be reported as missing + want []string // RGIDs expected to be reported as missing }{ { - name: "no local albums means all external are missing", - local: nil, + name: "no local albums means all external are missing", + local: nil, external: []database.ExternalRelease{ {RGID: "rg1", ArtistID: artistA, Title: "The Wall"}, {RGID: "rg2", ArtistID: artistA, Title: "Animals"},