#[test]
fn tutorial_scenarios_use_real_kinds() {
let kinds: std::collections::HashSet<_> = kshana::api::list_scenario_kinds()
.iter()
.map(|m| m.name)
.collect();
for k in [
"clock",
"orbit",
"integrity",
"spoof",
"hybrid",
"inertial",
"timetransfer",
"gnss-sim",
] {
assert!(
kinds.contains(k),
"tutorial kind {k} missing from list_scenario_kinds"
);
}
}
#[test]
fn annotated_tutorial_scenarios_run() {
for src in [
include_str!("../docs/tutorials/scenarios/clock.toml"),
include_str!("../docs/tutorials/scenarios/orbit.toml"),
include_str!("../docs/tutorials/scenarios/integrity.toml"),
include_str!("../docs/tutorials/scenarios/security.toml"),
include_str!("../docs/tutorials/scenarios/hybrid.toml"),
include_str!("../docs/tutorials/scenarios/inertial.toml"),
include_str!("../docs/tutorials/scenarios/timetransfer.toml"),
include_str!("../docs/tutorials/scenarios/gnss-sim.toml"),
] {
let out = kshana::api::run_toml(src).expect("tutorial scenario runs");
assert!(out.json.starts_with('{'));
assert!(out.svg.starts_with("<svg"));
assert!(!out.summary.is_empty());
}
}
#[test]
fn tutorial1_orbit_headline_holds() {
let out =
kshana::api::run_toml(include_str!("../docs/tutorials/scenarios/orbit.toml")).unwrap();
let v: serde_json::Value = serde_json::from_str(&out.json).unwrap();
let g = &v["geometry"];
let pdop = g["best_pdop"].as_f64().unwrap();
assert!((pdop - 1.071).abs() < 0.01, "best PDOP {pdop}");
assert!((g["best_position_sigma_m"].as_f64().unwrap() - pdop).abs() < 1e-6);
assert_eq!(g["samples_total"].as_u64().unwrap(), 361);
assert!(out.summary.contains("345/361"));
}
#[test]
fn tutorial1_satellites_are_in_the_gps_meo_shell() {
let sp3 =
kshana::api::export_sp3(include_str!("../docs/tutorials/scenarios/orbit.toml")).unwrap();
let line = sp3.lines().find(|l| l.starts_with("PG01")).unwrap();
let xs: Vec<f64> = line[4..]
.split_whitespace()
.take(3)
.map(|s| s.parse().unwrap())
.collect();
let r = (xs[0] * xs[0] + xs[1] * xs[1] + xs[2] * xs[2]).sqrt();
assert!(
(26000.0..27200.0).contains(&r),
"PG01 geocentric radius {r} km not in the GPS MEO shell"
);
}
#[test]
fn tutorial2_clock_holdover_holds() {
let out =
kshana::api::run_toml(include_str!("../docs/tutorials/scenarios/clock.toml")).unwrap();
let v: serde_json::Value = serde_json::from_str(&out.json).unwrap();
let q = v["quantum"]["fom"]["holdover_s"].as_f64().unwrap();
let c = v["classical"]["fom"]["holdover_s"].as_f64().unwrap();
assert!(q >= c, "optical must hold >= CSAC");
assert!((q - 6600.0).abs() < 1.0, "optical holds the full outage");
assert!(
(2000.0..3200.0).contains(&c),
"CSAC holdover {c}s off the SP-1065 prediction band"
);
}
#[test]
fn tutorial3_spoof_analytic_matches_montecarlo() {
let out =
kshana::api::run_toml(include_str!("../docs/tutorials/scenarios/security.toml")).unwrap();
let v: serde_json::Value = serde_json::from_str(&out.json).unwrap();
for side in ["quantum", "classical"] {
let a = v[side]["detection"]["analytic_pmd"].as_f64().unwrap();
let m = v[side]["detection"]["mc_pmd"].as_f64().unwrap();
assert!(
(a - m).abs() < 0.05,
"{side} analytic {a} vs MC {m} disagree"
);
}
assert!(v["quantum"]["security_fom"].as_f64().unwrap() > 0.9);
}
#[test]
fn tutorial_reproducibility_is_bit_stable() {
let s = include_str!("../docs/tutorials/scenarios/clock.toml");
let a = kshana::api::run_toml(s).unwrap().json;
let b = kshana::api::run_toml(s).unwrap().json;
assert_eq!(a, b, "deterministic engine");
}