//! IR deserialization tests against the AFI fixture (KDL). //! //! The fixture is captured from `cores/mizan-python/src/mizan_core/ir.py::build_ir()` //! against `tests/afi/fixture.py`. Each test exercises a different facet //! of the IR — function set, per-function field decoding, context-param //! elevation, and named-type presence — to confirm the typed Rust structs //! match the KDL shape the backend emits. use std::path::PathBuf; use mizan_codegen::fetch::parse_ir_from_str; use mizan_codegen::ir::{AffectKind, IsContext, NamedType, Primitive, Transport}; fn load_fixture() -> mizan_codegen::ir::MizanIR { let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")) .join("tests/fixtures/afi_ir.kdl"); let raw = std::fs::read_to_string(&path) .unwrap_or_else(|e| panic!("read {}: {e}", path.display())); parse_ir_from_str(&raw).unwrap_or_else(|e| panic!("parse IR: {e}")) } #[test] fn afi_fixture_deserializes_function_set() { let ir = load_fixture(); let names: Vec<&str> = ir.functions.iter().map(|f| f.name.as_str()).collect(); assert_eq!(ir.functions.len(), 7, "expected 7 functions, got {}: {names:?}", ir.functions.len()); for expected in [ "echo", "whoami", "user_profile", "user_orders", "update_profile", "find_user", "rename_user", ] { assert!(names.contains(&expected), "missing function {expected:?} in {names:?}"); } } #[test] fn afi_fixture_function_field_decode() { let ir = load_fixture(); let echo = ir.functions.iter().find(|f| f.name == "echo").unwrap(); assert_eq!(echo.camel_name, "echo"); assert!(echo.has_input); assert_eq!(echo.input_type.as_deref(), Some("echoInput")); assert_eq!(echo.output_type, "echoOutput"); assert!(!echo.output_nullable); assert_eq!(echo.transport, Transport::Http); assert_eq!(echo.is_context, IsContext::No); let whoami = ir.functions.iter().find(|f| f.name == "whoami").unwrap(); assert!(!whoami.has_input); // `find_user` returns `ProfileOutput | None` — outputNullable must be true. let find_user = ir.functions.iter().find(|f| f.name == "find_user").unwrap(); assert!(find_user.output_nullable, "find_user must be outputNullable"); // Context-typed function picks up the context name. let user_profile = ir.functions.iter().find(|f| f.name == "user_profile").unwrap(); assert_eq!(user_profile.is_context.as_str(), Some("user")); // Mutation with `affects="user"` lands in `affects` as a context target. let update_profile = ir.functions.iter().find(|f| f.name == "update_profile").unwrap(); assert_eq!(update_profile.affects.len(), 1); assert_eq!(update_profile.affects[0].kind, AffectKind::Context); assert_eq!(update_profile.affects[0].name, "user"); // Mutation with `merge="user"`. let rename_user = ir.functions.iter().find(|f| f.name == "rename_user").unwrap(); assert_eq!(rename_user.merge, vec!["user".to_string()]); } #[test] fn afi_fixture_context_param_elevation() { let ir = load_fixture(); let user = ir.contexts.get("user").expect("user context group"); // Both context functions share `user_id` as a required param. let user_id = user.params.get("user_id").expect("user_id param"); assert_eq!(user_id.ty, Primitive::Integer); assert!(user_id.required, "user_id is required (declared by every fn in the group)"); assert!(user_id.shared_by.contains(&"user_profile".to_string())); assert!(user_id.shared_by.contains(&"user_orders".to_string())); } #[test] fn afi_fixture_named_types_present() { let ir = load_fixture(); // Every IR function references its Input / Output // type by name; the IR's `type` section must declare each as a named // type (struct, alias to a list, etc.). for expected in [ "echoInput", "echoOutput", "whoamiOutput", "userProfileInput", "userProfileOutput", "userOrdersInput", "updateProfileInput", "updateProfileOutput", "findUserInput", "findUserOutput", "renameUserInput", "renameUserOutput", ] { let ty = ir.types.get(expected) .unwrap_or_else(|| panic!("missing type {expected:?}")); // Each named type is one of the four KDL shapes — sanity-check // we round-tripped a non-trivial declaration. match ty { NamedType::Struct(_) | NamedType::List(_) | NamedType::Enum(_) | NamedType::Alias(_) => {} } } }