use crate::core::ir::{ParamDef, TypeRef};
use ahash::{AHashMap, AHashSet};
use super::orchestration::gen_function_wrapper_footer;
use super::params::{ParamConversionContext, gen_param_conversion_with_enums};
use super::return_handling::return_type_needs_non_serde_named;
#[test]
fn return_type_needs_non_serde_named_vec_non_serde() {
let mut serde_names: AHashSet<String> = AHashSet::new();
serde_names.insert("ExtractionResult".to_string());
let vec_non_serde = TypeRef::Vec(Box::new(TypeRef::Named("PatternMatch".to_string())));
assert!(
return_type_needs_non_serde_named(&vec_non_serde, &serde_names),
"Vec<PatternMatch> without Serialize must be detected as needing stub"
);
}
#[test]
fn return_type_needs_non_serde_named_vec_serde_ok() {
let mut serde_names: AHashSet<String> = AHashSet::new();
serde_names.insert("ExtractionResult".to_string());
let vec_serde = TypeRef::Vec(Box::new(TypeRef::Named("ExtractionResult".to_string())));
assert!(
!return_type_needs_non_serde_named(&vec_serde, &serde_names),
"Vec<ExtractionResult> with Serialize must NOT be detected as needing stub"
);
}
#[test]
fn return_type_needs_non_serde_named_primitive_vec_not_affected() {
let serde_names: AHashSet<String> = AHashSet::new();
assert!(!return_type_needs_non_serde_named(
&TypeRef::Vec(Box::new(TypeRef::String)),
&serde_names
));
assert!(!return_type_needs_non_serde_named(
&TypeRef::Vec(Box::new(TypeRef::Primitive(crate::core::ir::PrimitiveType::U64))),
&serde_names
));
}
#[test]
fn named_param_is_mut_call_site_passes_local_directly() {
let p = ParamDef {
name: "result".to_string(),
ty: TypeRef::Named("ExtractionResult".to_string()),
optional: false,
default: None,
sanitized: false,
typed_default: None,
is_ref: false,
is_mut: true,
newtype_wrapper: None,
original_type: None,
map_is_ahash: false,
map_key_is_cow: false,
vec_inner_is_ref: false,
map_is_btree: false,
core_wrapper: crate::core::ir::CoreWrapper::None,
};
let rs = format!("{}_rs", p.name);
let result = if p.is_mut {
rs.clone()
} else if p.is_ref {
format!("&{rs}")
} else {
rs.clone()
};
assert_eq!(
result, "result_rs",
"is_mut Named param must pass local directly (already &mut T)"
);
}
#[test]
fn enum_param_local_name_uses_param_name_not_type_name() {
let mut enum_names: AHashSet<String> = AHashSet::new();
enum_names.insert("RedactionStrategy".to_string());
let p = ParamDef {
name: "strategy".to_string(),
ty: TypeRef::Named("RedactionStrategy".to_string()),
optional: false,
default: None,
sanitized: false,
typed_default: None,
is_ref: false,
is_mut: false,
newtype_wrapper: None,
original_type: None,
map_is_ahash: false,
map_key_is_cow: false,
vec_inner_is_ref: false,
map_is_btree: false,
core_wrapper: crate::core::ir::CoreWrapper::None,
};
let output = gen_param_conversion_with_enums(
&p,
&ParamConversionContext {
has_error: false,
is_bytes_result: false,
return_type: &TypeRef::Unit,
ffi_return_type: None,
core_import: "sample_crate",
path_map: &AHashMap::new(),
enum_names: &enum_names,
},
);
assert!(
output.contains("let strategy_rs ="),
"enum local must be named after param (strategy_rs), got:\n{output}"
);
assert!(
output.contains("redaction_strategy_from_i32_rs(strategy)"),
"enum helper must receive the FFI param name (strategy), got:\n{output}"
);
}
#[test]
fn panic_footer_uses_existing_failure_sentinel() {
let pointer_footer = gen_function_wrapper_footer(
&Some("*mut std::ffi::c_char".to_string()),
&TypeRef::String,
false,
false,
);
assert!(pointer_footer.contains("AssertUnwindSafe(set_panic_error)"));
assert!(pointer_footer.contains("std::ptr::null_mut()"));
let const_pointer_footer = gen_function_wrapper_footer(
&Some("*const sample_runtime::RawValue".to_string()),
&TypeRef::Named("Value".to_string()),
false,
false,
);
assert!(const_pointer_footer.contains("std::ptr::null()"));
assert!(!const_pointer_footer.contains("std::ptr::null_mut()"));
let status_footer = gen_function_wrapper_footer(&Some("i32".to_string()), &TypeRef::Unit, true, false);
assert!(status_footer.contains("-1"));
}
#[test]
fn panic_footer_skips_closure_close_for_trivial_call() {
let footer = gen_function_wrapper_footer(&None, &TypeRef::Unit, false, true);
assert!(
!footer.contains("})) {"),
"trivial_call footer must not re-close a closure the header never opened:\n{footer}"
);
assert!(
footer.trim_start().starts_with("Ok(value) => value,"),
"trivial_call footer must start directly at the match arms:\n{footer}"
);
}
#[test]
fn scalar_handle_override_controls_parameter_failure_sentinel() {
let parameter = ParamDef {
name: "source".to_string(),
ty: TypeRef::String,
..ParamDef::default()
};
let output = gen_param_conversion_with_enums(
¶meter,
&ParamConversionContext {
has_error: false,
is_bytes_result: false,
return_type: &TypeRef::String,
ffi_return_type: Some("AlefHandle"),
core_import: "sample_lib",
path_map: &AHashMap::new(),
enum_names: &AHashSet::new(),
},
);
assert!(output.contains("return 0;"), "{output}");
assert!(!output.contains("return std::ptr::null_mut();"), "{output}");
}