fix: address code review findings

- Fix artist-ID namespace mismatch in MusicBrainz provider: SyncArtistDiscography
  now stores the canonical Navidrome artist ID (artist_settings.id) as
  external_releases.artist_id instead of the MusicBrainz MBID. Previously the
  MBID was stored, which violated the FK to artist_settings and broke the
  scanner join (local_albums.artist_id is the Navidrome ID), causing every
  external release to be falsely reported as missing and the sync insert to
  fail at runtime. getArtistFilterOptions now also resolves by the Navidrome ID.
- Resolve threshold in FindMissingReleases so the exported primitive honors the
  same zero-means-default contract as ScanArtist/ScanAll.
- Remove dead maxLen==0 guard in scanner.Similarity.
- Inline trivial buildPath helper; drop unused url import in client.go.
- Replace hand-rolled itoa with strconv.Itoa in tests.
- Rewrite SyncArtistDiscography tests to seed artist_settings with the Navidrome
  ID (tests previously seeded the MBID to mask the FK mismatch).
- Fix TestFuzzySmoke to exercise the real dependency (fuzzy.LevenshteinDistance /
  scanner.Similarity) instead of an unused API.
- Fix TestAppRun_ScanLogsMissingReleases to run the scan against a live context
  and assert the missing release is found.
- Document cached_at column in Specification.md and note startup scan / required
  musicbrainz.user_agent in README.
- Stop tracking .serena/ tooling config; add it to .gitignore.
This commit is contained in:
2026-07-19 18:41:18 +03:00
parent 06e09220ae
commit 8a5b58a817
14 changed files with 152 additions and 299 deletions

View File

@@ -4,42 +4,33 @@ import (
"testing"
"github.com/lithammer/fuzzysearch/fuzzy"
"naviwatcher/internal/scanner"
)
// TestFuzzySmoke verifies the fuzzysearch dependency is importable and that
// its ranking API behaves as the scanner engine will expect.
//
// Note: this library does NOT expose a `fuzzy.Ratio` (0-100) function as the
// plan's Technical Details assumed. The relevant signal here is RankMatch,
// which returns 0 for an exact match, a small positive distance for near
// matches, and -1 when source is not a subsequence of target. Task 3 will
// convert this into a normalized 0.0-1.0 similarity score.
// TestFuzzySmoke verifies the fuzzysearch dependency and the Levenshtein-based
// similarity primitive that the scanner engine actually uses (scanner.Similarity
// delegates to fuzzy.LevenshteinDistance). This guards against the library
// changing the distance semantics the engine relies on.
func TestFuzzySmoke(t *testing.T) {
// Exact match scores 0 (distance).
if got := fuzzy.RankMatch("the wall", "the wall"); got != 0 {
t.Errorf("expected RankMatch of identical strings to be 0, got %d", got)
// Identical strings: zero edit distance.
if d := fuzzy.LevenshteinDistance("the wall", "the wall"); d != 0 {
t.Errorf("expected LevenshteinDistance of identical strings to be 0, got %d", d)
}
// Similar strings score closer to 0 than dissimilar ones, and a real
// subsequence match returns a non-negative distance.
similar := fuzzy.RankMatch("the wall", "the wall remastered")
dissimilar := fuzzy.RankMatch("the wall", "completely different album")
if similar < 0 {
t.Errorf("expected similar to be a valid match (>=0), got %d", similar)
}
if dissimilar >= 0 {
t.Errorf("expected dissimilar to be a non-match (-1), got %d", dissimilar)
}
if dissimilar != -1 {
t.Errorf("expected dissimilar to be -1 (no subsequence match), got %d", dissimilar)
// A small edit (remaster suffix) is closer than a wholly different title.
near := fuzzy.LevenshteinDistance("the wall", "the wall remastered")
far := fuzzy.LevenshteinDistance("the wall", "completely different album")
if near >= far {
t.Errorf("expected near match distance (%d) < far match distance (%d)", near, far)
}
// A near match (valid, >=0) is preferable to a total miss (-1).
if similar < 0 {
t.Errorf("expected similar to be a valid match (>=0), got %d", similar)
// The scanner's similarity score should report the near match as more
// similar than the far one, and the identical pair as a perfect match.
if s := scanner.Similarity("the wall", "the wall"); s != 1.0 {
t.Errorf("expected Similarity of identical strings to be 1.0, got %f", s)
}
if dissimilar != -1 {
t.Errorf("expected dissimilar to be a non-match (-1), got %d", dissimilar)
if scanner.Similarity("the wall", "the wall remastered") <= scanner.Similarity("the wall", "completely different album") {
t.Error("expected near match to score higher than far match")
}
}