fix(filters): blank-typed browse 500 on id-valued fields (library/collection/added_by)
The browse-on-click blank sentinel was trimmed to empty (correct), but the typed query had already been bound unconditionally — the id-source suggestion query has no score CASE to reference it, leaving an unused parameter Postgres rejects (42P18). Every click of an empty library / collection / added-by box answered 'No matching values' via the error path. - ComposedFieldValues binds the typed query ONLY in branches that reference it and only when non-blank; the score expression is '0 AS score' for browse (no param) and the similarity CASE when typing. composedIDSourceValues takes the raw typed string and binds it only when the name filter is built. userBind already followed this discipline; audited every bind site in the file (fuzzy helpers, composed search, count) — all interpolate immediately. - Tests: library browse under rules (exactly the surviving library, count pinned), series browse blank, added_by browse blank (no error), collection browse blank (offered with count) and under a zero-match rule (never offered). Verified end-to-end against the live dev data: author+Sapkowski AND narrator+Kenny → Audiobooks (8).
This commit is contained in:
1 parent
9c006a9a2f
commit
ab713e2feb
2 files changed
+80
-14
No files matched your search
@@ -547,6 +547,58 @@ func TestFilterSuggestionsUnderRules(t *testing.T) {
|
||||
}
|
||||
}
|
||||
assert.True(t, found, "the visible library is offered")
|
||||
|
||||
// BROWSE the library field UNDER rules (the blank-typed click): the
|
||||
// id-source query must not choke on the unused typed parameter and
|
||||
// must list exactly the library holding the surviving books.
|
||||
status, results = suggest("library", " ", qfAll(qfRule("author", "contains", "Voss")))
|
||||
require.Equal(t, http.StatusOK, status, "library browse under rules (blank typed)")
|
||||
require.Len(t, results, 1, "exactly the fixture library survives the rules")
|
||||
assert.Equal(t, libUUID.String(), results[0]["value"])
|
||||
assert.InDelta(t, float64(2), results[0]["count"], 0.001, "both Voss books live there")
|
||||
|
||||
// Series browse (blank typed, under rules): full-membership source.
|
||||
status, results = suggest("series", " ", qfAll(qfRule("author", "contains", "Voss")))
|
||||
require.Equal(t, http.StatusOK, status)
|
||||
assert.Contains(t, valuesOf(results), "Voss Saga")
|
||||
|
||||
// Added-by browse (blank typed): books without added_by → an empty
|
||||
// menu, but NO error (the parameter-discipline regression net).
|
||||
status, results = suggest("added_by", " ", "")
|
||||
require.Equal(t, http.StatusOK, status)
|
||||
|
||||
// Collection browse (blank typed): the container sources ride the
|
||||
// same id-source query — a collection holding a surviving book is
|
||||
// offered with the surviving count; zero-match containers never are.
|
||||
userA := getRegularUserID(t, setup.DB)
|
||||
var collID pgtype.UUID
|
||||
err = setup.DBPool.QueryRow(ctx,
|
||||
`INSERT INTO collections (user_id, name) VALUES ($1, $2) RETURNING id`,
|
||||
pgtype.UUID{Bytes: [16]byte(userA), Valid: true},
|
||||
fmt.Sprintf("Test Builder Sug Coll %d", time.Now().UnixNano())).Scan(&collID)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() { _ = setup.DB.DeleteCollection(ctx, collID) })
|
||||
var tideID pgtype.UUID
|
||||
err = setup.DBPool.QueryRow(ctx, `SELECT id FROM media_items WHERE title = $1`, "Sug Tide").Scan(&tideID)
|
||||
require.NoError(t, err)
|
||||
_, err = setup.DBPool.Exec(ctx,
|
||||
`INSERT INTO collection_items (collection_id, media_item_id) VALUES ($1, $2)`, collID, tideID)
|
||||
require.NoError(t, err)
|
||||
|
||||
status, results = suggest("collection", " ", "")
|
||||
require.Equal(t, http.StatusOK, status)
|
||||
assert.True(t, func() bool {
|
||||
for _, r := range results {
|
||||
if r["value"] == collID.String() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}(), "the collection holding a book is offered on browse")
|
||||
|
||||
status, results = suggest("collection", " ", qfAll(qfRule("genre", "is", "spacewestern")))
|
||||
require.Equal(t, http.StatusOK, status)
|
||||
assert.Empty(t, results, "zero-match containers never offered")
|
||||
}
|
||||
|
||||
func TestFilterFamilyLens(t *testing.T) {
|
||||
|
||||
Reference in new issue
Block a user