use super::{sync, SyncConfig, SyncLanguage};
use std::fs;
use tempfile::TempDir;
const MINIMAL_TTL: &str = r#"
@prefix bos: <https://chatmangpt.com/businessos#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
@base <https://chatmangpt.com/businessos/> .
<OrderService> a bos:Service ;
rdfs:label "Order API" ;
bos:port 8001 ;
bos:language "Go" .
"#;
const SERVICES_QUERY: &str = r#"
PREFIX bos: <https://chatmangpt.com/businessos#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
SELECT ?service WHERE {
?s a bos:Service .
BIND(REPLACE(STR(?s), ".*[/#]", "") AS ?service)
}
"#;
fn setup_fixture(
ttl: &str, queries: &[(&str, &str)],
) -> (TempDir, std::path::PathBuf, std::path::PathBuf) {
let dir = TempDir::new().expect("temp dir");
let ont_path = dir.path().join("ontology.ttl");
let queries_dir = dir.path().join("queries");
fs::write(&ont_path, ttl).expect("write ttl");
fs::create_dir_all(&queries_dir).expect("queries dir");
for (name, rq) in queries {
fs::write(queries_dir.join(format!("{}.rq", name)), rq).expect("write rq");
}
(dir, ont_path, queries_dir)
}
#[test]
fn test_sync_dry_run_produces_no_files() {
let (dir, ont_path, queries_dir) = setup_fixture(MINIMAL_TTL, &[("services", SERVICES_QUERY)]);
let output_dir = dir.path().join("output");
let config = SyncConfig {
ontology_path: ont_path,
queries_dir,
output_dir: output_dir.clone(),
language: SyncLanguage::Go,
validate: false,
dry_run: true,
};
let result = sync(config).expect("sync must succeed in dry-run mode");
assert!(
!result.files_generated.is_empty(),
"dry_run should still report files that would be generated"
);
assert!(
!output_dir.exists(),
"dry_run must not create the output directory on disk"
);
for path in &result.files_generated {
assert!(
path.starts_with(&output_dir),
"reported path {:?} should be under output_dir",
path
);
}
}
#[test]
fn test_sync_go_generates_service_struct() {
let (dir, ont_path, queries_dir) = setup_fixture(MINIMAL_TTL, &[("services", SERVICES_QUERY)]);
let output_dir = dir.path().join("output");
let config = SyncConfig {
ontology_path: ont_path,
queries_dir,
output_dir: output_dir.clone(),
language: SyncLanguage::Go,
validate: false,
dry_run: false,
};
let result = sync(config).expect("sync must succeed");
assert!(
!result.files_generated.is_empty(),
"sync must produce at least one Go file"
);
let go_files: Vec<_> = result
.files_generated
.iter()
.filter(|p| p.extension().and_then(|e| e.to_str()) == Some("go"))
.collect();
assert!(!go_files.is_empty(), "expected at least one .go file");
let content = fs::read_to_string(go_files[0]).expect("read generated go file");
assert!(
content.contains("struct"),
"generated Go file should contain a struct definition, got:\n{}",
content
);
}
#[test]
fn test_sync_receipt_is_deterministic() {
let (dir1, ont1, qdir1) = setup_fixture(MINIMAL_TTL, &[("services", SERVICES_QUERY)]);
let out1 = dir1.path().join("out1");
let receipt1 = sync(SyncConfig {
ontology_path: ont1,
queries_dir: qdir1,
output_dir: out1,
language: SyncLanguage::Go,
validate: false,
dry_run: false,
})
.expect("first sync")
.receipt;
let (dir2, ont2, qdir2) = setup_fixture(MINIMAL_TTL, &[("services", SERVICES_QUERY)]);
let out2 = dir2.path().join("out2");
let receipt2 = sync(SyncConfig {
ontology_path: ont2,
queries_dir: qdir2,
output_dir: out2,
language: SyncLanguage::Go,
validate: false,
dry_run: false,
})
.expect("second sync")
.receipt;
assert_eq!(
receipt1, receipt2,
"receipt must be deterministic for identical inputs"
);
assert_eq!(
receipt1.len(),
64,
"sha256 receipt must be 64 hex characters"
);
assert!(
receipt1.chars().all(|c| c.is_ascii_hexdigit()),
"receipt must be a valid hex string"
);
}
#[test]
fn test_sync_produces_coherence_report() {
use ggen_graph::coherence::Pole;
let (dir, ont_path, queries_dir) = setup_fixture(MINIMAL_TTL, &[("services", SERVICES_QUERY)]);
let output_dir = dir.path().join("output");
let config = SyncConfig {
ontology_path: ont_path,
queries_dir,
output_dir: output_dir.clone(),
language: SyncLanguage::Go,
validate: false,
dry_run: false,
};
let result = sync(config).expect("sync must succeed");
let report = result
.coherence_report
.expect("sync must produce a coherence_report");
assert_eq!(
report.poles.len(),
3,
"three-pole report must have exactly 3 poles"
);
let ont_pole = report
.poles
.iter()
.find(|p| p.pole == Pole::Ontology)
.expect("ontology pole must be present");
assert!(
!ont_pole.hash.is_empty(),
"ontology pole hash must be non-empty"
);
let art_pole = report
.poles
.iter()
.find(|p| p.pole == Pole::Artifact)
.expect("artifact pole must be present");
assert!(
!art_pole.hash.is_empty(),
"artifact pole hash must be non-empty"
);
let receipt_path = output_dir
.join(".ggen")
.join("receipts")
.join("coherence-latest.json");
assert!(
receipt_path.exists(),
"coherence-latest.json must exist at {:?}",
receipt_path
);
let json_content = fs::read_to_string(&receipt_path).expect("read coherence json");
assert!(!json_content.is_empty(), "coherence JSON must be non-empty");
let parsed: serde_json::Value =
serde_json::from_str(&json_content).expect("coherence JSON must be valid");
assert!(
parsed.get("poles").is_some(),
"coherence JSON must contain 'poles' field"
);
}