#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
mod support;
use std::path::{Path, PathBuf};
use chicago_tdd_tools::cli_proof::CliHarness;
use support::scaffold_pack;
use tempfile::TempDir;
fn ggen_bin() -> std::path::PathBuf {
if let Ok(path) = std::env::var("CARGO_BIN_EXE_ggen") {
let p = std::path::PathBuf::from(path);
if p.exists() {
return p;
}
}
let target_root = std::env::var_os("CARGO_TARGET_DIR")
.map(std::path::PathBuf::from)
.or_else(|| {
let manifest_dir =
std::env::var_os("CARGO_MANIFEST_DIR").map(std::path::PathBuf::from)?;
let mut dir: &std::path::Path = manifest_dir.as_path();
loop {
if dir.join("Cargo.lock").exists() {
return Some(dir.join("target"));
}
match dir.parent() {
Some(p) => dir = p,
None => return None,
}
}
});
if let Some(target) = target_root {
for profile in &["debug", "release"] {
let candidate = target.join(profile).join("ggen");
if candidate.is_file() {
return candidate;
}
let candidate_exe = target.join(profile).join("ggen.exe");
if candidate_exe.is_file() {
return candidate_exe;
}
}
}
panic!(
"could not resolve the `ggen` binary: CARGO_BIN_EXE_ggen unset and no \
target/debug/ggen found; build it with `cargo build -p ggen-cli-lib --bin ggen`"
);
}
fn packs_dir() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR")).join("../../packs")
}
fn scaffold_pack_project(pack_name: &str) -> (TempDir, PathBuf) {
scaffold_pack(&packs_dir().join(pack_name))
}
#[test]
fn wasm4pm_compat_pack_syncs() {
let (_dir, project) = scaffold_pack_project("wasm4pm-compat-pack");
let output = CliHarness::from_path(ggen_bin())
.args(["sync", "run"])
.current_dir(&project)
.run()
.expect("run sync");
let _ = output.assert_success();
let events = project.join("src/wasm4pm_compat_events.rs");
assert!(
events.is_file(),
"pack must write src/wasm4pm_compat_events.rs"
);
let events_src = std::fs::read_to_string(&events).expect("events.rs");
assert!(
events_src.contains("pub enum EmittedEventType"),
"{events_src}"
);
assert!(events_src.contains("GraphUnionHashed"), "{events_src}");
assert!(
events_src.contains("\"graph_union_hashed\""),
"{events_src}"
);
assert!(
events_src.contains("pub fn emit_pack_lock_verified"),
"{events_src}"
);
assert!(
events_src.contains("use wasm4pm_compat::ocel::"),
"{events_src}"
);
assert!(events_src.contains("OCELEvent"), "{events_src}");
assert!(events_src.contains("OCELRelationship"), "{events_src}");
let doc = std::fs::read_to_string(project.join("docs/wasm4pm_compat_emission.md"))
.expect("emission doc");
for name in [
"graph_union_hashed",
"pack_lock_verified",
"receipt_chained",
] {
assert!(doc.contains(name), "doc missing {name}: {doc}");
}
let lock = std::fs::read_to_string(project.join("ggen.lock")).expect("ggen.lock");
assert!(lock.contains("[packs.wasm4pm-compat-pack]"), "lock: {lock}");
assert!(lock.contains("content_hash = \"blake3:"), "lock: {lock}");
let _ = CliHarness::from_path(ggen_bin())
.args(["graph", "validate"])
.current_dir(&project)
.run()
.expect("run graph validate")
.assert_success();
let before = std::fs::read_to_string(&events).expect("events.rs");
let _ = CliHarness::from_path(ggen_bin())
.args(["sync", "run"])
.current_dir(&project)
.run()
.expect("second sync")
.assert_success();
let after = std::fs::read_to_string(&events).expect("events.rs after");
assert_eq!(before, after, "second sync must leave outputs unchanged");
let lock2 = std::fs::read_to_string(project.join("ggen.lock")).expect("ggen.lock 2");
assert_eq!(
lock, lock2,
"ggen.lock must be byte-identical across identical runs"
);
}
#[test]
fn lsp_max_pack_syncs() {
let (_dir, project) = scaffold_pack_project("lsp-max-pack");
let _ = CliHarness::from_path(ggen_bin())
.args(["sync", "run"])
.current_dir(&project)
.run()
.expect("run sync")
.assert_success();
assert!(
!project.join("generated").exists(),
"no generated/ dir allowed"
);
let unwrap_rule = project.join("rules/lsp_max_lspmax_unwrap_001.toml");
assert!(unwrap_rule.is_file(), "expected {}", unwrap_rule.display());
let toml = std::fs::read_to_string(&unwrap_rule).expect("rule toml");
assert!(
toml.contains("id = \"LSPMAX-UNWRAP-001\""),
"rule toml: {toml}"
);
assert!(toml.contains("severity = \"error\""), "rule toml: {toml}");
assert!(
toml.contains(r"pattern = '\.unwrap\(\)|\.expect\('"),
"rule toml: {toml}"
);
assert!(
toml.contains("RulePackServer handlers return ClassifiedFindings"),
"rationale must flow from the ontology: {toml}"
);
assert!(
project
.join("rules/lsp_max_lspmax_rawjson_002.toml")
.is_file(),
"second rule file missing"
);
assert!(
project
.join("rules/lsp_max_lspmax_wallclock_003.toml")
.is_file(),
"third rule file missing"
);
let index =
std::fs::read_to_string(project.join("docs/lsp_max_rule_pack.md")).expect("rule index doc");
assert!(index.contains("`LSPMAX-WALLCLOCK-003`"), "index: {index}");
let lock = std::fs::read_to_string(project.join("ggen.lock")).expect("ggen.lock");
assert!(lock.contains("[packs.lsp-max-pack]"), "lock: {lock}");
assert!(lock.contains("content_hash = \"blake3:"), "lock: {lock}");
let _ = CliHarness::from_path(ggen_bin())
.args(["graph", "validate"])
.current_dir(&project)
.run()
.expect("run graph validate")
.assert_success();
let before = std::fs::read(&unwrap_rule).expect("rule bytes");
let _ = CliHarness::from_path(ggen_bin())
.args(["sync", "run"])
.current_dir(&project)
.run()
.expect("second sync")
.assert_success();
let after = std::fs::read(&unwrap_rule).expect("rule bytes after");
assert_eq!(
before, after,
"second sync must leave the rule file unchanged"
);
}
#[test]
fn star_toml_pack_syncs() {
let (_dir, project) = scaffold_pack_project("star-toml-pack");
let _ = CliHarness::from_path(ggen_bin())
.args(["sync", "run"])
.current_dir(&project)
.run()
.expect("run sync")
.assert_success();
assert!(
!project.join("generated").exists(),
"no generated/ dir allowed"
);
let module = project.join("src/star_toml_config.rs");
assert!(module.is_file(), "pack must write src/star_toml_config.rs");
let src = std::fs::read_to_string(&module).expect("config module");
assert!(src.contains("pub struct AdmissionConfig"), "module: {src}");
assert!(src.contains("pub struct TelemetryConfig"), "module: {src}");
assert!(
src.contains("pub witness_dir: std::path::PathBuf"),
"module: {src}"
);
assert!(src.contains("pub fail_closed: bool"), "module: {src}");
assert!(
src.contains("pub exporter_endpoint: String"),
"module: {src}"
);
assert!(src.contains("pub sample_rate: f64"), "module: {src}");
assert!(
src.contains("BLAKE3 ConfigWitness envelopes are persisted"),
"distinctive ontology doc string must flow into the module: {src}"
);
assert!(
src.contains("#[serde(deny_unknown_fields)]"),
"module: {src}"
);
assert!(
src.contains("star_toml::load_file::<Self>(path)"),
"module: {src}"
);
let admission_doc = std::fs::read_to_string(project.join("docs/star_toml/admission.md"))
.expect("admission doc");
assert!(
admission_doc.contains("`witness_dir`"),
"doc: {admission_doc}"
);
let telemetry_doc = std::fs::read_to_string(project.join("docs/star_toml/telemetry.md"))
.expect("telemetry doc");
assert!(
telemetry_doc.contains("`sample_rate`"),
"doc: {telemetry_doc}"
);
let lock = std::fs::read_to_string(project.join("ggen.lock")).expect("ggen.lock");
assert!(lock.contains("[packs.star-toml-pack]"), "lock: {lock}");
assert!(lock.contains("content_hash = \"blake3:"), "lock: {lock}");
let _ = CliHarness::from_path(ggen_bin())
.args(["graph", "validate"])
.current_dir(&project)
.run()
.expect("run graph validate")
.assert_success();
let module_before = std::fs::read(&module).expect("module bytes");
let _ = CliHarness::from_path(ggen_bin())
.args(["sync", "run"])
.current_dir(&project)
.run()
.expect("second sync")
.assert_success();
assert_eq!(
std::fs::read(&module).expect("module bytes after"),
module_before,
"second sync must leave src/star_toml_config.rs unchanged"
);
let lock2 = std::fs::read_to_string(project.join("ggen.lock")).expect("ggen.lock 2");
assert_eq!(
lock, lock2,
"ggen.lock must be byte-identical across identical runs"
);
}
#[test]
fn chicago_tdd_tools_pack_syncs() {
let (_dir, project) = scaffold_pack_project("chicago-tdd-tools-pack");
let _ = CliHarness::from_path(ggen_bin())
.args(["sync", "run"])
.current_dir(&project)
.run()
.expect("run sync")
.assert_success();
assert!(
!project.join("generated").exists(),
"no generated/ dir allowed"
);
let boundary = project.join("tests/chicago_tdd_tools_boundary.rs");
assert!(
boundary.is_file(),
"pack must write tests/chicago_tdd_tools_boundary.rs"
);
let src = std::fs::read_to_string(&boundary).expect("boundary tests");
assert!(
src.contains("include!(\"chicago_tdd_tools_boundary_runtime.rs\");"),
"{src}"
);
assert!(src.contains("run_boundary_spec"), "{src}");
assert!(src.contains("fn receiptctl_help_lists_verbs()"), "{src}");
assert!(src.contains("fn receiptctl_version_emits_name()"), "{src}");
assert!(
src.contains("fn receiptctl_unknown_verb_fails_closed()"),
"{src}"
);
assert!(src.contains("binary: \"receiptctl\""), "{src}");
assert!(src.contains("exit_code: 1"), "{src}");
assert!(src.contains("stdout_needle: Some(\"Usage\")"), "{src}");
assert!(src.contains("stderr_needle: Some(\"error\")"), "{src}");
let doc = std::fs::read_to_string(project.join("docs/chicago_tdd_tools_boundary.md"))
.expect("boundary doc");
assert!(doc.contains("receiptctl_help_lists_verbs"), "{doc}");
assert!(
doc.contains("an unknown subcommand exits nonzero with a clap error on stderr"),
"{doc}"
);
let lock = std::fs::read_to_string(project.join("ggen.lock")).expect("ggen.lock");
assert!(
lock.contains("[packs.chicago-tdd-tools-pack]"),
"lock: {lock}"
);
assert!(lock.contains("content_hash = \"blake3:"), "lock: {lock}");
let _ = CliHarness::from_path(ggen_bin())
.args(["graph", "validate"])
.current_dir(&project)
.run()
.expect("run graph validate")
.assert_success();
let _ = CliHarness::from_path(ggen_bin())
.args(["sync", "run"])
.current_dir(&project)
.run()
.expect("second sync")
.assert_success();
let src2 = std::fs::read_to_string(&boundary).expect("boundary tests after");
assert_eq!(src, src2, "second sync must leave outputs unchanged");
let lock2 = std::fs::read_to_string(project.join("ggen.lock")).expect("ggen.lock 2");
assert_eq!(
lock, lock2,
"ggen.lock must be byte-identical across identical runs"
);
}
#[test]
fn clap_noun_verb_pack_syncs() {
let (_dir, project) = scaffold_pack_project("clap-noun-verb-pack");
let _ = CliHarness::from_path(ggen_bin())
.args(["sync", "run"])
.current_dir(&project)
.run()
.expect("run sync")
.assert_success();
assert!(
!project.join("generated").exists(),
"no generated/ dir allowed"
);
let routes_path = project.join("src/clap_noun_verb_routes.rs");
assert!(
routes_path.is_file(),
"pack must write src/clap_noun_verb_routes.rs"
);
let routes = std::fs::read_to_string(&routes_path).expect("routes.rs");
assert!(
routes.contains("Verify the active session token against the stored keyring credential."),
"distinctive cnv:doc label missing: {routes}"
);
assert!(
routes.contains(r#"#[verb("verify", "session")]"#),
"verb attribute missing: {routes}"
);
assert!(
routes.contains("fn session_verify("),
"route fn missing: {routes}"
);
assert!(
routes.contains("crate::verbs::handlers::user_create_handler("),
"handler stub call missing: {routes}"
);
assert!(
routes.contains(r"static REGISTER_SESSION_NOUN: fn() = register_session_noun;"),
"session noun registration missing: {routes}"
);
assert!(
routes.contains("Manage authenticated session tokens."),
"session noun about text missing: {routes}"
);
let doc =
std::fs::read_to_string(project.join("docs/clap_noun_verb_routes.md")).expect("routes doc");
assert!(
doc.contains("| `session` | `login` | `session_login_handler` |"),
"doc: {doc}"
);
let lock = std::fs::read_to_string(project.join("ggen.lock")).expect("ggen.lock");
assert!(lock.contains("[packs.clap-noun-verb-pack]"), "lock: {lock}");
assert!(lock.contains("content_hash = \"blake3:"), "lock: {lock}");
let _ = CliHarness::from_path(ggen_bin())
.args(["graph", "validate"])
.current_dir(&project)
.run()
.expect("run graph validate")
.assert_success();
let _ = CliHarness::from_path(ggen_bin())
.args(["sync", "run"])
.current_dir(&project)
.run()
.expect("second sync")
.assert_success();
let routes_again = std::fs::read_to_string(&routes_path).expect("routes.rs after");
assert_eq!(
routes, routes_again,
"second sync must leave the route file unchanged"
);
}
#[test]
fn praxis_core_pack_syncs() {
let (_dir, project) = scaffold_pack_project("praxis-core-pack");
let _ = CliHarness::from_path(ggen_bin())
.args(["sync", "run"])
.current_dir(&project)
.run()
.expect("run sync")
.assert_success();
assert!(
!project.join("generated").exists(),
"no generated/ dir allowed"
);
let rs_path = project.join("src/praxis_core_refusal_table.rs");
assert!(
rs_path.is_file(),
"pack must write src/praxis_core_refusal_table.rs"
);
let rs = std::fs::read_to_string(&rs_path).expect("refusal table");
assert!(
rs.contains("pub const PRAXIS_CORE_REFUSAL_TAXONOMY"),
"{rs}"
);
assert!(
rs.contains("Conformance gate rejected the POWL replay projection."),
"distinctive ontology note must flow into the Rust table: {rs}"
);
assert!(rs.contains("scenario: \"WatchdogDrained\""), "{rs}");
assert!(rs.contains("denial_lane: \"WATCHDOG_DRAINED\""), "{rs}");
assert!(rs.contains("category: \"temporal\""), "{rs}");
let md = std::fs::read_to_string(project.join("docs/praxis_core_refusal_taxonomy.md"))
.expect("taxonomy doc");
assert!(
md.contains("| `AuthorizationDenied` | authorization | `AUTHORIZATION_DENIED` |"),
"markdown row: {md}"
);
for name in [
"WatchdogDrained",
"AuthorizationDenied",
"ResourceExhausted",
"ConformanceGateFailed",
] {
assert!(md.contains(name), "doc missing {name}: {md}");
}
let lock = std::fs::read_to_string(project.join("ggen.lock")).expect("ggen.lock");
assert!(lock.contains("[packs.praxis-core-pack]"), "lock: {lock}");
assert!(lock.contains("content_hash = \"blake3:"), "lock: {lock}");
let _ = CliHarness::from_path(ggen_bin())
.args(["graph", "validate"])
.current_dir(&project)
.run()
.expect("run graph validate")
.assert_success();
let before = std::fs::read(&rs_path).expect("table bytes");
let _ = CliHarness::from_path(ggen_bin())
.args(["sync", "run"])
.current_dir(&project)
.run()
.expect("second sync")
.assert_success();
let after = std::fs::read(&rs_path).expect("table bytes after");
assert_eq!(before, after, "second sync must leave the table unchanged");
let lock2 = std::fs::read_to_string(project.join("ggen.lock")).expect("ggen.lock idem");
assert_eq!(
lock, lock2,
"ggen.lock must be byte-identical across identical runs"
);
}
#[allow(clippy::too_many_lines)]
#[test]
fn wasm4pm_cognition_pack_syncs() {
let (_dir, project) = scaffold_pack_project("wasm4pm-cognition-pack");
let _ = CliHarness::from_path(ggen_bin())
.args(["sync", "run"])
.current_dir(&project)
.run()
.expect("run sync")
.assert_success();
assert!(
!project.join("generated").exists(),
"no generated/ dir allowed"
);
let catalog_path = project.join("src/w4pm_cognition_catalog.rs");
assert!(
catalog_path.is_file(),
"pack must write src/w4pm_cognition_catalog.rs"
);
let catalog = std::fs::read_to_string(&catalog_path).expect("catalog.rs");
assert!(catalog.contains("pub enum CognitionBreedId"), "{catalog}");
assert!(catalog.contains("pub const BREED_CATALOG"), "{catalog}");
assert!(
catalog.contains("pub fn from_breed_id(s: &str) -> Option<CognitionBreedId>"),
"{catalog}"
);
assert!(
catalog.contains("Fikes, R. E., & Nilsson, N. J. (1971). STRIPS"),
"STRIPS citation must flow from the ontology: {catalog}"
);
for variant in [
"Strips",
"Gps",
"HtnPlanning",
"PartialOrderPlan",
"Prolog",
"BayesianNetwork",
"DempsterShafer",
"FuzzyLogic",
"Mycin",
"Eliza",
"Soar",
"ActR",
"Hearsay",
] {
assert!(catalog.contains(variant), "catalog missing breed {variant}");
}
assert!(!catalog.contains("breedStatus"), "{catalog}");
let dispatch_path = project.join("src/w4pm_cognition_dispatch.rs");
assert!(
dispatch_path.is_file(),
"pack must write src/w4pm_cognition_dispatch.rs"
);
let dispatch = std::fs::read_to_string(&dispatch_path).expect("dispatch.rs");
let handler_path = project.join("src/w4pm_cognition_dispatch_handler.rs");
assert!(
handler_path.is_file(),
"pack must write src/w4pm_cognition_dispatch_handler.rs"
);
let handler = std::fs::read_to_string(&handler_path).expect("dispatch handler");
assert!(
handler.contains("pub fn dispatch_cognition_run(breed_id: &str, input_json: &str) -> Result<String, String>"),
"{handler}"
);
assert!(
dispatch.contains("pub fn run_strips(input_json: &str) -> Result<String, String>"),
"{dispatch}"
);
assert!(
dispatch.contains("dispatch_cognition_run(\"dempster_shafer\", input_json)"),
"{dispatch}"
);
assert!(
dispatch.contains("Shafer, G. (1976). A Mathematical Theory of Evidence."),
"Dempster–Shafer citation must flow into the stub doc: {dispatch}"
);
assert!(
dispatch.contains("`cognition_run`"),
"6-verb ABI reference missing: {dispatch}"
);
let lock = std::fs::read_to_string(project.join("ggen.lock")).expect("ggen.lock");
assert!(
lock.contains("[packs.wasm4pm-cognition-pack]"),
"lock: {lock}"
);
assert!(lock.contains("content_hash = \"blake3:"), "lock: {lock}");
let _ = CliHarness::from_path(ggen_bin())
.args(["graph", "validate"])
.current_dir(&project)
.run()
.expect("run graph validate")
.assert_success();
let before = std::fs::read(&catalog_path).expect("catalog bytes");
let _ = CliHarness::from_path(ggen_bin())
.args(["sync", "run"])
.current_dir(&project)
.run()
.expect("second sync")
.assert_success();
assert_eq!(
std::fs::read(&catalog_path).expect("catalog bytes after"),
before,
"second sync must leave the catalog unchanged"
);
let lock2 = std::fs::read_to_string(project.join("ggen.lock")).expect("ggen.lock 2");
assert_eq!(
lock, lock2,
"ggen.lock must be byte-identical across identical runs"
);
}
#[test]
fn wasm4pm_algorithms_pack_syncs() {
let (_dir, project) = scaffold_pack_project("wasm4pm-algorithms-pack");
let _ = CliHarness::from_path(ggen_bin())
.args(["sync", "run"])
.current_dir(&project)
.run()
.expect("run sync")
.assert_success();
assert!(
!project.join("generated").exists(),
"no generated/ dir allowed"
);
let catalog_path = project.join("src/w4pm_algorithms_catalog.rs");
assert!(
catalog_path.is_file(),
"pack must write src/w4pm_algorithms_catalog.rs"
);
let catalog = std::fs::read_to_string(&catalog_path).expect("catalog.rs");
assert!(catalog.contains("pub enum AlgorithmId"), "{catalog}");
assert!(catalog.contains("InductiveMiner"), "{catalog}");
assert!(catalog.contains("EtconformancePrecision"), "{catalog}");
assert!(
catalog.contains("\"discover_inductive_miner\" => Some(AlgorithmId::InductiveMiner)"),
"by_wasm_export arm missing: {catalog}"
);
assert!(
catalog.contains("algorithm_id: \"predict_remaining_time\""),
"{catalog}"
);
assert!(
catalog.contains("wasm_export: \"predict_case_duration\""),
"wasm export names must be carried over exactly: {catalog}"
);
assert!(
catalog.contains("pub fn by_wasm_export(export: &str)"),
"{catalog}"
);
let doc =
std::fs::read_to_string(project.join("docs/w4pm_algorithms.md")).expect("algorithms doc");
assert!(
doc.contains(
"Leemans, S.J.J., Fahland, D., & van der Aalst, W.M.P. (2013). \
Discovering Block-Structured Process Models from Event Logs."
),
"inductive miner citation missing: {doc}"
);
assert!(
doc.contains("Munoz-Gama, J., & Carmona, J. (2010)"),
"precision citation missing: {doc}"
);
for label in [
"Dfg",
"Alignments",
"PredictNextActivity",
"MonteCarloSimulation",
] {
assert!(doc.contains(label), "doc missing {label}: {doc}");
}
let lock = std::fs::read_to_string(project.join("ggen.lock")).expect("ggen.lock");
assert!(
lock.contains("[packs.wasm4pm-algorithms-pack]"),
"lock: {lock}"
);
assert!(lock.contains("content_hash = \"blake3:"), "lock: {lock}");
let _ = CliHarness::from_path(ggen_bin())
.args(["graph", "validate"])
.current_dir(&project)
.run()
.expect("run graph validate")
.assert_success();
let before = std::fs::read(&catalog_path).expect("catalog bytes");
let _ = CliHarness::from_path(ggen_bin())
.args(["sync", "run"])
.current_dir(&project)
.run()
.expect("second sync")
.assert_success();
let after = std::fs::read(&catalog_path).expect("catalog bytes after");
assert_eq!(
before, after,
"second sync must leave the catalog unchanged"
);
let lock2 = std::fs::read_to_string(project.join("ggen.lock")).expect("ggen.lock 2");
assert_eq!(
lock, lock2,
"ggen.lock must be byte-identical across identical runs"
);
}
fn write_synthetic_pack(dir: &Path, name: &str, ontology_ttl: &str, gate_rq: Option<&str>) {
let pack = dir.join(name);
std::fs::create_dir_all(pack.join("templates")).expect("mkdir pack templates");
std::fs::write(
pack.join("pack.toml"),
format!(
"[pack]\nname = \"{name}\"\nversion = \"0.0.1\"\ndescription = \"synthetic test pack\"\n"
),
)
.expect("write pack.toml");
std::fs::write(pack.join("ontology.ttl"), ontology_ttl).expect("write pack ontology");
if let Some(gate) = gate_rq {
std::fs::create_dir_all(pack.join("gates")).expect("mkdir pack gates");
std::fs::write(pack.join("gates/gate.rq"), gate).expect("write pack gate");
}
std::fs::write(
pack.join("templates/marker.txt.tmpl"),
format!("---\nto: {name}_marker.txt\nforce: true\n---\npack {name} ran\n"),
)
.expect("write pack template");
}
fn scaffold_synthetic_consumer(
dir: &Path, pack_names: &[&str], aggregate_modules: bool,
) -> PathBuf {
use std::fmt::Write as _;
let project = dir.join("consumer");
std::fs::create_dir_all(project.join("templates")).expect("mkdir templates");
std::fs::write(project.join("ontology.ttl"), "").expect("write ontology.ttl");
let packs_lines: String = pack_names.iter().fold(String::new(), |mut lines, n| {
let _ = writeln!(lines, "{n} = {{ path = \"../{n}\" }}");
lines
});
let aggregate_line = if aggregate_modules {
"aggregate_modules = true\n"
} else {
""
};
std::fs::write(
project.join("ggen.toml"),
format!(
"[project]\nname = \"consumer\"\n\n\
[ontology]\nsource = \"ontology.ttl\"\n\n\
[packs]\n{packs_lines}\n\
[templates]\ndir = \"templates\"\n{aggregate_line}"
),
)
.expect("write ggen.toml");
project
}
const GATE_MAX_ONE_VAL: &str = r"# MESSAGE: a Thing must carry exactly one ex:val (mirror-drift guard)
PREFIX ex: <http://example.org/gap#>
SELECT ?thing ?v1 ?v2 WHERE {
?thing a ex:Thing ; ex:val ?v1 , ?v2 .
FILTER(STR(?v1) < STR(?v2))
}
ORDER BY ?thing
";
#[test]
fn pack_gate_refuses_a_violating_union_graph() {
let dir = TempDir::new().expect("tempdir");
write_synthetic_pack(
dir.path(),
"guarded-pack",
r#"
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix ex: <http://example.org/gap#> .
ex:t1 a ex:Thing ; ex:val "from-pack" .
"#,
Some(GATE_MAX_ONE_VAL),
);
let project = scaffold_synthetic_consumer(dir.path(), &["guarded-pack"], false);
std::fs::write(
project.join("ontology.ttl"),
r#"
@prefix ex: <http://example.org/gap#> .
ex:t1 ex:val "diverged-in-consumer" .
"#,
)
.expect("write violating consumer ontology");
let output = CliHarness::from_path(ggen_bin())
.args(["sync", "run"])
.current_dir(&project)
.run()
.expect("run sync");
let _ = output.assert_failure();
let _ = output.assert_stderr_contains("FM-PACK-013");
let _ = output.assert_stderr_contains("guarded-pack");
assert!(
!project.join("guarded-pack_marker.txt").exists(),
"a refused sync must not have written any template output"
);
std::fs::write(project.join("ontology.ttl"), "").expect("clear consumer ontology");
std::fs::remove_file(project.join("ggen.lock")).ok();
let _ = CliHarness::from_path(ggen_bin())
.args(["sync", "run"])
.current_dir(&project)
.run()
.expect("run sync 2")
.assert_success();
assert!(project.join("guarded-pack_marker.txt").is_file());
}
#[test]
fn legacy_pack_shapes_ttl_is_refused_loudly() {
let dir = TempDir::new().expect("tempdir");
write_synthetic_pack(
dir.path(),
"legacy-pack",
"@prefix ex: <http://example.org/gap#> .\n",
None,
);
std::fs::write(
dir.path().join("legacy-pack/shapes.ttl"),
"@prefix sh: <http://www.w3.org/ns/shacl#> .\n",
)
.expect("write legacy shapes.ttl");
let project = scaffold_synthetic_consumer(dir.path(), &["legacy-pack"], false);
let output = CliHarness::from_path(ggen_bin())
.args(["sync", "run"])
.current_dir(&project)
.run()
.expect("run sync");
let _ = output.assert_failure();
let _ = output.assert_stderr_contains("FM-PACK-012");
let _ = output.assert_stderr_contains("legacy-pack");
let _ = output.assert_stderr_contains("no longer supported");
let _ = output.assert_stderr_contains("gates/*.rq");
assert!(
!project.join("legacy-pack_marker.txt").exists(),
"a refused sync must not have written any template output"
);
std::fs::remove_file(dir.path().join("legacy-pack/shapes.ttl")).expect("rm shapes.ttl");
std::fs::remove_file(project.join("ggen.lock")).ok();
let _ = CliHarness::from_path(ggen_bin())
.args(["sync", "run"])
.current_dir(&project)
.run()
.expect("run sync 2")
.assert_success();
assert!(project.join("legacy-pack_marker.txt").is_file());
}
#[test]
fn aggregate_modules_emits_one_engine_owned_aggregator() {
let dir = TempDir::new().expect("tempdir");
write_synthetic_pack(
dir.path(),
"pack-alpha",
"@prefix ex: <http://example.org/gap#> .\n",
None,
);
write_synthetic_pack(
dir.path(),
"pack-beta",
"@prefix ex: <http://example.org/gap#> .\n",
None,
);
for (pack, module) in [
("pack-alpha", "alpha_catalog"),
("pack-beta", "beta_catalog"),
] {
std::fs::write(
dir.path()
.join(pack)
.join("templates")
.join(format!("{module}.rs.tmpl")),
format!(
"---\nto: src/{module}.rs\nforce: true\n---\npub const {}: &str = \"{module}\";\n",
module.to_uppercase()
),
)
.expect("write module template");
}
let project = scaffold_synthetic_consumer(dir.path(), &["pack-alpha", "pack-beta"], true);
let _ = CliHarness::from_path(ggen_bin())
.args(["sync", "run"])
.current_dir(&project)
.run()
.expect("run sync")
.assert_success();
let aggregator_path = project.join("src/ggen_pack_mods.rs");
let aggregator = std::fs::read_to_string(&aggregator_path).expect("aggregator");
assert!(
aggregator.contains("#[path = \"alpha_catalog.rs\"]\npub mod alpha_catalog;"),
"{aggregator}"
);
assert!(
aggregator.contains("#[path = \"beta_catalog.rs\"]\npub mod beta_catalog;"),
"{aggregator}"
);
assert!(
aggregator.find("alpha_catalog").expect("alpha present")
< aggregator.find("beta_catalog").expect("beta present"),
"aggregator entries must be deterministically sorted: {aggregator}"
);
assert!(
!aggregator.contains("ggen_pack_mods.rs\"]"),
"aggregator must not self-mount: {aggregator}"
);
let before = std::fs::read(&aggregator_path).expect("bytes");
let _ = CliHarness::from_path(ggen_bin())
.args(["sync", "run"])
.current_dir(&project)
.run()
.expect("second sync")
.assert_success();
assert_eq!(before, std::fs::read(&aggregator_path).expect("bytes 2"));
}
#[test]
fn aggregate_modules_off_by_default_emits_no_aggregator() {
let dir = TempDir::new().expect("tempdir");
write_synthetic_pack(
dir.path(),
"pack-alpha",
"@prefix ex: <http://example.org/gap#> .\n",
None,
);
std::fs::write(
dir.path()
.join("pack-alpha/templates/alpha_catalog.rs.tmpl"),
"---\nto: src/alpha_catalog.rs\nforce: true\n---\npub const A: &str = \"a\";\n",
)
.expect("write module template");
let project = scaffold_synthetic_consumer(dir.path(), &["pack-alpha"], false);
let _ = CliHarness::from_path(ggen_bin())
.args(["sync", "run"])
.current_dir(&project)
.run()
.expect("run sync")
.assert_success();
assert!(
project.join("src/alpha_catalog.rs").is_file(),
"module itself still generated"
);
assert!(
!project.join("src/ggen_pack_mods.rs").exists(),
"no aggregator without the opt-in flag (existing consumers unchanged)"
);
}