test(heal): reject failed object repair receipts

Cover the receipt consumer path where a storage repair result carries both an error and a matching positive receipt. The failure may be recorded, but the receipt must not create repaired, healthy, or absent proof.

Co-Authored-By: heihutu <heihutu@gmail.com>

Co-Authored-By: zhi22915 <qiuzgang@gmail.com>
This commit is contained in:
houseme
2026-09-08 14:20:12 +08:00
parent 8d339da706
commit 084338add6
+38
View File
@@ -1365,6 +1365,44 @@ async fn cancelled_object_heal_rejects_matching_positive_storage_receipt() {
assert!(outcome.objects.is_empty());
}
#[tokio::test]
async fn failed_object_heal_rejects_matching_positive_storage_receipt() {
let incarnation = Uuid::new_v4();
let storage = Arc::new(MockStorage {
heal_object_outcome: Mutex::new(Some(MockHealObjectOutcome::OkWithOtherError("commit failed"))),
heal_object_receipts: Mutex::new(HashMap::from([(
"object-a".to_string(),
VecDeque::from([object_receipt(
"object-a",
Some("version-a"),
HealObjectDisposition::Repaired,
incarnation,
)]),
)])),
bucket_incarnation_id: Mutex::new(Some(incarnation)),
..Default::default()
});
let task = HealTask::from_request(
HealRequest::object("bucket-a".to_string(), "object-a".to_string(), Some("version-a".to_string())),
storage,
);
let result = task.execute().await;
let outcome = task.get_outcome().await;
assert!(result.is_err());
assert_eq!(outcome.counters.healed, 0);
assert_eq!(outcome.counters.unchanged, 0);
assert!(outcome.objects.iter().all(|object| {
!matches!(
object.disposition,
HealObjectDisposition::Repaired
| HealObjectDisposition::VerifiedHealthy
| HealObjectDisposition::AuthoritativelyAbsent
)
}));
}
#[tokio::test]
async fn object_heal_latches_expected_incarnation_before_repair() {
let original_incarnation = Uuid::new_v4();