From 42e7316f2cd9393665b194eebd480f6ee6340a5f Mon Sep 17 00:00:00 2001 From: dawn <90008@klbr.net> Date: Wed, 7 Oct 2026 00:35:07 +0300 Subject: [PATCH] [control] test a bulk resync with a repo row that has no metadata hydrant-2tg was filed when one db.repos row without repo_metadata failed a whole POST /repos/resync batch. 45396ea already fixed _resync to give such a row fresh tracked metadata and a pending entry, but its test only resyncs the orphan on its own. this adds the bead's acceptance case: two tracked repos sitting in resync after a failed backfill plus a relay style row without metadata, resynced in one batch. all three get queued, the orphan gets tracked metadata whose index_id names its pending entry, its row stays as it was, and repos, pending, resync and error_transport counts end at 3, 3, 0 and 0. break-check: with the old metadata lookup (bail on a missing row) the test fails with "repo metadata not found for did:web:listed.example" and none of the batch is queued. --- src/control/repos/indexer.rs | 82 ++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/src/control/repos/indexer.rs b/src/control/repos/indexer.rs index 03c6496..c59c929 100644 --- a/src/control/repos/indexer.rs +++ b/src/control/repos/indexer.rs @@ -1244,4 +1244,86 @@ mod tests { assert!(repos.track([did.clone()]).await?.is_empty()); assert_queued_without_touching_the_row(&state, &did, &row) } + + #[tokio::test] + async fn a_bulk_resync_queues_valid_repos_and_a_row_without_metadata() -> miette::Result<()> { + use crate::types::{ResyncErrorKind, ResyncState}; + let tmp = tempfile::tempdir().into_diagnostic()?; + let config = crate::config::Config { + database_path: tmp.path().to_path_buf(), + ..Default::default() + }; + let state = Arc::new(AppState::new(&config)?); + let db = &state.db; + let repos = ReposControl(state.clone()); + let valid = + ["did:web:one.example", "did:web:two.example"].map(|s| Did::new_static(s).unwrap()); + repos.track(valid.clone()).await?; + let error = rmp_serde::to_vec(&ResyncState::Error { + kind: ResyncErrorKind::Transport, + retry_count: 0, + next_retry: 0, + }) + .into_diagnostic()?; + for did in &valid { + let index_id = crate::db::deser_repo_meta( + &db.repo_metadata + .get(keys::repo_metadata_key(did)) + .into_diagnostic()? + .expect("tracked repo has metadata"), + )? + .index_id; + let mut txn = crate::db::Txn::new(db); + txn.hold_repo_write_lock(did); + db.indexer + .stage_pending_remove(&mut txn.batch, did, &keys::pending_key(index_id)); + txn.batch + .insert(&db.indexer.resync, keys::repo_key(did), &error); + txn.transition_lifecycle(did, GaugeState::Resync(Some(ResyncErrorKind::Transport)))?; + txn.commit()?; + } + let orphan = Did::new_static("did:web:listed.example").into_diagnostic()?; + let row = row_without_metadata(&state, &orphan)?; + assert_eq!(db.get_count_sync("resync"), 3); + assert_eq!(db.get_count_sync("error_transport"), 2); + + let batch = vec![valid[0].clone(), orphan.clone(), valid[1].clone()]; + assert_eq!(repos.resync(batch.clone()).await?, batch); + + assert_eq!( + db.repos + .get(keys::repo_key(&orphan)) + .into_diagnostic()? + .as_ref(), + Some(&row), + "the orphan's row was written over" + ); + for did in &batch { + let metadata = crate::db::deser_repo_meta( + &db.repo_metadata + .get(keys::repo_metadata_key(did)) + .into_diagnostic()? + .expect("queued repo has metadata"), + )?; + assert!(metadata.tracked); + assert!( + db.indexer + .is_pending(&keys::pending_key(metadata.index_id)) + .into_diagnostic()? + ); + assert!( + !db.indexer + .resync + .contains_key(keys::repo_key(did)) + .into_diagnostic()? + ); + } + assert_eq!(db.indexer.pending_iter().count(), 3); + db.indexer.assert_pending_dids_match(); + assert_eq!(db.get_count_sync("repos"), 3); + assert_eq!(db.get_count_sync("pending"), 3); + assert_eq!(db.get_count_sync("resync"), 0); + assert_eq!(db.get_count_sync("error_transport"), 0); + Ok(()) + } } -- 2.51.2