use crate::e2e::escape::{escape_php, sanitize_filename};
use crate::e2e::fixture::{Fixture, HttpFixture, ValidationErrorExpectation};
use crate::e2e::codegen::client;
pub(super) struct PhpTestClientRenderer;
impl client::TestClientRenderer for PhpTestClientRenderer {
fn language_name(&self) -> &'static str {
"php"
}
fn sanitize_test_name(&self, id: &str) -> String {
sanitize_filename(id)
}
fn render_test_open(&self, out: &mut String, fn_name: &str, description: &str, skip_reason: Option<&str>) {
let escaped_reason = skip_reason.map(escape_php);
let rendered = crate::e2e::template_env::render(
"php/http_test_open.jinja",
minijinja::context! {
fn_name => fn_name,
description => description,
skip_reason => escaped_reason,
},
);
out.push_str(&rendered);
}
fn render_test_close(&self, out: &mut String) {
let rendered = crate::e2e::template_env::render("php/http_test_close.jinja", minijinja::context! {});
out.push_str(&rendered);
}
fn render_call(&self, out: &mut String, ctx: &client::CallCtx<'_>) {
let method = ctx.method.to_uppercase();
let mut opts: Vec<String> = Vec::new();
if let Some(body) = ctx.body {
let php_body = super::values::json_to_php(body);
opts.push(format!("'json' => {php_body}"));
}
let mut header_pairs: Vec<String> = Vec::new();
if let Some(ct) = ctx.content_type {
if !ctx.headers.keys().any(|k| k.to_lowercase() == "content-type") {
header_pairs.push(format!("\"Content-Type\" => \"{}\"", escape_php(ct)));
}
}
for (k, v) in ctx.headers {
header_pairs.push(format!("\"{}\" => \"{}\"", escape_php(k), escape_php(v)));
}
if !header_pairs.is_empty() {
opts.push(format!("'headers' => [{}]", header_pairs.join(", ")));
}
if !ctx.cookies.is_empty() {
let cookie_str = ctx
.cookies
.iter()
.map(|(k, v)| format!("{}={}", k, v))
.collect::<Vec<_>>()
.join("; ");
opts.push(format!("'headers' => ['Cookie' => \"{}\"]", escape_php(&cookie_str)));
}
if !ctx.query_params.is_empty() {
let pairs: Vec<String> = ctx
.query_params
.iter()
.map(|(k, v)| {
let val_str = match v {
serde_json::Value::String(s) => s.clone(),
other => other.to_string(),
};
format!("\"{}\" => \"{}\"", escape_php(k), escape_php(&val_str))
})
.collect();
opts.push(format!("'query' => [{}]", pairs.join(", ")));
}
let path_lit = escape_php(ctx.path);
let rendered = crate::e2e::template_env::render(
"php/http_request.jinja",
minijinja::context! {
method => method,
path => path_lit,
opts => opts,
response_var => ctx.response_var,
},
);
out.push_str(&rendered);
}
fn render_assert_status(&self, out: &mut String, _response_var: &str, status: u16) {
let rendered = crate::e2e::template_env::render(
"php/http_assertions.jinja",
minijinja::context! {
response_var => "",
status_code => status,
headers => Vec::<std::collections::HashMap<&str, String>>::new(),
body_assertion => String::new(),
partial_body => Vec::<std::collections::HashMap<&str, String>>::new(),
validation_errors => Vec::<std::collections::HashMap<&str, String>>::new(),
},
);
out.push_str(&rendered);
}
fn render_assert_header(&self, out: &mut String, _response_var: &str, name: &str, expected: &str) {
let header_key = name.to_lowercase();
let header_key_lit = format!("\"{}\"", escape_php(&header_key));
let assertion_code = match expected {
"<<present>>" => {
format!("$this->assertTrue($response->hasHeader({header_key_lit}));")
}
"<<absent>>" => {
format!("$this->assertFalse($response->hasHeader({header_key_lit}));")
}
"<<uuid>>" => {
format!(
"$this->assertMatchesRegularExpression('/^[0-9a-f]{{8}}-[0-9a-f]{{4}}-[0-9a-f]{{4}}-[0-9a-f]{{4}}-[0-9a-f]{{12}}$/i', $response->getHeaderLine({header_key_lit}));"
)
}
literal => {
let val_lit = format!("\"{}\"", escape_php(literal));
format!("$this->assertEquals({val_lit}, $response->getHeaderLine({header_key_lit}));")
}
};
let mut headers = vec![std::collections::HashMap::new()];
headers[0].insert("assertion_code", assertion_code);
let rendered = crate::e2e::template_env::render(
"php/http_assertions.jinja",
minijinja::context! {
response_var => "",
status_code => 0u16,
headers => headers,
body_assertion => String::new(),
partial_body => Vec::<std::collections::HashMap<&str, String>>::new(),
validation_errors => Vec::<std::collections::HashMap<&str, String>>::new(),
},
);
out.push_str(&rendered);
}
fn render_assert_json_body(&self, out: &mut String, _response_var: &str, expected: &serde_json::Value) {
let body_assertion = match expected {
serde_json::Value::String(s) if !s.is_empty() => {
let php_val = format!("\"{}\"", escape_php(s));
format!("$this->assertEquals({php_val}, (string) $response->getBody());")
}
_ => {
let php_val = super::values::json_to_php(expected);
format!(
"$body = json_decode((string) $response->getBody(), true, 512, JSON_THROW_ON_ERROR);\n $this->assertEquals({php_val}, $body);"
)
}
};
let rendered = crate::e2e::template_env::render(
"php/http_assertions.jinja",
minijinja::context! {
response_var => "",
status_code => 0u16,
headers => Vec::<std::collections::HashMap<&str, String>>::new(),
body_assertion => body_assertion,
partial_body => Vec::<std::collections::HashMap<&str, String>>::new(),
validation_errors => Vec::<std::collections::HashMap<&str, String>>::new(),
},
);
out.push_str(&rendered);
}
fn render_assert_partial_body(&self, out: &mut String, _response_var: &str, expected: &serde_json::Value) {
if let Some(obj) = expected.as_object() {
let mut partial_body: Vec<std::collections::HashMap<&str, String>> = Vec::new();
for (key, val) in obj {
let php_key = format!("\"{}\"", escape_php(key));
let php_val = super::values::json_to_php(val);
let assertion_code = format!("$this->assertEquals({php_val}, $body[{php_key}]);");
let mut entry = std::collections::HashMap::new();
entry.insert("assertion_code", assertion_code);
partial_body.push(entry);
}
let rendered = crate::e2e::template_env::render(
"php/http_assertions.jinja",
minijinja::context! {
response_var => "",
status_code => 0u16,
headers => Vec::<std::collections::HashMap<&str, String>>::new(),
body_assertion => String::new(),
partial_body => partial_body,
validation_errors => Vec::<std::collections::HashMap<&str, String>>::new(),
},
);
out.push_str(&rendered);
}
}
fn render_assert_validation_errors(
&self,
out: &mut String,
_response_var: &str,
errors: &[ValidationErrorExpectation],
) {
let mut validation_errors: Vec<std::collections::HashMap<&str, String>> = Vec::new();
for err in errors {
let msg_lit = format!("\"{}\"", escape_php(&err.msg));
let assertion_code =
format!("$this->assertStringContainsString({msg_lit}, json_encode($body, JSON_UNESCAPED_SLASHES));");
let mut entry = std::collections::HashMap::new();
entry.insert("assertion_code", assertion_code);
validation_errors.push(entry);
}
let rendered = crate::e2e::template_env::render(
"php/http_assertions.jinja",
minijinja::context! {
response_var => "",
status_code => 0u16,
headers => Vec::<std::collections::HashMap<&str, String>>::new(),
body_assertion => String::new(),
partial_body => Vec::<std::collections::HashMap<&str, String>>::new(),
validation_errors => validation_errors,
},
);
out.push_str(&rendered);
}
}
pub(super) fn render_http_test_method(out: &mut String, fixture: &Fixture, http: &HttpFixture) {
if http.expected_response.status_code == 101 {
let method_name = sanitize_filename(&fixture.id);
let description = &fixture.description;
out.push_str(&crate::e2e::template_env::render(
"php/http_test_skip_101.jinja",
minijinja::context! {
method_name => method_name,
description => description,
},
));
return;
}
client::http_call::render_http_test(out, &PhpTestClientRenderer, fixture);
}