use super::*;
#[tokio::test]
async fn is_leader_gauge_is_one_while_leadership_held() {
let leadership = Arc::new(FakeLeadershipService::new(Some(
LeadershipEvent::StartedLeading,
)));
let platform_service = Arc::new(FakePlatformService::new(leadership));
let create_endpoint_calls = Arc::new(AtomicUsize::new(0));
let create_consumer_calls = Arc::new(AtomicUsize::new(0));
let metrics = Arc::new(RecordingMetricsCollector {
events: Mutex::new(Vec::new()),
});
let mut master = build_error_delegate_master_with_metrics(
platform_service,
Arc::clone(&create_endpoint_calls),
Arc::clone(&create_consumer_calls),
None,
0,
None,
30,
Arc::clone(&metrics) as Arc<dyn MetricsCollector>,
);
let (tx, mut rx) = tokio::sync::mpsc::channel(16);
let cancel = CancellationToken::new();
let ctx = ConsumerContext::new(tx, cancel.clone(), METRICS_TEST_ROUTE.to_string());
master.start(ctx).await.unwrap();
let first = timeout(Duration::from_secs(5), rx.recv())
.await
.unwrap()
.unwrap();
assert_eq!(first.exchange.input.body.as_text(), Some("ok"));
assert!(
await_counter_observations(&metrics, "master_leadership_transitions_total", 1).await,
"initial-snapshot acquisition should be recorded within 5s"
);
assert!(
await_leadership_gauge(&metrics, true).await,
"leadership gauge should read 1 within 5s of the acquire edge"
);
assert_eq!(
metrics.leadership_gauge(METRICS_TEST_LOCK),
Some(1.0),
"gauge must read 1 for the held lock"
);
cancel.cancel();
master.stop().await.unwrap();
}
#[tokio::test]
async fn is_leader_gauge_is_zero_after_leadership_lost() {
let leadership = Arc::new(FakeLeadershipService::new(Some(
LeadershipEvent::StartedLeading,
)));
let platform_service = Arc::new(FakePlatformService::new(leadership.clone()));
let create_endpoint_calls = Arc::new(AtomicUsize::new(0));
let create_consumer_calls = Arc::new(AtomicUsize::new(0));
let metrics = Arc::new(RecordingMetricsCollector {
events: Mutex::new(Vec::new()),
});
let mut master = build_error_delegate_master_with_metrics(
platform_service,
Arc::clone(&create_endpoint_calls),
Arc::clone(&create_consumer_calls),
None,
0,
None,
30,
Arc::clone(&metrics) as Arc<dyn MetricsCollector>,
);
let (tx, mut rx) = tokio::sync::mpsc::channel(16);
let cancel = CancellationToken::new();
let ctx = ConsumerContext::new(tx, cancel.clone(), METRICS_TEST_ROUTE.to_string());
master.start(ctx).await.unwrap();
let first = timeout(Duration::from_secs(5), rx.recv())
.await
.unwrap()
.unwrap();
assert_eq!(first.exchange.input.body.as_text(), Some("ok"));
assert!(
await_counter_observations(&metrics, "master_leadership_transitions_total", 1).await,
"initial acquisition should be recorded within 5s"
);
leadership.emit(LeadershipEvent::StoppedLeading).await;
assert!(
await_counter_observations(&metrics, "master_leadership_transitions_total", 2).await,
"lost transition should be recorded within 5s of the StoppedLeading delivery"
);
assert!(
await_leadership_gauge(&metrics, false).await,
"leadership gauge should read 0 within 5s of the lose edge"
);
assert_eq!(
metrics.leadership_gauge(METRICS_TEST_LOCK),
Some(0.0),
"gauge must read 0 after leadership is lost"
);
cancel.cancel();
master.stop().await.unwrap();
}
#[tokio::test]
async fn repeated_identical_delivery_does_not_reemit() {
let leadership = Arc::new(FakeLeadershipService::new(Some(
LeadershipEvent::StartedLeading,
)));
let platform_service = Arc::new(FakePlatformService::new(leadership.clone()));
let create_endpoint_calls = Arc::new(AtomicUsize::new(0));
let create_consumer_calls = Arc::new(AtomicUsize::new(0));
let metrics = Arc::new(RecordingMetricsCollector {
events: Mutex::new(Vec::new()),
});
let mut master = build_error_delegate_master_with_metrics(
platform_service,
Arc::clone(&create_endpoint_calls),
Arc::clone(&create_consumer_calls),
None,
0,
None,
30,
Arc::clone(&metrics) as Arc<dyn MetricsCollector>,
);
let (tx, mut rx) = tokio::sync::mpsc::channel(16);
let cancel = CancellationToken::new();
let ctx = ConsumerContext::new(tx, cancel.clone(), METRICS_TEST_ROUTE.to_string());
master.start(ctx).await.unwrap();
let first = timeout(Duration::from_secs(5), rx.recv())
.await
.unwrap()
.unwrap();
assert_eq!(first.exchange.input.body.as_text(), Some("ok"));
assert!(
await_counter_observations(&metrics, "master_leadership_transitions_total", 1).await,
"initial acquisition should be recorded within 5s"
);
let baseline_started = lifecycle_events(&metrics, "started");
let baseline_stopped = lifecycle_events(&metrics, "stopped");
assert_eq!(
baseline_started, 1,
"baseline delegate should have started exactly once"
);
assert_eq!(baseline_stopped, 0, "baseline should have no stops");
leadership.emit(LeadershipEvent::StartedLeading).await;
sleep(Duration::from_millis(500)).await;
assert_eq!(
create_consumer_calls.load(Ordering::SeqCst),
1,
"duplicate StartedLeading must not recreate the delegate"
);
assert_eq!(
lifecycle_events(&metrics, "started"),
baseline_started,
"duplicate StartedLeading must not emit a started lifecycle observation"
);
assert_eq!(
lifecycle_events(&metrics, "stopped"),
baseline_stopped,
"duplicate StartedLeading must not emit a stopped lifecycle observation"
);
let acquired_count = metrics
.counters_named("master_leadership_transitions_total")
.iter()
.filter(|(_, labels)| *labels == expected_transition_labels("acquired"))
.count();
assert_eq!(
acquired_count, 1,
"true→true delivery must not re-emit the acquired transition"
);
leadership.emit(LeadershipEvent::StoppedLeading).await;
assert!(
await_counter_observations(&metrics, "master_leadership_transitions_total", 2).await,
"lost transition should be recorded within 5s of the edge"
);
assert!(
await_counter_observations(
&metrics,
"master_delegate_lifecycle_total",
baseline_started + baseline_stopped + 1
)
.await,
"delegate stop at the lost edge should be recorded within 5s"
);
assert_eq!(
create_consumer_calls.load(Ordering::SeqCst),
1,
"no delivery up to the lost edge may recreate the delegate"
);
assert_eq!(
lifecycle_events(&metrics, "started"),
baseline_started,
"duplicate StartedLeading must not emit a started lifecycle observation"
);
assert_eq!(
lifecycle_events(&metrics, "stopped"),
baseline_stopped + 1,
"the lost edge must stop the delegate exactly once"
);
leadership.emit(LeadershipEvent::StoppedLeading).await;
leadership.emit(LeadershipEvent::StoppedLeading).await;
sleep(Duration::from_millis(500)).await;
let lost_count = metrics
.counters_named("master_leadership_transitions_total")
.iter()
.filter(|(_, labels)| *labels == expected_transition_labels("lost"))
.count();
assert_eq!(
lost_count, 1,
"false→false deliveries must not re-emit the lost transition"
);
cancel.cancel();
master.stop().await.unwrap();
}
#[tokio::test]
async fn term_bump_while_active_reconciles_once() {
let leadership = Arc::new(FakeLeadershipService::new(Some(
LeadershipEvent::StartedLeading,
)));
let platform_service = Arc::new(FakePlatformService::new(leadership.clone()));
let create_endpoint_calls = Arc::new(AtomicUsize::new(0));
let create_consumer_calls = Arc::new(AtomicUsize::new(0));
let metrics = Arc::new(RecordingMetricsCollector {
events: Mutex::new(Vec::new()),
});
let mut master = build_error_delegate_master_with_metrics(
platform_service,
Arc::clone(&create_endpoint_calls),
Arc::clone(&create_consumer_calls),
None,
0,
None,
30,
Arc::clone(&metrics) as Arc<dyn MetricsCollector>,
);
let (tx, mut rx) = tokio::sync::mpsc::channel(16);
let cancel = CancellationToken::new();
let ctx = ConsumerContext::new(tx, cancel.clone(), METRICS_TEST_ROUTE.to_string());
master.start(ctx).await.unwrap();
let first = timeout(Duration::from_secs(5), rx.recv())
.await
.unwrap()
.unwrap();
assert_eq!(first.exchange.input.body.as_text(), Some("ok"));
assert!(
await_counter_observations(&metrics, "master_leadership_transitions_total", 1).await,
"initial acquisition should be recorded within 5s"
);
let baseline_started = lifecycle_events(&metrics, "started");
let baseline_stopped = lifecycle_events(&metrics, "stopped");
assert_eq!(
baseline_started, 1,
"baseline delegate should have started exactly once"
);
assert_eq!(baseline_stopped, 0, "baseline should have no stops");
assert_eq!(
create_consumer_calls.load(Ordering::SeqCst),
1,
"baseline delegate should be created exactly once"
);
leadership.leader_epoch().store(2, Ordering::Release);
leadership.emit(LeadershipEvent::StartedLeading).await;
tokio::time::timeout(Duration::from_millis(7500), async {
loop {
if create_consumer_calls.load(Ordering::SeqCst) >= 2 {
break;
}
sleep(Duration::from_millis(5)).await;
}
})
.await
.expect("reconcile observation (delegate recreation) must land within 7.5s");
assert!(
await_counter_observations(
&metrics,
"master_delegate_lifecycle_total",
baseline_started + baseline_stopped + 2
)
.await,
"stopped+started lifecycle pair should be recorded within 5s"
);
assert_eq!(
create_consumer_calls.load(Ordering::SeqCst),
2,
"term bump must recreate the delegate exactly once"
);
assert_eq!(
lifecycle_events(&metrics, "started") - baseline_started,
1,
"term bump must emit exactly one started lifecycle observation"
);
assert_eq!(
lifecycle_events(&metrics, "stopped") - baseline_stopped,
1,
"term bump must emit exactly one stopped lifecycle observation"
);
let second = timeout(Duration::from_secs(5), rx.recv())
.await
.unwrap()
.unwrap();
assert_eq!(second.exchange.input.body.as_text(), Some("ok"));
let epoch_prop = second
.exchange
.properties
.get(crate::leadership::LEADER_EPOCH_PROPERTY)
.expect("recreated bridge must stamp x-camel-leader-epoch");
assert_eq!(
epoch_prop,
&serde_json::Value::String("2".to_string()),
"recreated bridge must carry the bumped epoch 2"
);
cancel.cancel();
master.stop().await.unwrap();
}