189 lines
6.8 KiB
Rust
189 lines
6.8 KiB
Rust
use std::collections::BTreeSet;
|
|
|
|
use crank_metrics::{
|
|
HTTP_ROUTE_DOMAIN, HttpRoute, LabelClass, LabelDomain, MAX_LOGICAL_SERIES_PER_PROCESS,
|
|
MAX_PRODUCT_LABELS_PER_FAMILY, MAX_RENDERED_SERIES_PER_PROCESS, METRIC_SCHEMA_VERSION,
|
|
MetricDefinition, MetricKind, MetricProcess, MetricService, MetricUnit, metric_schema,
|
|
metric_schema_for_service, render_metric_schema_json, schema_budget, validate_budget,
|
|
validate_schema,
|
|
};
|
|
|
|
#[test]
|
|
fn schema_v1_is_complete_deterministic_and_inside_the_frozen_budget() {
|
|
assert_eq!(METRIC_SCHEMA_VERSION, 1);
|
|
assert_eq!(metric_schema().len(), 23);
|
|
|
|
let budget = schema_budget().expect("canonical schema must be valid");
|
|
assert!(budget.logical_series <= MAX_LOGICAL_SERIES_PER_PROCESS);
|
|
assert!(budget.rendered_series <= MAX_RENDERED_SERIES_PER_PROCESS);
|
|
|
|
for definition in metric_schema() {
|
|
assert!(!definition.description.trim().is_empty());
|
|
assert!(definition.labels.len() <= MAX_PRODUCT_LABELS_PER_FAMILY);
|
|
assert!(!definition.processes.is_empty());
|
|
assert_eq!(definition.labels.len(), definition.label_domains.len());
|
|
assert!(definition.label_domains.iter().all(|domain| {
|
|
!domain.values.is_empty()
|
|
&& domain.values.iter().all(|value| !value.is_empty())
|
|
&& domain.values.iter().copied().collect::<BTreeSet<_>>().len()
|
|
== domain.values.len()
|
|
}));
|
|
if definition.kind.as_str() == "histogram" {
|
|
assert!(!definition.buckets.is_empty());
|
|
assert!(definition.buckets.windows(2).all(|pair| pair[0] < pair[1]));
|
|
} else {
|
|
assert!(definition.buckets.is_empty());
|
|
}
|
|
}
|
|
|
|
let first = render_metric_schema_json().expect("schema renders");
|
|
let second = render_metric_schema_json().expect("schema renders deterministically");
|
|
assert_eq!(first, second);
|
|
assert!(first.len() < 65_536);
|
|
assert!(!first.contains("trace_id"));
|
|
assert!(!first.contains("request_id"));
|
|
}
|
|
|
|
#[test]
|
|
fn frozen_budget_edges_are_exact() {
|
|
assert!(validate_budget(4_999, 14_999).is_ok());
|
|
assert!(validate_budget(5_000, 15_000).is_ok());
|
|
assert!(validate_budget(5_001, 15_000).is_err());
|
|
assert!(validate_budget(5_000, 15_001).is_err());
|
|
assert_eq!(crank_metrics::max_exemplar_slots(), 9_170);
|
|
}
|
|
|
|
#[test]
|
|
fn every_registered_route_round_trips_without_dynamic_segments() {
|
|
assert_eq!(HTTP_ROUTE_DOMAIN.len(), 57);
|
|
for route in HTTP_ROUTE_DOMAIN {
|
|
assert_eq!(HttpRoute::from_matched_path(route).as_str(), *route);
|
|
assert!(!route.contains("trace_id"));
|
|
assert!(!route.contains("request_id"));
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn canonical_schema_validation_reports_its_category() {
|
|
assert_eq!(
|
|
schema_budget(),
|
|
Ok(crank_metrics::SchemaBudget {
|
|
logical_series: 4_513,
|
|
rendered_series: 14_338
|
|
})
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn exact_budget_math_counts_histogram_bucket_sum_and_count_series() {
|
|
for definition in metric_schema() {
|
|
let logical = definition
|
|
.label_domains
|
|
.iter()
|
|
.map(|domain| domain.values.len())
|
|
.product::<usize>();
|
|
let logical = logical.max(1);
|
|
let rendered = if definition.kind.as_str() == "histogram" {
|
|
logical * (definition.buckets.len() + 3)
|
|
} else {
|
|
logical
|
|
};
|
|
assert_eq!(
|
|
definition.max_logical_series, logical,
|
|
"{}",
|
|
definition.name
|
|
);
|
|
assert_eq!(
|
|
definition.max_rendered_series, rendered,
|
|
"{}",
|
|
definition.name
|
|
);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn prometheus_reserved_and_digit_prefixed_label_names_are_rejected() {
|
|
static DOMAIN: LabelDomain = LabelDomain {
|
|
name: "valid",
|
|
class: LabelClass::ProductClosed,
|
|
values: &["value"],
|
|
};
|
|
for label in ["1invalid", "__reserved"] {
|
|
let mut domain = DOMAIN;
|
|
domain.name = label;
|
|
let definition = MetricDefinition {
|
|
name: "crank_test_total",
|
|
kind: MetricKind::Counter,
|
|
unit: MetricUnit::Count,
|
|
labels: Box::leak(vec![label].into_boxed_slice()),
|
|
label_domains: Box::leak(vec![domain].into_boxed_slice()),
|
|
buckets: &[],
|
|
processes: &[MetricProcess::AdminApi],
|
|
max_logical_series: 1,
|
|
max_rendered_series: 1,
|
|
description: "test metric",
|
|
};
|
|
assert!(validate_schema(&[definition]).is_err(), "accepted {label}");
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn process_projection_excludes_mcp_only_metrics_from_admin() {
|
|
let admin = metric_schema_for_service(MetricService::AdminApi)
|
|
.map(|definition| definition.name)
|
|
.collect::<BTreeSet<_>>();
|
|
let mcp = metric_schema_for_service(MetricService::McpServer)
|
|
.map(|definition| definition.name)
|
|
.collect::<BTreeSet<_>>();
|
|
assert!(!admin.contains("crank_mcp_active_sessions"));
|
|
assert!(!admin.contains("crank_catalog_tools"));
|
|
assert!(mcp.contains("crank_mcp_active_sessions"));
|
|
assert!(admin.contains("crank_http_requests_total"));
|
|
assert!(mcp.contains("crank_http_requests_total"));
|
|
}
|
|
|
|
#[test]
|
|
fn registered_admin_and_mcp_routes_are_exactly_the_schema_domain() {
|
|
let admin_source = include_str!("../../../apps/admin-api/src/app.rs");
|
|
let mcp_source = include_str!("../../crank-community-mcp/src/app.rs");
|
|
let auth = ["/login", "/logout", "/session", "/profile", "/password"];
|
|
let admin_root = ["/capabilities", "/workspaces", "/workspaces/{workspace_id}"];
|
|
let mut actual = BTreeSet::from(["unmatched".to_owned()]);
|
|
for route in route_literals(admin_source) {
|
|
let canonical = if matches!(route.as_str(), "/health" | "/ready") {
|
|
route
|
|
} else if auth.contains(&route.as_str()) {
|
|
format!("/api/auth{route}")
|
|
} else if admin_root.contains(&route.as_str()) {
|
|
format!("/api/admin{route}")
|
|
} else {
|
|
format!("/api/admin/workspaces/{{workspace_id}}{route}")
|
|
};
|
|
actual.insert(canonical);
|
|
}
|
|
actual.extend(route_literals(mcp_source));
|
|
let expected = HTTP_ROUTE_DOMAIN
|
|
.iter()
|
|
.map(|route| (*route).to_owned())
|
|
.collect::<BTreeSet<_>>();
|
|
assert_eq!(actual, expected);
|
|
}
|
|
|
|
fn route_literals(source: &str) -> Vec<String> {
|
|
let mut routes = Vec::new();
|
|
let mut remaining = source;
|
|
while let Some(offset) = remaining.find(".route(") {
|
|
remaining = &remaining[offset + ".route(".len()..];
|
|
let Some(start) = remaining.find('"') else {
|
|
break;
|
|
};
|
|
let after_start = &remaining[start + 1..];
|
|
let Some(end) = after_start.find('"') else {
|
|
break;
|
|
};
|
|
routes.push(after_start[..end].to_owned());
|
|
remaining = &after_start[end + 1..];
|
|
}
|
|
routes
|
|
}
|