use crate::core::config::ResolvedCrateConfig;
use crate::core::hash::{self, CommentStyle};
use crate::e2e::config::E2eConfig;
use crate::e2e::fixture::Fixture;
use std::fmt::Write as FmtWrite;
use super::test_case::render_test_case;
#[allow(clippy::too_many_arguments)]
pub(super) fn render_test_file(
category: &str,
fixtures: &[&Fixture],
result_is_simple: bool,
result_is_r_list: bool,
e2e_config: &E2eConfig,
config: &ResolvedCrateConfig,
type_defs: &[crate::core::ir::TypeDef],
errors: &[crate::core::ir::ErrorDef],
) -> String {
let mut out = String::new();
out.push_str(&hash::header(CommentStyle::Hash));
let _ = writeln!(out, "# E2e tests for category: {category}");
let _ = writeln!(out);
for (i, fixture) in fixtures.iter().enumerate() {
render_test_case(
&mut out,
fixture,
e2e_config,
result_is_simple,
result_is_r_list,
config,
type_defs,
errors,
);
crate::e2e::codegen::error_path_assertions::emit(&mut out, fixture, "# ", "r");
if i + 1 < fixtures.len() {
let _ = writeln!(out);
}
}
while out.ends_with("\n\n") {
out.pop();
}
if !out.ends_with('\n') {
out.push('\n');
}
out
}
#[cfg(test)]
mod error_path_marker_tests {
use super::render_test_file;
use crate::core::config::ResolvedCrateConfig;
use crate::e2e::config::E2eConfig;
use crate::e2e::fixture::{Assertion, Fixture};
fn render_with_errors(
extra: Vec<Assertion>,
declared_value: Option<&str>,
errors: &[crate::core::ir::ErrorDef],
) -> String {
let mut assertions = vec![Assertion {
assertion_type: "error".into(),
value: declared_value.map(|v| serde_json::Value::String(v.to_string())),
..Assertion::default()
}];
assertions.extend(extra);
let fixture = Fixture {
id: "rate_limited".into(),
description: "Rejects the request".into(),
assertions,
..Fixture::default()
};
let mut e2e_config = E2eConfig::default();
e2e_config.call.function = "parse".into();
let _ = crate::e2e::codegen::take_skip_records();
render_test_file(
"error",
&[&fixture],
false,
false,
&e2e_config,
&ResolvedCrateConfig::default(),
&[],
errors,
)
}
fn render(extra: Vec<Assertion>) -> String {
render_with_errors(extra, None, &[])
}
#[test]
fn r_equals_on_an_error_field_is_named_instead_of_dropped() {
let out = render(vec![Assertion {
assertion_type: "equals".into(),
field: Some("error.status_code".into()),
..Assertion::default()
}]);
assert!(out.contains("expect_error("), "the error block must render:\n{out}");
assert!(
out.contains(
"# skipped: assertion type 'equals' has no accessor for error field error.status_code in this backend"
),
"got:\n{out}"
);
let records = crate::e2e::codegen::take_skip_records();
assert_eq!(records.len(), 1, "got: {records:?}");
assert_eq!(records[0].language, "r");
assert_eq!(records[0].field, "equals");
}
#[test]
fn r_a_lone_error_assertion_renders_no_marker() {
let out = render(Vec::new());
assert!(out.contains("expect_error("), "the error block must render:\n{out}");
assert!(!out.contains("has no accessor for error field"), "got:\n{out}");
}
#[test]
fn r_skips_a_known_variant_it_cannot_substantiate() {
use crate::core::ir::{ErrorDef, ErrorVariant};
let errors = vec![ErrorDef {
name: "ApiError".to_string(),
rust_path: "lib::ApiError".to_string(),
original_rust_path: String::new(),
variants: vec![ErrorVariant {
name: "Authentication".to_string(),
error_code: Some(100),
is_unit: true,
..ErrorVariant::default()
}],
doc: String::new(),
methods: vec![],
binding_excluded: false,
binding_exclusion_reason: None,
version: Default::default(),
}];
let out = render_with_errors(Vec::new(), Some("Authentication"), &errors);
assert!(out.contains("expect_error("), "the error block must render:\n{out}");
assert!(
out.contains(
"# skipped: declared error variant 'Authentication' not yet preserved as a distinct identity by \
this backend's generator"
),
"got:\n{out}"
);
assert!(
!out.contains("tryCatch"),
"must not render a check that can never pass, got:\n{out}"
);
}
}