use std::net::{IpAddr, Ipv4Addr, SocketAddr}; use axum::{ body::{Body, to_bytes}, http::{Request, StatusCode, header}, }; use crank_observability::{ DURATION_BUCKETS_SECONDS, MetricsConfig, MetricsConfigError, MetricsSurface, ServiceIdentity, metric_schema, }; use tower::ServiceExt; fn identity() -> ServiceIdentity { ServiceIdentity::try_new("admin-api", "0.3.1", "test").expect("valid identity") } #[test] fn loopback_is_allowed_without_a_token() { let config = MetricsConfig::new( true, SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 9464), None, ) .expect("loopback metrics must be safe by default"); assert_eq!(config.bind_addr().to_string(), "127.0.0.1:9464"); assert!(!config.requires_authentication()); } #[test] fn non_loopback_without_a_token_is_rejected_without_secret_data() { let error = MetricsConfig::new( true, SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 9464), None, ) .expect_err("external metrics must require authentication"); assert!(matches!( error, MetricsConfigError::MissingTokenForExternalBind )); assert!(!error.to_string().contains("token=")); } #[test] fn schema_is_closed_and_uses_fixed_duration_buckets() { let schema = metric_schema(); let names: Vec<_> = schema.iter().map(|metric| metric.name).collect(); assert!(names.contains(&"crank_http_requests_total")); assert!(names.contains(&"crank_http_request_duration_seconds")); assert!(names.contains(&"crank_mcp_requests_total")); assert!(names.contains(&"crank_tool_invocations_total")); assert!(names.contains(&"crank_runtime_inflight")); assert!(names.contains(&"crank_db_pool_connections")); assert!(names.contains(&"crank_catalog_tools")); assert!(names.contains(&"crank_invocation_history_lost_total")); assert!(names.contains(&"crank_telemetry_export_failures_total")); for metric in schema { for forbidden in [ "workspace", "agent_id", "operation_id", "request_id", "url", "error_message", "text", ] { assert!( !metric.labels.contains(&forbidden), "{} exposes forbidden label {forbidden}", metric.name ); } } assert_eq!( DURATION_BUCKETS_SECONDS, &[ 0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1.0, 2.5, 5.0, 10.0, 30.0, 60.0 ] ); } #[tokio::test] async fn external_surface_protects_both_routes_and_exposes_nothing_else() { let token = "metrics-canary-secret"; let config = MetricsConfig::new( true, SocketAddr::new(IpAddr::V4(Ipv4Addr::UNSPECIFIED), 9464), Some(token.to_owned()), ) .expect("external metrics with token"); let surface = MetricsSurface::for_test(config, identity()).expect("test metrics surface"); let app = surface.router(); for path in ["/metrics", "/health"] { let unauthorized = app .clone() .oneshot(Request::get(path).body(Body::empty()).expect("request")) .await .expect("response"); assert_eq!(unauthorized.status(), StatusCode::UNAUTHORIZED); let authorized = app .clone() .oneshot( Request::get(path) .header(header::AUTHORIZATION, format!("Bearer {token}")) .body(Body::empty()) .expect("request"), ) .await .expect("response"); assert_eq!(authorized.status(), StatusCode::OK); let body = to_bytes(authorized.into_body(), 1024 * 1024) .await .expect("bounded body"); assert!(!String::from_utf8_lossy(&body).contains(token)); } let absent = app .oneshot( Request::get("/api/operations") .header(header::AUTHORIZATION, format!("Bearer {token}")) .body(Body::empty()) .expect("request"), ) .await .expect("response"); assert_eq!(absent.status(), StatusCode::NOT_FOUND); }