test(pds-server): listRepos-Test unabhängig von der Tabellengröße machen

sync_list_repos_includes_recent_user paginierte von vorn durch listRepos und
gab nach 50 Seiten à 50 Zeilen auf. Die repos-Tabelle der Dev-Instanz ist
inzwischen auf ~3800 Zeilen gewachsen, der frisch angelegte DID sortierte
dahinter — der Test war rot, obwohl der Endpoint korrekt antwortet
(manuell mit passendem Cursor verifiziert).

Der Cursor startet jetzt unmittelbar *vor* dem Ziel-DID. did_cursor_lt()
taugte dafür nicht: es dekrementiert das erste Byte und landet damit vor
jedem did:..., also wieder am Tabellenanfang.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013HC9HLrUU1LNwkzp8nkDLX
This commit is contained in:
tomdebone
2026-09-09 21:36:47 +02:00
co-authored by Claude Opus 5
parent 465a88e4e5
commit bce4c7862f
+25 -2
View File
@@ -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<String> = 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<String> = 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());