mod fixtures;
use amalgam_codegen::{nickel::NickelCodegen, Codegen};
use amalgam_core::{
ir::{Import, Module, TypeDefinition},
types::{Field, Type},
IR,
};
use amalgam_parser::{crd::CRDParser, package::PackageGenerator, Parser};
use fixtures::Fixtures;
use std::collections::BTreeMap;
#[test]
fn test_k8s_type_reference_detection() {
let crd = Fixtures::simple_with_metadata();
let parser = CRDParser::new();
let ir = parser.parse(crd).expect("Failed to parse CRD");
assert_eq!(ir.modules.len(), 1);
let module = &ir.modules[0];
assert_eq!(module.types.len(), 1);
let type_def = &module.types[0];
if let Type::Record { fields, .. } = &type_def.ty {
assert!(fields.contains_key("metadata"));
let metadata_field = &fields["metadata"];
match &metadata_field.ty {
Type::Reference(name) => {
assert_eq!(name, "io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta");
}
Type::Optional(inner) => {
if let Type::Reference(name) = &**inner {
assert_eq!(name, "io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta");
} else {
}
}
Type::Record { .. } => {
}
Type::Any => {
}
_ => panic!("Unexpected type for metadata: {:?}", metadata_field.ty),
}
} else {
panic!("Expected Record type, got {:?}", type_def.ty);
}
}
#[test]
fn test_import_generation_for_k8s_types() {
let mut package = PackageGenerator::new(
"test-package".to_string(),
std::path::PathBuf::from("/tmp/test"),
);
let crd1 = Fixtures::simple_with_metadata();
let crd2 = Fixtures::with_arrays();
package.add_crd(crd1);
package.add_crd(crd2);
let ns_package = package
.generate_package()
.expect("Failed to generate package");
if let Some(content) = ns_package.generate_kind_file("test.io", "v1", "simple") {
assert!(content.contains("import"), "Missing import statement");
assert!(content.contains("k8s_io"), "Missing k8s import");
assert!(
content.contains("objectmeta.ncl"),
"Missing objectmeta import path"
);
} else {
let crd = Fixtures::simple_with_metadata();
let parser = CRDParser::new();
let ir = parser.parse(crd).expect("Failed to parse CRD");
let mut codegen = amalgam_codegen::nickel::NickelCodegen::new();
let content = codegen.generate(&ir).expect("Failed to generate");
assert!(
content.contains("k8s_io") || content.contains("k8s_v1"),
"Missing k8s import resolution in: {}",
content
);
}
}
#[test]
fn test_reference_resolution_to_alias() {
let mut ir = IR::new();
let mut fields = BTreeMap::new();
fields.insert(
"metadata".to_string(),
Field {
ty: Type::Reference("io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta".to_string()),
required: false,
default: None,
description: Some("Standard Kubernetes metadata".to_string()),
},
);
let module = Module {
name: "test.example.io".to_string(),
imports: vec![Import {
path: "../../k8s_io/v1/objectmeta.ncl".to_string(),
alias: Some("k8s_io_v1".to_string()),
items: vec![],
}],
types: vec![TypeDefinition {
name: "TestResource".to_string(),
ty: Type::Record {
fields,
open: false,
},
documentation: None,
annotations: BTreeMap::new(),
}],
constants: vec![],
metadata: Default::default(),
};
ir.add_module(module);
let mut codegen = NickelCodegen::new();
let generated = codegen
.generate(&ir)
.expect("Failed to generate Nickel code");
assert!(
generated.contains("let k8s_io_v1 = import"),
"Missing import statement in generated code"
);
assert!(
generated.contains("k8s_io_v1.ObjectMeta"),
"Reference not resolved to alias. Generated:\n{}",
generated
);
assert!(
!generated.contains("io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta"),
"Original reference still present. Generated:\n{}",
generated
);
}
#[test]
fn test_multiple_k8s_type_references() {
let crd = Fixtures::multiple_k8s_refs();
let parser = CRDParser::new();
let ir = parser.parse(crd).expect("Failed to parse CRD");
let mut codegen = amalgam_codegen::nickel::NickelCodegen::new();
let content = codegen.generate(&ir).expect("Failed to generate");
assert!(content.contains("MultiRef"), "Missing type name");
assert!(content.contains("spec"), "Missing spec field");
}
#[test]
fn test_no_import_for_local_types() {
let crd = Fixtures::nested_objects();
let parser = CRDParser::new();
let ir = parser.parse(crd).expect("Failed to parse CRD");
assert_eq!(
ir.modules[0].imports.len(),
0,
"Unexpected imports for CRD without k8s types"
);
}
#[test]
fn test_import_path_calculation() {
use amalgam_parser::imports::TypeReference;
let type_ref =
TypeReference::from_qualified_name("io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta")
.expect("Failed to parse type reference");
let import_path = type_ref.import_path("example.io", "v1");
assert_eq!(import_path, "../../k8s_io/v1/objectmeta.ncl");
let alias = type_ref.module_alias();
assert_eq!(alias, "k8s_io_v1");
}
#[test]
fn test_case_insensitive_type_matching() {
let mut ir = IR::new();
let mut fields = BTreeMap::new();
fields.insert(
"metadata".to_string(),
Field {
ty: Type::Reference("io.k8s.apimachinery.pkg.apis.meta.v1.ObjectMeta".to_string()),
required: false,
default: None,
description: None,
},
);
let module = Module {
name: "test".to_string(),
imports: vec![Import {
path: "../../k8s_io/v1/objectmeta.ncl".to_string(),
alias: Some("k8s_v1".to_string()),
items: vec![],
}],
types: vec![TypeDefinition {
name: "Test".to_string(),
ty: Type::Record {
fields,
open: false,
},
documentation: None,
annotations: BTreeMap::new(),
}],
constants: vec![],
metadata: Default::default(),
};
ir.add_module(module);
let mut codegen = NickelCodegen::new();
let generated = codegen.generate(&ir).expect("Failed to generate");
assert!(
generated.contains("k8s_v1.ObjectMeta"),
"Failed to resolve with case difference. Generated:\n{}",
generated
);
}
#[test]
fn test_package_structure_generation() {
let mut package = PackageGenerator::new(
"test-package".to_string(),
std::path::PathBuf::from("/tmp/test"),
);
let crd1 = Fixtures::simple_with_metadata();
let crd2 = Fixtures::with_arrays();
let crd3 = Fixtures::multi_version();
package.add_crd(crd1);
package.add_crd(crd2);
package.add_crd(crd3);
let ns_package = package
.generate_package()
.expect("Failed to generate package");
let main_module = ns_package.generate_main_module();
assert!(
main_module.contains("test_io"),
"Missing test.io group in main module"
);
}