use std::fmt::Write as FmtWrite;
use crate::e2e::config::E2eConfig;
use crate::e2e::fixture::Fixture;
use super::helpers::{body_references_symbol, resolve_function_name_for_call, resolve_module, resolve_module_for_call};
use super::test_function::render_test_function;
use crate::e2e::codegen::rust::args::{resolve_handle_config_type, resolve_visitor_trait};
#[allow(clippy::too_many_arguments)]
pub fn render_test_file(
category: &str,
fixtures: &[&Fixture],
e2e_config: &E2eConfig,
config: &crate::core::config::ResolvedCrateConfig,
type_defs: &[crate::core::ir::TypeDef],
enums: &[crate::core::ir::EnumDef],
functions: &[crate::core::ir::FunctionDef],
dep_name: &str,
needs_mock_server: bool,
docs_client: Option<&crate::e2e::fixture::FixtureDocsClient>,
snippet_expects_error: bool,
) -> String {
let mut out = String::new();
out.push_str(&crate::core::hash::e2e_header(
crate::core::hash::CommentStyle::DoubleSlash,
));
let _ = writeln!(out, "//! E2e tests for category: {category}");
let _ = writeln!(out);
let module = resolve_module(e2e_config, dep_name);
let file_has_call_based = fixtures.iter().any(|f| {
if f.mock_response.is_some() {
return true;
}
if f.http.is_none() && f.mock_response.is_none() {
let call_config = e2e_config.resolve_call_for_fixture(
f.call.as_deref(),
&f.id,
&f.resolved_category(),
&f.tags,
&f.input,
);
let fn_name = resolve_function_name_for_call(call_config);
return !fn_name.is_empty();
}
false
});
let rust_call_override = e2e_config.call.overrides.get("rust");
let client_factory = rust_call_override.and_then(|o| o.client_factory.as_deref());
let mut imported: std::collections::BTreeSet<(String, String)> = std::collections::BTreeSet::new();
if file_has_call_based && client_factory.is_none() {
for fixture in fixtures.iter().filter(|f| {
if f.mock_response.is_some() {
return true;
}
if f.http.is_none() && f.mock_response.is_none() {
let call_config = e2e_config.resolve_call_for_fixture(
f.call.as_deref(),
&f.id,
&f.resolved_category(),
&f.tags,
&f.input,
);
let fn_name = resolve_function_name_for_call(call_config);
return !fn_name.is_empty();
}
false
}) {
let call_config = e2e_config.resolve_call_for_fixture(
fixture.call.as_deref(),
&fixture.id,
&fixture.resolved_category(),
&fixture.tags,
&fixture.input,
);
let fn_name = resolve_function_name_for_call(call_config);
let mod_name = resolve_module_for_call(call_config, dep_name);
imported.insert((mod_name, fn_name));
}
}
for fixture in fixtures.iter().filter(|fixture| fixture.http.is_none()) {
let call_config = e2e_config.resolve_call_for_fixture(
fixture.call.as_deref(),
&fixture.id,
&fixture.resolved_category(),
&fixture.tags,
&fixture.input,
);
if resolve_function_name_for_call(call_config).is_empty() {
continue;
}
let recipe = crate::e2e::codegen::recipe::E2eCallRecipe::resolve("rust", fixture, call_config, type_defs);
if let Some((teardown, _)) = recipe.rust_handle_teardown(rust_call_override) {
imported.insert((resolve_module_for_call(call_config, dep_name), teardown.to_string()));
}
}
let mut by_module: std::collections::BTreeMap<String, Vec<String>> = std::collections::BTreeMap::new();
for (mod_name, fn_name) in &imported {
by_module.entry(mod_name.clone()).or_default().push(fn_name.clone());
}
for (mod_name, fns) in &by_module {
if fns.len() == 1 {
let _ = writeln!(out, "use {mod_name}::{};", fns[0]);
} else {
let joined = fns.join(", ");
let _ = writeln!(out, "use {mod_name}::{{{joined}}};");
}
}
let mut body_buf = String::new();
for fixture in fixtures {
render_test_function(
&mut body_buf,
fixture,
e2e_config,
config,
type_defs,
enums,
functions,
dep_name,
client_factory,
docs_client,
snippet_expects_error,
);
let _ = writeln!(body_buf);
}
let mut crate_imports: std::collections::BTreeSet<String> = std::collections::BTreeSet::new();
let has_handle_args = fixtures.iter().any(|fixture| {
let call_config = e2e_config.resolve_call_for_fixture(
fixture.call.as_deref(),
&fixture.id,
&fixture.resolved_category(),
&fixture.tags,
&fixture.input,
);
let recipe = crate::e2e::codegen::recipe::E2eCallRecipe::resolve("rust", fixture, call_config, type_defs);
recipe.args.iter().any(|a| a.arg_type == "handle")
});
if has_handle_args {
let mut handle_config_types: std::collections::BTreeSet<String> = std::collections::BTreeSet::new();
for fixture in fixtures {
let call_config = e2e_config.resolve_call_for_fixture(
fixture.call.as_deref(),
&fixture.id,
&fixture.resolved_category(),
&fixture.tags,
&fixture.input,
);
let recipe = crate::e2e::codegen::recipe::E2eCallRecipe::resolve("rust", fixture, call_config, type_defs);
for arg in recipe.args.iter().filter(|arg| arg.arg_type == "handle") {
let value = crate::e2e::codegen::resolve_field(&fixture.input, &arg.field);
if value.is_null() || value.is_object() && value.as_object().is_some_and(|o| o.is_empty()) {
continue;
}
if let Some(config_type) = resolve_handle_config_type(arg, recipe.options_type, type_defs) {
handle_config_types.insert(config_type);
}
}
}
for config_type in handle_config_types {
if body_references_symbol(&body_buf, &config_type) {
crate_imports.insert(config_type);
}
}
let mut constructor_names: std::collections::BTreeSet<String> = std::collections::BTreeSet::new();
for fixture in fixtures {
let call_config = e2e_config.resolve_call_for_fixture(
fixture.call.as_deref(),
&fixture.id,
&fixture.resolved_category(),
&fixture.tags,
&fixture.input,
);
let recipe = crate::e2e::codegen::recipe::E2eCallRecipe::resolve("rust", fixture, call_config, type_defs);
for arg in recipe.args.iter().filter(|a| a.arg_type == "handle") {
use heck::ToSnakeCase;
constructor_names.insert(format!("create_{}", arg.name.to_snake_case()));
}
}
for constructor_name in constructor_names {
if body_references_symbol(&body_buf, &constructor_name) {
crate_imports.insert(constructor_name);
}
}
}
if client_factory.is_some() && file_has_call_based {
let trait_imports: Vec<String> = e2e_config
.call
.overrides
.get("rust")
.map(|o| o.trait_imports.clone())
.unwrap_or_default();
for trait_name in trait_imports {
crate_imports.insert(trait_name);
}
}
if file_has_call_based {
for fixture in fixtures {
let call_config = e2e_config.resolve_call_for_fixture(
fixture.call.as_deref(),
&fixture.id,
&fixture.resolved_category(),
&fixture.tags,
&fixture.input,
);
let recipe = crate::e2e::codegen::recipe::E2eCallRecipe::resolve("rust", fixture, call_config, type_defs);
if recipe
.args
.iter()
.any(|a| a.arg_type == "json_object" && a.element_type.is_none())
&& let Some(opts_type) = recipe.options_type
&& body_references_symbol(&body_buf, opts_type)
{
crate_imports.insert(opts_type.to_string());
}
}
}
for symbol in &crate_imports {
let _ = writeln!(out, "use {module}::{symbol};");
}
let file_needs_common = fixtures.iter().any(|fixture| {
let call_config = e2e_config.resolve_call_for_fixture(
fixture.call.as_deref(),
&fixture.id,
&fixture.resolved_category(),
&fixture.tags,
&fixture.input,
);
let recipe = crate::e2e::codegen::recipe::E2eCallRecipe::resolve("rust", fixture, call_config, type_defs);
let has_handle_teardown = fixture.http.is_none()
&& !resolve_function_name_for_call(call_config).is_empty()
&& recipe.rust_handle_teardown(rust_call_override).is_some();
fixture.needs_mock_server() || call_config.r#async || has_handle_teardown
});
if file_needs_common {
let _ = writeln!(out, "mod common;");
}
let file_needs_mock = needs_mock_server
&& fixtures
.iter()
.any(|f| f.mock_response.is_some() || f.needs_mock_server());
if file_needs_mock {
let _ = writeln!(out, "mod mock_server;");
let _ = writeln!(out, "#[allow(unused_imports)]");
let _ = writeln!(out, "use mock_server::{{MockRoute, MockServer}};");
}
let file_needs_visitor = fixtures.iter().any(|f| f.visitor.is_some());
if file_needs_visitor {
let visitor_trait = resolve_visitor_trait(rust_call_override).unwrap_or_else(|| {
panic!(
"category '{}': fixture declares a visitor block but \
`[e2e.call.overrides.rust] visitor_trait` is not configured",
category
)
});
let _ = writeln!(
out,
"use {module}::visitor::{{{visitor_trait}, NodeContext, VisitResult}};"
);
}
if file_has_call_based {
let mut element_types: std::collections::BTreeSet<String> = std::collections::BTreeSet::new();
for fixture in fixtures.iter().filter(|f| {
if f.mock_response.is_some() {
return true;
}
if f.http.is_none() && f.mock_response.is_none() {
let call_config = e2e_config.resolve_call_for_fixture(
f.call.as_deref(),
&f.id,
&f.resolved_category(),
&f.tags,
&f.input,
);
let fn_name = resolve_function_name_for_call(call_config);
return !fn_name.is_empty();
}
false
}) {
let call_config = e2e_config.resolve_call_for_fixture(
fixture.call.as_deref(),
&fixture.id,
&fixture.resolved_category(),
&fixture.tags,
&fixture.input,
);
for arg in fixture.resolved_args(call_config) {
if arg.arg_type == "json_object"
&& let Some(ref elem_type) = arg.element_type
{
element_types.insert(elem_type.clone());
}
}
}
for elem_type in &element_types {
if matches!(
elem_type.as_str(),
"String"
| "str"
| "bool"
| "i8"
| "i16"
| "i32"
| "i64"
| "i128"
| "isize"
| "u8"
| "u16"
| "u32"
| "u64"
| "u128"
| "usize"
| "f32"
| "f64"
| "char"
) {
continue;
}
if body_references_symbol(&body_buf, elem_type) {
let _ = writeln!(out, "use {module}::{elem_type};");
}
}
}
if let Ok(capture) = crate::e2e::codegen::mock_assertions::mock_capture("rust")
&& body_references_symbol(&body_buf, capture.helper_name)
&& let Some(rendered) = crate::e2e::codegen::mock_assertions::render_helper("rust")
{
out.push_str(&rendered);
let _ = writeln!(out);
}
let _ = writeln!(out);
out.push_str(&body_buf);
if !out.ends_with('\n') {
out.push('\n');
}
out
}