use crate::core::config::ResolvedCrateConfig;
use crate::e2e::codegen::call_ir::{CallIr, resolve_declared_result_type};
use crate::e2e::codegen::declared_error_variant::{DeclaredErrorAssertion, classify, skip_line};
use crate::e2e::config::E2eConfig;
use crate::e2e::escape::{escape_elixir, 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::{ElixirArgsContext, build_args_and_setup};
use super::assertions::render_assertion;
use super::visitor::build_elixir_visitor;
fn exception_field(method: &str) -> &str {
match method {
"status_code" => "status",
other => other,
}
}
fn error_field_checks(indent: &str, fixture: &Fixture, errors: &[crate::core::ir::ErrorDef]) -> String {
let reachable = crate::e2e::codegen::error_field_checks::reachable_errors(
errors,
crate::e2e::codegen::error_field_checks::STATUS_CODE,
);
crate::e2e::codegen::error_field_checks::render(
fixture,
&reachable,
&format!("{indent}# "),
"elixir",
|_, method, expected| {
format!(
"{indent}assert %{{{}: {expected}}} = __reason\n",
exception_field(method)
)
},
)
}
fn emit_error_assertion(
out: &mut String,
indent: &str,
call_expr: &str,
fixture: &Fixture,
errors: &[crate::core::ir::ErrorDef],
) {
let field_checks = error_field_checks(indent, fixture, errors);
match classify("elixir", fixture, errors) {
DeclaredErrorAssertion::Undeclared if field_checks.is_empty() => {
let _ = writeln!(out, "{indent}assert {{:error, _}} = {call_expr}");
}
DeclaredErrorAssertion::Undeclared => {
let _ = writeln!(out, "{indent}assert {{:error, __reason}} = {call_expr}");
}
DeclaredErrorAssertion::Assert(value) => {
let escaped = escape_elixir(value);
let _ = writeln!(out, "{indent}assert {{:error, __reason}} = {call_expr}");
let _ = writeln!(out, "{indent}assert String.contains?(inspect(__reason), \"{escaped}\")");
}
DeclaredErrorAssertion::Unsubstantiable(variant) => {
let _ = writeln!(out, "{indent}assert {{:error, __reason}} = {call_expr}");
let _ = writeln!(out, "{indent}{}", skip_line("", "#", variant, &fixture.id, "elixir"));
}
}
out.push_str(&field_checks);
}
fn emit_stream_error_assertion(
out: &mut String,
indent: &str,
call_expr: &str,
fixture: &Fixture,
errors: &[crate::core::ir::ErrorDef],
) {
let _ = writeln!(out, "{indent}__reason =");
let _ = writeln!(out, "{indent} case {call_expr} do");
let _ = writeln!(out, "{indent} {{:error, reason}} ->");
let _ = writeln!(out, "{indent} reason");
let _ = writeln!(out, "{indent} {{:ok, __stream}} ->");
let _ = writeln!(out, "{indent} try do");
let _ = writeln!(out, "{indent} Enum.to_list(__stream)");
let _ = writeln!(out, "{indent} nil");
let _ = writeln!(out, "{indent} rescue");
let _ = writeln!(out, "{indent} exception -> exception");
let _ = writeln!(out, "{indent} end");
let _ = writeln!(out, "{indent} end");
let _ = writeln!(out);
let _ = writeln!(
out,
"{indent}assert __reason != nil, \"the stream ended without an error\""
);
match classify("elixir", fixture, errors) {
DeclaredErrorAssertion::Undeclared => {}
DeclaredErrorAssertion::Assert(value) => {
let escaped = escape_elixir(value);
let _ = writeln!(out, "{indent}assert String.contains?(inspect(__reason), \"{escaped}\")");
}
DeclaredErrorAssertion::Unsubstantiable(variant) => {
let _ = writeln!(out, "{indent}{}", skip_line("", "#", variant, &fixture.id, "elixir"));
}
}
out.push_str(&error_field_checks(indent, fixture, errors));
}
#[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],
errors: &[crate::core::ir::ErrorDef],
functions: &[crate::core::ir::FunctionDef],
) {
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 lang = "elixir";
let (ir_reachable_fields, ir_known_excluded_fields, ir_optional_fields) = FieldResolver::ir_field_sets(type_defs);
let call_root_type = resolve_declared_result_type(call_config, lang, CallIr { functions, type_defs });
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(),
)
.with_display_as_text_fields(e2e_config.effective_fields_display_as_text(call_config).clone())
.with_enum_fields(e2e_config.effective_fields_enum(call_config).clone())
.with_ir_enum_map(FieldResolver::ir_enum_fields(type_defs, enums), call_root_type.clone())
.with_ir_result_fields(FieldResolver::ir_result_field_facts(type_defs, lang), call_root_type)
.with_ir_fields(ir_reachable_fields, ir_known_excluded_fields, ir_optional_fields);
let field_resolver = &call_field_resolver;
let call_overrides = call_config.overrides.get(lang);
let not_error_result_is_option = call_config.result_is_option || call_overrides.is_some_and(|o| o.result_is_option);
let not_error_may_assert_presence =
crate::e2e::codegen::not_error_presence::may_assert_presence(fixture, not_error_result_is_option);
let base_fn = call_overrides
.and_then(|o| o.function.as_ref())
.cloned()
.unwrap_or_else(|| call_config.function.clone());
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 result_var = call_config.effective_result_var().to_string();
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 force_keyword_args = call_overrides.is_some_and(|o| o.keyword_args)
|| e2e_config.call.overrides.get(lang).is_some_and(|o| o.keyword_args);
let (mut setup_lines, args_str, teardown_block) = build_args_and_setup(
&fixture.input,
resolved_args,
ElixirArgsContext {
module_path: &module_path,
options_type: resolved_options_type.as_deref(),
options_default_fn: resolved_options_default_fn.as_deref(),
enum_fields: resolved_enum_fields_ref,
fixture,
_handle_struct_type: resolved_handle_struct_type.as_deref(),
_handle_atom_list_fields: resolved_handle_atom_list_fields_ref,
test_documents_path: &test_documents_path,
adapter_request_type: adapter_request_type.as_deref(),
enums,
config,
type_defs,
force_keyword_args,
},
);
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 function_name = if call_config.r#async
&& client_factory.is_some()
&& !base_fn.ends_with("_async")
&& !base_fn.ends_with("_stream")
{
format!("{base_fn}_async")
} else {
base_fn
};
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());
}
}
if !teardown_block.is_empty() {
for line in teardown_block.lines() {
if !line.is_empty() {
cleaned_setup_lines.push(line.to_string());
}
}
}
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 handle_arg_name = resolved_args.iter().find(|a| a.arg_type == "handle").map(|a| &a.name);
let create_line_idx = handle_arg_name.and_then(|name| {
let prefix = format!("{{:ok, {name}}}");
cleaned_setup_lines.iter().position(|line| line.starts_with(&prefix))
});
if let Some(idx) = create_line_idx {
for line in &cleaned_setup_lines[..idx] {
let _ = writeln!(out, " {line}");
}
let line = &cleaned_setup_lines[idx];
let rhs = line.split_once('=').map(|(_, r)| r.trim()).unwrap_or(line.as_str());
let bound_var = handle_arg_name.expect("create_line_idx is Some only when handle_arg_name is Some");
let _ = writeln!(out, " case {rhs} do");
let _ = writeln!(out, " {{:error, __reason}} ->");
match classify("elixir", fixture, errors) {
DeclaredErrorAssertion::Undeclared => {
let _ = writeln!(out, " :ok");
}
DeclaredErrorAssertion::Assert(value) => {
let escaped = escape_elixir(value);
let _ = writeln!(
out,
" assert String.contains?(inspect(__reason), \"{escaped}\")"
);
}
DeclaredErrorAssertion::Unsubstantiable(variant) => {
let _ = writeln!(out, " {}", skip_line("", "#", variant, &fixture.id, "elixir"));
}
}
out.push_str(&error_field_checks(" ", fixture, errors));
let _ = writeln!(out, " {{:ok, {bound_var}}} ->");
for line in &cleaned_setup_lines[idx + 1..] {
let _ = writeln!(out, " {line}");
}
let call_invocation = if effective_args.is_empty() {
format!("{module_path}.{function_name}()")
} else {
format!("{module_path}.{function_name}({effective_args})")
};
emit_error_assertion(out, " ", &call_invocation, fixture, errors);
let _ = writeln!(out, " end");
} else {
for line in &cleaned_setup_lines {
let _ = writeln!(out, " {line}");
}
let call_invocation = if effective_args.is_empty() {
format!("{module_path}.{function_name}()")
} else {
format!("{module_path}.{function_name}({effective_args})")
};
emit_error_assertion(out, " ", &call_invocation, fixture, errors);
}
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})")
};
if crate::e2e::codegen::streaming_assertions::resolve_is_streaming(fixture, call_config.streaming_enabled()) {
emit_stream_error_assertion(out, " ", &call_invocation, fixture, errors);
} else {
emit_error_assertion(out, " ", &call_invocation, fixture, errors);
}
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 all_not_error = fixture
.assertions
.iter()
.all(|assertion| assertion.assertion_type == "not_error");
let void_tuple_case = call_config.returns_void && returns_result;
let not_error_renders_nothing =
all_not_error && (void_tuple_case || (!call_config.returns_void && !not_error_may_assert_presence));
let actual_result_var = if !is_streaming && (fixture.assertions.is_empty() || not_error_renders_nothing) {
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
&& let Some(collect) = crate::e2e::codegen::streaming_assertions::StreamingFieldResolver::collect_snippet(
"elixir",
&result_var,
chunks_var,
)
{
let _ = writeln!(out, " {collect}");
}
let mut assertions_body = String::new();
for assertion in &fixture.assertions {
render_assertion(
&mut assertions_body,
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,
call_config.returns_void,
returns_result,
not_error_may_assert_presence,
);
}
apply_vacuous_assertion_fallback(
&mut assertions_body,
!fixture.assertions.is_empty(),
&VacuousFallbackCall {
is_streaming,
chunks_binding: chunks_var,
result_binding: &result_var,
returns_void: call_config.returns_void,
returns_result,
not_error_result_is_option,
},
);
crate::e2e::codegen::fail_on_unavailable_field_markers(
&assertions_body,
"elixir",
&fixture.id,
&fixture.assertions,
);
crate::e2e::codegen::fail_on_unsupported_assertion_type_markers(&assertions_body, "elixir", &fixture.id);
out.push_str(&assertions_body);
out.push_str(&crate::e2e::codegen::inert_example::coverage_warning(
&assertions_body,
"elixir",
&fixture.id,
));
if needs_api_key_skip {
let _ = writeln!(out, " end");
}
let _ = writeln!(out, " end");
let _ = writeln!(out, " end");
}
struct VacuousFallbackCall<'a> {
is_streaming: bool,
chunks_binding: &'a str,
result_binding: &'a str,
returns_void: bool,
returns_result: bool,
not_error_result_is_option: bool,
}
fn apply_vacuous_assertion_fallback(
assertions_body: &mut String,
has_declared_assertions: bool,
call: &VacuousFallbackCall<'_>,
) {
let has_real_assertion = assertions_body.lines().any(|line| {
let trimmed = line.trim();
!trimmed.is_empty() && !trimmed.starts_with('#')
});
if !has_declared_assertions || has_real_assertion {
return;
}
let fallback_var = if call.is_streaming {
call.chunks_binding
} else {
call.result_binding
};
if call.returns_void {
if !call.returns_result {
let _ = writeln!(assertions_body, " assert {fallback_var} == :ok");
}
return;
}
if call.not_error_result_is_option {
return;
}
if call.returns_result {
let _ = writeln!(assertions_body, " refute is_nil({fallback_var})");
} else {
let _ = writeln!(assertions_body, " refute {fallback_var} in [nil, :error]");
}
}
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()
}
#[cfg(test)]
mod dropped_field_marker_tests {
use super::render_test_case;
use crate::e2e::config::{CallConfig, E2eConfig};
use crate::e2e::fixture::{Assertion, Fixture};
use std::collections::{HashMap, HashSet};
fn make_fixture(id: &str, field: &str) -> Fixture {
Fixture {
docs: None,
requirements: Vec::new(),
id: id.to_string(),
category: None,
description: "test".to_string(),
tags: vec![],
skip: None,
env: None,
setup: Vec::new(),
call: None,
input: serde_json::Value::Null,
mock_response: None,
source: String::new(),
http: None,
asyncapi: None,
websocket: None,
client_options: None,
preserve_input_urls: false,
assertions: vec![Assertion {
assertion_type: "equals".to_string(),
field: Some(field.to_string()),
value: Some(serde_json::json!("x")),
..Default::default()
}],
visitor: None,
args: vec![],
assertion_recipes: vec![],
}
}
#[test]
fn dropped_field_assertion_carries_the_marker_that_arms_the_strict_mode() {
let fixture = make_fixture("process_smoke", "nonexistent_field");
let call = CallConfig {
function: "process".to_string(),
module: "MyLib".to_string(),
result_var: "result".to_string(),
result_fields: HashSet::from(["content".to_string()]),
returns_result: true,
..Default::default()
};
let e2e_config = E2eConfig {
call,
..Default::default()
};
let config = crate::core::config::ResolvedCrateConfig::default();
let type_defs: Vec<crate::core::ir::TypeDef> = Vec::new();
let mut out = String::new();
render_test_case(
&mut out,
&fixture,
&e2e_config,
"",
"",
"",
&[],
None,
None,
&HashMap::new(),
None,
&HashSet::new(),
&[],
&[],
&config,
&type_defs,
&[],
&[],
);
assert!(
out.contains("field 'nonexistent_field' not available on result type"),
"got:\n{out}"
);
}
#[test]
fn dropped_field_assertion_still_gets_a_real_fallback_assertion() {
let fixture = make_fixture("process_smoke", "nonexistent_field");
let call = CallConfig {
function: "process".to_string(),
module: "MyLib".to_string(),
result_var: "result".to_string(),
result_fields: HashSet::from(["content".to_string()]),
returns_result: true,
..Default::default()
};
let e2e_config = E2eConfig {
call,
..Default::default()
};
let config = crate::core::config::ResolvedCrateConfig::default();
let type_defs: Vec<crate::core::ir::TypeDef> = Vec::new();
let mut out = String::new();
render_test_case(
&mut out,
&fixture,
&e2e_config,
"",
"",
"",
&[],
None,
None,
&HashMap::new(),
None,
&HashSet::new(),
&[],
&[],
&config,
&type_defs,
&[],
&[],
);
assert!(
out.contains("refute is_nil(result)"),
"expected a real fallback assertion on the bound result, got:\n{out}"
);
assert!(
out.contains("{:ok, result} ="),
"the result must be bound (not `_result`) once a real assertion references it, got:\n{out}"
);
}
#[test]
fn zero_declared_assertions_are_left_untouched() {
let mut fixture = make_fixture("process_smoke", "nonexistent_field");
fixture.assertions = Vec::new();
let call = CallConfig {
function: "process".to_string(),
module: "MyLib".to_string(),
result_var: "result".to_string(),
result_fields: HashSet::from(["content".to_string()]),
returns_result: true,
..Default::default()
};
let e2e_config = E2eConfig {
call,
..Default::default()
};
let config = crate::core::config::ResolvedCrateConfig::default();
let type_defs: Vec<crate::core::ir::TypeDef> = Vec::new();
let mut out = String::new();
render_test_case(
&mut out,
&fixture,
&e2e_config,
"",
"",
"",
&[],
None,
None,
&HashMap::new(),
None,
&HashSet::new(),
&[],
&[],
&config,
&type_defs,
&[],
&[],
);
assert!(
!out.contains("refute is_nil(result)"),
"a fixture with zero declared assertions must stay vacuous, got:\n{out}"
);
}
#[cfg(test)]
#[path = "void_not_error_binding_tests.rs"]
mod void_not_error_binding_tests;
#[cfg(test)]
#[path = "bare_atom_not_error_tests.rs"]
mod bare_atom_not_error_tests;
}