use super::*;
use crate::e2e::config::{ArgMapping, CallConfig, CallOverride};
fn dto_arg() -> ArgMapping {
ArgMapping {
name: "config".into(),
field: "config".into(),
arg_type: "json_object".into(),
optional: true,
owned: false,
element_type: None,
go_type: None,
vec_inner_is_ref: false,
trait_name: None,
}
}
fn source_arg() -> ArgMapping {
ArgMapping {
name: "source".into(),
field: "source".into(),
arg_type: "string".into(),
optional: false,
owned: false,
element_type: None,
go_type: None,
vec_inner_is_ref: false,
trait_name: None,
}
}
fn functions_with_optional_config() -> Vec<crate::core::ir::FunctionDef> {
vec![crate::core::ir::FunctionDef {
name: "extract_bytes".into(),
params: vec![
crate::core::ir::ParamDef {
name: "source".into(),
ty: crate::core::ir::TypeRef::String,
optional: false,
..crate::core::ir::ParamDef::default()
},
crate::core::ir::ParamDef {
name: "config".into(),
ty: crate::core::ir::TypeRef::Named("ExtractionConfig".into()),
optional: true,
..crate::core::ir::ParamDef::default()
},
],
return_type: crate::core::ir::TypeRef::String,
..crate::core::ir::FunctionDef::default()
}]
}
fn omitted_config_call() -> CallConfig {
let mut call = CallConfig {
function: "extract_bytes".into(),
result_var: "result".into(),
args: vec![source_arg(), dto_arg()],
..CallConfig::default()
};
for language in ["kotlin", "kotlin_android"] {
call.overrides.insert(
language.into(),
CallOverride {
options_type: Some("ExtractionConfig".into()),
..CallOverride::default()
},
);
}
call
}
fn omitted_config_fixture() -> Fixture {
Fixture {
id: "extract_bytes_basic".into(),
description: "Extract with the default configuration".into(),
input: serde_json::json!({"source": "doc.pdf"}),
..Fixture::default()
}
}
fn render(kotlin_android_style: bool, functions: &[crate::core::ir::FunctionDef]) -> String {
render_snippet_body_with_ir(
&omitted_config_fixture(),
&E2eConfig {
call: omitted_config_call(),
..E2eConfig::default()
},
&ResolvedCrateConfig {
name: "sample_api".into(),
..ResolvedCrateConfig::default()
},
&[],
&[],
kotlin_android_style,
functions,
)
.expect("snippet renders")
}
#[test]
fn android_snippet_passes_null_for_an_omitted_argument_the_target_declares_optional() {
let body = render(true, &functions_with_optional_config());
assert!(
body.contains("SampleApi.extractBytes(\"doc.pdf\", null)"),
"a declared-optional DTO parameter must be filled with null, not a constructor: {body}"
);
assert!(
!body.contains("ExtractionConfig()"),
"must not guess that ExtractionConfig is default-constructible: {body}"
);
}
#[test]
fn jvm_snippet_passes_null_rather_than_a_builder_the_backend_may_not_have_emitted() {
let body = render(false, &functions_with_optional_config());
assert!(
body.contains("SampleApi.extractBytes(\"doc.pdf\", null)"),
"a declared-optional DTO parameter must be filled with null, not a builder chain: {body}"
);
assert!(
!body.contains("builder()"),
"must not guess that a builder factory was emitted for ExtractionConfig: {body}"
);
}
#[test]
fn android_snippet_keeps_the_constructor_fallback_when_no_ir_is_in_scope() {
let body = render(true, &[]);
assert!(
body.contains("SampleApi.extractBytes(\"doc.pdf\", ExtractionConfig())"),
"without IR the pre-existing constructor fallback must be unchanged: {body}"
);
}
#[test]
fn android_snippet_still_refuses_null_for_a_declared_required_argument() {
let functions = vec![crate::core::ir::FunctionDef {
name: "extract_bytes".into(),
params: vec![
crate::core::ir::ParamDef {
name: "source".into(),
ty: crate::core::ir::TypeRef::String,
optional: false,
..crate::core::ir::ParamDef::default()
},
crate::core::ir::ParamDef {
name: "config".into(),
ty: crate::core::ir::TypeRef::Named("ExtractionConfig".into()),
optional: false,
..crate::core::ir::ParamDef::default()
},
],
return_type: crate::core::ir::TypeRef::String,
..crate::core::ir::FunctionDef::default()
}];
let body = render(true, &functions);
assert!(
body.contains("SampleApi.extractBytes(\"doc.pdf\", ExtractionConfig())"),
"a declared-required DTO parameter still needs a positional value: {body}"
);
assert!(
!body.contains(", null)"),
"a declared-required Kotlin parameter must never receive a bare null: {body}"
);
}