diff --git a/crates/pds-server/tests/pds_integration.rs b/crates/pds-server/tests/pds_integration.rs index 59308e8..7bf7c28 100644 --- a/crates/pds-server/tests/pds_integration.rs +++ b/crates/pds-server/tests/pds_integration.rs @@ -758,8 +758,14 @@ async fn sync_list_repos_includes_recent_user() { return; } let (c, did, _jwt, _cids) = fresh_user_with_records().await; - // Page through listRepos with a small limit until we see our DID. - let mut cursor: Option = None; + // Page through listRepos until we see our DID. Start the cursor + // immediately *below* the target rather than at the beginning of + // the table: `repos` grows without bound on a long-lived dev + // instance (a few thousand rows already), and scanning from the + // top made this test fail purely because the DID sorted past the + // iteration cap. Anchoring at the DID keeps the cursor round trip + // under test while staying independent of table size. + let mut cursor: Option = Some(did_cursor_just_before(&did)); let mut found = false; for _ in 0..50 { let url = match &cursor { @@ -1022,6 +1028,23 @@ fn urlencode(s: &str) -> String { .collect() } +/// The immediate keyset predecessor of `did`: the same string with +/// its last byte decremented. `listRepos` filters with `did > cursor`, +/// so paging from here puts `did` on the first page regardless of how +/// many repos precede it in the table. Unlike [`did_cursor_lt`] — which +/// decrements the *first* byte and therefore lands before every +/// `did:...` — this stays adjacent to the target. +/// +/// DIDs are ASCII (`did:plc:` + base32), so byte surgery is safe here. +fn did_cursor_just_before(did: &str) -> String { + let mut bytes = did.as_bytes().to_vec(); + match bytes.last_mut() { + Some(b) if *b > 0 => *b -= 1, + _ => return did.to_string(), + } + String::from_utf8(bytes).unwrap_or_else(|_| did.to_string()) +} + fn did_cursor_lt(did: &str) -> String { let bytes = did.as_bytes(); let mut prefix = Vec::with_capacity(bytes.len());