use std::collections::BTreeMap;
use provide_telemetry::testing::{acquire_test_state_lock, config_defaults_probe};
use provide_telemetry::{setup_telemetry, shutdown_telemetry, TelemetryConfig};
use serde_json::Value;
const LANGUAGE: &str = "rust";
#[derive(Debug)]
struct Expected {
type_name: String,
default: String,
applicable: bool,
probe_visible: bool,
}
fn spec_document() -> Value {
let path = concat!(env!("CARGO_MANIFEST_DIR"), "/../spec/telemetry-api.yaml");
let text = std::fs::read_to_string(path).expect("spec/telemetry-api.yaml should be readable");
serde_yaml::from_str(&text).expect("spec/telemetry-api.yaml should parse")
}
fn render_default(value: &Value) -> String {
match value {
Value::Null => String::new(),
Value::String(text) => text.clone(),
other => other.to_string(),
}
}
fn expected_entries() -> BTreeMap<String, Expected> {
let spec = spec_document();
let groups = spec["config_defaults"]
.as_object()
.expect("config_defaults should be a mapping");
let mut expected = BTreeMap::new();
for entries in groups.values() {
for entry in entries.as_array().expect("each group should be a list") {
let env = entry["env"]
.as_str()
.expect("every entry should name an env var");
let overrides = &entry["overrides"][LANGUAGE];
let applicable = entry["applicability"]
.as_array()
.is_some_and(|langs| langs.iter().any(|lang| lang == LANGUAGE));
expected.insert(
env.to_string(),
Expected {
type_name: render_default(pick(overrides, "type", &entry["type"])),
default: render_default(pick(overrides, "default", &entry["default"])),
applicable,
probe_visible: overrides["probe_visible"].as_bool().unwrap_or(true),
},
);
}
}
expected
}
fn pick<'a>(overrides: &'a Value, key: &str, fallback: &'a Value) -> &'a Value {
match &overrides[key] {
Value::Null => fallback,
override_value => override_value,
}
}
fn defaults_match(type_name: &str, expected: &str, observed: &str) -> bool {
if expected == observed {
return true;
}
if type_name != "int" && type_name != "float" {
return false;
}
match (expected.parse::<f64>(), observed.parse::<f64>()) {
(Ok(left), Ok(right)) => left == right,
_ => false,
}
}
#[test]
fn rust_config_probe_matches_applicable_contract_defaults() {
let expected = expected_entries();
let names: Vec<String> = expected.keys().cloned().collect();
let observed = config_defaults_probe(&names);
let mut errors: Vec<String> = Vec::new();
for (env, want) in &expected {
let got = &observed[env];
if !want.probe_visible {
continue;
}
if want.applicable != got.applicable {
errors.push(format!(
"{env}: spec says applicable={} for {LANGUAGE}, but the SDK reports {}",
want.applicable, got.applicable
));
continue;
}
if !want.applicable {
continue;
}
if want.type_name != got.type_name {
errors.push(format!(
"{env}: type spec={:?} observed={:?}",
want.type_name, got.type_name
));
}
if !defaults_match(&want.type_name, &want.default, &got.default) {
errors.push(format!(
"{env}: default spec={:?} observed={:?}",
want.default, got.default
));
}
}
assert!(
errors.is_empty(),
"config contract drift:\n {}",
errors.join("\n ")
);
assert!(
expected.values().any(|want| !want.applicable),
"the contract must still mark something inapplicable to Rust, or this test proves nothing"
);
}
#[test]
fn invalid_shared_config_is_rejected_without_clamping() {
let _guard = acquire_test_state_lock();
shutdown_telemetry(None).expect("pre-test shutdown should succeed");
let mut config = TelemetryConfig::default();
config.sampling.logs_rate = 1.01;
let err = setup_telemetry(Some(config)).expect_err("a rate above one must be rejected");
assert!(
err.message.contains("PROVIDE_SAMPLING_LOGS_RATE"),
"the message should name the variable a caller would recognise: {}",
err.message
);
assert!(
provide_telemetry::get_runtime_config().is_none(),
"a rejected config must not be installed, clamped or otherwise"
);
shutdown_telemetry(None).expect("shutdown should succeed");
}