use super::*;
use std::sync::MutexGuard;
use std::time::Instant;
use wiremock::matchers::{method, path};
use wiremock::{Mock, MockServer, ResponseTemplate};
fn telemetry_lock() -> MutexGuard<'static, ()> {
static LOCK: Mutex<()> = Mutex::new(());
LOCK.lock().unwrap_or_else(|e| e.into_inner())
}
struct TelemetryTestEnv {
_guard: MutexGuard<'static, ()>,
_stamp_dir: tempfile::TempDir,
stamp_path: PathBuf,
previous_adapters: std::collections::BTreeSet<String>,
}
impl TelemetryTestEnv {
fn on() -> Self {
let guard = telemetry_lock();
let dir = tempfile::tempdir().expect("tempdir");
let stamp_path = dir.path().join("rust-telemetry-last-sent");
std::env::remove_var("AXONFLOW_TELEMETRY");
std::env::remove_var("AXONFLOW_TRY");
std::env::remove_var("ORG_ID");
std::env::remove_var("AXONFLOW_CHECKPOINT_URL");
*STAMP_PATH_OVERRIDE
.lock()
.unwrap_or_else(|e| e.into_inner()) = Some(Some(stamp_path.clone()));
reset_gate_for_tests();
let previous_adapters = reset_adapter_registry_for_tests();
TELEMETRY_ARMED_FOR_TESTS.with(|armed| armed.set(true));
Self {
_guard: guard,
_stamp_dir: dir,
stamp_path,
previous_adapters,
}
}
fn set(&self, key: &str, value: &str) {
std::env::set_var(key, value);
}
fn without_a_stamp_path(&self) {
*STAMP_PATH_OVERRIDE
.lock()
.unwrap_or_else(|e| e.into_inner()) = Some(None);
}
fn advance_past_the_guard(&self) {
reopen_gate_for_tests();
}
fn advance_past_the_heartbeat_interval(&self) {
cross_the_heartbeat_interval_for_tests();
let _ = std::fs::remove_file(&self.stamp_path);
}
fn stamp_exists(&self) -> bool {
self.stamp_path.exists()
}
}
impl Drop for TelemetryTestEnv {
fn drop(&mut self) {
restore_adapter_registry_for_tests(std::mem::take(&mut self.previous_adapters));
TELEMETRY_ARMED_FOR_TESTS.with(|armed| armed.set(false));
*STAMP_PATH_OVERRIDE
.lock()
.unwrap_or_else(|e| e.into_inner()) = None;
std::env::remove_var("AXONFLOW_TELEMETRY");
std::env::remove_var("AXONFLOW_TRY");
std::env::remove_var("ORG_ID");
std::env::remove_var("AXONFLOW_CHECKPOINT_URL");
reset_gate_for_tests();
}
}
fn ctx_for(endpoint: &str, checkpoint_url: &str) -> HeartbeatContext {
HeartbeatContext {
endpoint: endpoint.to_string(),
checkpoint_url: checkpoint_url.to_string(),
stamp_path: None,
stream: None,
deployment_mode: DEPLOYMENT_MODE_SELF_HOSTED,
endpoint_type: ENDPOINT_TYPE_LOCALHOST,
org_id: "test-org".to_string(),
}
}
const UNREACHABLE_ENDPOINT: &str = "http://127.0.0.1:1";
async fn mount_checkpoint(server: &MockServer, status: u16) {
Mock::given(method("POST"))
.and(path("/v1/ping"))
.respond_with(ResponseTemplate::new(status).set_body_string("{\"latest_version\":null}"))
.mount(server)
.await;
}
async fn mount_health_json(server: &MockServer, body: serde_json::Value) {
Mock::given(method("GET"))
.and(path("/health"))
.respond_with(ResponseTemplate::new(200).set_body_json(body))
.mount(server)
.await;
}
fn checkpoint_url(server: &MockServer) -> String {
format!("{}/v1/ping", server.uri())
}
async fn seen(server: &MockServer) -> Vec<(String, String)> {
server
.received_requests()
.await
.unwrap_or_default()
.iter()
.map(|r| (r.method.to_string(), r.url.path().to_string()))
.collect()
}
async fn only_ping_body(server: &MockServer) -> serde_json::Value {
let requests = server.received_requests().await.unwrap_or_default();
let pings: Vec<_> = requests
.iter()
.filter(|r| r.url.path() == "/v1/ping")
.collect();
assert_eq!(
pings.len(),
1,
"expected exactly one checkpoint POST, saw {}: {:?}",
pings.len(),
seen(server).await
);
serde_json::from_slice(&pings[0].body).expect("ping body is valid JSON")
}
fn count_pings(seen: &[(String, String)]) -> usize {
seen.iter()
.filter(|(m, p)| m == "POST" && p == "/v1/ping")
.count()
}
fn count_health(seen: &[(String, String)]) -> usize {
seen.iter()
.filter(|(m, p)| m == "GET" && p == "/health")
.count()
}
#[derive(Default)]
struct LogCapture {
buf: Mutex<Vec<u8>>,
armed: Mutex<Option<std::thread::ThreadId>>,
}
struct LogCaptureArmed(
&'static LogCapture,
#[allow(dead_code)]
MutexGuard<'static, ()>,
);
impl LogCapture {
fn global() -> &'static LogCapture {
static CAP: OnceLock<LogCapture> = OnceLock::new();
let cap = CAP.get_or_init(LogCapture::default);
static INSTALLED: OnceLock<()> = OnceLock::new();
INSTALLED.get_or_init(|| {
let subscriber = tracing_subscriber::fmt()
.with_max_level(tracing::Level::DEBUG)
.with_writer(CapWriter(cap))
.with_ansi(false)
.finish();
tracing::subscriber::set_global_default(subscriber)
.expect("no other global tracing subscriber may be installed in this test binary");
});
cap
}
fn arm() -> LogCaptureArmed {
static ARM_LOCK: Mutex<()> = Mutex::new(());
let guard = ARM_LOCK.lock().unwrap_or_else(|e| e.into_inner());
let cap = LogCapture::global();
cap.buf.lock().unwrap_or_else(|e| e.into_inner()).clear();
*cap.armed.lock().unwrap_or_else(|e| e.into_inner()) = Some(std::thread::current().id());
LogCaptureArmed(cap, guard)
}
}
impl LogCaptureArmed {
fn contents(&self) -> String {
String::from_utf8_lossy(&self.0.buf.lock().unwrap_or_else(|e| e.into_inner())).to_string()
}
}
impl Drop for LogCaptureArmed {
fn drop(&mut self) {
*self.0.armed.lock().unwrap_or_else(|e| e.into_inner()) = None;
}
}
#[derive(Clone, Copy)]
struct CapWriter(&'static LogCapture);
impl std::io::Write for CapWriter {
fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
let armed = *self.0.armed.lock().unwrap_or_else(|e| e.into_inner());
if armed == Some(std::thread::current().id()) {
self.0
.buf
.lock()
.unwrap_or_else(|e| e.into_inner())
.extend_from_slice(buf);
}
Ok(buf.len())
}
fn flush(&mut self) -> std::io::Result<()> {
Ok(())
}
}
impl<'a> tracing_subscriber::fmt::MakeWriter<'a> for CapWriter {
type Writer = Self;
fn make_writer(&'a self) -> Self::Writer {
*self
}
}
#[test]
fn classify_endpoint_localhost_variants() {
assert_eq!(
classify_endpoint("http://localhost:8080"),
ENDPOINT_TYPE_LOCALHOST
);
assert_eq!(
classify_endpoint("https://127.0.0.1:8080"),
ENDPOINT_TYPE_LOCALHOST
);
assert_eq!(
classify_endpoint("http://0.0.0.0:9090"),
ENDPOINT_TYPE_LOCALHOST
);
assert_eq!(
classify_endpoint("http://my.localhost"),
ENDPOINT_TYPE_LOCALHOST
);
assert_eq!(
classify_endpoint("http://[::1]:8080"),
ENDPOINT_TYPE_LOCALHOST
);
}
#[test]
fn classify_endpoint_private_variants() {
assert_eq!(classify_endpoint("http://10.1.2.3"), ENDPOINT_TYPE_PRIVATE);
assert_eq!(
classify_endpoint("http://192.168.1.1"),
ENDPOINT_TYPE_PRIVATE
);
assert_eq!(
classify_endpoint("http://172.16.0.1"),
ENDPOINT_TYPE_PRIVATE
);
assert_eq!(classify_endpoint("http://api.local"), ENDPOINT_TYPE_PRIVATE);
assert_eq!(
classify_endpoint("http://api.internal"),
ENDPOINT_TYPE_PRIVATE
);
}
#[test]
fn classify_endpoint_remote() {
assert_eq!(
classify_endpoint("https://api.example.com"),
ENDPOINT_TYPE_REMOTE
);
assert_eq!(
classify_endpoint("https://203.0.113.5"),
ENDPOINT_TYPE_REMOTE
);
}
#[test]
fn classify_endpoint_unknown() {
assert_eq!(classify_endpoint(""), ENDPOINT_TYPE_UNKNOWN);
assert_eq!(classify_endpoint("not a url"), ENDPOINT_TYPE_UNKNOWN);
}
#[test]
fn stream_for_mode_classification() {
assert_eq!(stream_for_mode(&Mode::Sandbox), Some(STREAM_SANDBOX));
assert_eq!(stream_for_mode(&Mode::Production), None);
}
#[test]
fn classify_deployment_mode_v1_schema() {
let env = TelemetryTestEnv::on();
assert_eq!(classify_deployment_mode(""), DEPLOYMENT_MODE_UNKNOWN);
assert_eq!(
classify_deployment_mode("not a url"),
DEPLOYMENT_MODE_UNKNOWN
);
assert_eq!(
classify_deployment_mode("https://api.example.com"),
DEPLOYMENT_MODE_SELF_HOSTED
);
assert_eq!(
classify_deployment_mode("https://try.getaxonflow.com"),
DEPLOYMENT_MODE_COMMUNITY_SAAS
);
assert_eq!(
classify_deployment_mode("https://eu.try.getaxonflow.com"),
DEPLOYMENT_MODE_COMMUNITY_SAAS
);
env.set("AXONFLOW_TRY", "1");
assert_eq!(
classify_deployment_mode("https://my-proxy.example.com"),
DEPLOYMENT_MODE_COMMUNITY_SAAS
);
}
#[test]
fn telemetry_off_recognizes_off_value() {
let env = TelemetryTestEnv::on();
env.set("AXONFLOW_TELEMETRY", "off");
assert!(telemetry_off());
env.set("AXONFLOW_TELEMETRY", "OFF");
assert!(telemetry_off());
env.set("AXONFLOW_TELEMETRY", " off ");
assert!(telemetry_off());
env.set("AXONFLOW_TELEMETRY", "");
assert!(!telemetry_off());
env.set("AXONFLOW_TELEMETRY", "on");
assert!(!telemetry_off());
std::env::remove_var("AXONFLOW_TELEMETRY");
assert!(!telemetry_off());
}
#[test]
fn telemetry_org_id_env_wins() {
let env = TelemetryTestEnv::on();
env.set("ORG_ID", "acme-corp");
assert_eq!(telemetry_org_id(), "acme-corp");
}
#[test]
fn telemetry_org_id_unset_returns_sentinel() {
let _env = TelemetryTestEnv::on();
assert_eq!(telemetry_org_id(), ORG_ID_LOCAL_DEV_SENTINEL);
assert_eq!(ORG_ID_LOCAL_DEV_SENTINEL, "local-dev-org");
}
#[test]
fn telemetry_org_id_empty_falls_through_to_sentinel() {
let env = TelemetryTestEnv::on();
env.set("ORG_ID", "");
assert_eq!(telemetry_org_id(), ORG_ID_LOCAL_DEV_SENTINEL);
}
#[test]
fn telemetry_org_id_cs_prefixed_passes_through() {
let env = TelemetryTestEnv::on();
let cs_id = "cs_e3a4b5c6-d7e8-4f90-a1b2-c3d4e5f6a7b8";
env.set("ORG_ID", cs_id);
assert_eq!(telemetry_org_id(), cs_id);
}
#[test]
fn normalize_rustc_version_keeps_the_version_and_drops_the_build_id() {
assert_eq!(
normalize_rustc_version(Some("rustc 1.95.0 (59807616e 2026-04-14)")),
"rustc 1.95.0"
);
assert_eq!(
normalize_rustc_version(Some("rustc 1.96.0-nightly (abcdef012 2026-05-01)")),
"rustc 1.96.0-nightly"
);
assert_eq!(
normalize_rustc_version(Some("rustc 1.95.0-beta.2 (deadbeef1 2026-03-01)")),
"rustc 1.95.0-beta.2"
);
assert_eq!(
normalize_rustc_version(Some("rustc 1.95.0")),
"rustc 1.95.0"
);
assert_eq!(
normalize_rustc_version(Some(" rustc 1.95.0 ")),
"rustc 1.95.0"
);
}
#[test]
fn normalize_rustc_version_refuses_anything_it_cannot_recognise() {
assert_eq!(normalize_rustc_version(None), RUNTIME_VERSION_UNKNOWN);
assert_eq!(normalize_rustc_version(Some("")), RUNTIME_VERSION_UNKNOWN);
assert_eq!(
normalize_rustc_version(Some(" ")),
RUNTIME_VERSION_UNKNOWN
);
assert_eq!(
normalize_rustc_version(Some("my-wrapper 1.0")),
RUNTIME_VERSION_UNKNOWN
);
assert_eq!(
normalize_rustc_version(Some("rustc")),
RUNTIME_VERSION_UNKNOWN
);
assert_eq!(
normalize_rustc_version(Some("rustc version-one")),
RUNTIME_VERSION_UNKNOWN
);
let long = format!("rustc 1{}", "9".repeat(MAX_RELAYED_VALUE_LEN));
assert_eq!(
normalize_rustc_version(Some(&long)),
RUNTIME_VERSION_UNKNOWN
);
}
#[test]
fn runtime_version_is_the_real_toolchain_and_never_the_old_literal() {
let v = runtime_version_str();
assert_ne!(
v, "rustc-stable",
"the fabricated pre-0.10.0 literal must not survive anywhere"
);
assert!(
v == RUNTIME_VERSION_UNKNOWN || v.starts_with("rustc "),
"runtime_version was {v:?}; expected the real toolchain or an honest 'unknown'"
);
assert!(
v.starts_with("rustc "),
"build.rs failed to capture the toolchain: runtime_version was {v:?}"
);
}
#[test]
fn budget_split_leaves_room_for_the_post() {
assert!(
HEALTH_BUDGET_CAP + MIN_BUDGET < HEARTBEAT_TIMEOUT,
"health cap {HEALTH_BUDGET_CAP:?} + floor {MIN_BUDGET:?} must leave the POST room inside {HEARTBEAT_TIMEOUT:?}"
);
assert!(HEALTH_BUDGET_CAP > MIN_BUDGET);
}
#[test]
fn learned_value_accepts_only_a_present_non_empty_bounded_string() {
let body = serde_json::json!({
"ok": "10.4.0",
"empty": "",
"number": 42,
"null": null,
"object": {"nested": "x"},
"array": ["x"],
"boolean": true,
"at_cap": "a".repeat(MAX_RELAYED_VALUE_LEN),
"over_cap": "a".repeat(MAX_RELAYED_VALUE_LEN + 1),
});
assert_eq!(learned_value(&body, "ok"), Some("10.4.0".to_string()));
assert_eq!(
learned_value(&body, "at_cap"),
Some("a".repeat(MAX_RELAYED_VALUE_LEN))
);
for key in [
"empty", "number", "null", "object", "array", "boolean", "over_cap", "absent",
] {
assert_eq!(
learned_value(&body, key),
None,
"key {key:?} must not be learned"
);
}
}
#[test]
fn learned_value_drops_an_over_long_value_whole_rather_than_truncating() {
let long = "E".repeat(MAX_RELAYED_VALUE_LEN + 1);
let body = serde_json::json!({ "tier": long });
assert_eq!(learned_value(&body, "tier"), None);
}
fn probe_client() -> reqwest::Client {
telemetry_client().expect("client")
}
#[tokio::test]
async fn probe_learns_every_field_when_health_answers() {
let server = MockServer::start().await;
mount_health_json(
&server,
serde_json::json!({
"status": "healthy",
"version": "10.4.0",
"tier": "Enterprise",
"edition": "enterprise",
"deployment_mode": "self_hosted",
}),
)
.await;
let probe = probe_platform_health(&probe_client(), &server.uri(), HEALTH_BUDGET_CAP).await;
assert_eq!(
probe,
HealthProbe {
platform_version: Some("10.4.0".into()),
license_tier: Some("Enterprise".into()),
edition: Some("enterprise".into()),
deployment_mode: Some("self_hosted".into()),
}
);
}
#[tokio::test]
async fn probe_learns_the_pre_3660_shape_without_edition_or_deployment_mode() {
let server = MockServer::start().await;
mount_health_json(
&server,
serde_json::json!({"status": "healthy", "version": "10.3.0", "tier": "Community"}),
)
.await;
let probe = probe_platform_health(&probe_client(), &server.uri(), HEALTH_BUDGET_CAP).await;
assert_eq!(probe.platform_version.as_deref(), Some("10.3.0"));
assert_eq!(probe.license_tier.as_deref(), Some("Community"));
assert_eq!(probe.edition, None);
assert_eq!(probe.deployment_mode, None);
}
#[tokio::test]
async fn probe_forwards_the_transient_starting_tier_verbatim() {
let server = MockServer::start().await;
mount_health_json(
&server,
serde_json::json!({"status": "starting", "tier": "starting"}),
)
.await;
let probe = probe_platform_health(&probe_client(), &server.uri(), HEALTH_BUDGET_CAP).await;
assert_eq!(probe.license_tier.as_deref(), Some("starting"));
}
#[tokio::test]
async fn probe_promotes_each_field_independently() {
let server = MockServer::start().await;
mount_health_json(
&server,
serde_json::json!({"version": "10.4.0", "tier": 42, "edition": null, "deployment_mode": ""}),
)
.await;
let probe = probe_platform_health(&probe_client(), &server.uri(), HEALTH_BUDGET_CAP).await;
assert_eq!(probe.platform_version.as_deref(), Some("10.4.0"));
assert_eq!(probe.license_tier, None);
assert_eq!(probe.edition, None);
assert_eq!(probe.deployment_mode, None);
}
#[tokio::test]
async fn probe_returns_nothing_when_health_is_unreachable() {
let probe =
probe_platform_health(&probe_client(), UNREACHABLE_ENDPOINT, HEALTH_BUDGET_CAP).await;
assert_eq!(probe, HealthProbe::default());
}
#[tokio::test]
async fn probe_returns_nothing_on_a_server_error() {
let server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/health"))
.respond_with(
ResponseTemplate::new(500).set_body_json(serde_json::json!({"tier": "Enterprise"})),
)
.mount(&server)
.await;
let probe = probe_platform_health(&probe_client(), &server.uri(), HEALTH_BUDGET_CAP).await;
assert_eq!(
probe,
HealthProbe::default(),
"a non-2xx body must not be read for values"
);
}
#[tokio::test]
async fn probe_returns_nothing_when_health_is_absent() {
let server = MockServer::start().await;
let probe = probe_platform_health(&probe_client(), &server.uri(), HEALTH_BUDGET_CAP).await;
assert_eq!(probe, HealthProbe::default());
}
#[tokio::test]
async fn probe_returns_nothing_on_a_non_json_body() {
let server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/health"))
.respond_with(ResponseTemplate::new(200).set_body_string("<html>not json</html>"))
.mount(&server)
.await;
let probe = probe_platform_health(&probe_client(), &server.uri(), HEALTH_BUDGET_CAP).await;
assert_eq!(probe, HealthProbe::default());
}
#[tokio::test]
async fn probe_returns_nothing_when_the_body_exceeds_the_cap() {
let server = MockServer::start().await;
let huge = serde_json::json!({
"version": "10.4.0",
"tier": "Enterprise",
"padding": "x".repeat(MAX_HEALTH_BODY_BYTES + 1),
});
mount_health_json(&server, huge).await;
let probe = probe_platform_health(&probe_client(), &server.uri(), HEALTH_BUDGET_CAP).await;
assert_eq!(probe, HealthProbe::default());
}
#[tokio::test]
async fn probe_makes_exactly_one_request_per_heartbeat() {
let server = MockServer::start().await;
mount_health_json(
&server,
serde_json::json!({"version": "10.4.0", "tier": "Community"}),
)
.await;
let _ = probe_platform_health(&probe_client(), &server.uri(), HEALTH_BUDGET_CAP).await;
let seen = seen(&server).await;
assert_eq!(
count_health(&seen),
1,
"every relayed dimension must ride ONE /health response; saw {seen:?}"
);
let requests = server.received_requests().await.unwrap_or_default();
let ua = requests[0]
.headers
.get("user-agent")
.and_then(|v| v.to_str().ok())
.unwrap_or_default()
.to_string();
assert!(
ua.starts_with("axonflow-sdk-rust/"),
"the /health probe must identify itself; User-Agent was {ua:?}"
);
for forbidden in ["authorization", "x-license-key", "x-client-id"] {
assert!(
requests[0].headers.get(forbidden).is_none(),
"the probe must not send {forbidden}"
);
}
}
#[tokio::test]
async fn probe_skips_a_blank_endpoint_without_attempting_a_request() {
let logs = LogCapture::arm();
for endpoint in ["", "/", " ", "///"] {
let probe = probe_platform_health(&probe_client(), endpoint, HEALTH_BUDGET_CAP).await;
assert_eq!(probe, HealthProbe::default(), "endpoint {endpoint:?}");
}
let captured = logs.contents();
assert!(
!captured.contains("/health probe failed"),
"a blank endpoint must be skipped, not attempted; logs:\n{captured}"
);
}
#[tokio::test]
async fn probe_tolerates_a_trailing_slash_on_the_endpoint() {
let server = MockServer::start().await;
mount_health_json(&server, serde_json::json!({"version": "10.4.0"})).await;
let probe = probe_platform_health(
&probe_client(),
&format!("{}/", server.uri()),
HEALTH_BUDGET_CAP,
)
.await;
assert_eq!(probe.platform_version.as_deref(), Some("10.4.0"));
}
#[tokio::test]
async fn ping_carries_every_relayed_field_when_health_answers() {
let server = MockServer::start().await;
mount_health_json(
&server,
serde_json::json!({
"version": "10.4.0",
"tier": "EnterprisePlus",
"edition": "enterprise",
"deployment_mode": "community_saas",
}),
)
.await;
mount_checkpoint(&server, 200).await;
let delivered = send_heartbeat(&ctx_for(&server.uri(), &checkpoint_url(&server))).await;
assert!(delivered);
let body = only_ping_body(&server).await;
assert_eq!(body["platform_version"], "10.4.0");
assert_eq!(body["license_tier"], "EnterprisePlus");
assert_eq!(body["edition"], "enterprise");
assert_eq!(body["platform_deployment_mode"], "community_saas");
assert_eq!(body["deployment_mode"], DEPLOYMENT_MODE_SELF_HOSTED);
assert_ne!(
body["deployment_mode"], body["platform_deployment_mode"],
"the SDK's topology classification was overwritten by the platform's answer"
);
assert_eq!(body["telemetry_type"], "sdk");
assert_eq!(body["sdk"], "rust");
assert_eq!(body["sdk_version"], env!("CARGO_PKG_VERSION"));
assert_eq!(body["org_id"], "test-org");
assert_eq!(body["features"], serde_json::json!([]));
assert!(body.get("stream").is_none(), "production mode omits stream");
}
#[tokio::test]
async fn ping_is_still_sent_and_omits_the_keys_on_every_health_failure() {
#[derive(Debug)]
enum Health {
Unreachable,
Missing,
ServerError,
NotJson,
NoKeys,
WrongTypes,
OversizedBody,
}
for case in [
Health::Unreachable,
Health::Missing,
Health::ServerError,
Health::NotJson,
Health::NoKeys,
Health::WrongTypes,
Health::OversizedBody,
] {
let server = MockServer::start().await;
mount_checkpoint(&server, 200).await;
match case {
Health::Unreachable | Health::Missing => {}
Health::ServerError => {
Mock::given(method("GET"))
.and(path("/health"))
.respond_with(ResponseTemplate::new(500))
.mount(&server)
.await;
}
Health::NotJson => {
Mock::given(method("GET"))
.and(path("/health"))
.respond_with(ResponseTemplate::new(200).set_body_string("nope"))
.mount(&server)
.await;
}
Health::NoKeys => {
mount_health_json(&server, serde_json::json!({"status": "healthy"})).await;
}
Health::WrongTypes => {
mount_health_json(
&server,
serde_json::json!({"version": 1, "tier": [], "edition": {}, "deployment_mode": false}),
)
.await;
}
Health::OversizedBody => {
mount_health_json(
&server,
serde_json::json!({
"version": "10.4.0",
"padding": "x".repeat(MAX_HEALTH_BODY_BYTES + 1),
}),
)
.await;
}
}
let endpoint = match case {
Health::Unreachable => UNREACHABLE_ENDPOINT.to_string(),
_ => server.uri(),
};
let delivered = send_heartbeat(&ctx_for(&endpoint, &checkpoint_url(&server))).await;
assert!(delivered, "case {case:?}: the ping must still be delivered");
let body = only_ping_body(&server).await;
for key in [
"platform_version",
"license_tier",
"edition",
"platform_deployment_mode",
] {
assert!(
body.get(key).is_none(),
"case {case:?}: key {key:?} must be ABSENT, found {:?}",
body.get(key)
);
}
assert_eq!(body["sdk"], "rust");
assert_eq!(body["org_id"], "test-org");
}
}
#[tokio::test]
async fn a_hostile_but_valid_health_value_neither_breaks_nor_escapes_the_serializer() {
let hostile = "10.4.0\", \"org_id\": \"pwned\", \"x\": \"\\\n\ttail";
let server = MockServer::start().await;
mount_health_json(
&server,
serde_json::json!({"version": hostile, "tier": "Community"}),
)
.await;
mount_checkpoint(&server, 200).await;
let delivered = send_heartbeat(&ctx_for(&server.uri(), &checkpoint_url(&server))).await;
assert!(delivered);
let body = only_ping_body(&server).await;
assert_eq!(
body["platform_version"], hostile,
"the value must arrive verbatim, as a value"
);
assert_eq!(
body["org_id"], "test-org",
"an injected key must not have overwritten a real one"
);
assert!(body.get("x").is_none(), "no injected key may appear");
assert_eq!(body["license_tier"], "Community");
}
#[tokio::test]
async fn an_oversized_health_value_is_dropped_without_costing_the_others() {
let server = MockServer::start().await;
mount_health_json(
&server,
serde_json::json!({
"version": "10.4.0",
"tier": "T".repeat(10 * 1024), "edition": "enterprise",
}),
)
.await;
mount_checkpoint(&server, 200).await;
assert!(send_heartbeat(&ctx_for(&server.uri(), &checkpoint_url(&server))).await);
let body = only_ping_body(&server).await;
assert!(
body.get("license_tier").is_none(),
"the oversized value must not reach the wire at all"
);
assert_eq!(body["platform_version"], "10.4.0");
assert_eq!(body["edition"], "enterprise");
let raw = serde_json::to_vec(&body).unwrap();
assert!(raw.len() < 64 * 1024, "ping was {} bytes", raw.len());
}
#[tokio::test]
async fn the_post_is_not_starved_when_health_consumes_its_whole_cap() {
let server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/health"))
.respond_with(
ResponseTemplate::new(200)
.set_body_json(serde_json::json!({"tier": "Enterprise"}))
.set_delay(Duration::from_secs(30)),
)
.mount(&server)
.await;
mount_checkpoint(&server, 200).await;
let started = Instant::now();
let delivered = send_heartbeat(&ctx_for(&server.uri(), &checkpoint_url(&server))).await;
let elapsed = started.elapsed();
assert!(
delivered,
"the POST must still go out after the probe burns its entire cap"
);
let body = only_ping_body(&server).await;
assert!(
body.get("license_tier").is_none(),
"a probe that timed out learned nothing"
);
assert!(
elapsed < HEARTBEAT_TIMEOUT,
"the whole telemetry path took {elapsed:?}, which is outside the shared {HEARTBEAT_TIMEOUT:?} budget"
);
assert!(
elapsed >= HEALTH_BUDGET_CAP,
"the probe should have used its cap; took {elapsed:?}"
);
}
#[tokio::test]
async fn sandbox_mode_tags_the_stream_and_still_relays() {
let server = MockServer::start().await;
mount_health_json(&server, serde_json::json!({"tier": "Community"})).await;
mount_checkpoint(&server, 200).await;
let mut ctx = ctx_for(&server.uri(), &checkpoint_url(&server));
ctx.stream = stream_for_mode(&Mode::Sandbox);
assert!(send_heartbeat(&ctx).await);
let body = only_ping_body(&server).await;
assert_eq!(body["stream"], STREAM_SANDBOX);
assert_eq!(body["license_tier"], "Community");
}
#[tokio::test]
async fn a_rejected_ping_reports_undelivered() {
let server = MockServer::start().await;
mount_health_json(&server, serde_json::json!({"tier": "Community"})).await;
mount_checkpoint(&server, 500).await;
assert!(
!send_heartbeat(&ctx_for(&server.uri(), &checkpoint_url(&server))).await,
"a 5xx from the checkpoint must not count as delivery"
);
}
#[tokio::test]
async fn an_unreachable_checkpoint_reports_undelivered() {
let server = MockServer::start().await;
mount_health_json(&server, serde_json::json!({"tier": "Community"})).await;
let ctx = ctx_for(&server.uri(), &format!("{UNREACHABLE_ENDPOINT}/v1/ping"));
assert!(!send_heartbeat(&ctx).await);
}
#[tokio::test]
async fn a_redirecting_health_endpoint_is_refused_not_followed() {
let upstream = MockServer::start().await;
mount_health_json(
&upstream,
serde_json::json!({"version": "9.9.9", "tier": "LeakedFromElsewhere"}),
)
.await;
let front = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/health"))
.respond_with(
ResponseTemplate::new(302)
.insert_header("Location", format!("{}/health", upstream.uri()).as_str()),
)
.mount(&front)
.await;
let probe = probe_platform_health(&probe_client(), &front.uri(), HEALTH_BUDGET_CAP).await;
assert_eq!(
probe,
HealthProbe::default(),
"a redirect must teach the SDK nothing"
);
assert_eq!(
count_health(&seen(&upstream).await),
0,
"the redirect target must never be contacted"
);
assert_eq!(
count_health(&seen(&front).await),
1,
"the configured endpoint must be contacted exactly once"
);
}
#[tokio::test]
async fn a_redirected_checkpoint_post_is_not_a_delivery() {
let sink = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/sink"))
.respond_with(ResponseTemplate::new(200).set_body_string("{}"))
.mount(&sink)
.await;
let server = MockServer::start().await;
mount_health_json(&server, serde_json::json!({"tier": "Community"})).await;
Mock::given(method("POST"))
.and(path("/v1/ping"))
.respond_with(
ResponseTemplate::new(302)
.insert_header("Location", format!("{}/sink", sink.uri()).as_str()),
)
.mount(&server)
.await;
let delivered = send_heartbeat(&ctx_for(&server.uri(), &checkpoint_url(&server))).await;
assert!(
!delivered,
"a 302 on the checkpoint URL must not be reported as a delivered ping"
);
assert!(
seen(&sink).await.is_empty(),
"the redirect target must never be contacted"
);
}
#[test]
fn the_guard_interval_widens_after_consecutive_failures() {
assert_eq!(guard_interval_for(0), HEARTBEAT_GUARD_INTERVAL);
assert_eq!(guard_interval_for(1), HEARTBEAT_GUARD_INTERVAL * 2);
assert_eq!(guard_interval_for(2), HEARTBEAT_GUARD_INTERVAL * 4);
assert!(guard_interval_for(3) > guard_interval_for(2));
assert_eq!(guard_interval_for(20), HEARTBEAT_INTERVAL);
assert_eq!(guard_interval_for(u32::MAX), HEARTBEAT_INTERVAL);
}
#[test]
fn the_widened_interval_actually_refuses_a_claim_at_the_call_site() {
let _env = TelemetryTestEnv::on();
let just_past_the_base = HEARTBEAT_GUARD_INTERVAL + Duration::from_secs(1);
set_gate_state_for_tests(just_past_the_base, 1);
assert!(
claim_gate_slot().is_none(),
"after a failed attempt the gate must wait longer than the base interval"
);
set_gate_state_for_tests(just_past_the_base, 0);
assert!(
claim_gate_slot().is_some(),
"with no failures the base interval must still let a claim through"
);
}
#[tokio::test]
async fn a_failed_attempt_backs_off_and_a_delivery_resets_it() {
let env = TelemetryTestEnv::on();
let server = MockServer::start().await;
mount_health_json(&server, serde_json::json!({"tier": "Community"})).await;
mount_checkpoint(&server, 500).await;
env.set("AXONFLOW_CHECKPOINT_URL", &checkpoint_url(&server));
assert!(heartbeat_pass_for_tests(&server.uri(), &Mode::Production).await);
assert_eq!(consecutive_failures_for_tests(), 1);
env.advance_past_the_guard();
assert!(heartbeat_pass_for_tests(&server.uri(), &Mode::Production).await);
assert_eq!(consecutive_failures_for_tests(), 2);
env.advance_past_the_guard();
let ok = MockServer::start().await;
mount_health_json(&ok, serde_json::json!({"tier": "Community"})).await;
mount_checkpoint(&ok, 200).await;
env.set("AXONFLOW_CHECKPOINT_URL", &checkpoint_url(&ok));
assert!(heartbeat_pass_for_tests(&ok.uri(), &Mode::Production).await);
assert_eq!(
consecutive_failures_for_tests(),
0,
"a delivered ping must clear the backoff"
);
}
#[tokio::test]
async fn a_pass_stopped_by_a_fresh_stamp_is_not_counted_as_a_failure() {
let env = TelemetryTestEnv::on();
let server = MockServer::start().await;
mount_checkpoint(&server, 200).await;
env.set("AXONFLOW_CHECKPOINT_URL", &checkpoint_url(&server));
std::fs::write(&env.stamp_path, "last_sent=now").expect("write stamp");
assert!(heartbeat_pass_for_tests(&server.uri(), &Mode::Production).await);
assert_eq!(consecutive_failures_for_tests(), 0);
}
#[tokio::test]
async fn telemetry_off_makes_no_request_at_all_not_even_the_health_probe() {
let env = TelemetryTestEnv::on();
let server = MockServer::start().await;
mount_health_json(&server, serde_json::json!({"tier": "Enterprise"})).await;
mount_checkpoint(&server, 200).await;
env.set("AXONFLOW_TELEMETRY", "off");
env.set("AXONFLOW_CHECKPOINT_URL", &checkpoint_url(&server));
let ran = heartbeat_pass_for_tests(&server.uri(), &Mode::Production).await;
assert!(!ran, "the pass must not run at all");
let seen = seen(&server).await;
assert!(
seen.is_empty(),
"AXONFLOW_TELEMETRY=off must suppress the /health probe as well as the ping; saw {seen:?}"
);
}
#[tokio::test]
async fn the_one_hour_guard_suppresses_a_second_pass() {
let env = TelemetryTestEnv::on();
let server = MockServer::start().await;
mount_health_json(&server, serde_json::json!({"tier": "Community"})).await;
mount_checkpoint(&server, 500).await;
env.set("AXONFLOW_CHECKPOINT_URL", &checkpoint_url(&server));
assert!(heartbeat_pass_for_tests(&server.uri(), &Mode::Production).await);
assert!(
!env.stamp_exists(),
"a rejected ping must not move the 7-day stamp"
);
let ran_again = heartbeat_pass_for_tests(&server.uri(), &Mode::Production).await;
assert!(!ran_again, "the second pass must be refused by the guard");
let seen = seen(&server).await;
assert_eq!(
count_pings(&seen),
1,
"exactly one ping should have been attempted; saw {seen:?}"
);
assert_eq!(count_health(&seen), 1, "and exactly one probe");
}
#[tokio::test]
async fn the_gate_reopens_once_the_guard_interval_has_passed() {
let env = TelemetryTestEnv::on();
let server = MockServer::start().await;
mount_health_json(&server, serde_json::json!({"tier": "Community"})).await;
mount_checkpoint(&server, 500).await;
env.set("AXONFLOW_CHECKPOINT_URL", &checkpoint_url(&server));
assert!(heartbeat_pass_for_tests(&server.uri(), &Mode::Production).await);
env.advance_past_the_guard();
assert!(
heartbeat_pass_for_tests(&server.uri(), &Mode::Production).await,
"a long-running process must get another chance after the guard expires — \
this is what the pre-0.10.0 `Once` gate made impossible"
);
assert_eq!(count_pings(&seen(&server).await), 2);
}
#[tokio::test]
async fn a_fresh_seven_day_stamp_suppresses_the_ping() {
let env = TelemetryTestEnv::on();
let server = MockServer::start().await;
mount_health_json(&server, serde_json::json!({"tier": "Community"})).await;
mount_checkpoint(&server, 200).await;
env.set("AXONFLOW_CHECKPOINT_URL", &checkpoint_url(&server));
std::fs::write(&env.stamp_path, "last_sent=now").expect("write stamp");
assert!(heartbeat_pass_for_tests(&server.uri(), &Mode::Production).await);
let seen = seen(&server).await;
assert!(
seen.is_empty(),
"a fresh stamp must stop the pass before the probe as well as the ping; saw {seen:?}"
);
}
#[tokio::test]
async fn a_stampless_environment_still_honours_the_seven_day_cadence() {
let env = TelemetryTestEnv::on();
env.without_a_stamp_path();
let server = MockServer::start().await;
mount_health_json(&server, serde_json::json!({"tier": "Community"})).await;
mount_checkpoint(&server, 200).await;
env.set("AXONFLOW_CHECKPOINT_URL", &checkpoint_url(&server));
assert!(heartbeat_pass_for_tests(&server.uri(), &Mode::Production).await);
for _ in 0..2 {
env.advance_past_the_guard();
heartbeat_pass_for_tests(&server.uri(), &Mode::Production).await;
}
let seen = seen(&server).await;
assert_eq!(
count_pings(&seen),
1,
"a machine with no usable stamp file must still ping at most once per 7 days, \
not once per guard interval; saw {seen:?}"
);
assert_eq!(
count_health(&seen),
1,
"and it must not probe the customer's own platform on every reopened guard"
);
}
#[tokio::test]
async fn the_stamp_moves_only_on_delivery() {
let env = TelemetryTestEnv::on();
{
let server = MockServer::start().await;
mount_health_json(&server, serde_json::json!({"tier": "Community"})).await;
mount_checkpoint(&server, 500).await;
env.set("AXONFLOW_CHECKPOINT_URL", &checkpoint_url(&server));
assert!(heartbeat_pass_for_tests(&server.uri(), &Mode::Production).await);
assert!(!env.stamp_exists());
}
{
env.advance_past_the_guard();
let server = MockServer::start().await;
mount_health_json(&server, serde_json::json!({"tier": "Community"})).await;
mount_checkpoint(&server, 200).await;
env.set("AXONFLOW_CHECKPOINT_URL", &checkpoint_url(&server));
assert!(heartbeat_pass_for_tests(&server.uri(), &Mode::Production).await);
assert!(env.stamp_exists(), "a delivered ping must move the stamp");
}
}
#[tokio::test]
async fn a_gate_slot_is_released_even_when_the_send_task_is_dropped() {
let env = TelemetryTestEnv::on();
let server = MockServer::start().await;
mount_checkpoint(&server, 200).await;
env.set("AXONFLOW_CHECKPOINT_URL", &checkpoint_url(&server));
{
let (_ctx, _slot) = prepare_heartbeat(&server.uri(), &Mode::Production)
.expect("the gate should be open on a fresh state");
}
env.advance_past_the_guard();
assert!(
prepare_heartbeat(&server.uri(), &Mode::Production).is_some(),
"the in-flight claim leaked: telemetry is now suppressed for the process"
);
}
#[tokio::test]
async fn a_second_caller_is_coalesced_onto_the_ping_already_in_flight() {
let _env = TelemetryTestEnv::on();
let held = claim_gate_slot().expect("first caller claims the slot");
assert!(
claim_gate_slot().is_none(),
"a second caller must coalesce onto the in-flight ping, not start another"
);
drop(held);
assert!(claim_gate_slot().is_none());
}
#[test]
fn no_tokio_runtime_skips_the_ping_and_releases_the_claim() {
let _env = TelemetryTestEnv::on();
maybe_send_heartbeat("http://127.0.0.1:9", &Mode::Production);
assert!(
claim_gate_slot().is_some(),
"a call that could not send must leave the gate untouched — otherwise a \
program that constructs its client outside a runtime and then makes \
requests inside one waits a whole hour for a ping it could have sent"
);
}
#[tokio::test]
async fn the_ping_is_still_sent_when_no_stamp_path_is_available() {
let _env = TelemetryTestEnv::on();
let server = MockServer::start().await;
mount_health_json(&server, serde_json::json!({"tier": "Community"})).await;
mount_checkpoint(&server, 200).await;
let mut ctx = ctx_for(&server.uri(), &checkpoint_url(&server));
ctx.stamp_path = None;
let slot = claim_gate_slot().expect("gate open");
gated_send(ctx, slot).await;
let body = only_ping_body(&server).await;
assert_eq!(body["license_tier"], "Community");
}
#[test]
fn the_real_stamp_path_is_under_the_user_cache_dir() {
let _env = TelemetryTestEnv::on();
*STAMP_PATH_OVERRIDE
.lock()
.unwrap_or_else(|e| e.into_inner()) = None;
let path = resolve_stamp_path().expect("a home directory exists on a test machine");
assert!(
path.ends_with("axonflow/rust-telemetry-last-sent"),
"{path:?}"
);
assert!(
path.to_string_lossy()
.contains(if cfg!(target_os = "macos") {
"Library/Caches"
} else {
".cache"
}),
"{path:?}"
);
}
#[tokio::test]
async fn the_first_request_delivers_the_ping() {
let env = TelemetryTestEnv::on();
let server = MockServer::start().await;
mount_health_json(
&server,
serde_json::json!({"version": "10.4.0", "tier": "Community"}),
)
.await;
mount_checkpoint(&server, 200).await;
Mock::given(method("GET"))
.and(path("/api/v1/connectors"))
.respond_with(
ResponseTemplate::new(200).set_body_json(serde_json::json!({"connectors": []})),
)
.mount(&server)
.await;
env.set("AXONFLOW_CHECKPOINT_URL", &checkpoint_url(&server));
let client =
crate::client::AxonFlowClient::new(crate::config::AxonFlowConfig::new(server.uri()))
.expect("client");
tokio::time::sleep(Duration::from_millis(200)).await;
assert_eq!(
count_pings(&seen(&server).await),
0,
"constructing a client must not ping — the heartbeat is a claim about USAGE"
);
client.list_connectors().await.expect("connectors call");
await_ping(&server, 1).await;
let body = only_ping_body(&server).await;
assert_eq!(body["platform_version"], "10.4.0");
assert_eq!(body["license_tier"], "Community");
}
#[tokio::test]
async fn a_request_re_triggers_the_heartbeat_after_the_guard_expires() {
let env = TelemetryTestEnv::on();
let server = MockServer::start().await;
mount_health_json(&server, serde_json::json!({"tier": "Community"})).await;
mount_checkpoint(&server, 200).await;
Mock::given(method("GET"))
.and(path("/api/v1/connectors"))
.respond_with(
ResponseTemplate::new(200).set_body_json(serde_json::json!({"connectors": []})),
)
.mount(&server)
.await;
env.set("AXONFLOW_CHECKPOINT_URL", &checkpoint_url(&server));
let client =
crate::client::AxonFlowClient::new(crate::config::AxonFlowConfig::new(server.uri()))
.expect("client");
client.list_connectors().await.expect("connectors call");
await_ping(&server, 1).await;
env.advance_past_the_heartbeat_interval();
client.list_connectors().await.expect("connectors call");
await_ping(&server, 2).await;
assert_eq!(
count_pings(&seen(&server).await),
2,
"the request site must have re-evaluated the gate"
);
}
#[tokio::test]
async fn a_request_does_not_ping_while_the_guard_is_warm() {
let env = TelemetryTestEnv::on();
let server = MockServer::start().await;
mount_health_json(&server, serde_json::json!({"tier": "Community"})).await;
mount_checkpoint(&server, 200).await;
Mock::given(method("GET"))
.and(path("/api/v1/connectors"))
.respond_with(
ResponseTemplate::new(200).set_body_json(serde_json::json!({"connectors": []})),
)
.mount(&server)
.await;
env.set("AXONFLOW_CHECKPOINT_URL", &checkpoint_url(&server));
let client =
crate::client::AxonFlowClient::new(crate::config::AxonFlowConfig::new(server.uri()))
.expect("client");
client.list_connectors().await.expect("connectors call");
await_ping(&server, 1).await;
for _ in 0..4 {
client.list_connectors().await.expect("connectors call");
}
tokio::time::sleep(Duration::from_millis(200)).await;
assert_eq!(
count_pings(&seen(&server).await),
1,
"the warm guard must suppress every subsequent request's ping"
);
}
async fn await_ping(server: &MockServer, n: usize) {
let deadline = Instant::now() + Duration::from_secs(10);
loop {
if count_pings(&seen(server).await) >= n {
return;
}
assert!(
Instant::now() < deadline,
"timed out waiting for ping #{n}; saw {:?}",
seen(server).await
);
tokio::time::sleep(Duration::from_millis(25)).await;
}
}
#[tokio::test]
async fn a_failed_probe_is_visible_in_the_debug_log_without_the_value() {
let logs = LogCapture::arm();
let server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/health"))
.respond_with(ResponseTemplate::new(503).set_body_string("SECRET-BODY-MARKER"))
.mount(&server)
.await;
let _ = probe_platform_health(&probe_client(), &server.uri(), HEALTH_BUDGET_CAP).await;
let server2 = MockServer::start().await;
mount_health_json(
&server2,
serde_json::json!({
"edition": format!("SECRET-EDITION-MARKER{}", "z".repeat(MAX_RELAYED_VALUE_LEN)),
}),
)
.await;
let _ = probe_platform_health(&probe_client(), &server2.uri(), HEALTH_BUDGET_CAP).await;
let _ = probe_platform_health(&probe_client(), UNREACHABLE_ENDPOINT, HEALTH_BUDGET_CAP).await;
let logs = logs.contents();
assert!(
logs.contains("503"),
"the operator must be able to see WHY the probe learned nothing; logs:\n{logs}"
);
assert!(
logs.contains("'edition'") && logs.contains(&MAX_RELAYED_VALUE_LEN.to_string()),
"the dropped field and its cap must be named; logs:\n{logs}"
);
assert!(
logs.contains("/health probe failed"),
"an unreachable platform must be visible; logs:\n{logs}"
);
assert!(
!logs.contains("SECRET-BODY-MARKER"),
"a non-2xx response body leaked into the log:\n{logs}"
);
assert!(
!logs.contains("SECRET-EDITION-MARKER"),
"an over-long relayed value leaked into the log:\n{logs}"
);
}
fn shipped_sources() -> Vec<(String, String)> {
fn walk(dir: &std::path::Path, out: &mut Vec<(String, String)>) {
for entry in std::fs::read_dir(dir).expect("read src") {
let entry = entry.expect("dir entry");
let p = entry.path();
if p.is_dir() {
walk(&p, out);
} else if p.extension().and_then(|e| e.to_str()) == Some("rs") {
let name = p.file_name().unwrap().to_string_lossy().to_string();
if name.ends_with("_tests.rs") {
continue;
}
let rel = p
.strip_prefix(env!("CARGO_MANIFEST_DIR"))
.unwrap_or(&p)
.to_string_lossy()
.to_string();
out.push((rel, std::fs::read_to_string(&p).expect("read source")));
}
}
}
let mut out = Vec::new();
walk(
&std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("src"),
&mut out,
);
assert!(out.len() > 5, "source walk found suspiciously little");
out
}
fn squashed(src: &str) -> String {
src.chars()
.filter(|c| !c.is_whitespace() && *c != '<' && *c != '>')
.collect()
}
#[test]
fn no_http_send_outside_the_dispatch_funnel() {
const ISSUING_TOKENS: &[&str] = &[
".send()",
".execute(",
"Client::execute(",
"reqwest::get(",
"RequestBuilder::send(",
];
for (file, src) in shipped_sources() {
let squashed = squashed(&src);
let issued: usize = ISSUING_TOKENS
.iter()
.map(|t| squashed.matches(t).count())
.sum();
let expected = if file == "src/client.rs" {
1 } else if file == "src/heartbeat.rs" {
2 } else {
0
};
assert_eq!(
issued, expected,
"{file} issues {issued} HTTP request(s), expected {expected}. Every SDK request must \
go through `AxonFlowClient::dispatch` so the heartbeat gate is consulted; the \
telemetry path is the deliberate exception and builds its own client. Note the \
match is over raw source with whitespace stripped, so an occurrence inside a \
comment, doc comment or string literal counts too."
);
}
}
#[test]
fn the_telemetry_path_builds_exactly_one_http_client() {
const CONSTRUCTING_TOKENS: &[&str] = &[
"Client::builder()",
"ClientBuilder::new()",
"reqwest::Client::new()",
"reqwest::Client::default()",
"Client::default()",
];
for (file, src) in shipped_sources() {
let squashed = squashed(&src);
let built: usize = CONSTRUCTING_TOKENS
.iter()
.map(|t| squashed.matches(t).count())
.sum();
let expected = if file == "src/client.rs" {
2 } else if file == "src/heartbeat.rs" {
1 } else {
0
};
assert_eq!(
built, expected,
"{file} builds {built} reqwest client(s), expected {expected}"
);
}
}
struct RegistryGuard {
_guard: MutexGuard<'static, ()>,
previous: std::collections::BTreeSet<String>,
}
impl RegistryGuard {
fn take() -> Self {
let guard = telemetry_lock();
Self {
_guard: guard,
previous: super::reset_adapter_registry_for_tests(),
}
}
}
impl Drop for RegistryGuard {
fn drop(&mut self) {
super::restore_adapter_registry_for_tests(std::mem::take(&mut self.previous));
}
}
#[test]
fn features_is_empty_by_default() {
let _g = RegistryGuard::take();
assert!(super::registered_features().is_empty());
}
#[test]
fn a_registered_adapter_reaches_the_features_array() {
let _g = RegistryGuard::take();
super::register_adapter("langchain");
assert_eq!(super::registered_features(), vec!["adapter:langchain"]);
}
#[test]
fn an_unregistered_adapter_does_not() {
let _g = RegistryGuard::take();
super::register_adapter("langchain");
let features = super::registered_features();
assert!(!features.contains(&"adapter:langgraph".to_string()));
assert_eq!(features, vec!["adapter:langchain"]);
}
#[test]
fn names_are_lowercased_trimmed_deduplicated_and_sorted() {
let _g = RegistryGuard::take();
super::register_adapter("LangChain");
super::register_adapter(" langchain\t\n");
super::register_adapter("LANGCHAIN");
super::register_adapter("langgraph");
super::register_adapter("some-framework-we-have-never-heard-of");
assert_eq!(
super::registered_features(),
vec![
"adapter:langchain",
"adapter:langgraph",
"adapter:some-framework-we-have-never-heard-of",
]
);
}
#[test]
fn an_unusable_name_is_refused_silently() {
let _g = RegistryGuard::take();
for bad in ["", " ", "\t\n"] {
super::register_adapter(bad);
}
assert!(super::registered_features().is_empty());
}
#[test]
fn the_relayed_value_cap_keeps_64_bytes_and_drops_65_whole() {
let _g = RegistryGuard::take();
super::register_adapter(&"a".repeat(64));
assert_eq!(
super::registered_features(),
vec![format!("adapter:{}", "a".repeat(64))]
);
super::reset_adapter_registry_for_tests();
super::register_adapter(&"a".repeat(65));
assert!(
super::registered_features().is_empty(),
"a truncated adapter name is a name nothing is running, and the receiver \
would record it as a real value"
);
}
#[test]
fn the_cap_counts_bytes_not_characters() {
let _g = RegistryGuard::take();
let name = "é".repeat(33);
assert!(
name.chars().count() <= 64,
"fixture premise: under the cap by CHARACTERS"
);
assert!(name.len() > 64, "fixture premise: over the cap by BYTES");
super::register_adapter(&name);
assert!(super::registered_features().is_empty());
}
#[test]
fn the_features_array_is_bounded_to_32_entries() {
let _g = RegistryGuard::take();
for i in 0..40 {
super::register_adapter(&format!("{i:02}"));
}
let features = super::registered_features();
assert_eq!(features.len(), 32);
assert_eq!(super::MAX_FEATURES, 32);
assert_eq!(features[0], "adapter:00");
assert_eq!(features[31], "adapter:31");
}
#[test]
fn bound_features_drops_an_overlong_entry_whole() {
assert!("adapter:".len() + super::MAX_RELAYED_VALUE_LEN <= super::MAX_FEATURE_BYTES);
assert_eq!(super::MAX_FEATURE_BYTES, 128);
let within = format!("adapter:{}", "b".repeat(128 - "adapter:".len()));
let over = format!("{within}b");
assert_eq!(within.len(), 128);
assert_eq!(
super::bound_features(vec![within.clone(), over]),
vec![within]
);
}
#[tokio::test]
async fn a_registered_adapter_reaches_the_wire() {
let env = TelemetryTestEnv::on();
let server = MockServer::start().await;
mount_health_json(&server, serde_json::json!({"tier": "Community"})).await;
mount_checkpoint(&server, 200).await;
env.set("AXONFLOW_CHECKPOINT_URL", &checkpoint_url(&server));
super::register_adapter("litellm");
assert!(heartbeat_pass_for_tests(&server.uri(), &Mode::Production).await);
await_ping(&server, 1).await;
let body = only_ping_body(&server).await;
assert_eq!(
body["features"],
serde_json::json!(["adapter:litellm"]),
"the registry is the only producer of this array"
);
}
#[tokio::test]
async fn the_cold_path_send_is_awaited_not_spawned() {
let env = TelemetryTestEnv::on();
let server = MockServer::start().await;
Mock::given(method("GET"))
.and(path("/health"))
.respond_with(
ResponseTemplate::new(200)
.set_delay(Duration::from_millis(300))
.set_body_json(serde_json::json!({"tier": "Community"})),
)
.mount(&server)
.await;
mount_checkpoint(&server, 200).await;
Mock::given(method("GET"))
.and(path("/api/v1/connectors"))
.respond_with(
ResponseTemplate::new(200).set_body_json(serde_json::json!({"connectors": []})),
)
.mount(&server)
.await;
env.set("AXONFLOW_CHECKPOINT_URL", &checkpoint_url(&server));
let client =
crate::client::AxonFlowClient::new(crate::config::AxonFlowConfig::new(server.uri()))
.expect("client");
assert_eq!(
count_pings(&seen(&server).await),
0,
"constructing a client must not ping"
);
client.list_connectors().await.expect("connectors call");
assert_eq!(
count_pings(&seen(&server).await),
1,
"the cold-path send must be AWAITED on the caller's task, not spawned: a spawned \
ping is lost when the process exits, measured at 1 delivery in 12 for a compiled \
one-call binary (runtime-e2e/fast_exit_delivery)"
);
}