From 49001e9beba9e3dedfa52bd5b3862c278eb0f113 Mon Sep 17 00:00:00 2001 From: Edouard Vanbelle Date: Wed, 2 Sep 2026 22:18:43 +0200 Subject: [PATCH] test(blob,manifest_consistency): sanity test on repair --- .../services/blobs_consistency_service.rs | 36 +++++++++++++++++++ .../services/manifests_consistency_service.rs | 29 +++++++++++++++ 2 files changed, 65 insertions(+) diff --git a/src/infrastructure/services/blobs_consistency_service.rs b/src/infrastructure/services/blobs_consistency_service.rs index 036e9267..29287afc 100644 --- a/src/infrastructure/services/blobs_consistency_service.rs +++ b/src/infrastructure/services/blobs_consistency_service.rs @@ -569,4 +569,40 @@ mod tests { fn empty_registry_refuses_to_build_page_statement() { let _ = chunk_page_sql(&BlobReferenceRegistry::new()); } + + /// Golden test — the repair statement is assembled from the same + /// registry as `chunk_page_sql`, so pin it byte-for-byte too. If + /// the registry ever changes what it produces at + /// `RefLevel::Chunk`, BOTH this test and + /// `chunk_page_statement_is_stable` above break together — an + /// operator using `?repair=true` shouldn't see the detection + /// formula report drift the repair formula can't clear. + /// + /// Ships the two-term formula (`storage.files` legacy-path count + + /// `storage.chunk_manifests` chunk-membership count) twice — once + /// in SET, once in the `<>` guard. Both must stay identical so the + /// guard is meaningful. + #[tokio::test] + async fn chunk_repair_statement_is_stable() { + let sql = chunk_repair_sql(&default_registry()); + let expected = r#"UPDATE storage.blobs b + SET ref_count = ((SELECT COUNT(*) FROM storage.files cnt_f + WHERE cnt_f.blob_hash = b.hash + AND NOT EXISTS ( + SELECT 1 FROM storage.chunk_manifests cnt_m + WHERE cnt_m.file_hash = cnt_f.blob_hash + )) + + (SELECT COUNT(*) FROM storage.chunk_manifests cnt_m + WHERE b.hash = ANY(cnt_m.chunk_hashes)))::bigint + WHERE b.hash = $1 + AND b.ref_count <> ((SELECT COUNT(*) FROM storage.files cnt_f + WHERE cnt_f.blob_hash = b.hash + AND NOT EXISTS ( + SELECT 1 FROM storage.chunk_manifests cnt_m + WHERE cnt_m.file_hash = cnt_f.blob_hash + )) + + (SELECT COUNT(*) FROM storage.chunk_manifests cnt_m + WHERE b.hash = ANY(cnt_m.chunk_hashes)))::bigint"#; + assert_eq!(sql, expected, "chunk repair statement changed:\n{sql}"); + } } diff --git a/src/infrastructure/services/manifests_consistency_service.rs b/src/infrastructure/services/manifests_consistency_service.rs index 4eeb0310..ddd9fe08 100644 --- a/src/infrastructure/services/manifests_consistency_service.rs +++ b/src/infrastructure/services/manifests_consistency_service.rs @@ -534,4 +534,33 @@ mod tests { fn empty_registry_refuses_to_build_page_statement() { let _ = manifest_page_sql(&BlobReferenceRegistry::new()); } + + /// Golden test — the repair statement is assembled from the same + /// registry as `manifest_page_sql`, so pin it byte-for-byte too. + /// If the registry ever changes what it produces at + /// `RefLevel::Manifest`, BOTH this test and + /// `manifest_page_statement_is_stable` above break together — an + /// operator using `?repair=true` shouldn't see the detection + /// formula report drift the repair formula can't clear. + /// + /// Ships the three-term formula (`storage.files` + + /// `storage.content_derived_blobs` + `storage.file_attached_blobs`) + /// twice — once in SET, once in the `<>` guard. Both must stay + /// identical so the guard is meaningful (else the UPDATE would fire + /// on drift the SET doesn't fix). + #[tokio::test] + async fn manifest_repair_statement_is_stable() { + let sql = manifest_repair_sql(&default_registry()); + let expected = r#"UPDATE storage.chunk_manifests m + SET ref_count = ((SELECT COUNT(*) FROM storage.files cnt_f + WHERE cnt_f.blob_hash = m.file_hash) + + (SELECT COUNT(*) FROM storage.content_derived_blobs cnt_d WHERE cnt_d.blob_hash = m.file_hash) + + (SELECT COUNT(*) FROM storage.file_attached_blobs cnt_a WHERE cnt_a.blob_hash = m.file_hash))::bigint + WHERE m.file_hash = $1 + AND m.ref_count <> ((SELECT COUNT(*) FROM storage.files cnt_f + WHERE cnt_f.blob_hash = m.file_hash) + + (SELECT COUNT(*) FROM storage.content_derived_blobs cnt_d WHERE cnt_d.blob_hash = m.file_hash) + + (SELECT COUNT(*) FROM storage.file_attached_blobs cnt_a WHERE cnt_a.blob_hash = m.file_hash))::bigint"#; + assert_eq!(sql, expected, "manifest repair statement changed:\n{sql}"); + } }