diff --git a/internal/recommendation/candidate_tags_integration_test.go b/internal/recommendation/candidate_tags_integration_test.go index 373b4134..19cefbc8 100644 --- a/internal/recommendation/candidate_tags_integration_test.go +++ b/internal/recommendation/candidate_tags_integration_test.go @@ -399,23 +399,57 @@ func TestSuggestArtists_UntaggedCandidateSurvivesAlongsideTagged(t *testing.T) { seedCandidateTag(t, pool, "match", "shoegaze", 1.0) // "loud" is deliberately left with NO cached tags at all. - out, err := SuggestArtists(context.Background(), pool, user.ID, 30, 12, 1.0) + // Compared against the same request with the term disabled, rather than + // against a literal. The pool score is signal-weighted by the seed query + // (ln(1+signal) x similarity), so hardcoding a number here would assert + // against the seeding maths — a different layer — and break whenever that + // changes. The property under test is only that the blend leaves an + // untagged candidate alone. + blended, err := SuggestArtists(context.Background(), pool, user.ID, 30, 12, 1.0) if err != nil { - t.Fatalf("SuggestArtists: %v", err) + t.Fatalf("SuggestArtists(blended): %v", err) } - if len(out) != 2 { - t.Fatalf("len = %d, want 2 — the untagged candidate must still appear", len(out)) + baseline, err := SuggestArtists(context.Background(), pool, user.ID, 30, 12, 0) + if err != nil { + t.Fatalf("SuggestArtists(baseline): %v", err) } - var loud *ArtistSuggestion - for i := range out { - if out[i].MBID == "loud" { - loud = &out[i] + if len(blended) != 2 { + t.Fatalf("len = %d, want 2 — the untagged candidate must still appear", len(blended)) + } + + scoreOf := func(out []ArtistSuggestion, mbid string) (float64, bool) { + for _, s := range out { + if s.MBID == mbid { + return s.Score, true + } } + return 0, false } - if loud == nil { - t.Fatal("untagged candidate vanished from the deck") + want, ok := scoreOf(baseline, "loud") + if !ok { + t.Fatal("untagged candidate missing from the baseline deck") } - if loud.Score != 0.9 { - t.Errorf("untagged score = %v, want 0.9 unchanged", loud.Score) + got, ok := scoreOf(blended, "loud") + if !ok { + t.Fatal("untagged candidate vanished once the tag term was enabled") + } + if got != want { + t.Errorf("untagged score = %v, want %v (identical to the term-disabled run)", got, want) + } + // Sanity: the tagged candidate DID move, so the comparison above is + // meaningful rather than both runs being trivially identical. + if tagged, _ := scoreOf(blended, "match"); tagged == mustScore(t, baseline, "match") { + t.Error("the tagged candidate's score did not change — the term did nothing") } } + +func mustScore(t *testing.T, out []ArtistSuggestion, mbid string) float64 { + t.Helper() + for _, s := range out { + if s.MBID == mbid { + return s.Score + } + } + t.Fatalf("no candidate %q in %v", mbid, out) + return 0 +}