use super::accessors::{swift_build_accessor, swift_stringy_aggregator_contains_assert};
use super::assertions::render_assertion;
use crate::e2e::field_access::FieldResolver;
use crate::e2e::fixture::Assertion;
use std::collections::{HashMap, HashSet};
fn make_resolver_tool_calls() -> FieldResolver {
let mut optional = HashSet::new();
optional.insert("choices.message.tool_calls".to_string());
let mut arrays = HashSet::new();
arrays.insert("choices".to_string());
FieldResolver::new(&HashMap::new(), &optional, &HashSet::new(), &arrays, &HashSet::new())
}
#[test]
fn swift_ir_reachable_field_absent_from_result_fields_is_not_skipped() {
let reachable: HashSet<String> = ["data".to_string()].into_iter().collect();
let resolver = FieldResolver::new(
&HashMap::new(),
&HashSet::new(),
&HashSet::new(),
&HashSet::new(),
&HashSet::new(),
)
.with_ir_fields(reachable, HashSet::new(), HashSet::new());
let assertion = Assertion {
skip: None,
assertion_type: "equals".to_string(),
field: Some("data".to_string()),
value: Some(serde_json::Value::String("hello".to_string())),
values: None,
method: None,
check: None,
args: None,
return_type: None,
};
let mut out = String::new();
render_assertion(
&mut out,
&assertion,
"result",
&resolver,
false,
false,
false,
false,
&HashMap::new(),
&HashSet::new(),
false,
false,
);
assert!(!out.contains("skipped"), "got: {out}");
}
#[test]
fn swift_ir_excluded_field_present_in_result_fields_is_still_skipped() {
let result_fields: HashSet<String> = ["internal_diagnostics".to_string()].into_iter().collect();
let excluded: HashSet<String> = ["internal_diagnostics".to_string()].into_iter().collect();
let resolver = FieldResolver::new(
&HashMap::new(),
&HashSet::new(),
&result_fields,
&HashSet::new(),
&HashSet::new(),
)
.with_ir_fields(HashSet::new(), excluded, HashSet::new());
let assertion = Assertion {
skip: None,
assertion_type: "equals".to_string(),
field: Some("internal_diagnostics".to_string()),
value: Some(serde_json::Value::String("hello".to_string())),
values: None,
method: None,
check: None,
args: None,
return_type: None,
};
let mut out = String::new();
render_assertion(
&mut out,
&assertion,
"result",
&resolver,
false,
false,
false,
false,
&HashMap::new(),
&HashSet::new(),
false,
false,
);
assert!(out.contains("skipped"), "got: {out}");
}
#[test]
fn not_empty_is_type_aware_for_optional_values() {
let cases = [
("quality_score", false, "result.qualityScore() != nil"),
("keywords", true, "result.keywords()?.isEmpty == false"),
];
for (field, is_collection, expected) in cases {
let mut optional = HashSet::new();
optional.insert(field.to_string());
let mut arrays = HashSet::new();
if is_collection {
arrays.insert(field.to_string());
}
let resolver = FieldResolver::new(&HashMap::new(), &optional, &HashSet::new(), &arrays, &HashSet::new());
let assertion = Assertion {
skip: None,
assertion_type: "not_empty".to_string(),
field: Some(field.to_string()),
value: None,
values: None,
method: None,
check: None,
args: None,
return_type: None,
};
let mut out = String::new();
render_assertion(
&mut out,
&assertion,
"result",
&resolver,
false,
false,
false,
false,
&HashMap::new(),
&HashSet::new(),
false,
false,
);
assert!(out.contains(expected), "field {field}: {out}");
assert!(!out.contains("toString"), "field {field}: {out}");
}
}
fn not_error_assertion() -> Assertion {
Assertion {
skip: None,
assertion_type: "not_error".to_string(),
field: None,
value: None,
values: None,
method: None,
check: None,
args: None,
return_type: None,
}
}
#[test]
fn not_error_emits_a_real_xc_tassert_not_nil_on_the_result() {
let resolver = FieldResolver::new(
&HashMap::new(),
&HashSet::new(),
&HashSet::new(),
&HashSet::new(),
&HashSet::new(),
);
let assertion = not_error_assertion();
let mut out = String::new();
render_assertion(
&mut out,
&assertion,
"result",
&resolver,
false,
false,
false,
false,
&HashMap::new(),
&HashSet::new(),
false,
false,
);
assert_eq!(out, " XCTAssertNotNil(result)\n");
}
#[test]
fn not_error_on_a_streaming_fixture_asserts_on_drained_chunks_not_result() {
let resolver = FieldResolver::new(
&HashMap::new(),
&HashSet::new(),
&HashSet::new(),
&HashSet::new(),
&HashSet::new(),
);
let assertion = not_error_assertion();
let mut out = String::new();
render_assertion(
&mut out,
&assertion,
"result",
&resolver,
false,
false,
false,
false,
&HashMap::new(),
&HashSet::new(),
true,
false,
);
assert_eq!(out, " XCTAssertNotNil(chunks)\n");
}
#[test]
fn not_error_on_a_returns_void_call_emits_nothing() {
let resolver = FieldResolver::new(
&HashMap::new(),
&HashSet::new(),
&HashSet::new(),
&HashSet::new(),
&HashSet::new(),
);
let assertion = not_error_assertion();
let mut out = String::new();
render_assertion(
&mut out,
&assertion,
"result",
&resolver,
false,
false,
false,
false,
&HashMap::new(),
&HashSet::new(),
false,
true,
);
assert!(
out.is_empty(),
"a returns_void call must not reference an unbound result, got: {out}"
);
}
#[test]
fn optional_vec_subscript_does_not_emit_trailing_question_mark_before_next_segment() {
let resolver = make_resolver_tool_calls();
let (accessor, has_optional) =
swift_build_accessor("choices[0].message.tool_calls[0].function.name", "result", &resolver);
assert!(
accessor.contains("toolCalls()?[0]"),
"expected `toolCalls()?[0]` for optional tool_calls, got: {accessor}"
);
assert!(
!accessor.contains("?[0]?"),
"must not emit trailing `?` after subscript index: {accessor}"
);
assert!(has_optional, "expected has_optional=true for optional field chain");
assert!(
accessor.contains("[0].function"),
"expected `.function` (non-optional) after subscript: {accessor}"
);
}
#[test]
fn contains_against_vec_dto_aggregates_stringy_accessors() {
use crate::e2e::field_access::{StringyField, StringyFieldKind, SwiftFirstClassMap};
let mut stringy_fields_by_type: HashMap<String, Vec<StringyField>> = HashMap::new();
stringy_fields_by_type.insert(
"ImportInfo".to_string(),
vec![
StringyField {
name: "source".to_string(),
kind: StringyFieldKind::Plain,
},
StringyField {
name: "items".to_string(),
kind: StringyFieldKind::Vec,
},
StringyField {
name: "alias".to_string(),
kind: StringyFieldKind::Optional,
},
],
);
let mut field_types: HashMap<String, HashMap<String, String>> = HashMap::new();
let mut process_fields = HashMap::new();
process_fields.insert("imports".to_string(), "ImportInfo".to_string());
field_types.insert("ProcessResult".to_string(), process_fields);
let mut arrays = HashSet::new();
arrays.insert("imports".to_string());
let map = SwiftFirstClassMap {
first_class_types: HashSet::new(),
field_types,
vec_field_names: HashSet::new(),
root_type: None,
stringy_fields_by_type,
};
let resolver = FieldResolver::new_with_swift_first_class(
&HashMap::new(),
&HashSet::new(),
&HashSet::new(),
&arrays,
&HashSet::new(),
&HashMap::new(),
map,
)
.with_swift_root_type(Some("ProcessResult".to_string()));
let line = swift_stringy_aggregator_contains_assert(Some("imports"), "result", &resolver, "\"os\"")
.expect("aggregator should fire for Vec<ImportInfo> contains");
assert!(
line.contains("result.imports().contains(where: { item in"),
"expected contains(where:) over result.imports(): {line}"
);
assert!(
line.contains("texts.append(item.source().toString())"),
"expected plain source() accessor: {line}"
);
assert!(
line.contains("texts.append(contentsOf: item.items().map { $0.as_str().toString() })"),
"expected vec items() flattened via .map as_str(): {line}"
);
assert!(
line.contains("if let v = item.alias()"),
"expected optional alias() unwrap: {line}"
);
assert!(
line.contains("$0.contains(\"os\")"),
"expected substring contains over expected value: {line}"
);
assert!(!line.contains("$0 == \"os\""), "must not use exact equality: {line}");
}
#[test]
fn contains_aggregator_skips_when_only_one_stringy_field() {
use crate::e2e::field_access::{StringyField, StringyFieldKind, SwiftFirstClassMap};
let mut stringy_fields_by_type: HashMap<String, Vec<StringyField>> = HashMap::new();
stringy_fields_by_type.insert(
"TagInfo".to_string(),
vec![StringyField {
name: "name".to_string(),
kind: StringyFieldKind::Plain,
}],
);
let mut field_types: HashMap<String, HashMap<String, String>> = HashMap::new();
let mut root_fields = HashMap::new();
root_fields.insert("tags".to_string(), "TagInfo".to_string());
field_types.insert("Root".to_string(), root_fields);
let mut arrays = HashSet::new();
arrays.insert("tags".to_string());
let map = SwiftFirstClassMap {
first_class_types: HashSet::new(),
field_types,
vec_field_names: HashSet::new(),
root_type: None,
stringy_fields_by_type,
};
let resolver = FieldResolver::new_with_swift_first_class(
&HashMap::new(),
&HashSet::new(),
&HashSet::new(),
&arrays,
&HashSet::new(),
&HashMap::new(),
map,
)
.with_swift_root_type(Some("Root".to_string()));
assert!(
swift_stringy_aggregator_contains_assert(Some("tags"), "result", &resolver, "\"x\"").is_none(),
"single-stringy-field types must not trigger the aggregator"
);
}
#[test]
fn chained_optional_only_emits_question_mark_on_first_optional() {
let mut optional = HashSet::new();
optional.insert("summary".to_string());
let resolver = FieldResolver::new(
&HashMap::new(),
&optional,
&HashSet::new(),
&HashSet::new(),
&HashSet::new(),
);
let (accessor, has_optional) = swift_build_accessor("summary.strategy", "result", &resolver);
assert!(
accessor.contains("summary()?"),
"expected `summary()?` for optional summary field: {accessor}"
);
assert!(
!accessor.contains("strategy()?"),
"must not emit `?` after already-unwrapped optional field: {accessor}"
);
assert_eq!(
accessor, "result.summary()?.strategy()",
"expected `result.summary()?.strategy()`, got: {accessor}"
);
assert!(has_optional, "expected has_optional=true for chain with optional root");
}
#[test]
fn test_file_renders_env_vars_in_class_setup() {
use crate::core::config::ResolvedCrateConfig;
use crate::e2e::config::E2eConfig;
let mut e2e_config = E2eConfig::default();
e2e_config.env.insert("ZEBRA".to_string(), "z_value".to_string());
e2e_config.env.insert("APPLE".to_string(), "a_value".to_string());
e2e_config.env.insert("BANANA".to_string(), "b_value".to_string());
let output = super::test_file::render_test_file(
"smoke",
&[],
&e2e_config,
"TestModule",
"TestCase",
"testFunction",
"result",
&[],
false,
None,
&Default::default(),
&ResolvedCrateConfig::default(),
&[],
false,
&[],
&[],
&[],
);
assert!(output.contains("APPLE"), "expected APPLE env var in output");
assert!(output.contains("BANANA"), "expected BANANA env var in output");
assert!(output.contains("ZEBRA"), "expected ZEBRA env var in output");
let apple_pos = output.find("APPLE").unwrap();
let banana_pos = output.find("BANANA").unwrap();
let zebra_pos = output.find("ZEBRA").unwrap();
assert!(
apple_pos < banana_pos && banana_pos < zebra_pos,
"env vars must be sorted alphabetically, got positions APPLE={}, BANANA={}, ZEBRA={}",
apple_pos,
banana_pos,
zebra_pos
);
assert!(
output.contains("setenv(key, val, 0)"),
"expected setenv(key, val, 0) calls in output"
);
}
#[test]
fn test_file_renders_no_env_block_when_env_empty() {
use crate::core::config::ResolvedCrateConfig;
use crate::e2e::config::E2eConfig;
let e2e_config = E2eConfig::default();
let output = super::test_file::render_test_file(
"smoke",
&[],
&e2e_config,
"TestModule",
"TestCase",
"testFunction",
"result",
&[],
false,
None,
&Default::default(),
&ResolvedCrateConfig::default(),
&[],
false,
&[],
&[],
&[],
);
assert!(
!output.contains("setenv"),
"empty env should not produce any setenv calls"
);
}
#[test]
fn test_file_error_assertion_with_declared_value_checks_message_and_type() {
use crate::core::config::ResolvedCrateConfig;
use crate::e2e::config::E2eConfig;
use crate::e2e::fixture::{Assertion, Fixture};
let mut e2e_config = E2eConfig::default();
e2e_config.call.function = "parseThing".into();
let mut fixture = Fixture {
id: "invalid_thing".into(),
description: "Invalid thing raises".into(),
..Fixture::default()
};
fixture.assertions.push(Assertion {
assertion_type: "error".into(),
value: Some(serde_json::json!("ThingNotFound")),
..Default::default()
});
let output = super::test_file::render_test_file(
"smoke",
&[&fixture],
&e2e_config,
"TestModule",
"TestCase",
"parseThing",
"result",
&[],
false,
None,
&Default::default(),
&ResolvedCrateConfig::default(),
&[],
false,
&[],
&[],
&[],
);
assert!(
output.contains("String(describing: error)"),
"expected error description capture, got:\n{output}"
);
assert!(
output.contains("String(describing: type(of: error))"),
"expected error type-name capture, got:\n{output}"
);
assert!(
output.contains(
"XCTAssertTrue(_errorMessage.contains(\"ThingNotFound\") || _errorType.contains(\"ThingNotFound\")"
),
"expected a disjunctive message-or-type check against the declared value, got:\n{output}"
);
assert!(output.contains("XCTFail(\"expected to throw\")"));
}
#[test]
fn test_file_error_assertion_without_declared_value_is_byte_identical() {
use crate::core::config::ResolvedCrateConfig;
use crate::e2e::config::E2eConfig;
use crate::e2e::fixture::{Assertion, Fixture};
let mut e2e_config = E2eConfig::default();
e2e_config.call.function = "parseThing".into();
let mut fixture = Fixture {
id: "invalid_thing".into(),
description: "Invalid thing raises".into(),
..Fixture::default()
};
fixture.assertions.push(Assertion {
assertion_type: "error".into(),
..Default::default()
});
let output = super::test_file::render_test_file(
"smoke",
&[&fixture],
&e2e_config,
"TestModule",
"TestCase",
"parseThing",
"result",
&[],
false,
None,
&Default::default(),
&ResolvedCrateConfig::default(),
&[],
false,
&[],
&[],
&[],
);
assert!(!output.contains("String(describing: error)"));
assert!(output.contains(" } catch {\n // success\n }"));
}
#[test]
fn test_file_error_assertion_escapes_declared_value_for_swift_string_literal() {
use crate::core::config::ResolvedCrateConfig;
use crate::e2e::config::E2eConfig;
use crate::e2e::fixture::{Assertion, Fixture};
let mut e2e_config = E2eConfig::default();
e2e_config.call.function = "parseThing".into();
let mut fixture = Fixture {
id: "invalid_thing".into(),
description: "Invalid thing raises".into(),
..Fixture::default()
};
fixture.assertions.push(Assertion {
assertion_type: "error".into(),
value: Some(serde_json::json!("bad \"field\" \\ value")),
..Default::default()
});
let output = super::test_file::render_test_file(
"smoke",
&[&fixture],
&e2e_config,
"TestModule",
"TestCase",
"parseThing",
"result",
&[],
false,
None,
&Default::default(),
&ResolvedCrateConfig::default(),
&[],
false,
&[],
&[],
&[],
);
let expected_escaped = crate::e2e::codegen::swift::values::escape_swift("bad \"field\" \\ value");
let expected_snippet = format!("_errorMessage.contains(\"{expected_escaped}\")");
assert!(
output.contains(&expected_snippet),
"expected escaped literal snippet `{expected_snippet}` in:\n{output}"
);
}
#[test]
fn app_harness_renders_fixtures_json_chunks_without_multiline_string_syntax_error() {
use crate::e2e::config::E2eConfig;
use crate::e2e::fixture::FixtureGroup;
let group = FixtureGroup {
category: "test".to_string(),
fixtures: vec![],
};
let e2e_config = E2eConfig::default();
let output = super::project::render_app_harness(&e2e_config, &[group], "TestModule");
assert!(
!output.contains("\"\"\"{{"),
"output must not have multiline string opening followed by JSON object on same line"
);
assert!(
!output.contains("\"\"\" {"),
"output must not have multiline string opening followed by space and JSON on same line"
);
assert!(
output.contains("let _FIXTURES_JSON: String = ["),
"expected array literal pattern: let _FIXTURES_JSON: String = ["
);
assert!(
output.contains("].joined()"),
"expected .joined() call to concatenate chunks"
);
assert!(!output.is_empty(), "rendered output should not be empty");
}
#[test]
fn test_file_does_not_emit_existing_binding_when_no_http_fixtures() {
use crate::core::config::ResolvedCrateConfig;
use crate::e2e::config::E2eConfig;
let e2e_config = E2eConfig::default();
let output = super::test_file::render_test_file(
"smoke",
&[],
&e2e_config,
"TestModule",
"TestCase",
"testFunction",
"result",
&[],
false,
None,
&Default::default(),
&ResolvedCrateConfig::default(),
&[],
false, &[],
&[],
&[],
);
assert!(
!output.contains("let _existing"),
"should not emit `let _existing` binding when has_http_fixtures=false"
);
}
#[test]
fn test_file_emits_existing_binding_when_has_http_fixtures() {
use crate::core::config::ResolvedCrateConfig;
use crate::e2e::config::E2eConfig;
let e2e_config = E2eConfig::default();
let output = super::test_file::render_test_file(
"smoke",
&[],
&e2e_config,
"TestModule",
"TestCase",
"testFunction",
"result",
&[],
false,
None,
&Default::default(),
&ResolvedCrateConfig::default(),
&[],
true, &[],
&[],
&[],
);
assert!(
output.contains("let _existing = ProcessInfo.processInfo.environment[\"SUT_URL\"]"),
"should emit `let _existing` binding when has_http_fixtures=true"
);
assert!(
output.contains("let _existing = ProcessInfo.processInfo.environment[\"SUT_URL\"]\n")
|| output.contains("let _existing = ProcessInfo.processInfo.environment[\"SUT_URL\"]\r\n"),
"binding should be followed by the if nil check"
);
}
#[test]
fn test_file_readiness_probe_requires_actual_http_response() {
use crate::core::config::ResolvedCrateConfig;
use crate::e2e::config::E2eConfig;
let e2e_config = E2eConfig::default();
let output = super::test_file::render_test_file(
"smoke",
&[],
&e2e_config,
"TestModule",
"TestCase",
"testFunction",
"result",
&[],
false,
None,
&Default::default(),
&ResolvedCrateConfig::default(),
&[],
true, &[],
&[],
&[],
);
assert!(
output.contains("error == nil, response is HTTPURLResponse"),
"probe must require a real HTTP response before treating the harness as ready"
);
assert!(
output.contains("_probeSucceeded"),
"probe must track response success separately from task completion"
);
assert!(
!output.contains("{ _, _, _ in _probeSema.signal() }"),
"probe must not discard the response/error and treat any completion as ready"
);
assert!(
output.contains("Harness did not become ready within 15s"),
"must still fatalError with a clear message when the harness never becomes ready"
);
assert!(
output.contains("Failed to start harness"),
"must still fatalError when the harness process fails to launch"
);
}
fn wildcard_resolver() -> FieldResolver {
FieldResolver::new(
&HashMap::new(),
&HashSet::new(),
&HashSet::new(),
&HashSet::from(["links".to_string()]),
&HashSet::new(),
)
}
fn render_field_assertion(
resolver: &FieldResolver,
assertion_type: &str,
field: &str,
value: Option<serde_json::Value>,
) -> String {
let assertion = Assertion {
skip: None,
assertion_type: assertion_type.to_string(),
field: Some(field.to_string()),
value,
values: None,
method: None,
check: None,
args: None,
return_type: None,
};
let mut out = String::new();
render_assertion(
&mut out,
&assertion,
"result",
resolver,
false,
false,
false,
false,
&HashMap::new(),
&HashSet::new(),
false,
false,
);
out
}
#[test]
fn swift_wildcard_equals_leaves_a_visible_skip_instead_of_asserting_element_zero() {
let out = render_field_assertion(
&wildcard_resolver(),
"equals",
"links[].url",
Some(serde_json::Value::String("https://example.com".to_string())),
);
assert_eq!(
out.trim_end(),
" // skipped: unsupported traversal assertion 'equals' on 'links[].url'",
"got: {out}"
);
assert!(!out.contains("[0]"), "wildcard must not pin element 0, got: {out}");
assert!(
!out.contains("XCTAssert"),
"a refused traversal must emit no assertion at all, got: {out}"
);
}
#[test]
fn swift_wildcard_scalar_assertions_all_refuse_visibly() {
let resolver = wildcard_resolver();
let cases = [
("starts_with", Some(serde_json::json!("http"))),
("ends_with", Some(serde_json::json!(".com"))),
("matches_regex", Some(serde_json::json!("^http"))),
("min_length", Some(serde_json::json!(1))),
("max_length", Some(serde_json::json!(80))),
("greater_than", Some(serde_json::json!(1))),
("less_than", Some(serde_json::json!(9))),
("greater_than_or_equal", Some(serde_json::json!(1))),
("less_than_or_equal", Some(serde_json::json!(9))),
("count_min", Some(serde_json::json!(1))),
("count_equals", Some(serde_json::json!(2))),
("is_true", None),
("is_false", None),
("is_empty", None),
("contains_any", None),
];
for (assertion_type, value) in cases {
let out = render_field_assertion(&resolver, assertion_type, "links[].url", value);
assert_eq!(
out.trim_end(),
format!(" // skipped: unsupported traversal assertion '{assertion_type}' on 'links[].url'"),
"assertion type {assertion_type}: {out}"
);
assert!(
!out.contains("[0]"),
"assertion type {assertion_type} must not pin element 0, got: {out}"
);
}
}
#[test]
fn swift_wildcard_is_empty_emits_no_reference_to_a_dropped_vec_local() {
let out = render_field_assertion(&wildcard_resolver(), "is_empty", "links[].url", None);
assert!(!out.contains("_vec_"), "got: {out}");
}
#[test]
fn swift_wildcard_contains_still_quantifies_over_every_element() {
let out = render_field_assertion(
&wildcard_resolver(),
"contains",
"links[].url",
Some(serde_json::Value::String("example".to_string())),
);
assert!(
out.contains("XCTAssertTrue(result.links().contains(where: { $0.url().toString().contains(\"example\") })"),
"got: {out}"
);
assert!(!out.contains("[0]"), "traversal must be index-free, got: {out}");
}
#[test]
fn swift_wildcard_not_contains_still_quantifies_over_every_element() {
let out = render_field_assertion(
&wildcard_resolver(),
"not_contains",
"links[].url",
Some(serde_json::Value::String("example".to_string())),
);
assert!(
out.contains("XCTAssertFalse(result.links().contains(where:"),
"got: {out}"
);
assert!(!out.contains("[0]"), "traversal must be index-free, got: {out}");
}
#[test]
fn swift_wildcard_not_empty_still_quantifies_over_every_element() {
let out = render_field_assertion(&wildcard_resolver(), "not_empty", "links[].url", None);
assert!(
out.contains("XCTAssertTrue(result.links().contains(where: { !$0.url().toString().isEmpty })"),
"got: {out}"
);
assert!(!out.contains("[0]"), "traversal must be index-free, got: {out}");
}
#[test]
fn swift_explicit_index_still_asserts_against_that_element() {
let out = render_field_assertion(
&wildcard_resolver(),
"equals",
"links[0].url",
Some(serde_json::Value::String("https://example.com".to_string())),
);
assert!(out.contains("XCTAssertEqual("), "got: {out}");
assert!(out.contains("[0].url().toString()"), "got: {out}");
assert!(
!out.contains("skipped"),
"an explicit index is not a traversal, got: {out}"
);
}
#[test]
fn swift_plain_field_equals_is_unaffected_by_the_wildcard_pre_dispatch() {
let out = render_field_assertion(
&wildcard_resolver(),
"equals",
"title",
Some(serde_json::Value::String("hello".to_string())),
);
assert_eq!(
out.trim_end(),
" XCTAssertEqual(result.title().toString(), \"hello\")"
);
}
#[test]
fn swift_equals_on_an_error_field_is_named_instead_of_dropped() {
use crate::core::config::ResolvedCrateConfig;
use crate::e2e::config::E2eConfig;
use crate::e2e::fixture::{Assertion, Fixture};
let mut e2e_config = E2eConfig::default();
e2e_config.call.function = "parseThing".into();
let mut fixture = Fixture {
id: "rate_limited".into(),
description: "Invalid thing raises".into(),
..Fixture::default()
};
fixture.assertions.push(Assertion {
assertion_type: "error".into(),
value: Some(serde_json::json!("ThingNotFound")),
..Default::default()
});
fixture.assertions.push(Assertion {
assertion_type: "equals".into(),
field: Some("error.status_code".into()),
..Default::default()
});
let _ = crate::e2e::codegen::take_skip_records();
let output = super::test_file::render_test_file(
"smoke",
&[&fixture],
&e2e_config,
"TestModule",
"TestCase",
"parseThing",
"result",
&[],
false,
None,
&Default::default(),
&ResolvedCrateConfig::default(),
&[],
false,
&[],
&[],
&[],
);
assert!(
output.contains("XCTFail(\"expected to throw\")"),
"the error block must render: {output}"
);
assert!(
output.contains(
"// skipped: assertion type 'equals' has no accessor for error field error.status_code in this backend"
),
"{output}"
);
let records = crate::e2e::codegen::take_skip_records();
assert_eq!(records.len(), 1, "got: {records:?}");
assert_eq!(records[0].language, "swift");
assert_eq!(records[0].field, "equals");
}
#[test]
fn swift_a_lone_error_assertion_renders_no_marker() {
use crate::core::config::ResolvedCrateConfig;
use crate::e2e::config::E2eConfig;
use crate::e2e::fixture::{Assertion, Fixture};
let mut e2e_config = E2eConfig::default();
e2e_config.call.function = "parseThing".into();
let mut fixture = Fixture {
id: "invalid_thing".into(),
description: "Invalid thing raises".into(),
..Fixture::default()
};
fixture.assertions.push(Assertion {
assertion_type: "error".into(),
..Default::default()
});
let output = super::test_file::render_test_file(
"smoke",
&[&fixture],
&e2e_config,
"TestModule",
"TestCase",
"parseThing",
"result",
&[],
false,
None,
&Default::default(),
&ResolvedCrateConfig::default(),
&[],
false,
&[],
&[],
&[],
);
assert!(
output.contains("XCTFail(\"expected to throw\")"),
"the error block must render: {output}"
);
assert!(!output.contains("has no accessor for error field"), "{output}");
}