| 110 |
110 |
|
);
|
| 111 |
111 |
|
}
|
| 112 |
112 |
|
|
|
113 |
+ |
/// The relevance expression has always scored `similarity(description) * 0.5`,
|
|
114 |
+ |
/// but the match clause only looked at the title — an item whose description was
|
|
115 |
+ |
/// a perfect match ranked as if it matched and was then never returned.
|
|
116 |
+ |
#[tokio::test]
|
|
117 |
+ |
async fn search_finds_published_item_by_description() {
|
|
118 |
+ |
let mut h = TestHarness::new().await;
|
|
119 |
+ |
let (_creator, item_id) =
|
|
120 |
+ |
make_discoverable_item(&mut h, "desccreator", "Unrelated Title", "digital").await;
|
|
121 |
+ |
sqlx::query("UPDATE items SET description = 'A hand-built theremin kit' WHERE id = $1::uuid")
|
|
122 |
+ |
.bind(&item_id)
|
|
123 |
+ |
.execute(&h.db)
|
|
124 |
+ |
.await
|
|
125 |
+ |
.unwrap();
|
|
126 |
+ |
|
|
127 |
+ |
let resp = h.client.get("/discover?mode=items&q=theremin").await;
|
|
128 |
+ |
assert!(resp.status.is_success());
|
|
129 |
+ |
assert!(
|
|
130 |
+ |
resp.text.contains("Unrelated Title"),
|
|
131 |
+ |
"Search should match on description, not title only"
|
|
132 |
+ |
);
|
|
133 |
+ |
}
|
|
134 |
+ |
|
|
135 |
+ |
/// The 0.5 weighting on description similarity is the reason the relevance
|
|
136 |
+ |
/// expression is written the way it is. Widening the match clause must not
|
|
137 |
+ |
/// flatten that ordering.
|
|
138 |
+ |
#[tokio::test]
|
|
139 |
+ |
async fn title_match_outranks_description_only_match() {
|
|
140 |
+ |
let mut h = TestHarness::new().await;
|
|
141 |
+ |
let (_, desc_item) =
|
|
142 |
+ |
make_discoverable_item(&mut h, "rankdesc", "Something Else", "digital").await;
|
|
143 |
+ |
sqlx::query("UPDATE items SET description = 'mentions theremin in passing' WHERE id = $1::uuid")
|
|
144 |
+ |
.bind(&desc_item)
|
|
145 |
+ |
.execute(&h.db)
|
|
146 |
+ |
.await
|
|
147 |
+ |
.unwrap();
|
|
148 |
+ |
let (_, title_item) =
|
|
149 |
+ |
make_discoverable_item(&mut h, "ranktitle", "Theremin", "digital").await;
|
|
150 |
+ |
sqlx::query("UPDATE items SET description = 'no match here' WHERE id = $1::uuid")
|
|
151 |
+ |
.bind(&title_item)
|
|
152 |
+ |
.execute(&h.db)
|
|
153 |
+ |
.await
|
|
154 |
+ |
.unwrap();
|
|
155 |
+ |
|
|
156 |
+ |
// No explicit sort: a search term makes match_score DESC the default order.
|
|
157 |
+ |
let resp = h.client.get("/discover?mode=items&q=theremin").await;
|
|
158 |
+ |
assert!(resp.status.is_success());
|
|
159 |
+ |
let title_pos = resp.text.find("Theremin").expect("title match should be returned");
|
|
160 |
+ |
let desc_pos = resp.text.find("Something Else").expect("description match should be returned");
|
|
161 |
+ |
assert!(
|
|
162 |
+ |
title_pos < desc_pos,
|
|
163 |
+ |
"Title match must outrank a description-only match (title at {title_pos}, description at {desc_pos})"
|
|
164 |
+ |
);
|
|
165 |
+ |
}
|
|
166 |
+ |
|
| 113 |
167 |
|
#[tokio::test]
|
| 114 |
168 |
|
async fn search_does_not_leak_draft_items() {
|
| 115 |
169 |
|
let mut h = TestHarness::new().await;
|
| 385 |
439 |
|
/// `ai_tier` was applied to the result set but to none of the four facet
|
| 386 |
440 |
|
/// queries, so selecting "Handmade only" narrowed the listing while the sidebar
|
| 387 |
441 |
|
/// kept reporting counts for the unfiltered catalog.
|
|
442 |
+ |
/// Facet counts and results share `ITEM_SEARCH_CLAUSE` — that parity was the
|
|
443 |
+ |
/// point of commit a668df6f. Widening the clause to match descriptions must
|
|
444 |
+ |
/// therefore widen the counts by the same amount, or the sidebar would report a
|
|
445 |
+ |
/// zero next to a facet the results list is populating.
|
|
446 |
+ |
#[tokio::test]
|
|
447 |
+ |
async fn facet_counts_widen_with_description_matches() {
|
|
448 |
+ |
let mut h = TestHarness::new().await;
|
|
449 |
+ |
let (_, item_id) =
|
|
450 |
+ |
make_discoverable_item(&mut h, "facetdesc", "Nothing Relevant", "audio").await;
|
|
451 |
+ |
sqlx::query("UPDATE items SET description = 'a hand-built theremin kit' WHERE id = $1::uuid")
|
|
452 |
+ |
.bind(&item_id)
|
|
453 |
+ |
.execute(&h.db)
|
|
454 |
+ |
.await
|
|
455 |
+ |
.expect("set description");
|
|
456 |
+ |
|
|
457 |
+ |
let mut filters = filters_for(&[]);
|
|
458 |
+ |
let term = String::from("theremin");
|
|
459 |
+ |
filters.search = Some(&term);
|
|
460 |
+ |
let counts = makenotwork::db::discover::get_item_type_counts(&h.db, &filters)
|
|
461 |
+ |
.await
|
|
462 |
+ |
.expect("item type counts");
|
|
463 |
+ |
let total: i64 = counts.iter().map(|c| c.count).sum();
|
|
464 |
+ |
|
|
465 |
+ |
assert_eq!(
|
|
466 |
+ |
total, 1,
|
|
467 |
+ |
"facet counts must count a description-only match, matching the results query"
|
|
468 |
+ |
);
|
|
469 |
+ |
}
|
|
470 |
+ |
|
| 388 |
471 |
|
#[tokio::test]
|
| 389 |
472 |
|
async fn facet_counts_apply_the_ai_tier_filter() {
|
| 390 |
473 |
|
let mut h = TestHarness::new().await;
|