use crate::core::config::ResolvedCrateConfig;
use crate::core::hash::{self, CommentStyle};
use crate::e2e::config::E2eConfig;
use crate::e2e::escape::sanitize_filename;
use crate::e2e::fixture::Fixture;
use heck::ToUpperCamelCase;
use super::test_method::render_test_method;
use super::values::{collect_nested_type_names, is_java_builtin_type, is_numeric_type_hint};
use super::visitor::{java_visitor_binding, java_visitor_imports};
fn resolve_handle_config_type(
arg: &crate::e2e::config::ArgMapping,
options_type: Option<&str>,
type_defs: &[crate::core::ir::TypeDef],
) -> Option<String> {
if arg.arg_type != "handle" {
return None;
}
options_type.map(str::to_string).or_else(|| {
let candidate = format!("{}Config", arg.name.to_upper_camel_case());
type_defs.iter().any(|ty| ty.name == candidate).then_some(candidate)
})
}
#[allow(clippy::too_many_arguments)]
pub(super) fn render_test_file(
category: &str,
fixtures: &[&Fixture],
class_name: &str,
function_name: &str,
java_group_id: &str,
binding_pkg: &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: &[crate::core::config::extras::AdapterConfig],
config: &ResolvedCrateConfig,
type_defs: &[crate::core::ir::TypeDef],
uses_harness: bool,
) -> String {
let header = hash::header(CommentStyle::DoubleSlash);
let test_class_name = format!("{}Test", sanitize_filename(category).to_upper_camel_case());
let (import_path, simple_class) = if class_name.contains('.') {
let simple = class_name.rsplit('.').next().unwrap_or(class_name);
(class_name, simple)
} else {
("", class_name)
};
let lang_for_om = "java";
let needs_object_mapper_for_handle = fixtures.iter().any(|f| {
let call_cfg =
e2e_config.resolve_call_for_fixture(f.call.as_deref(), &f.id, &f.resolved_category(), &f.tags, &f.input);
let recipe = crate::e2e::codegen::recipe::ResolvedE2eCallRecipe::resolve(lang_for_om, f, call_cfg, type_defs);
recipe.args.iter().filter(|a| a.arg_type == "handle").any(|a| {
let v = super::super::resolve_field(&f.input, &a.field);
!(v.is_null() || v.is_object() && v.as_object().is_some_and(|o| o.is_empty()))
})
});
let has_http_fixtures = fixtures.iter().any(|f| f.http.is_some());
let needs_object_mapper = needs_object_mapper_for_handle || has_http_fixtures;
let mut all_options_types: std::collections::BTreeSet<String> = std::collections::BTreeSet::new();
if let Some(t) = options_type {
all_options_types.insert(t.to_string());
}
for f in fixtures.iter() {
let call_cfg =
e2e_config.resolve_call_for_fixture(f.call.as_deref(), &f.id, &f.resolved_category(), &f.tags, &f.input);
if let Some(ov) = call_cfg.overrides.get(lang_for_om) {
if let Some(t) = &ov.options_type {
all_options_types.insert(t.clone());
}
}
let java_has_type = call_cfg
.overrides
.get(lang_for_om)
.and_then(|o| o.options_type.as_deref())
.is_some();
if !java_has_type {
for cand in ["csharp", "c", "go", "php", "python"] {
if let Some(o) = call_cfg.overrides.get(cand) {
if let Some(t) = &o.options_type {
all_options_types.insert(t.clone());
break;
}
}
}
}
let recipe = crate::e2e::codegen::recipe::ResolvedE2eCallRecipe::resolve(lang_for_om, f, call_cfg, type_defs);
if f.visitor.is_some() {
if let Some(binding) = java_visitor_binding(config, type_defs, f.visitor.as_ref(), recipe.options_type) {
all_options_types.insert(binding.options_type);
}
}
for arg in recipe.args.iter().filter(|arg| arg.arg_type == "handle") {
let value = super::super::resolve_field(&f.input, &arg.field);
if value.is_null() || value.is_object() && value.as_object().is_some_and(|o| o.is_empty()) {
continue;
}
if let Some(handle_type) = resolve_handle_config_type(arg, recipe.options_type, type_defs) {
all_options_types.insert(handle_type);
}
}
for arg in &call_cfg.args {
if let Some(elem_type) = &arg.element_type {
if arg.arg_type == "json_object" && !is_numeric_type_hint(elem_type) && !is_java_builtin_type(elem_type)
{
all_options_types.insert(elem_type.clone());
}
}
}
}
let mut nested_types_used: std::collections::BTreeSet<String> = std::collections::BTreeSet::new();
for f in fixtures.iter() {
let call_cfg =
e2e_config.resolve_call_for_fixture(f.call.as_deref(), &f.id, &f.resolved_category(), &f.tags, &f.input);
for arg in &call_cfg.args {
if arg.arg_type == "json_object" {
let field = arg.field.strip_prefix("input.").unwrap_or(&arg.field);
if let Some(val) = f.input.get(field) {
if !val.is_null() && !val.is_array() {
if let Some(obj) = val.as_object() {
collect_nested_type_names(obj, nested_types, &mut nested_types_used);
}
}
}
}
}
}
let binding_pkg_for_imports: String = if !binding_pkg.is_empty() {
binding_pkg.to_string()
} else if !import_path.is_empty() {
import_path
.rsplit_once('.')
.map(|(p, _)| p.to_string())
.unwrap_or_default()
} else {
String::new()
};
let mut imports: Vec<String> = Vec::new();
imports.push("import org.junit.jupiter.api.Test;".to_string());
imports.push("import static org.junit.jupiter.api.Assertions.*;".to_string());
if !import_path.is_empty() {
imports.push(format!("import {import_path};"));
} else if !binding_pkg_for_imports.is_empty() && !class_name.is_empty() {
imports.push(format!("import {binding_pkg_for_imports}.{class_name};"));
}
if needs_object_mapper {
imports.push("import com.fasterxml.jackson.databind.ObjectMapper;".to_string());
imports.push("import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;".to_string());
}
if !all_options_types.is_empty() {
for opts_type in &all_options_types {
let qualified = if binding_pkg_for_imports.is_empty() {
opts_type.clone()
} else {
format!("{binding_pkg_for_imports}.{opts_type}")
};
imports.push(format!("import {qualified};"));
}
}
if !nested_types_used.is_empty() && !binding_pkg_for_imports.is_empty() {
for type_name in &nested_types_used {
imports.push(format!("import {binding_pkg_for_imports}.{type_name};"));
}
}
let has_visitor_fixtures = fixtures.iter().any(|f| f.visitor.is_some());
if has_visitor_fixtures && !binding_pkg_for_imports.is_empty() {
for type_name in java_visitor_imports(config, type_defs, fixtures) {
imports.push(format!("import {binding_pkg_for_imports}.{type_name};"));
}
}
if !all_options_types.is_empty() {
imports.push("import java.util.Optional;".to_string());
if !binding_pkg_for_imports.is_empty() {
imports.push(format!("import {binding_pkg_for_imports}.JsonUtil;"));
}
}
let has_streaming_fixture = fixtures.iter().any(|f| {
let call_cfg =
e2e_config.resolve_call_for_fixture(f.call.as_deref(), &f.id, &f.resolved_category(), &f.tags, &f.input);
crate::e2e::codegen::streaming_assertions::resolve_is_streaming(f, call_cfg.streaming_enabled())
});
if has_streaming_fixture && !binding_pkg_for_imports.is_empty() {
let mut streaming_imports: std::collections::BTreeSet<String> = std::collections::BTreeSet::new();
for adapter in adapters {
if !matches!(adapter.pattern, crate::core::config::extras::AdapterPattern::Streaming) {
continue;
}
if let Some(item) = adapter.item_type.as_deref() {
let simple = item.rsplit("::").next().unwrap_or(item);
if !simple.is_empty() {
streaming_imports.insert(simple.to_string());
}
}
if let Some(req) = adapter.request_type.as_deref() {
let simple = req.rsplit("::").next().unwrap_or(req);
if !simple.is_empty() {
streaming_imports.insert(simple.to_string());
}
}
}
for ty in streaming_imports {
imports.push(format!("import {binding_pkg_for_imports}.{ty};"));
}
}
let mut fixtures_body = String::new();
for (i, fixture) in fixtures.iter().enumerate() {
render_test_method(
&mut fixtures_body,
fixture,
simple_class,
function_name,
result_var,
args,
options_type,
result_is_simple,
e2e_config,
nested_types,
nested_types_optional,
adapters,
config,
type_defs,
);
if i + 1 < fixtures.len() {
fixtures_body.push('\n');
}
}
let env_entries: Vec<(String, String)> = e2e_config
.env
.iter()
.map(|(k, v)| (k.clone(), v.clone()))
.collect::<Vec<_>>()
.into_iter()
.collect::<Vec<_>>();
let mut sorted_env: Vec<(String, String)> = env_entries;
sorted_env.sort_by(|a, b| a.0.cmp(&b.0));
crate::e2e::template_env::render(
"java/test_file.jinja",
minijinja::context! {
header => header,
java_group_id => java_group_id,
test_class_name => test_class_name,
category => category,
imports => imports,
needs_object_mapper => needs_object_mapper,
fixtures_body => fixtures_body,
uses_harness => uses_harness,
env_entries => sorted_env,
},
)
}