//! Upload type-shape lowers to TS `File` across cardinalities. Separate from //! the byte-parity baselines (which mustn't carry an upload field — the //! three-way AFI parity gate includes the Rust adapter, which doesn't wire //! uploads yet). use std::path::PathBuf; use mizan_codegen::config::{Config, SourceConfig}; use mizan_codegen::emit::stage1::Stage1; use mizan_codegen::emit::CodegenTarget; use mizan_codegen::fetch::parse_ir_from_str; const UPLOAD_IR: &str = r#" type "SetAvatarInput" { struct { field "user_id" { primitive "integer" } field "avatar" { upload max-size=5242880 { content-type "image/png" content-type "image/jpeg" } } field "photos" { list { upload } } field "thumb" required=#false { optional { upload } } } } type "setAvatarOutput" { alias { primitive "string" } } function "set_avatar" { camel "setAvatar" has-input #true input "SetAvatarInput" output "setAvatarOutput" transport "http" affects "user" } "#; fn cfg() -> Config { Config { project_id: None, output: PathBuf::from("/tmp"), targets: vec!["stage1".to_string()], source: SourceConfig { fastapi: None, django: None, rust: None, script: None }, rust_kernel: None, rust_crate_name: None, } } #[test] fn upload_fields_lower_to_file_type() { let ir = parse_ir_from_str(UPLOAD_IR).expect("upload IR parses"); let files = Stage1.emit(&ir, &cfg()); let types = files .iter() .find(|f| f.rel_path.to_string_lossy().contains("types.ts")) .expect("types.ts emitted"); let src = &types.content; assert!(src.contains("avatar: File"), "required upload → File:\n{src}"); assert!(src.contains("File[]"), "list[upload] → File[]:\n{src}"); assert!(src.contains("File | null"), "optional upload → File | null:\n{src}"); }