//! IR deserialization tests against the AFI fixture schema. //! //! The fixture is captured from the FastAPI backend's `build_schema()` //! against `tests/afi/fixture.py`. Each test exercises a different facet //! of the IR — function set, per-function field decoding, context-param //! elevation, and components.schemas presence — to confirm the typed //! Rust structs match the JSON shape the backends emit. use std::path::PathBuf; use mizan_codegen::fetch::parse_ir_from_str; use mizan_codegen::ir::{AffectKind, IsContext, Transport}; fn load_fixture() -> mizan_codegen::ir::MizanIR { let path = PathBuf::from(env!("CARGO_MANIFEST_DIR")) .join("tests/fixtures/afi_schema.json"); 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(); // Seven fixture functions per tests/afi/fixture.py. 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"); } #[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, "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_components_schemas_present() { let ir = load_fixture(); // Each fixture function pairs with an *Input/Output schema in components. for expected in [ "echoInput", "echoOutput", "whoamiOutput", "userProfileInput", "userProfileOutput", "updateProfileInput", "updateProfileOutput", "findUserInput", "findUserOutput", ] { assert!( ir.components.schemas.contains_key(expected), "missing schema {expected:?}", ); } }