camel-core 0.56.0

Core engine for rust-camel
Documentation
use super::*;

/// Minimal double recording component-operation calls.
struct OpsRecorder(std::sync::Mutex<Vec<String>>);

impl MetricsCollector for OpsRecorder {
    fn increment_errors(&self, _route_id: &str, _error_type: &str) {}
    fn record_exchange_duration(&self, _route_id: &str, _seconds: std::time::Duration) {}
    fn increment_exchanges(&self, _route_id: &str) {}
    fn set_queue_depth(&self, _queue: &str, _depth: usize) {}
    fn record_circuit_breaker_change(&self, _route: &str, _from: &str, _to: &str) {}
    fn record_component_operation(&self, component: &str, operation: &str, outcome: &str) {
        self.0
            .lock()
            .expect("ops lock") // allow-unwrap
            .push(format!("{component}:{operation}:{outcome}"));
    }
}

fn build_ctx(enabled: bool) -> Arc<ControllerComponentContext> {
    Arc::new(ControllerComponentContext::new(
        Arc::new(std::sync::Mutex::new(Registry::new())),
        Arc::new(std::sync::Mutex::new(std::collections::HashMap::new())),
        Arc::new(OpsRecorder(std::sync::Mutex::new(Vec::new()))),
        Arc::new(camel_api::NoopPlatformService::default()),
        Arc::new(crate::health_registry::HealthCheckRegistry::new(
            std::time::Duration::from_secs(30),
        )),
        Some("r".to_string()),
        enabled,
    ))
}

/// Task 4.1 review finding: the production controller path must thread
/// the `[observability.metrics].components` lever snapshot through
/// `ControllerComponentContext` — endpoints reach `component_metrics()`
/// through this impl, not through `CamelContext`.
#[test]
fn controller_path_component_metrics_reflects_lever() {
    let on = build_ctx(true);
    assert!(
        on.component_metrics_enabled(),
        "enabled snapshot must surface on the ComponentContext seam"
    );
    let recorder = Arc::new(OpsRecorder(std::sync::Mutex::new(Vec::new())));
    let facade =
        camel_api::ComponentMetrics::new(Arc::clone(&recorder) as Arc<dyn MetricsCollector>, true);
    facade.observe("wasm", "invoke", true);
    let calls = recorder.0.lock().expect("ops lock").clone();
    assert_eq!(
        calls,
        vec!["wasm:invoke:failure"],
        "lever-on facade must record the component operation"
    );

    let off = build_ctx(false);
    assert!(
        !off.component_metrics_enabled(),
        "disabled snapshot must stay off (opt-in default)"
    );
}

/// Boundary lock (rc-4bfnk): `ControllerComponentContext` intentionally
/// keeps the trait's `None` default for `shutdown_token` — see the
/// impl-block doc in `controller_component_context.rs`.
#[test]
fn shutdown_token_is_none_boundary_lock() {
    let ctx = build_ctx(false);
    let token = ComponentContext::shutdown_token(&*ctx);
    assert!(
        token.is_none(),
        "boundary lock: ControllerComponentContext::shutdown_token must stay None \
         (endpoint-creation-time adapter context; no slot-backed accessor is \
         reachable through it). If a slot-backed accessor is ever added to the \
         trait, update this test deliberately — do not delete it."
    );
}