use k9iser::abi::{parse_must_rule, ConfigFormat, SafetyTier};
use k9iser::codegen::contract::build_k9_contract;
use k9iser::codegen::parser::{parse_config_string, ParsedEntry, ValueType};
use k9iser::codegen::validator::validate_config;
use k9iser::manifest;
use tempfile::TempDir;
#[test]
fn test_init_creates_manifest() {
let dir = TempDir::new().unwrap();
let path = dir.path().to_str().unwrap();
manifest::init_manifest(path).unwrap();
let manifest_path = dir.path().join("k9iser.toml");
assert!(manifest_path.exists(), "k9iser.toml should be created");
let content = std::fs::read_to_string(&manifest_path).unwrap();
assert!(content.contains("[project]"), "Should have [project] section");
assert!(content.contains("safety-tier"), "Should have safety-tier field");
assert!(content.contains("[[configs]]"), "Should have [[configs]] section");
assert!(content.contains("[validation]"), "Should have [validation] section");
assert!(content.contains("must"), "Should have must pillar");
assert!(content.contains("trust"), "Should have trust pillar");
assert!(content.contains("dust"), "Should have dust pillar");
assert!(content.contains("intend"), "Should have intend pillar");
}
#[test]
fn test_load_and_validate_manifest() {
let dir = TempDir::new().unwrap();
let path = dir.path().to_str().unwrap();
manifest::init_manifest(path).unwrap();
let manifest_path = dir.path().join("k9iser.toml");
let m = manifest::load_manifest(manifest_path.to_str().unwrap()).unwrap();
manifest::validate(&m).unwrap();
assert_eq!(m.project.name, "my-config-project");
assert_eq!(m.project.safety_tier, "kennel");
assert_eq!(m.configs.len(), 1);
assert_eq!(m.configs[0].name, "app-config");
assert_eq!(m.configs[0].format, "toml");
assert!(!m.configs[0].must.is_empty());
assert!(m.validation.strict);
}
#[test]
fn test_validate_rejects_empty_name() {
let dir = TempDir::new().unwrap();
let manifest_content = r#"
[project]
name = ""
safety-tier = "kennel"
[validation]
strict = true
"#;
let path = dir.path().join("k9iser.toml");
std::fs::write(&path, manifest_content).unwrap();
let m = manifest::load_manifest(path.to_str().unwrap()).unwrap();
let result = manifest::validate(&m);
assert!(result.is_err(), "Empty name should be rejected");
assert!(
result.unwrap_err().to_string().contains("name"),
"Error should mention name"
);
}
#[test]
fn test_generate_produces_k9_contracts() {
let dir = TempDir::new().unwrap();
let config_dir = dir.path().join("config");
std::fs::create_dir_all(&config_dir).unwrap();
std::fs::write(
config_dir.join("app.toml"),
"[server]\nport = 8080\nhost = \"localhost\"\n",
)
.unwrap();
let manifest_content = r#"
[project]
name = "test-project"
safety-tier = "kennel"
[[configs]]
name = "app-config"
source = "config/app.toml"
format = "toml"
must = ["port > 0", "port < 65536", "host != ''"]
trust = ["signed-by: ci-pipeline"]
dust = ["remove: deprecated-keys"]
intend = ["production-ready"]
[validation]
strict = true
"#;
let manifest_path = dir.path().join("k9iser.toml");
std::fs::write(&manifest_path, manifest_content).unwrap();
let m = manifest::load_manifest(manifest_path.to_str().unwrap()).unwrap();
let output_dir = dir.path().join("generated");
let original_dir = std::env::current_dir().unwrap();
std::env::set_current_dir(dir.path()).unwrap();
let result = k9iser::codegen::generate_all(&m, output_dir.to_str().unwrap());
std::env::set_current_dir(&original_dir).unwrap();
assert!(result.is_ok(), "Generation should succeed: {:?}", result);
let contract_file = output_dir.join("app-config.k9");
assert!(contract_file.exists(), ".k9 contract file should exist");
let content = std::fs::read_to_string(&contract_file).unwrap();
assert!(content.contains("[must]"));
assert!(content.contains("port : int"));
assert!(content.contains("[trust]"));
assert!(content.contains("[dust]"));
assert!(content.contains("[intend]"));
}
#[test]
fn test_generate_produces_validator() {
let entries = vec![
ParsedEntry {
key: "server.port".into(),
value: "8080".into(),
value_type: ValueType::Int,
},
ParsedEntry {
key: "server.host".into(),
value: "localhost".into(),
value_type: ValueType::String,
},
];
let contract = build_k9_contract(
"test",
"config/test.toml",
ConfigFormat::Toml,
SafetyTier::Kennel,
&[
"port > 0".into(),
"port < 65536".into(),
"host != ''".into(),
],
&["signed-by: ci".into()],
&["remove: old-keys".into()],
&["staging".into()],
);
let result = validate_config(&entries, &contract);
assert!(result.is_pass(), "Valid config should pass: {}", result);
let bad_entries = vec![ParsedEntry {
key: "port".into(),
value: "-5".into(),
value_type: ValueType::Int,
}];
let bad_result = validate_config(&bad_entries, &contract);
assert!(
!bad_result.is_pass(),
"Invalid config should fail validation"
);
}
#[test]
fn test_must_rule_parsing() {
let cases = vec![
("port > 0", "port", ">", "0"),
("port < 65536", "port", "<", "65536"),
("count >= 1", "count", ">=", "1"),
("timeout <= 30", "timeout", "<=", "30"),
("mode == 'production'", "mode", "==", "'production'"),
("host != ''", "host", "!=", "''"),
];
for (input, expected_key, expected_op, expected_val) in cases {
let rule = parse_must_rule(input);
assert!(rule.is_some(), "Should parse: {}", input);
let rule = rule.unwrap();
assert_eq!(rule.key, expected_key, "Key mismatch for: {}", input);
assert_eq!(rule.operator, expected_op, "Operator mismatch for: {}", input);
assert_eq!(rule.value, expected_val, "Value mismatch for: {}", input);
}
}
#[test]
fn test_toml_config_parsing() {
let content = r#"
[server]
port = 8080
host = "localhost"
debug = false
weight = 1.5
[database]
url = "postgres://localhost/mydb"
pool-size = 10
"#;
let entries = parse_config_string(content, ConfigFormat::Toml).unwrap();
let port = entries.iter().find(|e| e.key == "server.port").unwrap();
assert_eq!(port.value, "8080");
assert_eq!(port.value_type, ValueType::Int);
let host = entries.iter().find(|e| e.key == "server.host").unwrap();
assert_eq!(host.value, "localhost");
assert_eq!(host.value_type, ValueType::String);
let debug = entries.iter().find(|e| e.key == "server.debug").unwrap();
assert_eq!(debug.value, "false");
assert_eq!(debug.value_type, ValueType::Bool);
let weight = entries.iter().find(|e| e.key == "server.weight").unwrap();
assert_eq!(weight.value, "1.5");
assert_eq!(weight.value_type, ValueType::Float);
let pool = entries.iter().find(|e| e.key == "database.pool-size").unwrap();
assert_eq!(pool.value, "10");
assert_eq!(pool.value_type, ValueType::Int);
}