#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
use std::fmt::Write as _;
use std::path::Path;
use ggen_engine::sync::{sync, SyncOptions};
use tempfile::TempDir;
const MARKETPLACE: &str = "/Users/sac/ggen-marketplace/packs";
const OK_PACKS: [&str; 3] = [
"aaif-vanilla-pack",
"aaif-profile-tailoring-pack",
"ai-chatbot-shadcn-pack",
];
const UNSATISFIED_PACK: &str = "a2a-hex-migration-pack";
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 packs_table(extra: Option<&str>) -> String {
let mut t = String::from("[packs]\n");
for p in OK_PACKS {
writeln!(t, "{p} = {{ path = \"{MARKETPLACE}/{p}\" }}").unwrap();
}
if let Some(p) = extra {
writeln!(t, "{p} = {{ path = \"{MARKETPLACE}/{p}\" }}").unwrap();
}
t
}
fn write_project(root: &Path, extra_pack: Option<&str>) {
write(
root,
"ggen.toml",
&format!(
"[project]\nname = \"annotated-pack-sync-smoke\"\n\n\
[ontology]\nsource = \"ontology.ttl\"\n\n\
{}\n\
[templates]\ndir = \"templates\"\n",
packs_table(extra_pack)
),
);
write(
root,
"ontology.ttl",
"@prefix ex: <http://example.org/> .\nex:one a ex:Thing .\n",
);
write(
root,
"templates/smoke.tmpl",
"---\nto: out/smoke.txt\nsparql:\n s: SELECT ?s WHERE { ?s a <http://example.org/Thing> } ORDER BY ?s\n---\n{% for row in s %}PACK_SYNC_SMOKE: {{ row.s }}\n{% endfor %}",
);
}
#[test]
fn real_annotated_packs_sync_green_and_render() {
for p in OK_PACKS {
let toml = std::fs::read_to_string(format!("{MARKETPLACE}/{p}/pack.toml"))
.expect("pack.toml readable");
assert!(
toml.contains("[capabilities]"),
"{p} must be a real [capabilities]-annotated pack"
);
}
let dir = TempDir::new().expect("tempdir");
write_project(dir.path(), None);
let report = sync(dir.path(), SyncOptions::default())
.expect("sync over 3 real annotated packs must succeed");
let out = std::fs::read_to_string(dir.path().join("out/smoke.txt")).expect("rendered output");
assert!(
out.contains("PACK_SYNC_SMOKE: http://example.org/one"),
"expected rendered marker, got: {out}"
);
assert!(
!report.written.is_empty(),
"sync must write files, got: {:?}",
report.written
);
let lock = std::fs::read_to_string(dir.path().join("ggen.lock")).expect("ggen.lock written");
for p in OK_PACKS {
assert!(
lock.contains(&format!("[packs.{p}]")),
"lock missing [packs.{p}]"
);
}
}
const CONSUMER_PACK: &str = "cargo-cicd-pack";
const PROVIDER_PACK: &str = "clap-noun-verb-pack";
const PROVIDER_DEPS: [&str; 2] = ["praxis-core-pack", "star-toml-pack"];
fn packs_table_multi(packs: &[&str]) -> String {
let mut t = String::from("[packs]\n");
for p in packs {
writeln!(t, "{p} = {{ path = \"{MARKETPLACE}/{p}\" }}").unwrap();
}
t
}
fn write_project_packs(root: &Path, packs: &[&str]) {
write(
root,
"ggen.toml",
&format!(
"[project]\nname = \"annotated-pack-sync-smoke\"\n\n\
[ontology]\nsource = \"ontology.ttl\"\n\n\
{}\n\
[templates]\ndir = \"templates\"\n",
packs_table_multi(packs)
),
);
write(
root,
"ontology.ttl",
"@prefix ex: <http://example.org/> .\nex:one a ex:Thing .\n",
);
write(
root,
"templates/smoke.tmpl",
"---\nto: out/smoke.txt\nsparql:\n s: SELECT ?s WHERE { ?s a <http://example.org/Thing> } ORDER BY ?s\n---\n{% for row in s %}PACK_SYNC_SMOKE: {{ row.s }}\n{% endfor %}",
);
}
#[test]
fn declared_provider_satisfies_urn_requires_and_syncs_green() {
for p in [CONSUMER_PACK, PROVIDER_PACK] {
let toml = std::fs::read_to_string(format!("{MARKETPLACE}/{p}/pack.toml"))
.expect("pack.toml readable");
assert!(
toml.contains("[capabilities]"),
"{p} must be a real [capabilities]-annotated pack"
);
}
let consumer = std::fs::read_to_string(format!("{MARKETPLACE}/{CONSUMER_PACK}/pack.toml"))
.expect("consumer pack.toml readable");
assert!(
consumer.contains(&format!("urn:ggen:pack:{PROVIDER_PACK}")),
"fixture precondition: {CONSUMER_PACK} requires urn:ggen:pack:{PROVIDER_PACK}"
);
let mut packs: Vec<&str> = vec![CONSUMER_PACK, PROVIDER_PACK];
packs.extend(PROVIDER_DEPS);
let dir = TempDir::new().expect("tempdir");
write_project_packs(dir.path(), &packs);
let report = sync(dir.path(), SyncOptions::default())
.expect("sync with declared provider must succeed (FM-PACK-018 must not fire)");
let out = std::fs::read_to_string(dir.path().join("out/smoke.txt")).expect("rendered output");
assert!(
out.contains("PACK_SYNC_SMOKE: http://example.org/one"),
"expected rendered marker, got: {out}"
);
assert!(
!report.written.is_empty(),
"sync must write files, got: {:?}",
report.written
);
}
#[test]
fn same_project_without_provider_declaration_refuses_with_fm_pack_018() {
let mut packs: Vec<&str> = vec![CONSUMER_PACK];
packs.extend(PROVIDER_DEPS);
let dir = TempDir::new().expect("tempdir");
write_project_packs(dir.path(), &packs);
let err = sync(dir.path(), SyncOptions::default())
.expect_err("missing provider declaration must refuse the sync");
let msg = err.to_string();
assert!(
msg.contains("[FM-PACK-018]"),
"expected typed FM-PACK-018 refusal, got: {msg}"
);
assert!(
msg.contains(&format!("urn:ggen:pack:{PROVIDER_PACK}")),
"refusal must name the missing provider capability, got: {msg}"
);
}
const NON_URN_CAPABILITY: &str = "example:transitive-capability";
const CONSUMER_V2_PACK: &str = "tier2-consumer-pack";
const PROVIDER_V2_PACK: &str = "tier2-provider-pack";
fn write_tier2_packs(root: &Path, with_capability: bool) {
let provides = if with_capability {
format!(
"[capabilities]\nprovides = [\"urn:ggen:pack:{PROVIDER_V2_PACK}\", \
\"{NON_URN_CAPABILITY}\"]\n"
)
} else {
format!("[capabilities]\nprovides = [\"urn:ggen:pack:{PROVIDER_V2_PACK}\"]\n")
};
write(
root,
&format!("packs-a/{PROVIDER_V2_PACK}/pack.toml"),
&format!(
"[pack]\nname = \"{PROVIDER_V2_PACK}\"\nversion = \"0.1.0\"\n\
description = \"tier-2 closure provider\"\n\n{provides}"
),
);
write(
root,
&format!("packs-a/{PROVIDER_V2_PACK}/ontology.ttl"),
"@prefix ex: <http://example.org/> .\nex:p a ex:Thing .\n",
);
write(
root,
&format!("packs-a/{PROVIDER_V2_PACK}/templates/p.tmpl"),
"---\nto: out/tier2-provider.txt\n---\nTIER2_PROVIDER\n",
);
write(
root,
&format!("packs-a/{CONSUMER_V2_PACK}/pack.toml"),
&format!(
"[pack]\nname = \"{CONSUMER_V2_PACK}\"\nversion = \"0.1.0\"\n\
description = \"tier-2 non-URN requires consumer\"\n\n\
[dependencies]\n\"{PROVIDER_V2_PACK}\" = \"0.1.0\"\n\n\
[capabilities]\nprovides = [\"urn:ggen:pack:{CONSUMER_V2_PACK}\"]\n\
requires = [\"{NON_URN_CAPABILITY}\"]\n"
),
);
write(
root,
&format!("packs-a/{CONSUMER_V2_PACK}/ontology.ttl"),
"@prefix ex: <http://example.org/> .\nex:c a ex:Thing .\n",
);
write(
root,
&format!("packs-a/{CONSUMER_V2_PACK}/templates/c.tmpl"),
"---\nto: out/tier2-consumer.txt\n---\nTIER2_CONSUMER\n",
);
}
fn write_project_tier2(root: &Path) {
write(
root,
"ggen.toml",
&format!(
"[project]\nname = \"annotated-pack-sync-smoke\"\n\n\
[ontology]\nsource = \"ontology.ttl\"\n\n\
[packs]\n\
{CONSUMER_V2_PACK} = {{ path = \"packs-a/{CONSUMER_V2_PACK}\" }}\n\
{PROVIDER_V2_PACK} = {{ path = \"packs-a/{PROVIDER_V2_PACK}\" }}\n\n\
[templates]\ndir = \"templates\"\n"
),
);
write(
root,
"ontology.ttl",
"@prefix ex: <http://example.org/> .\nex:one a ex:Thing .\n",
);
write(
root,
"templates/smoke.tmpl",
"---\nto: out/smoke.txt\nsparql:\n s: SELECT ?s WHERE { ?s a <http://example.org/Thing> } ORDER BY ?s\n---\n{% for row in s %}PACK_SYNC_SMOKE: {{ row.s }}\n{% endfor %}",
);
}
#[test]
fn non_urn_requires_satisfied_via_declared_dependency_closure() {
let dir = TempDir::new().expect("tempdir");
write_tier2_packs(dir.path(), true);
write_project_tier2(dir.path());
let report = sync(dir.path(), SyncOptions::default())
.expect("non-URN requires satisfied via declared closure must sync green");
let out = std::fs::read_to_string(dir.path().join("out/smoke.txt")).expect("rendered output");
assert!(
out.contains("PACK_SYNC_SMOKE: http://example.org/one"),
"expected rendered marker, got: {out}"
);
assert!(
!report.written.is_empty(),
"sync must write files, got: {:?}",
report.written
);
}
#[test]
fn non_urn_requires_without_closure_provider_refuse_with_fm_pack_018() {
let dir = TempDir::new().expect("tempdir");
write_tier2_packs(dir.path(), false);
write_project_tier2(dir.path());
let err = sync(dir.path(), SyncOptions::default())
.expect_err("non-URN requires with no closure provider must refuse the sync");
let msg = err.to_string();
assert!(
msg.contains("[FM-PACK-018]"),
"expected typed FM-PACK-018 refusal, got: {msg}"
);
assert!(
msg.contains(NON_URN_CAPABILITY),
"refusal must name the missing closure capability, got: {msg}"
);
}
const TRANSITIVE_CAPABILITY: &str = "example:transitive-deep-capability";
const A_PACK: &str = "tier2-transitive-consumer-pack";
const B_PACK: &str = "tier2-transitive-middle-pack";
const C_PACK: &str = "tier2-transitive-root-pack";
fn write_transitive_packs(root: &Path, with_capability: bool) {
let c_provides = if with_capability {
format!(
"[capabilities]\nprovides = [\"urn:ggen:pack:{C_PACK}\", \
\"{TRANSITIVE_CAPABILITY}\"]\n"
)
} else {
format!("[capabilities]\nprovides = [\"urn:ggen:pack:{C_PACK}\"]\n")
};
write(
root,
&format!("packs-t/{C_PACK}/pack.toml"),
&format!(
"[pack]\nname = \"{C_PACK}\"\nversion = \"0.1.0\"\n\
description = \"tier-2 transitive closure root\"\n\n{c_provides}"
),
);
write(
root,
&format!("packs-t/{C_PACK}/ontology.ttl"),
"@prefix ex: <http://example.org/> .\nex:c a ex:Thing .\n",
);
write(
root,
&format!("packs-t/{C_PACK}/templates/c.tmpl"),
"---\nto: out/t2-root.txt\n---\nT2_ROOT\n",
);
write(
root,
&format!("packs-t/{B_PACK}/pack.toml"),
&format!(
"[pack]\nname = \"{B_PACK}\"\nversion = \"0.1.0\"\n\
description = \"tier-2 transitive closure middle\"\n\n\
[dependencies]\n\"{C_PACK}\" = \"0.1.0\"\n\n\
[capabilities]\nprovides = [\"urn:ggen:pack:{B_PACK}\"]\n"
),
);
write(
root,
&format!("packs-t/{B_PACK}/ontology.ttl"),
"@prefix ex: <http://example.org/> .\nex:b a ex:Thing .\n",
);
write(
root,
&format!("packs-t/{B_PACK}/templates/b.tmpl"),
"---\nto: out/t2-middle.txt\n---\nT2_MIDDLE\n",
);
write(
root,
&format!("packs-t/{A_PACK}/pack.toml"),
&format!(
"[pack]\nname = \"{A_PACK}\"\nversion = \"0.1.0\"\n\
description = \"tier-2 transitive closure consumer\"\n\n\
[dependencies]\n\"{B_PACK}\" = \"0.1.0\"\n\n\
[capabilities]\nprovides = [\"urn:ggen:pack:{A_PACK}\"]\n\
requires = [\"{TRANSITIVE_CAPABILITY}\"]\n"
),
);
write(
root,
&format!("packs-t/{A_PACK}/ontology.ttl"),
"@prefix ex: <http://example.org/> .\nex:a a ex:Thing .\n",
);
write(
root,
&format!("packs-t/{A_PACK}/templates/a.tmpl"),
"---\nto: out/t2-consumer.txt\n---\nT2_CONSUMER\n",
);
}
fn write_project_transitive(root: &Path, with_root: bool) {
let root_line = if with_root {
format!("{C_PACK} = {{ path = \"packs-t/{C_PACK}\" }}\n")
} else {
String::new()
};
write(
root,
"ggen.toml",
&format!(
"[project]\nname = \"annotated-pack-sync-smoke\"\n\n\
[ontology]\nsource = \"ontology.ttl\"\n\n\
[packs]\n\
{A_PACK} = {{ path = \"packs-t/{A_PACK}\" }}\n\
{B_PACK} = {{ path = \"packs-t/{B_PACK}\" }}\n\
{root_line}\n\
[templates]\ndir = \"templates\"\n"
),
);
write(
root,
"ontology.ttl",
"@prefix ex: <http://example.org/> .\nex:one a ex:Thing .\n",
);
write(
root,
"templates/smoke.tmpl",
"---\nto: out/smoke.txt\nsparql:\n s: SELECT ?s WHERE { ?s a <http://example.org/Thing> } ORDER BY ?s\n---\n{% for row in s %}PACK_SYNC_SMOKE: {{ row.s }}\n{% endfor %}",
);
}
#[test]
fn non_urn_requires_satisfied_via_two_level_transitive_closure() {
let dir = TempDir::new().expect("tempdir");
write_transitive_packs(dir.path(), true);
write_project_transitive(dir.path(), true);
let report = sync(dir.path(), SyncOptions::default())
.expect("non-URN requires satisfied via 2-level transitive closure must sync green");
let out = std::fs::read_to_string(dir.path().join("out/smoke.txt")).expect("rendered output");
assert!(
out.contains("PACK_SYNC_SMOKE: http://example.org/one"),
"expected rendered marker, got: {out}"
);
assert!(
!report.written.is_empty(),
"sync must write files, got: {:?}",
report.written
);
}
#[test]
fn broken_transitive_chain_refuses_with_fm_pack_018() {
let dir = TempDir::new().expect("tempdir");
write_transitive_packs(dir.path(), false);
write_project_transitive(dir.path(), true);
let err = sync(dir.path(), SyncOptions::default())
.expect_err("transitive chain broken at the middle must refuse the sync");
let msg = err.to_string();
assert!(
msg.contains("[FM-PACK-018]"),
"expected typed FM-PACK-018 refusal, got: {msg}"
);
assert!(
msg.contains(TRANSITIVE_CAPABILITY),
"refusal must name the missing transitive capability, got: {msg}"
);
}
#[test]
fn unsatisfied_capability_requires_refuse_with_fm_pack_018() {
let toml = std::fs::read_to_string(format!("{MARKETPLACE}/{UNSATISFIED_PACK}/pack.toml"))
.expect("pack.toml readable");
assert!(
toml.contains("urn:ggen:pack:ash-extension-pack"),
"fixture precondition: {UNSATISFIED_PACK} requires ash-extension-pack"
);
let dir = TempDir::new().expect("tempdir");
write_project(dir.path(), Some(UNSATISFIED_PACK));
let err = sync(dir.path(), SyncOptions::default())
.expect_err("unsatisfied capabilities.requires must refuse the sync");
let msg = err.to_string();
assert!(
msg.contains("[FM-PACK-018]"),
"expected typed FM-PACK-018 refusal, got: {msg}"
);
assert!(
msg.contains("urn:ggen:pack:ash-extension-pack"),
"refusal must name the missing capability, got: {msg}"
);
}