@@ -326,27 +326,28 @@ mod tests {
326326 // sorts past the first cursor, so an unqualified OR name predicate
327327 // would incorrectly serve it again on the next page.
328328 //
329- // Rows are inserted out of order and assigned IDs that invert name order
330- // within each owner, so neither insertion order / created_at nor d.id can
331- // satisfy the asserted (owner, name) ordering. Sized to 6 entries so the
332- // last page is full-sized (limit 2), proving hasNextPage distinguishes a
333- // full terminal page from a page with remaining rows.
334- db. create_repo ( & repo ( "r1" , owner2, "d-repo" , true ) )
329+ // Rows are inserted out of order and not in reverse asserted order.
330+ // Assigned IDs do not track name order, and owner2 contains both
331+ // "B-repo" and "a-repo" whose byte order (B < a) differs from their
332+ // case-folded order (a < b), ensuring lower(d.name) cannot satisfy
333+ // the keyset query. Sized to 6 entries so the last page is full-sized
334+ // (limit 2), proving hasNextPage distinguishes a full terminal page.
335+ db. create_repo ( & repo ( "r5" , owner2, "c-repo" , true ) )
335336 . await
336337 . unwrap ( ) ;
337- db. create_repo ( & repo ( "r6 " , owner2 , "c -repo" , true ) )
338+ db. create_repo ( & repo ( "r9 " , earlier_owner , "zz -repo" , true ) )
338339 . await
339340 . unwrap ( ) ;
340- db. create_repo ( & repo ( "r7 " , owner2, "a -repo" , true ) )
341+ db. create_repo ( & repo ( "r3 " , owner2, "B -repo" , true ) )
341342 . await
342343 . unwrap ( ) ;
343- db. create_repo ( & repo ( "r2 " , owner1, "z-repo" , true ) )
344+ db. create_repo ( & repo ( "r8 " , owner1, "z-repo" , true ) )
344345 . await
345346 . unwrap ( ) ;
346- db. create_repo ( & repo ( "r8 " , owner1, "b-repo" , true ) )
347+ db. create_repo ( & repo ( "r2 " , owner1, "b-repo" , true ) )
347348 . await
348349 . unwrap ( ) ;
349- db. create_repo ( & repo ( "r9 " , earlier_owner , "zz -repo" , true ) )
350+ db. create_repo ( & repo ( "r1 " , owner2 , "a -repo" , true ) )
350351 . await
351352 . unwrap ( ) ;
352353
@@ -379,7 +380,7 @@ mod tests {
379380 p2[ "nodes" ] ,
380381 serde_json:: json!( [
381382 { "name" : "z-repo" , "ownerDid" : owner1} ,
382- { "name" : "a -repo" , "ownerDid" : owner2} ,
383+ { "name" : "B -repo" , "ownerDid" : owner2} ,
383384 ] )
384385 ) ;
385386 let cursor = p2[ "endCursor" ] . as_str ( ) . unwrap ( ) ;
@@ -393,12 +394,45 @@ mod tests {
393394 assert_eq ! (
394395 p3[ "nodes" ] ,
395396 serde_json:: json!( [
397+ { "name" : "a-repo" , "ownerDid" : owner2} ,
396398 { "name" : "c-repo" , "ownerDid" : owner2} ,
397- { "name" : "d-repo" , "ownerDid" : owner2} ,
398399 ] )
399400 ) ;
400401 }
401402
403+ #[ sqlx:: test]
404+ async fn repos_page_at_documented_maximum_distinguishes_terminal_page ( pool : PgPool ) {
405+ let db = db ( pool) . await ;
406+ let total = crate :: db:: MAX_VISIBLE_REPO_PAGE_SIZE ;
407+ for index in 0 ..total {
408+ let name = format ! ( "repo-{index:03}" ) ;
409+ db. create_repo ( & repo ( & name, OWNER , & name, true ) )
410+ . await
411+ . unwrap ( ) ;
412+ }
413+ let schema = schema ( db. clone ( ) ) ;
414+ let query =
415+ format ! ( "{{ reposPage(limit: {total}) {{ nodes {{ name }} hasNextPage endCursor }} }}" ) ;
416+ let resp200 = anon ( & schema, & query) . await ;
417+ assert ! ( resp200. errors. is_empty( ) , "{:?}" , resp200. errors) ;
418+ let p200 = resp200. data . into_json ( ) . unwrap ( ) [ "reposPage" ] . clone ( ) ;
419+ assert_eq ! ( p200[ "hasNextPage" ] , false ) ;
420+ let nodes200 = p200[ "nodes" ] . as_array ( ) . unwrap ( ) . clone ( ) ;
421+ assert_eq ! ( nodes200. len( ) , total) ;
422+
423+ // Adding row 201 keeps page 1 content identical while flipping hasNextPage to true.
424+ let name201 = format ! ( "repo-{total:03}" ) ;
425+ db. create_repo ( & repo ( & name201, OWNER , & name201, true ) )
426+ . await
427+ . unwrap ( ) ;
428+ let resp201 = anon ( & schema, & query) . await ;
429+ assert ! ( resp201. errors. is_empty( ) , "{:?}" , resp201. errors) ;
430+ let p201 = resp201. data . into_json ( ) . unwrap ( ) [ "reposPage" ] . clone ( ) ;
431+ assert_eq ! ( p201[ "hasNextPage" ] , true ) ;
432+ assert_eq ! ( p201[ "nodes" ] , p200[ "nodes" ] ) ;
433+ assert_eq ! ( p201[ "endCursor" ] , p200[ "endCursor" ] ) ;
434+ }
435+
402436 #[ sqlx:: test]
403437 async fn repos_page_visibility_matches_the_shared_gate ( pool : PgPool ) {
404438 use crate :: db:: VisibilityMode ;
0 commit comments