use crate::core::config::ResolvedCrateConfig;
use crate::e2e::config::E2eConfig;
use crate::e2e::escape::sanitize_ident;
use crate::e2e::field_access::FieldResolver;
use crate::e2e::fixture::Fixture;
use std::collections::HashMap;
use std::fmt::Write as _;
use super::args::build_args_and_setup;
use super::assertions::render_assertion;
use super::visitor::build_elixir_visitor;
#[allow(clippy::too_many_arguments)]
pub(super) fn render_test_case(
out: &mut String,
fixture: &Fixture,
e2e_config: &E2eConfig,
_default_module_path: &str,
_default_function_name: &str,
_default_result_var: &str,
_args: &[crate::e2e::config::ArgMapping],
options_type: Option<&str>,
options_default_fn: Option<&str>,
_enum_fields: &HashMap<String, String>,
handle_struct_type: Option<&str>,
_handle_atom_list_fields: &std::collections::HashSet<String>,
adapters: &[crate::core::config::extras::AdapterConfig],
enums: &[crate::core::ir::EnumDef],
config: &ResolvedCrateConfig,
type_defs: &[crate::core::ir::TypeDef],
) {
let test_name = sanitize_ident(&fixture.id);
let test_label = fixture.id.replace('"', "\\\"");
fn extract_trait_bridge_parts(setup_block: &str) -> (String, String) {
if let Some(pos) = setup_block.find("__TRAIT_BRIDGE_MODULE_DEFS_END__") {
let marker_start = setup_block[..pos].rfind('\n').unwrap_or(0);
let marker_end = if let Some(nl) = setup_block[pos + 32..].find('\n') {
pos + 32 + nl + 1
} else {
setup_block.len()
};
let module_defs = setup_block[..marker_start].trim_end().to_string();
let test_setup = setup_block[marker_end..].trim_start().to_string();
(module_defs, test_setup)
} else {
(String::new(), setup_block.to_string())
}
}
if fixture.mock_response.is_none() && !fixture_has_elixir_callable(fixture, e2e_config) {
let _ = writeln!(out, " describe \"{test_name}\" do");
let _ = writeln!(out, " @tag :skip");
let _ = writeln!(out, " test \"{test_label}\" do");
let _ = writeln!(
out,
" # non-HTTP fixture: Elixir binding does not expose a callable for the configured `[e2e.call]` function"
);
let _ = writeln!(out, " :ok");
let _ = writeln!(out, " end");
let _ = writeln!(out, " end");
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 lang = "elixir";
let call_overrides = call_config.overrides.get(lang);
let base_fn = call_overrides
.and_then(|o| o.function.as_ref())
.cloned()
.unwrap_or_else(|| call_config.function.clone());
if base_fn.starts_with("batch_extract_") {
let _ = writeln!(
out,
" describe \"{test_name}\" do",
test_name = sanitize_ident(&fixture.id)
);
let _ = writeln!(out, " @tag :skip");
let _ = writeln!(
out,
" test \"{test_label}\" do",
test_label = fixture.id.replace('"', "\\\"")
);
let _ = writeln!(
out,
" # batch functions excluded from Elixir binding: unsafe NIF tuple marshalling"
);
let _ = writeln!(out, " :ok");
let _ = writeln!(out, " end");
let _ = writeln!(out, " end");
return;
}
let raw_module = call_overrides
.and_then(|o| o.module.as_ref())
.cloned()
.unwrap_or_else(|| call_config.module.clone());
let module_path = if raw_module.contains('.') || raw_module.chars().next().is_some_and(|c| c.is_uppercase()) {
raw_module
} else {
super::values::elixir_module_name(&raw_module)
};
let function_name = if call_config.r#async && !base_fn.ends_with("_async") && !base_fn.ends_with("_stream") {
format!("{base_fn}_async")
} else {
base_fn
};
let result_var = call_config.result_var.clone();
let expects_error = fixture.assertions.iter().any(|a| a.assertion_type == "error");
let validation_creation_failure = expects_error && fixture.resolved_category() == "validation";
let co = call_config.overrides.get(lang);
let empty_enum_fields_local: HashMap<String, String> = HashMap::new();
let empty_atom_fields_local: std::collections::HashSet<String> = std::collections::HashSet::new();
let resolved_args = fixture.resolved_args(call_config);
let resolved_options_type = co
.and_then(|o| o.options_type.clone())
.or_else(|| options_type.map(|s| s.to_string()));
let resolved_options_default_fn = co
.and_then(|o| o.options_via.clone())
.or_else(|| options_default_fn.map(|s| s.to_string()));
let resolved_enum_fields_ref = co.map(|o| &o.enum_fields).unwrap_or(&empty_enum_fields_local);
let resolved_handle_struct_type = co
.and_then(|o| o.handle_struct_type.clone())
.or_else(|| handle_struct_type.map(|s| s.to_string()));
let resolved_handle_atom_list_fields_ref = co
.map(|o| &o.handle_atom_list_fields)
.unwrap_or(&empty_atom_fields_local);
let test_documents_path = e2e_config.test_documents_relative_from(0);
let adapter_request_type: Option<String> = adapters
.iter()
.find(|a| a.name == call_config.function.as_str())
.and_then(|a| a.request_type.as_deref())
.map(|rt| rt.rsplit("::").next().unwrap_or(rt).to_string());
let (mut setup_lines, args_str) = build_args_and_setup(
&fixture.input,
resolved_args,
&module_path,
resolved_options_type.as_deref(),
resolved_options_default_fn.as_deref(),
resolved_enum_fields_ref,
fixture,
resolved_handle_struct_type.as_deref(),
resolved_handle_atom_list_fields_ref,
&test_documents_path,
adapter_request_type.as_deref(),
enums,
config,
type_defs,
);
let visitor_var = fixture
.visitor
.as_ref()
.map(|visitor_spec| build_elixir_visitor(&mut setup_lines, visitor_spec));
let final_args = if let Some(ref visitor_var) = visitor_var {
let parts: Vec<&str> = args_str.split(", ").collect();
if parts.len() == 2 && parts[1] == "nil" {
format!("{}, %{{visitor: {}}}", parts[0], visitor_var)
} else if parts.len() == 2 {
setup_lines.push(format!(
"{} = Map.put({}, :visitor, {})",
parts[1], parts[1], visitor_var
));
args_str
} else if parts.len() == 1 {
format!("{}, %{{visitor: {}}}", parts[0], visitor_var)
} else {
args_str
}
} else {
args_str
};
let client_factory = call_overrides.and_then(|o| o.client_factory.as_deref()).or_else(|| {
e2e_config
.call
.overrides
.get("elixir")
.and_then(|o| o.client_factory.as_deref())
});
let extra_args: Vec<String> = call_overrides.map(|o| o.extra_args.clone()).unwrap_or_default();
let final_args_with_extras = if extra_args.is_empty() {
final_args
} else if final_args.is_empty() {
extra_args.join(", ")
} else {
format!("{final_args}, {}", extra_args.join(", "))
};
let effective_args = if client_factory.is_some() {
if final_args_with_extras.is_empty() {
"client".to_string()
} else {
format!("client, {final_args_with_extras}")
}
} else {
final_args_with_extras
};
let has_mock = fixture.mock_response.is_some() || fixture.http.is_some();
let api_key_var_opt = fixture.env.as_ref().and_then(|e| e.api_key_var.as_deref());
let needs_api_key_skip = !has_mock && api_key_var_opt.is_some();
let needs_env_fallback = has_mock && api_key_var_opt.is_some();
let mut cleaned_setup_lines = Vec::new();
for line in setup_lines.iter() {
if line.contains("__TRAIT_BRIDGE_MODULE_DEFS_END__") {
let (_module_part, test_part) = extract_trait_bridge_parts(line);
for test_line in test_part.lines() {
if !test_line.is_empty() {
cleaned_setup_lines.push(test_line.to_string());
}
}
} else {
cleaned_setup_lines.push(line.clone());
}
}
let _ = writeln!(out, " describe \"{test_name}\" do");
let _ = writeln!(out, " test \"{test_label}\" do");
if needs_api_key_skip {
let api_key_var = api_key_var_opt.unwrap_or("");
let _ = writeln!(out, " if System.get_env(\"{api_key_var}\") in [nil, \"\"] do");
let _ = writeln!(out, " # {api_key_var} not set — skipping live smoke test");
let _ = writeln!(out, " :ok");
let _ = writeln!(out, " else");
}
if validation_creation_failure {
let mut emitted_error_assertion = false;
for line in &cleaned_setup_lines {
if !emitted_error_assertion && line.starts_with("{:ok,") {
if let Some(rhs) = line.split_once('=').map(|x| x.1) {
let rhs = rhs.trim();
let _ = writeln!(out, " assert {{:error, _}} = {rhs}");
emitted_error_assertion = true;
} else {
let _ = writeln!(out, " {line}");
}
} else {
let _ = writeln!(out, " {line}");
}
}
if !emitted_error_assertion {
let call_invocation = if effective_args.is_empty() {
format!("{module_path}.{function_name}()")
} else {
format!("{module_path}.{function_name}({effective_args})")
};
let _ = writeln!(out, " assert {{:error, _}} = {call_invocation}");
}
if needs_api_key_skip {
let _ = writeln!(out, " end");
}
let _ = writeln!(out, " end");
let _ = writeln!(out, " end");
return;
}
if expects_error {
for line in &cleaned_setup_lines {
let _ = writeln!(out, " {line}");
}
if let Some(factory) = client_factory {
let fixture_id = &fixture.id;
let base_url_expr = if fixture.has_host_root_route() {
let env_key = format!("MOCK_SERVER_{}", fixture_id.to_uppercase());
format!(
"(System.get_env(\"{env_key}\") || (System.get_env(\"MOCK_SERVER_URL\") || \"\") <> \"/fixtures/{fixture_id}\")"
)
} else {
format!("(System.get_env(\"MOCK_SERVER_URL\") || \"\") <> \"/fixtures/{fixture_id}\"")
};
let _ = writeln!(
out,
" {{:ok, client}} = {module_path}.{factory}(\"test-key\", base_url: {base_url_expr})"
);
}
let call_invocation = if effective_args.is_empty() {
format!("{module_path}.{function_name}()")
} else {
format!("{module_path}.{function_name}({effective_args})")
};
let _ = writeln!(out, " assert {{:error, _}} = {call_invocation}");
if needs_api_key_skip {
let _ = writeln!(out, " end");
}
let _ = writeln!(out, " end");
let _ = writeln!(out, " end");
return;
}
for line in &cleaned_setup_lines {
let _ = writeln!(out, " {line}");
}
if let Some(factory) = client_factory {
let fixture_id = &fixture.id;
if needs_env_fallback {
let api_key_var = api_key_var_opt.unwrap_or("");
let mock_url_expr = if fixture.has_host_root_route() {
let env_key = format!("MOCK_SERVER_{}", fixture_id.to_uppercase());
format!(
"System.get_env(\"{env_key}\") || (System.get_env(\"MOCK_SERVER_URL\") || \"\") <> \"/fixtures/{fixture_id}\""
)
} else {
format!("(System.get_env(\"MOCK_SERVER_URL\") || \"\") <> \"/fixtures/{fixture_id}\"")
};
let _ = writeln!(out, " api_key_val = System.get_env(\"{api_key_var}\")");
let _ = writeln!(
out,
" {{api_key_val, client_opts}} = if api_key_val && api_key_val != \"\" do"
);
let _ = writeln!(
out,
" IO.puts(\"{fixture_id}: using real API ({api_key_var} is set)\")"
);
let _ = writeln!(out, " {{api_key_val, []}}");
let _ = writeln!(out, " else");
let _ = writeln!(
out,
" IO.puts(\"{fixture_id}: using mock server ({api_key_var} not set)\")"
);
let _ = writeln!(out, " {{\"test-key\", [base_url: {mock_url_expr}]}}");
let _ = writeln!(out, " end");
let _ = writeln!(
out,
" {{:ok, client}} = {module_path}.{factory}(api_key_val, client_opts)"
);
} else {
let base_url_expr = if fixture.has_host_root_route() {
let env_key = format!("MOCK_SERVER_{}", fixture_id.to_uppercase());
format!(
"(System.get_env(\"{env_key}\") || (System.get_env(\"MOCK_SERVER_URL\") || \"\") <> \"/fixtures/{fixture_id}\")"
)
} else {
format!("(System.get_env(\"MOCK_SERVER_URL\") || \"\") <> \"/fixtures/{fixture_id}\"")
};
let _ = writeln!(
out,
" {{:ok, client}} = {module_path}.{factory}(\"test-key\", base_url: {base_url_expr})"
);
}
}
let returns_result = call_overrides
.and_then(|o| o.returns_result)
.unwrap_or(call_config.returns_result || client_factory.is_some());
let result_is_simple = call_config.result_is_simple || call_overrides.is_some_and(|o| o.result_is_simple);
let is_streaming =
crate::e2e::codegen::streaming_assertions::resolve_is_streaming(fixture, call_config.streaming_enabled());
let chunks_var = "chunks";
let actual_result_var = if fixture.assertions.is_empty() && !is_streaming {
format!("_{result_var}")
} else {
result_var.to_string()
};
let call_invocation = if effective_args.is_empty() {
format!("{module_path}.{function_name}()")
} else {
format!("{module_path}.{function_name}({effective_args})")
};
if returns_result {
let _ = writeln!(out, " {{:ok, {actual_result_var}}} = {call_invocation}");
} else {
let _ = writeln!(out, " {actual_result_var} = {call_invocation}");
}
if is_streaming {
if let Some(collect) = crate::e2e::codegen::streaming_assertions::StreamingFieldResolver::collect_snippet(
"elixir",
&result_var,
chunks_var,
) {
let _ = writeln!(out, " {collect}");
}
}
for assertion in &fixture.assertions {
render_assertion(
out,
assertion,
if is_streaming { chunks_var } else { &result_var },
field_resolver,
&module_path,
e2e_config.effective_fields_enum(call_config),
resolved_enum_fields_ref,
result_is_simple,
is_streaming,
);
}
if needs_api_key_skip {
let _ = writeln!(out, " end");
}
let _ = writeln!(out, " end");
let _ = writeln!(out, " end");
}
fn fixture_has_elixir_callable(fixture: &Fixture, e2e_config: &E2eConfig) -> bool {
if fixture.is_http_test() {
return false;
}
let call_config = e2e_config.resolve_call_for_fixture(
fixture.call.as_deref(),
&fixture.id,
&fixture.resolved_category(),
&fixture.tags,
&fixture.input,
);
let elixir_override = call_config
.overrides
.get("elixir")
.or_else(|| e2e_config.call.overrides.get("elixir"));
if elixir_override.and_then(|o| o.client_factory.as_deref()).is_some() {
return true;
}
let function_from_override = elixir_override.and_then(|o| o.function.as_deref());
function_from_override.is_some() || !call_config.function.is_empty()
}