Files
crank/crates/crank-observability/tests/http_metrics.rs
bsodfather 0e8f1ca03a
CI / Rust Checks (push) Failing after 4m28s
CI / UI Checks (push) Has been skipped
CI / Frontend E2E (push) Has been skipped
CI / Community Image Smoke (push) Has been skipped
CI / Deploy (push) Has been skipped
наблюдаемость: завершить базовый контур Community
Добавить структурированные журналы, метрики, трассировку и безопасный канал критических ошибок. Усилить границы рантайма, тесты, проверку зависимостей и сценарии развёртывания.
2026-07-31 01:01:14 +03:00

93 lines
2.9 KiB
Rust

use std::net::{IpAddr, Ipv4Addr, SocketAddr};
use axum::{
Router,
body::{Body, to_bytes},
http::{Request, StatusCode},
middleware,
routing::get,
};
use crank_observability::{
MetricsConfig, ObservabilityConfig, RedactionLimits, ServiceIdentity, record_http_request,
};
use tower::ServiceExt;
#[tokio::test]
async fn http_metrics_use_matched_routes_and_closed_labels() {
let identity =
ServiceIdentity::try_new("metrics-test", "0.3.1", "test").expect("valid identity");
let lifecycle = crank_observability::init(ObservabilityConfig::new(
identity,
"off",
RedactionLimits::default(),
))
.expect("observability lifecycle");
let config = MetricsConfig::new(
true,
SocketAddr::new(IpAddr::V4(Ipv4Addr::LOCALHOST), 9464),
None,
)
.expect("loopback metrics");
let metrics = lifecycle.metrics_surface(config).router();
let app = Router::new()
.route(
"/documents/{document_id}",
get(|| async { StatusCode::NO_CONTENT }),
)
.layer(middleware::from_fn(record_http_request));
let sensitive_path_segment = "customer-secret-document-id";
for index in 0..100 {
let response = app
.clone()
.oneshot(
Request::get(format!("/documents/{sensitive_path_segment}-{index}"))
.body(Body::empty())
.expect("request"),
)
.await
.expect("response");
assert_eq!(response.status(), StatusCode::NO_CONTENT);
}
let response = app
.oneshot(
Request::get("/unknown/customer-controlled-path")
.body(Body::empty())
.expect("request"),
)
.await
.expect("response");
assert_eq!(response.status(), StatusCode::NOT_FOUND);
let response = metrics
.oneshot(
Request::get("/metrics")
.body(Body::empty())
.expect("request"),
)
.await
.expect("metrics response");
let body = to_bytes(response.into_body(), 1024 * 1024)
.await
.expect("bounded metrics body");
let body = String::from_utf8(body.to_vec()).expect("utf-8 metrics");
assert!(body.contains("crank_http_requests_total"));
assert!(body.contains("route=\"/documents/{document_id}\""));
assert!(body.contains("method=\"GET\""));
assert!(body.contains("status_class=\"2xx\""));
assert!(body.contains("crank_http_request_duration_seconds_bucket"));
assert!(!body.contains(sensitive_path_segment));
assert_eq!(
body.lines()
.filter(|line| {
line.starts_with("crank_http_requests_total{")
&& line.contains("route=\"/documents/{document_id}\"")
})
.count(),
1,
"different entity ids must not create additional series"
);
}