sync from monorepo @ 2452e92e
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
#![cfg(feature = "test-utils")]
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use dirigent_archivist::backend::mock::MockBackend;
|
||||
use dirigent_archivist::backend::{ArchiveBackend, HealthStatus};
|
||||
use dirigent_archivist::coordinator::Archivist;
|
||||
use dirigent_archivist::registry::{ArchiveRegistration, FailureMode, WritePolicy};
|
||||
use dirigent_archivist::types::SessionMetadata;
|
||||
use uuid::Uuid;
|
||||
|
||||
fn reg(
|
||||
name: &str,
|
||||
backend: Arc<MockBackend>,
|
||||
priority: u32,
|
||||
failure: FailureMode,
|
||||
) -> Arc<ArchiveRegistration> {
|
||||
Arc::new(ArchiveRegistration::new(
|
||||
name.into(),
|
||||
"mock",
|
||||
backend as Arc<dyn ArchiveBackend>,
|
||||
true,
|
||||
failure,
|
||||
priority,
|
||||
true,
|
||||
WritePolicy::Inline,
|
||||
None,
|
||||
HealthStatus::Healthy,
|
||||
))
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn five_consecutive_failures_drifts_to_unavailable() {
|
||||
let primary = Arc::new(MockBackend::new());
|
||||
let secondary = Arc::new(MockBackend::new());
|
||||
secondary.inject_write_failures(10);
|
||||
|
||||
let archivist = Archivist::from_registrations(vec![
|
||||
reg("primary", primary.clone(), 0, FailureMode::Required),
|
||||
reg("secondary", secondary.clone(), 10, FailureMode::BestEffort),
|
||||
]);
|
||||
|
||||
for _ in 0..5 {
|
||||
archivist
|
||||
.append_messages(Uuid::new_v4(), vec![], None)
|
||||
.await
|
||||
.ok();
|
||||
}
|
||||
|
||||
let snapshot = archivist.list_archives_with_health().await;
|
||||
let secondary_status = snapshot.iter().find(|s| s.name == "secondary").unwrap();
|
||||
assert!(
|
||||
matches!(secondary_status.health, HealthStatus::Unavailable { .. }),
|
||||
"secondary should be Unavailable after 5 failures; got {:?}",
|
||||
secondary_status.health
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn success_after_failure_recovers_to_healthy() {
|
||||
let backend = Arc::new(MockBackend::new());
|
||||
backend.inject_write_failures(1);
|
||||
|
||||
let archivist = Archivist::from_registrations(vec![reg(
|
||||
"only",
|
||||
backend.clone(),
|
||||
0,
|
||||
FailureMode::Required,
|
||||
)]);
|
||||
|
||||
// First call fails.
|
||||
let _ = archivist
|
||||
.append_messages(Uuid::new_v4(), vec![], None)
|
||||
.await;
|
||||
let snapshot = archivist.list_archives_with_health().await;
|
||||
assert!(
|
||||
matches!(snapshot[0].health, HealthStatus::Degraded { .. }),
|
||||
"expected Degraded after first failure; got {:?}",
|
||||
snapshot[0].health
|
||||
);
|
||||
|
||||
// Second call succeeds — health returns to Healthy.
|
||||
archivist
|
||||
.append_messages(Uuid::new_v4(), vec![], None)
|
||||
.await
|
||||
.unwrap();
|
||||
let snapshot = archivist.list_archives_with_health().await;
|
||||
assert!(
|
||||
matches!(snapshot[0].health, HealthStatus::Healthy),
|
||||
"expected Healthy after recovery; got {:?}",
|
||||
snapshot[0].health
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn unavailable_backend_skipped_during_read_walk() {
|
||||
let primary = Arc::new(MockBackend::new());
|
||||
let secondary = Arc::new(MockBackend::new());
|
||||
|
||||
let scroll = Uuid::new_v4();
|
||||
secondary
|
||||
.put_session(SessionMetadata::stub(scroll))
|
||||
.await
|
||||
.unwrap();
|
||||
secondary.break_permanently("kaput");
|
||||
|
||||
let primary_reg = reg("primary", primary.clone(), 0, FailureMode::Required);
|
||||
let secondary_reg = reg("secondary", secondary.clone(), 10, FailureMode::Required);
|
||||
// Force secondary's cached health to Unavailable BEFORE the walk,
|
||||
// so the routing layer skips it entirely rather than attempting + failing.
|
||||
*secondary_reg.last_health.write().await = HealthStatus::Unavailable {
|
||||
reason: "test".into(),
|
||||
};
|
||||
|
||||
let archivist = Archivist::from_registrations(vec![primary_reg, secondary_reg]);
|
||||
|
||||
// Primary doesn't have the session; secondary has it but is marked Unavailable.
|
||||
// Read walk should skip secondary → Ok(None)-style ergonomics, bubbling up
|
||||
// as `SessionUnknown` per `get_session_metadata`'s contract.
|
||||
let result = archivist.get_session_metadata(scroll, None).await;
|
||||
assert!(
|
||||
result.is_err(),
|
||||
"expected SessionUnknown error when Unavailable backend is skipped"
|
||||
);
|
||||
assert!(matches!(
|
||||
result.unwrap_err(),
|
||||
dirigent_archivist::error::ArchivistError::SessionUnknown(_)
|
||||
));
|
||||
}
|
||||
Reference in New Issue
Block a user