use std::fmt::Write as FmtWrite;
use crate::e2e::codegen::field_skip::FieldSkip;
use crate::e2e::config::E2eConfig;
use crate::e2e::field_access::FieldResolver;
use crate::e2e::fixture::{Assertion, Fixture};
use super::super::assertions::render_assertion;
use super::super::helpers::{resolve_assert_enum_fields, strip_redundant_call_arg_parens};
use super::super::json::value_to_python_string;
fn has_real_assertion(body: &str) -> bool {
body.lines().any(|line| {
let trimmed = line.trim();
!trimmed.is_empty() && !trimmed.starts_with('#')
})
}
fn apply_vacuous_assertion_fallback(
temp_assertions: &mut String,
has_declared_assertions: bool,
result_var: &str,
returns_void: bool,
) {
if !has_declared_assertions || has_real_assertion(temp_assertions) {
return;
}
if returns_void {
return;
}
let _ = writeln!(temp_assertions, " assert {result_var} is not None");
}
#[allow(clippy::too_many_arguments)]
pub(super) fn emit_result_and_assertions(
out: &mut String,
fixture: &Fixture,
e2e_config: &E2eConfig,
call_config: &crate::e2e::config::CallConfig,
call_expr: &str,
result_var: &str,
field_resolver: &FieldResolver,
result_is_simple: bool,
is_streaming: bool,
force_bind_result: bool,
streaming_item_type: Option<&str>,
) {
let chunks_var = "chunks";
let fields_enum = e2e_config.effective_fields_enum(call_config);
let assert_enum_fields = resolve_assert_enum_fields(call_config);
if is_streaming {
let _ = writeln!(out, " {result_var} = {call_expr}");
if let Some(collect) = crate::e2e::codegen::streaming_assertions::StreamingFieldResolver::collect_snippet(
"python", result_var, chunks_var,
) {
let _ = writeln!(out, " {collect}");
}
let mut streaming_assertions = String::new();
for assertion in &fixture.assertions {
if assertion.assertion_type == "not_error" || assertion.assertion_type == "error" {
continue;
}
if let Some(f) = &assertion.field
&& crate::e2e::codegen::streaming_assertions::is_streaming_virtual_field(f)
{
emit_streaming_virtual_assertion(
&mut streaming_assertions,
assertion,
f,
chunks_var,
streaming_item_type,
field_resolver.python_typeddict_map(),
);
continue;
}
if let Some(f) = assertion.field.as_deref().filter(|f| !f.is_empty()) {
let _ = writeln!(
streaming_assertions,
" # skipped: {}",
FieldSkip::NotAvailableOnStreamingResultType.message(f)
);
}
}
apply_vacuous_assertion_fallback(
&mut streaming_assertions,
!fixture.assertions.is_empty(),
chunks_var,
call_config.returns_void,
);
crate::e2e::codegen::fail_on_unavailable_field_markers(
&streaming_assertions,
"python",
&fixture.id,
&fixture.assertions,
);
crate::e2e::codegen::fail_on_unsupported_assertion_type_markers(&streaming_assertions, "python", &fixture.id);
out.push_str(&streaming_assertions);
} else {
let mut temp_assertions = String::new();
for assertion in &fixture.assertions {
if assertion.assertion_type == "not_error" {
continue;
}
render_assertion(
&mut temp_assertions,
assertion,
result_var,
field_resolver,
fields_enum,
assert_enum_fields,
result_is_simple,
);
}
apply_vacuous_assertion_fallback(
&mut temp_assertions,
!fixture.assertions.is_empty(),
result_var,
call_config.returns_void,
);
crate::e2e::codegen::fail_on_unavailable_field_markers(
&temp_assertions,
"python",
&fixture.id,
&fixture.assertions,
);
crate::e2e::codegen::fail_on_unsupported_assertion_type_markers(&temp_assertions, "python", &fixture.id);
let result_var_used = temp_assertions.lines().any(|line| {
let trimmed = line.trim();
!trimmed.starts_with('#') && trimmed.contains(result_var)
});
let result_binding =
(result_var_used || fixture.has_docs_presentation() || force_bind_result).then_some(result_var);
out.push_str(&crate::e2e::template_env::render(
"python/call_statement.py.jinja",
minijinja::context! { result_binding => result_binding, call_expr => call_expr },
));
out.push_str(&temp_assertions);
}
}
fn emit_streaming_virtual_assertion(
out: &mut String,
assertion: &Assertion,
field: &str,
chunks_var: &str,
streaming_item_type: Option<&str>,
typeddict_map: &crate::e2e::field_access::PythonTypedDictMap,
) {
use crate::e2e::codegen::streaming_assertions::StreamingFieldResolver;
let Some(expr) = StreamingFieldResolver::accessor_with_typeddict_map(
field,
"python",
chunks_var,
None,
streaming_item_type,
Some(typeddict_map),
) else {
let _ = writeln!(
out,
" # skipped: {}",
FieldSkip::NoPythonStreamingAccessor.message(field)
);
return;
};
let expr_arg = strip_redundant_call_arg_parens(&expr);
match assertion.assertion_type.as_str() {
"count_min" => {
if let Some(val) = &assertion.value
&& let Some(n) = val.as_u64()
{
let _ = writeln!(out, " assert len({expr_arg}) >= {n}");
}
}
"count_equals" => {
if let Some(val) = &assertion.value
&& let Some(n) = val.as_u64()
{
let _ = writeln!(out, " assert len({expr_arg}) == {n}");
}
}
"equals" => {
if let Some(val) = &assertion.value {
let expected = value_to_python_string(val);
let op = if val.is_boolean() || val.is_null() { "is" } else { "==" };
if val.is_string() {
let _ = writeln!(out, " assert {expr}.strip() {op} {expected}.strip()");
} else {
let _ = writeln!(out, " assert {expr} {op} {expected}");
}
}
}
"not_empty" => {
let _ = writeln!(
out,
" assert {expr} is not None and (not hasattr({expr_arg}, \"__len__\") or len({expr_arg}) > 0)"
);
}
"is_empty" => {
let _ = writeln!(out, " assert not {expr}");
}
"is_true" => {
let py_expr = if expr == "true" {
"True".to_string()
} else if expr == "false" {
"False".to_string()
} else {
expr.clone()
};
let _ = writeln!(out, " assert {py_expr}");
}
"is_false" => {
let py_expr = if expr == "true" {
"True".to_string()
} else if expr == "false" {
"False".to_string()
} else {
expr.clone()
};
let _ = writeln!(out, " assert not {py_expr}");
}
"greater_than" => {
if let Some(val) = &assertion.value {
let expected = value_to_python_string(val);
let _ = writeln!(out, " assert {expr} > {expected}");
}
}
"greater_than_or_equal" => {
if let Some(val) = &assertion.value {
let expected = value_to_python_string(val);
let _ = writeln!(out, " assert {expr} >= {expected}");
}
}
"contains" => {
if let Some(val) = &assertion.value {
let expected = value_to_python_string(val);
let _ = writeln!(out, " assert {expected} in {expr}");
}
}
other => {
panic!("Python e2e generator: unsupported assertion type '{other}' on synthetic field '{field}'");
}
}
}
#[cfg(test)]
#[path = "result_assertions/tests.rs"]
mod tests;