use crate::core::config::ResolvedCrateConfig;
use crate::core::config::extras::AdapterConfig;
use crate::e2e::config::E2eConfig;
use crate::e2e::escape::escape_java;
use crate::e2e::field_access::FieldResolver;
use crate::e2e::fixture::Fixture;
use heck::{ToLowerCamelCase, ToUpperCamelCase};
use super::args::{JavaArgsContext, build_args_and_setup};
use super::assertions::render_assertion;
use super::http::render_http_test_method;
use super::values::{java_builder_expression, json_to_java};
use super::visitor::{apply_java_visitor_arg, build_java_visitor, java_visitor_binding};
#[allow(clippy::too_many_arguments)]
pub(super) fn render_test_method(
out: &mut String,
fixture: &Fixture,
class_name: &str,
_function_name: &str,
_result_var: &str,
_args: &[crate::e2e::config::ArgMapping],
options_type: Option<&str>,
result_is_simple: bool,
e2e_config: &E2eConfig,
nested_types: &std::collections::HashMap<String, String>,
nested_types_optional: bool,
adapters: &[AdapterConfig],
config: &ResolvedCrateConfig,
type_defs: &[crate::core::ir::TypeDef],
) {
if let Some(http) = &fixture.http {
render_http_test_method(out, fixture, http);
return;
}
let call_config = e2e_config.resolve_call_for_fixture(
fixture.call.as_deref(),
&fixture.id,
&fixture.resolved_category(),
&fixture.tags,
&fixture.input,
);
let call_field_resolver = FieldResolver::new(
e2e_config.effective_fields(call_config),
e2e_config.effective_fields_optional(call_config),
e2e_config.effective_result_fields(call_config),
e2e_config.effective_fields_array(call_config),
&std::collections::HashSet::new(),
);
let field_resolver = &call_field_resolver;
let effective_enum_fields = e2e_config.effective_fields_enum(call_config);
let enum_fields = effective_enum_fields;
let lang = "java";
let call_overrides = call_config.overrides.get(lang);
let effective_function_name = call_overrides
.and_then(|o| o.function.as_ref())
.cloned()
.unwrap_or_else(|| call_config.function.to_lower_camel_case());
let effective_result_var = &call_config.result_var;
let function_name = effective_function_name.as_str();
let result_var = effective_result_var.as_str();
let recipe = crate::e2e::codegen::recipe::ResolvedE2eCallRecipe::resolve(lang, fixture, call_config, type_defs);
let args: &[crate::e2e::config::ArgMapping] = recipe.args;
let method_name = fixture.id.to_upper_camel_case();
let description = &fixture.description;
let expects_error = fixture.assertions.iter().any(|a| a.assertion_type == "error");
let effective_options_type: Option<String> = recipe
.options_type
.map(str::to_string)
.or_else(|| options_type.map(str::to_string))
.or_else(|| {
recipe
.compatible_options_type(&["csharp", "c", "go", "php", "python"])
.map(str::to_string)
});
let effective_options_type = effective_options_type.as_deref();
let auto_from_json = effective_options_type.is_some()
&& call_overrides.and_then(|o| o.options_via.as_deref()).is_none()
&& e2e_config
.call
.overrides
.get(lang)
.and_then(|o| o.options_via.as_deref())
.is_none();
let client_factory: Option<String> = call_overrides.and_then(|o| o.client_factory.clone()).or_else(|| {
e2e_config
.call
.overrides
.get(lang)
.and_then(|o| o.client_factory.clone())
});
let options_via: String = call_overrides
.and_then(|o| o.options_via.clone())
.or_else(|| e2e_config.call.overrides.get(lang).and_then(|o| o.options_via.clone()))
.unwrap_or_else(|| {
if auto_from_json {
"from_json".to_string()
} else {
"kwargs".to_string()
}
});
let effective_result_is_simple =
call_overrides.is_some_and(|o| o.result_is_simple) || call_config.result_is_simple || result_is_simple;
let effective_result_is_bytes = call_overrides.is_some_and(|o| o.result_is_bytes);
let effective_result_is_option = call_overrides.is_some_and(|o| o.result_is_option) || call_config.result_is_option;
let needs_deser = effective_options_type.is_some()
&& args.iter().any(|arg| {
if arg.arg_type != "json_object" {
return false;
}
let val = super::super::resolve_field(&fixture.input, &arg.field);
!val.is_null() && !val.is_array()
});
let mut builder_expressions = String::new();
if let (true, Some(opts_type)) = (needs_deser, effective_options_type) {
for arg in args {
if arg.arg_type == "json_object" {
let val = super::super::resolve_field(&fixture.input, &arg.field);
if !val.is_null() && !val.is_array() {
if options_via == "from_json" {
let normalized = super::super::transform_json_keys_for_language(val, "snake_case");
let json_str = serde_json::to_string(&normalized).unwrap_or_default();
let escaped = escape_java(&json_str);
let var_name = &arg.name;
builder_expressions.push_str(&format!(
" var {var_name} = JsonUtil.fromJson(\"{escaped}\", {opts_type}.class);\n",
));
} else if let Some(obj) = val.as_object() {
let empty_path_fields: Vec<String> = Vec::new();
let path_fields = call_overrides.map(|o| &o.path_fields).unwrap_or(&empty_path_fields);
let builder_expr = java_builder_expression(
obj,
opts_type,
enum_fields,
nested_types,
nested_types_optional,
path_fields,
);
let var_name = &arg.name;
builder_expressions.push_str(&format!(" var {} = {};\n", var_name, builder_expr));
}
}
}
}
}
let adapter = adapters.iter().find(|a| a.name == call_config.function.as_str());
let adapter_request_type: Option<String> = adapter
.and_then(|a| a.request_type.as_deref())
.map(|rt| rt.rsplit("::").next().unwrap_or(rt).to_string());
let is_streaming_adapter =
adapter.is_some_and(|a| matches!(a.pattern, crate::core::config::extras::AdapterPattern::Streaming));
let filtered_args: Vec<_> = if adapter.is_some_and(|a| a.owner_type.is_some()) && !is_streaming_adapter {
args.iter().filter(|arg| arg.arg_type != "handle").cloned().collect()
} else {
args.to_vec()
};
let streaming_owner_handle: Option<String> =
if is_streaming_adapter && adapter.is_some_and(|a| a.owner_type.is_some()) {
filtered_args
.iter()
.find(|a| a.arg_type == "handle")
.map(|a| a.name.clone())
} else {
None
};
let mut teardown_block = String::new();
let (mut setup_lines, args_str) = build_args_and_setup(
&fixture.input,
&filtered_args,
JavaArgsContext {
class_name,
options_type: effective_options_type,
fixture,
adapter_request_type: adapter_request_type.as_deref(),
owner_handle_is_receiver: streaming_owner_handle.is_some(),
config,
type_defs,
teardown_block: &mut teardown_block,
},
);
let extra_args_slice: &[String] = recipe.extra_args;
let mut final_args = args_str;
if let Some(visitor_spec) = &fixture.visitor {
if let Some(binding) = java_visitor_binding(config, type_defs, Some(visitor_spec), effective_options_type) {
let visitor_var = build_java_visitor(&mut setup_lines, visitor_spec, class_name, &binding);
final_args = apply_java_visitor_arg(&mut setup_lines, &final_args, args, &visitor_var, &binding);
} else {
setup_lines.push(format!(
"org.junit.jupiter.api.Assumptions.assumeTrue(false, \"java visitor fixture '{}' requires trait_bridge options_type, options_field, context_type, and result_type metadata\");",
escape_java(&fixture.id)
));
}
}
if !extra_args_slice.is_empty() {
let extra_str = extra_args_slice.join(", ");
final_args = if final_args.is_empty() {
extra_str
} else {
format!("{final_args}, {extra_str}")
};
}
let mut assertions_body = String::new();
let needs_source_var = fixture
.assertions
.iter()
.any(|a| a.assertion_type == "method_result" && a.method.as_deref() == Some("run_query"));
if needs_source_var {
if let Some(source_arg) = args.iter().find(|a| a.field == "source_code") {
let field = source_arg.field.strip_prefix("input.").unwrap_or(&source_arg.field);
if let Some(val) = fixture.input.get(field) {
let java_val = json_to_java(val);
assertions_body.push_str(&format!(" var source = {}.getBytes();\n", java_val));
}
}
}
let assert_enum_types: std::collections::HashMap<String, String> = if let Some(co) = call_overrides {
co.assert_enum_fields.clone()
} else {
std::collections::HashMap::new()
};
let mut effective_enum_fields: std::collections::HashSet<String> = enum_fields.clone();
if let Some(co) = call_overrides {
for k in co.enum_fields.keys() {
effective_enum_fields.insert(k.clone());
}
}
let is_streaming =
crate::e2e::codegen::streaming_assertions::resolve_is_streaming(fixture, call_config.streaming_enabled());
let streaming_item_type =
crate::e2e::codegen::recipe::streaming_item_type(call_config, adapters, &[call_config.function.as_str()]);
for assertion in &fixture.assertions {
render_assertion(
&mut assertions_body,
assertion,
result_var,
class_name,
field_resolver,
effective_result_is_simple,
effective_result_is_bytes,
effective_result_is_option,
is_streaming,
streaming_item_type,
&effective_enum_fields,
&assert_enum_types,
);
}
let throws_clause = " throws Exception";
let (client_setup_lines, call_target) = if let Some(factory) = client_factory.as_deref() {
let factory_name = factory.to_lower_camel_case();
let fixture_id = &fixture.id;
let mut setup: Vec<String> = Vec::new();
let has_mock = fixture.mock_response.is_some() || fixture.http.is_some();
let api_key_var = fixture.env.as_ref().and_then(|e| e.api_key_var.as_deref());
if let Some(var) = api_key_var.filter(|_| has_mock) {
setup.push(format!("String apiKey = System.getenv(\"{var}\");"));
setup.push(format!(
"String mockServerUrl = System.getProperty(\"mockServerUrl\"); if (mockServerUrl == null) {{ mockServerUrl = System.getenv(\"MOCK_SERVER_URL\"); }} String baseUrl = (apiKey != null && !apiKey.isEmpty()) ? null : (mockServerUrl != null ? mockServerUrl + \"/fixtures/{fixture_id}\" : \"http://localhost:8000/fixtures/{fixture_id}\");"
));
setup.push(format!(
"System.out.println(\"{fixture_id}: \" + (baseUrl == null ? \"using real API ({var} is set)\" : \"using mock server ({var} not set)\"));"
));
setup.push(format!(
"var client = {class_name}.{factory_name}(baseUrl == null ? apiKey : \"test-key\", baseUrl, null, null, null);"
));
} else if has_mock {
if fixture.has_host_root_route() {
setup.push(format!(
"String mockServerUrl = System.getProperty(\"mockServerUrl\"); if (mockServerUrl == null) {{ mockServerUrl = System.getenv(\"MOCK_SERVER_URL\"); }} String defaultUrl = (mockServerUrl != null ? mockServerUrl : \"http://localhost:8000\") + \"/fixtures/{fixture_id}\"; String mockUrl = System.getProperty(\"mockServer.{fixture_id}\", defaultUrl);"
));
} else {
setup.push(format!(
"String mockServerUrl = System.getProperty(\"mockServerUrl\"); if (mockServerUrl == null) {{ mockServerUrl = System.getenv(\"MOCK_SERVER_URL\"); }} String mockUrl = (mockServerUrl != null ? mockServerUrl : \"http://localhost:8000\") + \"/fixtures/{fixture_id}\";"
));
}
setup.push(format!(
"var client = {class_name}.{factory_name}(\"test-key\", mockUrl, null, null, null);"
));
} else if let Some(api_key_var) = api_key_var {
setup.push(format!("String apiKey = System.getenv(\"{api_key_var}\");"));
setup.push(format!(
"org.junit.jupiter.api.Assumptions.assumeTrue(apiKey != null && !apiKey.isEmpty(), \"{api_key_var} not set\");"
));
setup.push(format!("var client = {class_name}.{factory_name}(apiKey);"));
} else {
setup.push(format!("var client = {class_name}.{factory_name}(\"test-key\");"));
}
(setup, "client".to_string())
} else {
(Vec::new(), class_name.to_string())
};
let combined_setup: Vec<String> = client_setup_lines.into_iter().chain(setup_lines).collect();
let call_expr = if let Some(ref handle_var) = streaming_owner_handle {
format!("{handle_var}.{function_name}({final_args})")
} else {
format!("{call_target}.{function_name}({final_args})")
};
let collect_snippet = if is_streaming && !expects_error {
crate::e2e::codegen::streaming_assertions::StreamingFieldResolver::collect_snippet_typed(
"java",
result_var,
"chunks",
streaming_item_type,
)
.unwrap_or_default()
} else {
String::new()
};
let rendered = crate::e2e::template_env::render(
"java/test_method.jinja",
minijinja::context! {
method_name => method_name,
description => description,
builder_expressions => builder_expressions,
setup_lines => combined_setup,
throws_clause => throws_clause,
expects_error => expects_error,
call_expr => call_expr,
result_var => result_var,
returns_void => call_config.returns_void,
collect_snippet => collect_snippet,
assertions_body => assertions_body,
teardown_block => teardown_block,
},
);
out.push_str(&rendered);
}