use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
const ASSET: &str = "apps/aion-ops-console/src/features/authoring/assets/awl-guide.json";
fn repo_root() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR"))
.parent()
.and_then(Path::parent)
.map_or_else(
|| unreachable!("crates/aion-cli always has two ancestors"),
Path::to_path_buf,
)
}
fn aion_binary() -> Option<PathBuf> {
let mut path = std::env::current_exe().ok()?;
path.pop();
if path.ends_with("deps") {
path.pop();
}
let candidate = path.join(if cfg!(windows) { "aion.exe" } else { "aion" });
candidate.is_file().then_some(candidate)
}
#[test]
fn the_console_asset_is_what_the_cli_renders_today() {
let asset_path = repo_root().join(ASSET);
let checked_in = fs::read_to_string(&asset_path).unwrap_or_default();
assert!(
!checked_in.is_empty(),
"{ASSET} is missing or empty; regenerate it with `aion awl guide --json > {ASSET}`"
);
let found = aion_binary();
assert!(
found.is_some(),
"the `aion` binary is not built beside this test, so the asset was not compared; run \
`cargo test -p aion-cli`, which builds it"
);
let Some(binary) = found else {
return;
};
let rendered = Command::new(&binary)
.args(["awl", "guide", "--json"])
.output();
let Ok(rendered) = rendered else {
unreachable!("the aion binary beside this test must be runnable");
};
assert!(
rendered.status.success(),
"`aion awl guide --json` failed: {}",
String::from_utf8_lossy(&rendered.stderr)
);
let rendered = String::from_utf8_lossy(&rendered.stdout).into_owned();
assert_eq!(
checked_in, rendered,
"{ASSET} no longer matches the language's glossary. Regenerate it:\n aion awl guide \
--json > {ASSET}"
);
}
fn inventory_ids<'a>(parsed: &'a serde_json::Value, key: &str, fields: &[&str]) -> Vec<&'a str> {
let rows = parsed[key].as_array();
assert!(
rows.is_some_and(|rows| !rows.is_empty()),
"{ASSET} carries no {key}"
);
let rows = rows.map_or(&[][..], Vec::as_slice);
for row in rows {
for field in fields {
assert!(
row[*field].as_str().is_some_and(|text| !text.is_empty()),
"a {key} row is missing `{field}`: {row}"
);
}
}
rows.iter().filter_map(|row| row["id"].as_str()).collect()
}
#[test]
fn the_console_asset_is_readable_json_with_the_shape_the_panel_uses() {
let asset_path = repo_root().join(ASSET);
let checked_in = fs::read_to_string(&asset_path).unwrap_or_default();
let parsed: serde_json::Value =
serde_json::from_str(&checked_in).unwrap_or(serde_json::Value::Null);
let form_ids = inventory_ids(&parsed, "forms", &["id", "label", "description"]);
let position_ids = inventory_ids(&parsed, "positions", &["id", "label"]);
let relation_ids = inventory_ids(&parsed, "relations", &["id", "label", "description"]);
assert!(
parsed["reference_text"]
.as_str()
.is_some_and(|text| text.contains("## 1. Document structure")),
"{ASSET} does not carry the language reference"
);
let entries = parsed["entries"].as_array().cloned().unwrap_or_default();
assert!(!entries.is_empty(), "{ASSET} carries no entries");
let known: Vec<&str> = entries
.iter()
.filter_map(|entry| entry["word"].as_str())
.collect();
for entry in &entries {
assert_entry_reads(entry);
assert_entry_is_placed_in_the_grammar(entry, &form_ids, &position_ids);
assert_entry_relations_name_real_words(entry, &relation_ids, &known);
}
}
fn assert_entry_reads(entry: &serde_json::Value) {
for field in ["word", "kind", "summary", "citation"] {
assert!(
entry[field].as_str().is_some_and(|text| !text.is_empty()),
"an entry is missing `{field}`: {entry}"
);
}
let examples = entry["examples"].as_array().cloned().unwrap_or_default();
assert!(
!examples.is_empty(),
"an entry carries no examples: {entry}"
);
for example in &examples {
assert!(
example.as_str().is_some_and(|text| !text.is_empty()),
"an entry carries an empty example: {entry}"
);
}
assert!(
entry["section"]["number"].as_u64().is_some(),
"an entry is missing its section number: {entry}"
);
assert!(
entry["section"]["title"]
.as_str()
.is_some_and(|title| !title.is_empty()),
"an entry is missing its section title: {entry}"
);
}
fn assert_entry_is_placed_in_the_grammar(
entry: &serde_json::Value,
form_ids: &[&str],
position_ids: &[&str],
) {
assert!(
entry["form"]
.as_str()
.is_some_and(|form| form_ids.contains(&form)),
"an entry is missing its form, or names one the taxonomy does not carry: {entry}"
);
let stands = entry["positions"].as_array().cloned().unwrap_or_default();
assert!(
!stands.is_empty(),
"an entry stands in no position: {entry}"
);
for position in &stands {
assert!(
position
.as_str()
.is_some_and(|position| position_ids.contains(&position)),
"an entry stands in a position the inventory does not carry: {entry}"
);
}
}
fn assert_entry_relations_name_real_words(
entry: &serde_json::Value,
relation_ids: &[&str],
known: &[&str],
) {
for relation in relation_ids {
let related = entry[*relation].as_array();
assert!(
related.is_some(),
"an entry is missing its `{relation}` relation: {entry}"
);
for id in related.map_or(&[][..], Vec::as_slice) {
assert!(
id.as_str().is_some_and(|id| known.contains(&id)),
"an entry's `{relation}` names a word this document has no entry for, which the \
console would render as a dead link: {entry}"
);
}
}
assert!(
entry["at_document_level"].as_bool().is_some(),
"an entry does not say whether the document is what encloses it: {entry}"
);
}