Skip to content

Commit 9e2bbdf

Browse files
committed
tests/routes/crates/list: Ensure unique ordering of seek-based pagination is correct
1 parent d85475f commit 9e2bbdf

File tree

1 file changed

+90
-20
lines changed

1 file changed

+90
-20
lines changed

src/tests/routes/crates/list.rs

Lines changed: 90 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -267,6 +267,8 @@ fn index_sorting() {
267267
let (app, anon, user) = TestApp::init().with_user();
268268
let user = user.as_model();
269269

270+
// To test that the unique ordering of seed-based pagination is correct, we need to
271+
// set some columns to the same value.
270272
app.db(|conn| {
271273
let krate1 = CrateBuilder::new("foo_sort", user.id)
272274
.description("bar_sort baz_sort const")
@@ -283,12 +285,12 @@ fn index_sorting() {
283285
let krate3 = CrateBuilder::new("baz_sort", user.id)
284286
.description("foo_sort bar_sort foo_sort bar_sort foo_sort bar_sort const")
285287
.downloads(100_000)
286-
.recent_downloads(10)
288+
.recent_downloads(50)
287289
.expect_build(conn);
288290

289291
let krate4 = CrateBuilder::new("other_sort", user.id)
290292
.description("other_sort const")
291-
.downloads(999_999)
293+
.downloads(100_000)
292294
.expect_build(conn);
293295

294296
// Set the created at column for each crate
@@ -300,11 +302,7 @@ fn index_sorting() {
300302
.set(crates::created_at.eq(now - 1.weeks()))
301303
.execute(conn)
302304
.unwrap();
303-
update(&krate3)
304-
.set(crates::created_at.eq(now - 2.weeks()))
305-
.execute(conn)
306-
.unwrap();
307-
update(&krate4)
305+
update(crates::table.filter(crates::id.eq_any(vec![krate3.id, krate4.id])))
308306
.set(crates::created_at.eq(now - 3.weeks()))
309307
.execute(conn)
310308
.unwrap();
@@ -314,14 +312,10 @@ fn index_sorting() {
314312
.set(crates::updated_at.eq(now - 3.weeks()))
315313
.execute(conn)
316314
.unwrap();
317-
update(&krate2)
315+
update(crates::table.filter(crates::id.eq_any(vec![krate2.id, krate3.id])))
318316
.set(crates::updated_at.eq(now - 5.days()))
319317
.execute(conn)
320318
.unwrap();
321-
update(&krate3)
322-
.set(crates::updated_at.eq(now - 10.seconds()))
323-
.execute(conn)
324-
.unwrap();
325319
update(&krate4)
326320
.set(crates::updated_at.eq(now))
327321
.execute(conn)
@@ -331,14 +325,14 @@ fn index_sorting() {
331325
// Sort by downloads
332326
for json in search_both(&anon, "sort=downloads") {
333327
assert_eq!(json.meta.total, 4);
334-
assert_eq!(json.crates[0].name, "other_sort");
335-
assert_eq!(json.crates[1].name, "baz_sort");
328+
assert_eq!(json.crates[0].name, "baz_sort");
329+
assert_eq!(json.crates[1].name, "other_sort");
336330
assert_eq!(json.crates[2].name, "bar_sort");
337331
assert_eq!(json.crates[3].name, "foo_sort");
338332
}
339333
let (resp, calls) = page_with_seek(&anon, "sort=downloads");
340-
assert_eq!(resp[0].crates[0].name, "other_sort");
341-
assert_eq!(resp[1].crates[0].name, "baz_sort");
334+
assert_eq!(resp[0].crates[0].name, "baz_sort");
335+
assert_eq!(resp[1].crates[0].name, "other_sort");
342336
assert_eq!(resp[2].crates[0].name, "bar_sort");
343337
assert_eq!(resp[3].crates[0].name, "foo_sort");
344338
assert_eq!(resp[3].meta.total, 4);
@@ -364,14 +358,14 @@ fn index_sorting() {
364358
for json in search_both(&anon, "sort=recent-updates") {
365359
assert_eq!(json.meta.total, 4);
366360
assert_eq!(json.crates[0].name, "other_sort");
367-
assert_eq!(json.crates[1].name, "baz_sort");
368-
assert_eq!(json.crates[2].name, "bar_sort");
361+
assert_eq!(json.crates[1].name, "bar_sort");
362+
assert_eq!(json.crates[2].name, "baz_sort");
369363
assert_eq!(json.crates[3].name, "foo_sort");
370364
}
371365
let (resp, calls) = page_with_seek(&anon, "sort=recent-updates");
372366
assert_eq!(resp[0].crates[0].name, "other_sort");
373-
assert_eq!(resp[1].crates[0].name, "baz_sort");
374-
assert_eq!(resp[2].crates[0].name, "bar_sort");
367+
assert_eq!(resp[1].crates[0].name, "bar_sort");
368+
assert_eq!(resp[2].crates[0].name, "baz_sort");
375369
assert_eq!(resp[3].crates[0].name, "foo_sort");
376370
assert_eq!(resp[3].meta.total, 4);
377371
assert_eq!(calls, 5);
@@ -392,6 +386,82 @@ fn index_sorting() {
392386
assert_eq!(resp[3].meta.total, 4);
393387
assert_eq!(calls, 5);
394388

389+
use std::cmp::Reverse;
390+
391+
fn decode_seek<D: for<'a> serde::Deserialize<'a>>(seek: &str) -> anyhow::Result<D> {
392+
use base64::{engine::general_purpose, Engine};
393+
let decoded = serde_json::from_slice(&general_purpose::URL_SAFE_NO_PAD.decode(seek)?)?;
394+
Ok(decoded)
395+
}
396+
397+
// Sort by alpha with query
398+
for query in ["sort=alpha&q=bar_sort", "sort=alpha&q=sort"] {
399+
let (resp, calls) = page_with_seek(&anon, query);
400+
assert_eq!(calls, resp[0].meta.total + 1);
401+
let decoded_seeks = resp
402+
.iter()
403+
.filter_map(|cl| {
404+
cl.meta
405+
.next_page
406+
.as_ref()
407+
.map(|next_page| (next_page, cl.crates[0].name.to_owned()))
408+
})
409+
.filter_map(|(q, name)| {
410+
let query = url::form_urlencoded::parse(q.trim_start_matches('?').as_bytes())
411+
.into_owned()
412+
.collect::<indexmap::IndexMap<String, String>>();
413+
query.get("seek").map(|s| {
414+
let d = decode_seek::<(bool, i32)>(s).unwrap();
415+
(d.0, name)
416+
})
417+
})
418+
.collect::<Vec<_>>();
419+
// ordering (exact match desc, name asc)
420+
let mut sorted = decoded_seeks.to_vec();
421+
sorted.sort_by_key(|k| (Reverse(k.0), k.1.to_owned()));
422+
assert_eq!(sorted, decoded_seeks);
423+
for json in search_both(&anon, query) {
424+
assert_eq!(json.meta.total, resp[0].meta.total);
425+
for (c, r) in json.crates.iter().zip(&resp) {
426+
assert_eq!(c.name, r.crates[0].name);
427+
}
428+
}
429+
}
430+
431+
// Sort by relevance
432+
for query in ["q=foo_sort", "q=sort"] {
433+
let (resp, calls) = page_with_seek(&anon, query);
434+
assert_eq!(calls, resp[0].meta.total + 1);
435+
let decoded_seeks = resp
436+
.iter()
437+
.filter_map(|cl| {
438+
cl.meta
439+
.next_page
440+
.as_ref()
441+
.map(|next_page| (next_page, cl.crates[0].name.to_owned()))
442+
})
443+
.filter_map(|(q, name)| {
444+
let query = url::form_urlencoded::parse(q.trim_start_matches('?').as_bytes())
445+
.into_owned()
446+
.collect::<indexmap::IndexMap<String, String>>();
447+
query.get("seek").map(|s| {
448+
let d = decode_seek::<(bool, f32, i32)>(s).unwrap();
449+
(d.0, (d.1 * 1e12) as i64, name)
450+
})
451+
})
452+
.collect::<Vec<_>>();
453+
// ordering (exact match desc, rank desc, name asc)
454+
let mut sorted = decoded_seeks.clone();
455+
sorted.sort_by_key(|k| (Reverse(k.0), Reverse(k.1), k.2.to_owned()));
456+
assert_eq!(sorted, decoded_seeks);
457+
for json in search_both(&anon, query) {
458+
assert_eq!(json.meta.total, resp[0].meta.total);
459+
for (c, r) in json.crates.iter().zip(&resp) {
460+
assert_eq!(c.name, r.crates[0].name);
461+
}
462+
}
463+
}
464+
395465
// Test for bug with showing null results first when sorting
396466
// by descending downloads
397467
for json in search_both(&anon, "sort=recent-downloads") {

0 commit comments

Comments
 (0)