@@ -267,6 +267,8 @@ fn index_sorting() {
267
267
let ( app, anon, user) = TestApp :: init ( ) . with_user ( ) ;
268
268
let user = user. as_model ( ) ;
269
269
270
+ // To test that the unique ordering of seed-based pagination is correct, we need to
271
+ // set some columns to the same value.
270
272
app. db ( |conn| {
271
273
let krate1 = CrateBuilder :: new ( "foo_sort" , user. id )
272
274
. description ( "bar_sort baz_sort const" )
@@ -283,12 +285,12 @@ fn index_sorting() {
283
285
let krate3 = CrateBuilder :: new ( "baz_sort" , user. id )
284
286
. description ( "foo_sort bar_sort foo_sort bar_sort foo_sort bar_sort const" )
285
287
. downloads ( 100_000 )
286
- . recent_downloads ( 10 )
288
+ . recent_downloads ( 50 )
287
289
. expect_build ( conn) ;
288
290
289
291
let krate4 = CrateBuilder :: new ( "other_sort" , user. id )
290
292
. description ( "other_sort const" )
291
- . downloads ( 999_999 )
293
+ . downloads ( 100_000 )
292
294
. expect_build ( conn) ;
293
295
294
296
// Set the created at column for each crate
@@ -300,11 +302,7 @@ fn index_sorting() {
300
302
. set ( crates:: created_at. eq ( now - 1 . weeks ( ) ) )
301
303
. execute ( conn)
302
304
. 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] ) ) )
308
306
. set ( crates:: created_at. eq ( now - 3 . weeks ( ) ) )
309
307
. execute ( conn)
310
308
. unwrap ( ) ;
@@ -314,14 +312,10 @@ fn index_sorting() {
314
312
. set ( crates:: updated_at. eq ( now - 3 . weeks ( ) ) )
315
313
. execute ( conn)
316
314
. unwrap ( ) ;
317
- update ( & krate2)
315
+ update ( crates :: table . filter ( crates :: id . eq_any ( vec ! [ krate2. id , krate3 . id ] ) ) )
318
316
. set ( crates:: updated_at. eq ( now - 5 . days ( ) ) )
319
317
. execute ( conn)
320
318
. unwrap ( ) ;
321
- update ( & krate3)
322
- . set ( crates:: updated_at. eq ( now - 10 . seconds ( ) ) )
323
- . execute ( conn)
324
- . unwrap ( ) ;
325
319
update ( & krate4)
326
320
. set ( crates:: updated_at. eq ( now) )
327
321
. execute ( conn)
@@ -331,14 +325,14 @@ fn index_sorting() {
331
325
// Sort by downloads
332
326
for json in search_both ( & anon, "sort=downloads" ) {
333
327
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 " ) ;
336
330
assert_eq ! ( json. crates[ 2 ] . name, "bar_sort" ) ;
337
331
assert_eq ! ( json. crates[ 3 ] . name, "foo_sort" ) ;
338
332
}
339
333
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 " ) ;
342
336
assert_eq ! ( resp[ 2 ] . crates[ 0 ] . name, "bar_sort" ) ;
343
337
assert_eq ! ( resp[ 3 ] . crates[ 0 ] . name, "foo_sort" ) ;
344
338
assert_eq ! ( resp[ 3 ] . meta. total, 4 ) ;
@@ -364,14 +358,14 @@ fn index_sorting() {
364
358
for json in search_both ( & anon, "sort=recent-updates" ) {
365
359
assert_eq ! ( json. meta. total, 4 ) ;
366
360
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 " ) ;
369
363
assert_eq ! ( json. crates[ 3 ] . name, "foo_sort" ) ;
370
364
}
371
365
let ( resp, calls) = page_with_seek ( & anon, "sort=recent-updates" ) ;
372
366
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 " ) ;
375
369
assert_eq ! ( resp[ 3 ] . crates[ 0 ] . name, "foo_sort" ) ;
376
370
assert_eq ! ( resp[ 3 ] . meta. total, 4 ) ;
377
371
assert_eq ! ( calls, 5 ) ;
@@ -392,6 +386,82 @@ fn index_sorting() {
392
386
assert_eq ! ( resp[ 3 ] . meta. total, 4 ) ;
393
387
assert_eq ! ( calls, 5 ) ;
394
388
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
+
395
465
// Test for bug with showing null results first when sorting
396
466
// by descending downloads
397
467
for json in search_both ( & anon, "sort=recent-downloads" ) {
0 commit comments