125 lines
3.7 KiB
Rust
125 lines
3.7 KiB
Rust
#![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 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,
|
|
))
|
|
}
|
|
|
|
fn sample_message(session: Uuid) -> dirigent_archivist::types::MessageRecord {
|
|
dirigent_archivist::types::MessageRecord {
|
|
version: 1,
|
|
message_id: Uuid::now_v7(),
|
|
session,
|
|
parent_id: None,
|
|
ts: chrono::Utc::now(),
|
|
role: "user".into(),
|
|
author: None,
|
|
content_md: "hi".into(),
|
|
content_parts: None,
|
|
attachments: vec![],
|
|
metadata: serde_json::Value::Null,
|
|
}
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn write_fans_out_to_both_backends() {
|
|
let a = Arc::new(MockBackend::new());
|
|
let b = Arc::new(MockBackend::new());
|
|
let archivist = Archivist::from_registrations(vec![
|
|
reg("a", a.clone(), 0, FailureMode::Required),
|
|
reg("b", b.clone(), 10, FailureMode::BestEffort),
|
|
]);
|
|
|
|
// Using a non-empty message vec for a robust positive-count check:
|
|
let scroll = Uuid::new_v4();
|
|
let m = sample_message(scroll);
|
|
archivist
|
|
.append_messages(scroll, vec![m], None)
|
|
.await
|
|
.unwrap();
|
|
assert_eq!(a.appended_count(scroll), 1);
|
|
assert_eq!(b.appended_count(scroll), 1);
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn best_effort_failure_does_not_propagate() {
|
|
let a = Arc::new(MockBackend::new());
|
|
let b = Arc::new(MockBackend::new());
|
|
b.inject_write_failures(1);
|
|
|
|
let archivist = Archivist::from_registrations(vec![
|
|
reg("a", a.clone(), 0, FailureMode::Required),
|
|
reg("b", b.clone(), 10, FailureMode::BestEffort),
|
|
]);
|
|
|
|
archivist
|
|
.append_messages(Uuid::new_v4(), vec![], None)
|
|
.await
|
|
.unwrap(); // Ok despite secondary failure
|
|
|
|
let snapshot = archivist.list_archives_with_health().await;
|
|
let b_status = snapshot.iter().find(|s| s.name == "b").unwrap();
|
|
assert!(matches!(b_status.health, HealthStatus::Degraded { .. }));
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn required_secondary_failure_propagates() {
|
|
let a = Arc::new(MockBackend::new());
|
|
let b = Arc::new(MockBackend::new());
|
|
b.inject_write_failures(1);
|
|
|
|
let archivist = Archivist::from_registrations(vec![
|
|
reg("a", a.clone(), 0, FailureMode::Required),
|
|
reg("b", b.clone(), 10, FailureMode::Required),
|
|
]);
|
|
|
|
let err = archivist
|
|
.append_messages(Uuid::new_v4(), vec![], None)
|
|
.await;
|
|
assert!(err.is_err(), "expected error when required secondary fails");
|
|
}
|
|
|
|
#[tokio::test]
|
|
async fn explicit_archive_overrides_default_primary() {
|
|
let a = Arc::new(MockBackend::new());
|
|
let b = Arc::new(MockBackend::new());
|
|
let archivist = Archivist::from_registrations(vec![
|
|
reg("a", a.clone(), 0, FailureMode::Required),
|
|
reg("b", b.clone(), 10, FailureMode::Required),
|
|
]);
|
|
|
|
let scroll = Uuid::new_v4();
|
|
let m = sample_message(scroll);
|
|
archivist
|
|
.append_messages(scroll, vec![m], Some("b".into()))
|
|
.await
|
|
.unwrap();
|
|
|
|
// Both receive the write: b is explicit primary, a is secondary via fanout.
|
|
assert_eq!(a.appended_count(scroll), 1);
|
|
assert_eq!(b.appended_count(scroll), 1);
|
|
}
|