#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
use std::path::Path;
use ggen_engine::sync::{sync, SyncOptions};
use tempfile::TempDir;
fn write(root: &Path, rel: &str, content: &str) {
let path = root.join(rel);
if let Some(parent) = path.parent() {
std::fs::create_dir_all(parent).expect("mkdir parent");
}
std::fs::write(path, content).expect("write file");
}
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`"
);
}
#[test]
fn authoritative_schema_valid_sync_is_accepted() {
let dir = TempDir::new().expect("tempdir");
write(
dir.path(),
"ggen.toml",
r#"
[project]
name = "demo"
version = "1.0.0"
[ontology]
source = "ontology.ttl"
[[generation.rules]]
name = "names"
query = { inline = "SELECT ?name WHERE { ?s <http://example.org/name> ?name } ORDER BY ?name" }
template = { inline = "{% for row in results %}{{ row.name }}\n{% endfor %}" }
output_file = "out/names.txt"
"#,
);
write(
dir.path(),
"ontology.ttl",
r#"
@prefix ex: <http://example.org/> .
ex:alice ex:name "alice" .
"#,
);
let report =
sync(dir.path(), SyncOptions::default()).expect("declarative-rules sync must succeed");
assert_eq!(
report.written,
vec![std::path::PathBuf::from("out/names.txt")]
);
let content = std::fs::read_to_string(dir.path().join("out/names.txt")).expect("read output");
assert_eq!(content, "alice\n");
}
#[test]
fn compatible_schema_valid_sync_is_accepted() {
let dir = TempDir::new().expect("tempdir");
write(
dir.path(),
"ggen.toml",
r#"
[project]
name = "demo"
[ontology]
source = "ontology.ttl"
[templates]
dir = "templates"
"#,
);
write(
dir.path(),
"ontology.ttl",
r#"
@prefix ex: <http://example.org/> .
ex:alice ex:name "alice" .
"#,
);
write(
dir.path(),
"templates/one.tmpl",
"---\nto: out/names.txt\nsparql:\n people: SELECT ?name WHERE { ?s <http://example.org/name> ?name } ORDER BY ?name\n---\n{% for row in results %}{{ row.name }}\n{% endfor %}",
);
let report = sync(dir.path(), SyncOptions::default()).expect("frontmatter sync must succeed");
assert_eq!(
report.written,
vec![std::path::PathBuf::from("out/names.txt")]
);
let content = std::fs::read_to_string(dir.path().join("out/names.txt")).expect("read output");
assert_eq!(content, "alice\n");
}
#[test]
fn unsupported_schema_sync_is_typed_refusal() {
let dir = TempDir::new().expect("tempdir");
write(
dir.path(),
"ggen.toml",
"[some_other_tool]\nkey = \"value\"\n",
);
let err = sync(dir.path(), SyncOptions::default()).expect_err("must be refused");
assert!(
err.to_string()
.contains(ggen_config::CONFIG_SCHEMA_UNSUPPORTED),
"{err}"
);
}
#[test]
fn ambiguous_schema_sync_is_typed_refusal() {
let dir = TempDir::new().expect("tempdir");
write(
dir.path(),
"ggen.toml",
r#"
[project]
name = "demo"
[ontology]
source = "ontology.ttl"
[templates]
dir = "templates"
[ai]
provider = "openai"
"#,
);
let err = sync(dir.path(), SyncOptions::default()).expect_err("must be refused");
let msg = err.to_string();
assert!(msg.contains(ggen_config::CONFIG_SCHEMA_AMBIGUOUS), "{msg}");
}
#[test]
fn malformed_toml_sync_is_typed_parse_failure() {
let dir = TempDir::new().expect("tempdir");
write(dir.path(), "ggen.toml", "not [ valid toml");
let err = sync(dir.path(), SyncOptions::default()).expect_err("must fail");
assert!(
err.to_string().contains(ggen_config::CONFIG_PARSE_FAILED),
"{err}"
);
}
#[test]
fn missing_required_field_is_field_specific_typed_error() {
let dir = TempDir::new().expect("tempdir");
write(
dir.path(),
"ggen.toml",
"[project]\nname = \"demo\"\nversion = \"1.0.0\"\n\n[[generation.rules]]\nname = \"x\"\nquery = { inline = \"SELECT * WHERE { ?s ?p ?o }\" }\ntemplate = { inline = \"hi\" }\noutput_file = \"out.txt\"\n",
);
let err =
sync(dir.path(), SyncOptions::default()).expect_err("must fail: ontology is required");
let msg = err.to_string().to_lowercase();
assert!(msg.contains("ontology"), "{msg}");
}
#[test]
fn unknown_field_is_rejected_under_strict_parsing() {
let dir = TempDir::new().expect("tempdir");
write(
dir.path(),
"ggen.toml",
r#"
[project]
name = "demo"
[ontology]
source = "ontology.ttl"
[templates]
dir = "templates"
[bogus_table]
whatever = 1
"#,
);
let err = sync(dir.path(), SyncOptions::default()).expect_err("must fail: unknown field");
let msg = err.to_string();
assert!(msg.contains("FM-CONFIG-002"), "{msg}");
}
#[test]
fn doctor_succeeds_with_correct_diagnostic_on_each_supported_schema() {
let frontmatter_dir = TempDir::new().expect("tempdir");
write(
frontmatter_dir.path(),
"ggen.toml",
"[project]\nname = \"demo\"\n\n[ontology]\nsource = \"ontology.ttl\"\n\n[templates]\ndir = \"templates\"\n",
);
write(
frontmatter_dir.path(),
"ontology.ttl",
"@prefix ex: <http://example.org/> .\nex:a ex:name \"a\" .\n",
);
std::fs::create_dir_all(frontmatter_dir.path().join("templates")).expect("mkdir templates");
let frontmatter_assert = assert_cmd::Command::new(ggen_bin())
.current_dir(frontmatter_dir.path())
.args(["doctor", "run"])
.assert()
.success();
let frontmatter_json: serde_json::Value =
serde_json::from_slice(&frontmatter_assert.get_output().stdout)
.expect("doctor stdout is JSON");
assert_eq!(frontmatter_json["healthy"], true, "{frontmatter_json}");
assert_eq!(
frontmatter_json["checks"]["lockfile_drift"]["status"], "pass",
"{frontmatter_json}"
);
let declarative_dir = TempDir::new().expect("tempdir");
write(
declarative_dir.path(),
"ggen.toml",
"[project]\nname = \"demo\"\nversion = \"1.0.0\"\n\n[ontology]\nsource = \"ontology.ttl\"\n\n[[generation.rules]]\nname = \"x\"\nquery = { inline = \"SELECT * WHERE { ?s ?p ?o } ORDER BY ?s ?p ?o\" }\ntemplate = { inline = \"hi\" }\noutput_file = \"out.txt\"\n",
);
write(
declarative_dir.path(),
"ontology.ttl",
"@prefix ex: <http://example.org/> .\nex:a ex:name \"a\" .\n",
);
let declarative_assert = assert_cmd::Command::new(ggen_bin())
.current_dir(declarative_dir.path())
.args(["doctor", "run"])
.assert()
.success();
let declarative_json: serde_json::Value =
serde_json::from_slice(&declarative_assert.get_output().stdout)
.expect("doctor stdout is JSON");
assert_eq!(declarative_json["healthy"], true, "{declarative_json}");
assert_eq!(
declarative_json["checks"]["lockfile_drift"]["status"], "skip",
"{declarative_json}"
);
assert_eq!(
declarative_json["checks"]["orphaned_artifacts"]["status"], "pass",
"{declarative_json}"
);
assert_eq!(
declarative_json["checks"]["receipt_staleness"]["status"], "pass",
"{declarative_json}"
);
}
#[test]
fn graph_validate_dispatches_on_each_supported_schema() {
let frontmatter_dir = TempDir::new().expect("tempdir");
write(
frontmatter_dir.path(),
"ggen.toml",
"[project]\nname = \"demo\"\n\n[ontology]\nsource = \"ontology.ttl\"\n\n[templates]\ndir = \"templates\"\n",
);
write(
frontmatter_dir.path(),
"ontology.ttl",
"@prefix ex: <http://example.org/> .\nex:a ex:name \"a\" .\n",
);
std::fs::create_dir_all(frontmatter_dir.path().join("templates")).expect("mkdir templates");
assert_cmd::Command::new(ggen_bin())
.current_dir(frontmatter_dir.path())
.args(["graph", "validate"])
.assert()
.success();
let declarative_dir = TempDir::new().expect("tempdir");
write(
declarative_dir.path(),
"ggen.toml",
"[project]\nname = \"demo\"\nversion = \"1.0.0\"\n\n[ontology]\nsource = \"ontology.ttl\"\n\n[[generation.rules]]\nname = \"x\"\nquery = { inline = \"SELECT * WHERE { ?s ?p ?o } ORDER BY ?s ?p ?o\" }\ntemplate = { inline = \"hi\" }\noutput_file = \"out.txt\"\n",
);
write(
declarative_dir.path(),
"ontology.ttl",
"@prefix ex: <http://example.org/> .\nex:a ex:name \"a\" .\n",
);
assert_cmd::Command::new(ggen_bin())
.current_dir(declarative_dir.path())
.args(["graph", "validate"])
.assert()
.success();
}
#[test]
fn graph_validate_refuses_ambiguous_schema_with_typed_code() {
let dir = TempDir::new().expect("tempdir");
write(
dir.path(),
"ggen.toml",
"[project]\nname = \"demo\"\n\n[ontology]\nsource = \"ontology.ttl\"\n\n[templates]\ndir = \"templates\"\n\n[ai]\nprovider = \"openai\"\n",
);
let assert = assert_cmd::Command::new(ggen_bin())
.current_dir(dir.path())
.args(["graph", "validate"])
.assert()
.failure();
let stderr = String::from_utf8_lossy(&assert.get_output().stderr);
assert!(
stderr.contains(ggen_config::CONFIG_SCHEMA_AMBIGUOUS),
"{stderr}"
);
}
#[test]
fn law_load_dispatches_on_each_supported_schema_and_refuses_ambiguous() {
let frontmatter_dir = TempDir::new().expect("tempdir");
write(
frontmatter_dir.path(),
"ggen.toml",
"[project]\nname = \"demo\"\n\n[ontology]\nsource = \"ontology.ttl\"\n\n[templates]\ndir = \"templates\"\n",
);
write(
frontmatter_dir.path(),
"ontology.ttl",
"@prefix ex: <http://example.org/> .\nex:a ex:name \"a\" .\n",
);
std::fs::create_dir_all(frontmatter_dir.path().join("templates")).expect("mkdir templates");
let fm_assert = assert_cmd::Command::new(ggen_bin())
.current_dir(frontmatter_dir.path())
.args(["law", "load"])
.assert()
.success();
let fm_json: serde_json::Value =
serde_json::from_slice(&fm_assert.get_output().stdout).expect("law load stdout is JSON");
assert_eq!(fm_json["rule_files"], 0, "{fm_json}");
let declarative_dir = TempDir::new().expect("tempdir");
write(
declarative_dir.path(),
"ggen.toml",
"[project]\nname = \"demo\"\nversion = \"1.0.0\"\n\n[ontology]\nsource = \"ontology.ttl\"\n\n[[generation.rules]]\nname = \"x\"\nquery = { inline = \"SELECT * WHERE { ?s ?p ?o } ORDER BY ?s ?p ?o\" }\ntemplate = { inline = \"hi\" }\noutput_file = \"out.txt\"\n",
);
write(
declarative_dir.path(),
"ontology.ttl",
"@prefix ex: <http://example.org/> .\nex:a ex:name \"a\" .\n",
);
assert_cmd::Command::new(ggen_bin())
.current_dir(declarative_dir.path())
.args(["law", "load"])
.assert()
.success();
let ambiguous_dir = TempDir::new().expect("tempdir");
write(
ambiguous_dir.path(),
"ggen.toml",
"[project]\nname = \"demo\"\n\n[ontology]\nsource = \"ontology.ttl\"\n\n[templates]\ndir = \"templates\"\n\n[ai]\nprovider = \"openai\"\n",
);
let ambiguous_assert = assert_cmd::Command::new(ggen_bin())
.current_dir(ambiguous_dir.path())
.args(["law", "load"])
.assert()
.failure();
let stderr = String::from_utf8_lossy(&ambiguous_assert.get_output().stderr);
assert!(
stderr.contains(ggen_config::CONFIG_SCHEMA_AMBIGUOUS),
"{stderr}"
);
}