use alef::core::config::NewAlefConfig;
use alef::e2e::codegen::E2eCodegen;
use alef::e2e::codegen::elixir::ElixirCodegen;
use alef::e2e::fixture::{Assertion, Fixture, FixtureGroup};
fn error_assertion(value: Option<&str>) -> Assertion {
Assertion {
skip: None,
assertion_type: "error".to_string(),
field: None,
value: value.map(|v| serde_json::Value::String(v.to_string())),
values: None,
method: None,
check: None,
args: None,
return_type: None,
}
}
fn base_fixture(id: &str, category: &str, value: Option<&str>) -> Fixture {
Fixture {
docs: None,
requirements: Vec::new(),
id: id.to_string(),
category: Some(category.to_string()),
description: "test fixture".to_string(),
tags: Vec::new(),
skip: None,
env: None,
setup: Vec::new(),
call: None,
input: serde_json::json!({
"config": { "max_depth": 200 },
"url": "https://example.com"
}),
mock_response: None,
visitor: None,
args: Vec::new(),
assertion_recipes: Vec::new(),
assertions: vec![error_assertion(value)],
source: format!("{category}/{id}.json"),
http: None,
asyncapi: None,
websocket: None,
preserve_input_urls: false,
}
}
fn group(fixture: Fixture, category: &str) -> FixtureGroup {
FixtureGroup {
category: category.to_string(),
fixtures: vec![fixture],
}
}
const TOML: &str = r#"
[workspace]
languages = ["elixir"]
[[crates]]
name = "demo_lib"
sources = ["src/lib.rs"]
[crates.e2e]
fixtures = "fixtures"
output = "e2e"
[crates.e2e.call]
function = "scrape"
module = "DemoLib"
result_var = "result"
returns_result = true
args = [
{ name = "engine", field = "config", type = "handle" },
{ name = "url", field = "url", type = "string" },
]
[crates.e2e.call.overrides.elixir]
module = "DemoLib"
returns_result = true
"#;
fn render(fixture: Fixture, category: &str) -> String {
let cfg: NewAlefConfig = toml::from_str(TOML).expect("config parses");
let resolved = cfg.clone().resolve().expect("config resolves").remove(0);
let e2e = cfg.crates[0].e2e.clone().expect("e2e config present");
let groups = vec![group(fixture, category)];
let files = ElixirCodegen
.generate(&groups, &e2e, &resolved, &[], &[], &[], &[])
.expect("generation succeeds");
files.iter().map(|f| f.content.clone()).collect::<Vec<_>>().join("\n")
}
#[test]
fn declared_error_value_emits_a_real_check_on_the_call_result() {
let content = render(base_fixture("error_bad_request", "error", Some("BadRequest")), "error");
assert!(
content.contains("assert {:error, __reason} ="),
"expected the error tuple to be bound for inspection, got:\n{content}"
);
assert!(
content.contains("assert String.contains?(inspect(__reason), \"BadRequest\")"),
"expected a real check against the declared error value, got:\n{content}"
);
assert!(
!content.contains("assert {:error, _} ="),
"declared value must replace the untyped {{:error, _}} match, got:\n{content}"
);
}
#[test]
fn no_declared_value_is_byte_identical_to_the_bare_match() {
let content = render(base_fixture("error_generic", "error", None), "error");
assert!(
content.contains("assert {:error, _} = DemoLib.scrape(engine, \"https://example.com\")"),
"expected the untouched bare match when no value is declared, got:\n{content}"
);
assert!(
!content.contains("__reason"),
"no declared value must not introduce the reason-binding check, got:\n{content}"
);
}
#[test]
fn declared_value_is_escaped_for_quotes_and_backslashes() {
let content = render(
base_fixture("error_escaped", "error", Some("bad \"field\\name\"")),
"error",
);
assert!(
content.contains("assert String.contains?(inspect(__reason), \"bad \\\"field\\\\name\\\"\")"),
"expected quotes and backslashes to be escaped for an Elixir string literal, got:\n{content}"
);
}
#[test]
fn validation_category_calls_the_operation_under_test_when_creation_succeeds() {
let content = render(
base_fixture("validation_max_depth_too_high", "validation", Some("max_depth")),
"validation",
);
assert!(
content.contains("DemoLib.scrape(engine, \"https://example.com\")"),
"expected the operation under test to be called inside the {{:ok, engine}} branch, got:\n{content}"
);
assert!(
content.contains("case DemoLib.create_engine("),
"expected engine creation to be attempted via a case, not asserted unconditionally, got:\n{content}"
);
assert!(
content.contains("{:ok, engine} ->"),
"expected a success branch that continues to the operation call, got:\n{content}"
);
assert!(
content.contains("{:error, __reason} ->"),
"expected a failure branch that checks the declared value on creation failure too, got:\n{content}"
);
}
#[test]
fn validation_category_without_declared_value_still_calls_the_operation() {
let content = render(base_fixture("validation_no_value", "validation", None), "validation");
assert!(
content.contains("DemoLib.scrape(engine, \"https://example.com\")"),
"expected the operation under test to be called even without a declared value, got:\n{content}"
);
assert!(
content.contains("assert {:error, _} = DemoLib.scrape(engine, \"https://example.com\")"),
"expected a bare error assertion on the operation call, got:\n{content}"
);
}