use otelite_core::telemetry::trace::{Span, SpanKind, SpanStatus, StatusCode as SpanStatusCode};
use otelite_storage::sqlite::SqliteBackend;
use otelite_storage::{StorageBackend, StorageConfig};
const W0: i64 = 1_787_529_600_000_000_000;
#[derive(serde::Deserialize)]
struct FixtureSpan {
trace: String,
id: String,
name: String,
#[serde(default)]
hour: Option<i64>,
#[serde(default)]
min: Option<i64>,
#[serde(default)]
sec: Option<i64>,
dur_ms: i64,
attrs: std::collections::HashMap<String, String>,
}
fn fixture() -> serde_json::Value {
let raw = std::fs::read_to_string(concat!(
env!("CARGO_MANIFEST_DIR"),
"/../otelite-api/tests/fixtures/capability_parity_v1.json"
))
.unwrap();
serde_json::from_str(&raw).unwrap()
}
async fn build_fixture_db(dir: &std::path::Path) {
let config = StorageConfig::default().with_data_dir(dir.to_path_buf());
let mut storage = SqliteBackend::new(config);
storage.initialize().await.unwrap();
let fx = fixture();
for value in fx["spans"].as_array().unwrap() {
let f: FixtureSpan = serde_json::from_value(value.clone()).unwrap();
let off_ns = match (f.hour, f.min, f.sec) {
(Some(h), _, _) => h * 3_600_000_000_000,
(None, Some(m), _) => m * 60_000_000_000,
(None, None, Some(x)) => x * 1_000_000_000,
_ => 0,
};
let span = Span {
resource: None,
trace_id: f.trace.clone(),
span_id: f.id.clone(),
parent_span_id: None,
name: f.name.clone(),
kind: SpanKind::Internal,
start_time: W0 + off_ns,
end_time: W0 + off_ns + f.dur_ms * 1_000_000,
attributes: f.attrs.clone(),
events: vec![],
status: SpanStatus {
code: SpanStatusCode::Ok,
message: None,
},
};
storage.write_span(&span).await.unwrap();
}
drop(storage);
}
fn run_cli(data_dir: &std::path::Path, extra_args: &[&str]) -> serde_json::Value {
let home = tempfile::TempDir::new().unwrap();
let mut args: Vec<String> = vec![
"capabilities".into(),
"--data-dir".into(),
data_dir.to_string_lossy().into_owned(),
];
args.extend(extra_args.iter().map(|a| a.to_string()));
let output = assert_cmd::Command::cargo_bin("otelite")
.unwrap()
.args(&args)
.env("HOME", home.path())
.output()
.unwrap();
assert!(
output.status.success(),
"cli failed: {}",
String::from_utf8_lossy(&output.stderr)
);
serde_json::from_slice(&output.stdout).unwrap()
}
#[tokio::test]
async fn cli_capabilities_matches_frozen_fixture() {
let temp = tempfile::TempDir::new().unwrap();
build_fixture_db(temp.path()).await;
let fx = fixture();
let args: Vec<&str> = fx["cli_args"]
.as_array()
.unwrap()
.iter()
.map(|v| v.as_str().unwrap())
.collect();
let got = run_cli(temp.path(), &args);
assert_eq!(
got, fx["cli"],
"CLI capabilities output drifted from frozen fixture v1"
);
}
#[tokio::test]
async fn cli_capabilities_empty_window() {
let temp = tempfile::TempDir::new().unwrap();
build_fixture_db(temp.path()).await;
let fx = fixture();
let args: Vec<&str> = fx["cli_args_empty"]
.as_array()
.unwrap()
.iter()
.map(|v| v.as_str().unwrap())
.collect();
let got = run_cli(temp.path(), &args);
assert_eq!(got, fx["cli_empty"]);
}
#[tokio::test]
async fn cli_capabilities_pretty_prints_vocabulary() {
let temp = tempfile::TempDir::new().unwrap();
build_fixture_db(temp.path()).await;
let home = tempfile::TempDir::new().unwrap();
let output = assert_cmd::Command::cargo_bin("otelite")
.unwrap()
.args([
"capabilities",
"--data-dir",
&temp.path().to_string_lossy(),
"--start",
"2026-08-24",
"--end",
"2026-08-26",
])
.env("HOME", home.path())
.output()
.unwrap();
assert!(output.status.success());
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(stdout.contains("openai/gpt-4o-mini"));
assert!(stdout.contains("sparse/invalid"));
assert!(stdout.contains("available/degenerate"));
assert!(stdout.contains("absent/not_assessed/unavailable"));
assert!(stdout.contains("1 duplicate deliveries collapsed"));
}