#![allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::needless_raw_string_hashes
)]
use ed25519_dalek::{Signer, SigningKey};
use std::fs;
use std::path::{Path, PathBuf};
use tempfile::TempDir;
use uuid::Uuid;
fn create_rust_artifact(dir: &Path, filename: &str, content: &str) -> PathBuf {
let path = dir.join(filename);
if let Some(parent) = path.parent() {
fs::create_dir_all(parent).expect("Failed to create parent directory");
}
fs::write(&path, content).expect("Failed to write Rust artifact");
path
}
fn create_ontology_file(dir: &Path, content: &str) -> PathBuf {
let path = dir.join("ontology.ttl");
fs::write(&path, content).expect("Failed to write ontology");
path
}
fn create_signing_key(dir: &Path) -> (PathBuf, SigningKey) {
let signing_key = SigningKey::from_bytes(&[0u8; 32]); let key_bytes = signing_key.to_bytes();
let hex_key = hex::encode(key_bytes);
let key_path = dir.join("signing.key");
fs::write(&key_path, hex_key).expect("Failed to write signing key");
(key_path, signing_key)
}
const RUST_SERVICE_CODE: &str = r#"
pub struct UserService {
pub name: String,
pub port: u16,
}
impl UserService {
pub fn get_user(&self, id: u32) -> String {
format!("User {}", id)
}
pub fn create_user(&self, name: String) -> bool {
!name.is_empty()
}
pub fn delete_user(&self, id: u32) -> bool {
id > 0
}
}
"#;
const ONTOLOGY_TTL: &str = r#"
@prefix code: <http://example.org/code/> .
@prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
code:UserService a code:Service ;
rdfs:label "User Management Service" ;
rdfs:comment "Manages user lifecycle" ;
code:hasMethod code:get_user, code:create_user, code:delete_user .
code:get_user a code:Method ;
rdfs:label "Get User" ;
code:parameter "id: u32" ;
code:returns "String" .
code:create_user a code:Method ;
rdfs:label "Create User" ;
code:parameter "name: String" ;
code:returns "bool" .
code:delete_user a code:Method ;
rdfs:label "Delete User" ;
code:parameter "id: u32" ;
code:returns "bool" .
"#;
#[test]
fn test_inverse_sync_happy_path_recover_and_coherent() {
let source_dir = TempDir::new().expect("Failed to create source dir");
let ontology_dir = TempDir::new().expect("Failed to create ontology dir");
let keys_dir = TempDir::new().expect("Failed to create keys dir");
let envelope_dir = TempDir::new().expect("Failed to create envelope dir");
create_rust_artifact(source_dir.path(), "service.rs", RUST_SERVICE_CODE);
let ontology_path = create_ontology_file(ontology_dir.path(), ONTOLOGY_TTL);
let (signing_key_path, _signing_key) = create_signing_key(keys_dir.path());
let envelope_path = envelope_dir.path().join("latest.json");
assert!(source_dir.path().exists(), "source dir must exist");
assert!(ontology_path.exists(), "ontology file must exist");
assert!(signing_key_path.exists(), "signing key must exist");
let ontology_content = fs::read_to_string(&ontology_path).expect("Failed to read ontology");
assert!(
ontology_content.contains("code:UserService"),
"Ontology must contain service"
);
assert!(
ontology_content.contains("code:Service"),
"Ontology must declare service type"
);
let key_content = fs::read_to_string(&signing_key_path).expect("Failed to read key");
assert!(!key_content.is_empty(), "Signing key must be non-empty");
assert_eq!(key_content.len(), 64, "Hex-encoded 32-byte key is 64 chars");
}
#[test]
fn test_inverse_sync_missing_source_dir_fails() {
let non_existent = PathBuf::from(format!(
"/tmp/this-directory-does-not-exist-{}",
Uuid::new_v4()
));
let artifact_result: std::result::Result<Vec<PathBuf>, String> = {
if !non_existent.exists() {
Err(format!(
"Source directory not found: {}",
non_existent.display()
))
} else {
Ok(Vec::new())
}
};
match artifact_result {
Err(e) => assert!(e.contains("Source directory not found")),
Ok(_) => panic!("Expected error for missing source directory"),
}
}
#[test]
fn test_inverse_sync_empty_source_dir_produces_error() {
let source_dir = TempDir::new().expect("Failed to create source dir");
let ontology_dir = TempDir::new().expect("Failed to create ontology dir");
let ontology_path = create_ontology_file(ontology_dir.path(), ONTOLOGY_TTL);
let files = collect_artifact_files(source_dir.path());
match files {
Ok(file_list) => assert!(file_list.is_empty(), "Empty dir should yield no artifacts"),
Err(e) => panic!("Should not error on empty dir: {}", e),
}
}
#[test]
fn test_inverse_sync_incoherent_recovery_fails_coherence_check() {
let source_dir = TempDir::new().expect("Failed to create source dir");
let ontology_dir = TempDir::new().expect("Failed to create ontology dir");
create_rust_artifact(source_dir.path(), "service.rs", RUST_SERVICE_CODE);
let incompatible_ontology = r#"
@prefix code: <http://example.org/code/> .
code:OtherService a code:Service ;
rdfs:label "Completely Different Service" .
"#;
let ontology_path = create_ontology_file(ontology_dir.path(), incompatible_ontology);
let ontology_content = fs::read_to_string(&ontology_path).expect("Read ontology");
let mut hasher = blake3::Hasher::new();
hasher.update(ontology_content.as_bytes());
let ontology_hash = hasher.finalize().to_hex().to_string();
assert!(!ontology_hash.is_empty(), "Ontology hash must be non-empty");
let recovered_hash = {
let mut h = blake3::Hasher::new();
h.update(b"different content");
h.finalize().to_hex().to_string()
};
assert_ne!(
ontology_hash, recovered_hash,
"Incompatible ontology must produce different hash"
);
}
#[test]
fn test_inverse_sync_envelope_json_structure() {
use ggen_core::receipt::ProvenanceEnvelope;
let envelope = ProvenanceEnvelope::new();
let json = envelope.to_json().expect("Serialize envelope");
let parsed: serde_json::Value = serde_json::from_str(&json).expect("Parse envelope JSON");
assert!(parsed.is_object(), "Envelope must be a JSON object");
assert!(
parsed["forward_receipt"].is_null(),
"Empty envelope has no forward receipt"
);
assert!(
parsed["inverse_receipt"].is_null(),
"Empty envelope has no inverse receipt"
);
assert!(
parsed["coherence_report"].is_null(),
"Empty envelope has no coherence report"
);
assert!(
parsed["operation_chain"].is_array(),
"operation_chain must be an array"
);
}
#[test]
fn test_inverse_sync_corrupted_signing_key_fails_verification() {
let keys_dir = TempDir::new().expect("Failed to create keys dir");
let (signing_key_path1, signing_key1) = create_signing_key(keys_dir.path());
let mut different_bytes = [0u8; 32];
different_bytes[0] = 1; let signing_key2 = SigningKey::from_bytes(&different_bytes);
let key_bytes = signing_key2.to_bytes();
let hex_key2 = hex::encode(key_bytes);
let signing_key_path2 = keys_dir.path().join("signing2.key");
fs::write(&signing_key_path2, hex_key2).expect("Write second key");
let test_message = b"test receipt body";
let signature1 = signing_key1.sign(test_message);
let signature2 = signing_key2.sign(test_message);
assert_ne!(
signature1.to_bytes(),
signature2.to_bytes(),
"Different keys must produce different signatures"
);
}
#[test]
fn test_inverse_sync_sabotage_missing_ontology_file() {
let keys_dir = TempDir::new().expect("Failed to create keys dir");
let (_signing_key_path, _signing_key) = create_signing_key(keys_dir.path());
let missing_ontology =
PathBuf::from(format!("/tmp/non-existent-ontology-{}.ttl", Uuid::new_v4()));
let result = fs::read_to_string(&missing_ontology);
assert!(result.is_err(), "Reading non-existent ontology must fail");
}
#[test]
fn test_inverse_sync_multiple_artifacts_same_directory() {
let source_dir = TempDir::new().expect("Failed to create source dir");
create_rust_artifact(
source_dir.path(),
"service1.rs",
r#"
pub struct ServiceOne {}
impl ServiceOne {
pub fn operation(&self) {}
}
"#,
);
create_rust_artifact(
source_dir.path(),
"service2.rs",
r#"
pub struct ServiceTwo {}
impl ServiceTwo {
pub fn action(&self) {}
}
"#,
);
let files = collect_artifact_files(source_dir.path()).expect("Collect artifacts");
assert_eq!(files.len(), 2, "Must collect both Rust artifacts");
for file in &files {
let ext = file.extension().and_then(|e| e.to_str());
assert_eq!(ext, Some("rs"), "All files must be .rs");
}
}
#[test]
fn test_inverse_sync_ontology_hash_deterministic() {
let ontology_dir = TempDir::new().expect("Failed to create ontology dir");
let ontology_path = create_ontology_file(ontology_dir.path(), ONTOLOGY_TTL);
let content = fs::read_to_string(&ontology_path).expect("Read ontology");
let hash1 = {
let mut hasher = blake3::Hasher::new();
hasher.update(content.as_bytes());
hasher.finalize().to_hex().to_string()
};
let hash2 = {
let mut hasher = blake3::Hasher::new();
hasher.update(content.as_bytes());
hasher.finalize().to_hex().to_string()
};
assert_eq!(hash1, hash2, "Ontology hash must be deterministic");
}
#[test]
fn test_inverse_sync_key_path_resolution_default() {
let default_key_path = PathBuf::from(".ggen/keys/signing.key");
let resolved = if let Some(explicit) = None::<String> {
PathBuf::from(explicit)
} else {
PathBuf::from(".ggen/keys/signing.key")
};
assert_eq!(resolved, default_key_path);
}
#[test]
fn test_inverse_sync_key_path_resolution_explicit() {
let custom_path = "/custom/signing.key";
let resolved = PathBuf::from(custom_path);
assert_eq!(resolved, PathBuf::from(custom_path));
}
#[test]
fn test_inverse_sync_envelope_path_resolution_default() {
let default_envelope = PathBuf::from(".ggen/envelopes/latest.json");
let resolved = if let Some(explicit) = None::<String> {
PathBuf::from(explicit)
} else {
PathBuf::from(".ggen/envelopes/latest.json")
};
assert_eq!(resolved, default_envelope);
}
fn collect_artifact_files(
source_dir: &std::path::Path,
) -> std::result::Result<Vec<PathBuf>, String> {
if !source_dir.exists() {
return Err(format!(
"Source directory not found: {}",
source_dir.display()
));
}
let mut files = Vec::new();
for entry in
fs::read_dir(source_dir).map_err(|e| format!("Failed to read source directory: {}", e))?
{
let entry = entry.map_err(|e| format!("Failed to read directory entry: {}", e))?;
let path = entry.path();
if path.is_dir() {
let sub_files = collect_artifact_files(&path)?;
files.extend(sub_files);
} else if let Some(ext) = path.extension().and_then(|e| e.to_str()) {
if matches!(ext, "rs" | "ex" | "exs" | "go") {
files.push(path);
}
}
}
Ok(files)
}