use crate::codegen::naming::{go_param_name, to_go_name};
use crate::e2e::escape::go_string_literal;
use crate::e2e::field_access::FieldResolver;
use crate::e2e::fixture::Fixture;
use heck::{ToSnakeCase, ToUpperCamelCase};
use std::fmt::Write as FmtWrite;
use super::assertions::render_assertion;
use super::client;
use super::setup::build_args_and_setup;
use super::visitors::visitor_struct_name;
pub(super) fn fixture_has_go_callable(fixture: &Fixture, e2e_config: &crate::e2e::config::E2eConfig) -> bool {
if fixture.is_http_test() {
return false;
}
let call_config = e2e_config.resolve_call_for_fixture(
fixture.call.as_deref(),
&fixture.id,
&fixture.resolved_category(),
&fixture.tags,
&fixture.input,
);
if call_config.skip_languages.iter().any(|l| l == "go") {
return false;
}
let go_override = call_config
.overrides
.get("go")
.or_else(|| e2e_config.call.overrides.get("go"));
if go_override.and_then(|o| o.client_factory.as_deref()).is_some() {
return true;
}
let fn_name = go_override
.and_then(|o| o.function.as_deref())
.filter(|s| !s.is_empty())
.unwrap_or(call_config.function.as_str());
!fn_name.is_empty()
}
pub(super) struct GoTestFunctionContext<'a> {
pub(super) import_alias: &'a str,
pub(super) e2e_config: &'a crate::e2e::config::E2eConfig,
pub(super) adapters: &'a [crate::core::config::AdapterConfig],
pub(super) data_enum_names: &'a std::collections::HashSet<&'a str>,
pub(super) config: &'a crate::core::config::ResolvedCrateConfig,
pub(super) type_defs: &'a [crate::core::ir::TypeDef],
pub(super) enums: &'a [crate::core::ir::EnumDef],
}
pub(super) fn render_test_function(out: &mut String, fixture: &Fixture, context: GoTestFunctionContext<'_>) {
let GoTestFunctionContext {
import_alias,
e2e_config,
adapters,
data_enum_names,
config,
type_defs,
enums,
} = context;
let fn_name = fixture.id.to_upper_camel_case();
let description = &fixture.description;
if fixture.http.is_some() {
render_http_test_function(out, fixture);
return;
}
if !fixture_has_go_callable(fixture, e2e_config) {
let _ = writeln!(out, "func Test_{fn_name}(t *testing.T) {{");
let _ = writeln!(out, "\t// {description}");
let _ = writeln!(
out,
"\tt.Skip(\"non-HTTP fixture: Go binding does not expose a callable for the configured `[e2e.call]` function\")"
);
let _ = writeln!(out, "}}");
return;
}
let call_config = e2e_config.resolve_call_for_fixture(
fixture.call.as_deref(),
&fixture.id,
&fixture.resolved_category(),
&fixture.tags,
&fixture.input,
);
let call_field_resolver = FieldResolver::new(
e2e_config.effective_fields(call_config),
e2e_config.effective_fields_optional(call_config),
e2e_config.effective_result_fields(call_config),
e2e_config.effective_fields_array(call_config),
&std::collections::HashSet::new(),
);
let field_resolver = &call_field_resolver;
let lang = "go";
let overrides = call_config.overrides.get(lang);
let base_function_name = overrides
.and_then(|o| o.function.as_deref())
.unwrap_or(&call_config.function);
let function_name = to_go_name(base_function_name);
let result_var = &call_config.result_var;
let recipe = crate::e2e::codegen::recipe::ResolvedE2eCallRecipe::resolve(lang, fixture, call_config, type_defs);
let args = recipe.args;
let returns_result = overrides
.and_then(|o| o.returns_result)
.unwrap_or(call_config.returns_result);
let returns_void = call_config.returns_void;
let result_is_simple = overrides.is_some_and(|o| o.result_is_simple)
|| call_config.result_is_simple
|| call_config
.overrides
.get("rust")
.map(|o| o.result_is_simple)
.unwrap_or(false);
let result_is_array = overrides.is_some_and(|o| o.result_is_array) || call_config.result_is_array;
let call_options_type = recipe.options_type.or_else(|| {
e2e_config
.call
.overrides
.get("go")
.and_then(|o| o.options_type.as_deref())
});
let call_options_ptr = overrides.map(|o| o.options_ptr).unwrap_or_else(|| {
e2e_config
.call
.overrides
.get("go")
.map(|o| o.options_ptr)
.unwrap_or(false)
});
let expects_error = fixture.assertions.iter().any(|a| a.assertion_type == "error");
let validation_creation_failure = expects_error && fixture.resolved_category() == "validation";
let client_factory = overrides.and_then(|o| o.client_factory.as_deref()).or_else(|| {
e2e_config
.call
.overrides
.get(lang)
.and_then(|o| o.client_factory.as_deref())
});
let (package_decls, mut setup_lines, args_str) = build_args_and_setup(
&fixture.input,
args,
import_alias,
call_options_type,
fixture,
call_options_ptr,
validation_creation_failure,
data_enum_names,
config,
type_defs,
enums,
);
for decl in &package_decls {
out.push_str(decl);
if !decl.ends_with('\n') {
out.push('\n');
}
out.push('\n');
}
let mut visitor_opts_var: Option<String> = None;
if fixture.visitor.is_some() {
let struct_name = visitor_struct_name(&fixture.id);
setup_lines.push(format!("visitor := &{struct_name}{{}}"));
let Some(opts_type) =
call_options_type.or_else(|| crate::e2e::codegen::recipe::trait_bridge_options_type(config))
else {
let _ = writeln!(out, "func Test_{fn_name}(t *testing.T) {{");
let _ = writeln!(
out,
"\tt.Skip(\"go: visitor fixture requires trait bridge options_type\")"
);
let _ = writeln!(out, "}}");
return;
};
let opts_var = "opts".to_string();
setup_lines.push(format!("opts := &{import_alias}.{opts_type}{{}}"));
setup_lines.push("opts.Visitor = visitor".to_string());
visitor_opts_var = Some(opts_var);
}
let go_extra_args = recipe.extra_args.to_vec();
let final_args = {
let mut parts: Vec<String> = Vec::new();
if !args_str.is_empty() {
let processed_args = if let Some(ref opts_var) = visitor_opts_var {
args_str.trim_end_matches(", nil").to_string() + ", " + opts_var
} else {
args_str
};
parts.push(processed_args);
}
parts.extend(go_extra_args);
parts.join(", ")
};
let _ = writeln!(out, "func Test_{fn_name}(t *testing.T) {{");
let _ = writeln!(out, "\t// {description}");
let has_mock = fixture.mock_response.is_some() || fixture.http.is_some();
let api_key_var = fixture.env.as_ref().and_then(|e| e.api_key_var.as_deref());
if let Some(var) = api_key_var {
if has_mock {
let fixture_id = &fixture.id;
let _ = writeln!(out, "\tapiKey := os.Getenv(\"{var}\")");
let _ = writeln!(out, "\tvar baseURL *string");
let _ = writeln!(out, "\tif apiKey != \"\" {{");
let _ = writeln!(out, "\t\tt.Logf(\"{fixture_id}: using real API ({var} is set)\")");
let _ = writeln!(out, "\t}} else {{");
let _ = writeln!(out, "\t\tt.Logf(\"{fixture_id}: using mock server ({var} not set)\")");
let _ = writeln!(
out,
"\t\tu := os.Getenv(\"MOCK_SERVER_URL\") + \"/fixtures/{fixture_id}\""
);
let _ = writeln!(out, "\t\tbaseURL = &u");
let _ = writeln!(out, "\t\tapiKey = \"test-key\"");
let _ = writeln!(out, "\t}}");
} else {
let _ = writeln!(out, "\tapiKey := os.Getenv(\"{var}\")");
let _ = writeln!(out, "\tif apiKey == \"\" {{");
let _ = writeln!(out, "\t\tt.Skipf(\"{var} not set\")");
let _ = writeln!(out, "\t}}");
}
}
for line in &setup_lines {
let _ = writeln!(out, "\t{line}");
}
let call_prefix = if let Some(factory) = client_factory {
let factory_name = to_go_name(factory);
let fixture_id = &fixture.id;
let (api_key_expr, base_url_expr) = if has_mock && api_key_var.is_some() {
("apiKey".to_string(), "baseURL".to_string())
} else if api_key_var.is_some() {
("apiKey".to_string(), "nil".to_string())
} else if fixture.has_host_root_route() {
let env_key = format!("MOCK_SERVER_{}", fixture_id.to_uppercase());
let _ = writeln!(out, "\tmockURL := os.Getenv(\"{env_key}\")");
let _ = writeln!(out, "\tif mockURL == \"\" {{");
let _ = writeln!(
out,
"\t\tmockURL = os.Getenv(\"MOCK_SERVER_URL\") + \"/fixtures/{fixture_id}\""
);
let _ = writeln!(out, "\t}}");
("\"test-key\"".to_string(), "&mockURL".to_string())
} else {
let _ = writeln!(
out,
"\tmockURL := os.Getenv(\"MOCK_SERVER_URL\") + \"/fixtures/{fixture_id}\""
);
("\"test-key\"".to_string(), "&mockURL".to_string())
};
let _ = writeln!(
out,
"\tclient, clientErr := {import_alias}.{factory_name}({api_key_expr}, {base_url_expr}, nil, nil, nil)"
);
let _ = writeln!(out, "\tif clientErr != nil {{");
let _ = writeln!(out, "\t\tt.Fatalf(\"create client failed: %v\", clientErr)");
let _ = writeln!(out, "\t}}");
"client".to_string()
} else {
import_alias.to_string()
};
let binding_returns_error_pre = args
.iter()
.any(|a| matches!(a.arg_type.as_str(), "json_object" | "bytes"));
let effective_returns_result_pre = returns_result || binding_returns_error_pre || client_factory.is_some();
if expects_error {
if effective_returns_result_pre && !returns_void {
let _ = writeln!(out, "\t_, err := {call_prefix}.{function_name}({final_args})");
} else {
let _ = writeln!(out, "\terr := {call_prefix}.{function_name}({final_args})");
}
let _ = writeln!(out, "\tif err == nil {{");
let _ = writeln!(out, "\t\tt.Errorf(\"expected an error, but call succeeded\")");
let _ = writeln!(out, "\t}}");
let _ = writeln!(out, "}}");
return;
}
let is_streaming =
crate::e2e::codegen::streaming_assertions::resolve_is_streaming(fixture, call_config.streaming_enabled());
let fn_snake = function_name.to_snake_case();
let base_snake = base_function_name.to_snake_case();
let streaming_item_type = is_streaming
.then(|| crate::e2e::codegen::recipe::streaming_item_type(call_config, adapters, &[&fn_snake, &base_snake]))
.flatten();
let has_usable_assertion = fixture.assertions.iter().any(|a| {
if a.assertion_type == "not_error" || a.assertion_type == "error" {
return false;
}
if a.assertion_type == "method_result" {
return true;
}
match &a.field {
Some(f) if !f.is_empty() => {
if is_streaming && crate::e2e::codegen::streaming_assertions::is_streaming_virtual_field(f) {
return true;
}
if result_is_simple {
return true;
}
field_resolver.is_valid_for_result(f)
}
_ => true,
}
});
let binding_returns_error = args
.iter()
.any(|a| matches!(a.arg_type.as_str(), "json_object" | "bytes"));
let effective_returns_result = returns_result || binding_returns_error || client_factory.is_some();
if !effective_returns_result && result_is_simple {
let result_binding = if has_usable_assertion {
result_var.to_string()
} else {
"_".to_string()
};
let assign_op = if result_binding == "_" { "=" } else { ":=" };
let _ = writeln!(
out,
"\t{result_binding} {assign_op} {call_prefix}.{function_name}({final_args})"
);
if has_usable_assertion && result_binding != "_" {
if result_is_array {
let _ = writeln!(out, "\tvalue := {result_var}");
} else {
let only_nil_assertions = fixture
.assertions
.iter()
.filter(|a| a.field.as_ref().is_none_or(|f| f.is_empty()))
.filter(|a| !matches!(a.assertion_type.as_str(), "not_error" | "error"))
.all(|a| matches!(a.assertion_type.as_str(), "is_empty" | "is_null"));
if !only_nil_assertions {
let result_is_ptr = overrides.map(|o| o.result_is_pointer).unwrap_or(true);
if result_is_ptr {
let _ = writeln!(out, "\tif {result_var} == nil {{");
let _ = writeln!(out, "\t\tt.Fatalf(\"expected non-nil result\")");
let _ = writeln!(out, "\t}}");
let _ = writeln!(out, "\tvalue := *{result_var}");
} else {
let _ = writeln!(out, "\tvalue := {result_var}");
}
}
}
}
} else if !effective_returns_result || returns_void {
let _ = writeln!(out, "\terr := {call_prefix}.{function_name}({final_args})");
let _ = writeln!(out, "\tif err != nil {{");
let _ = writeln!(out, "\t\tt.Fatalf(\"call failed: %v\", err)");
let _ = writeln!(out, "\t}}");
let _ = writeln!(out, "}}");
return;
} else {
let result_binding = if is_streaming {
"stream".to_string()
} else if has_usable_assertion {
result_var.to_string()
} else {
"_".to_string()
};
let _ = writeln!(
out,
"\t{result_binding}, err := {call_prefix}.{function_name}({final_args})"
);
let _ = writeln!(out, "\tif err != nil {{");
let _ = writeln!(out, "\t\tt.Fatalf(\"call failed: %v\", err)");
let _ = writeln!(out, "\t}}");
if is_streaming {
if let Some(streaming_item_type) = streaming_item_type {
let _ = writeln!(out, "\tvar chunks []{import_alias}.{streaming_item_type}");
let _ = writeln!(out, "\tfor chunk := range stream {{");
let _ = writeln!(out, "\t\tchunks = append(chunks, chunk)");
let _ = writeln!(out, "\t}}");
} else {
let _ = writeln!(
out,
"\t// skipped: streaming fixture requires adapter item_type for Go e2e codegen"
);
}
}
if result_is_simple && has_usable_assertion && result_binding != "_" {
if result_is_array {
let _ = writeln!(out, "\tvalue := {}", result_var);
} else {
let only_nil_assertions = fixture
.assertions
.iter()
.filter(|a| a.field.as_ref().is_none_or(|f| f.is_empty()))
.filter(|a| !matches!(a.assertion_type.as_str(), "not_error" | "error"))
.all(|a| matches!(a.assertion_type.as_str(), "is_empty" | "is_null"));
if !only_nil_assertions {
let result_is_ptr = overrides.map(|o| o.result_is_pointer).unwrap_or(true);
if result_is_ptr {
let _ = writeln!(out, "\tif {} == nil {{", result_var);
let _ = writeln!(out, "\t\tt.Fatalf(\"expected non-nil result\")");
let _ = writeln!(out, "\t}}");
let _ = writeln!(out, "\tvalue := *{}", result_var);
} else {
let _ = writeln!(out, "\tvalue := {}", result_var);
}
}
}
}
}
let result_is_ptr = overrides.map(|o| o.result_is_pointer).unwrap_or(true);
let has_deref_value = if result_is_simple && has_usable_assertion && !result_is_array && result_is_ptr {
let only_nil_assertions = fixture
.assertions
.iter()
.filter(|a| a.field.as_ref().is_none_or(|f| f.is_empty()))
.filter(|a| !matches!(a.assertion_type.as_str(), "not_error" | "error"))
.all(|a| matches!(a.assertion_type.as_str(), "is_empty" | "is_null"));
!only_nil_assertions
} else if result_is_simple && has_usable_assertion && result_is_ptr {
true
} else {
result_is_simple && has_usable_assertion
};
let effective_result_var = if has_deref_value {
"value".to_string()
} else {
result_var.to_string()
};
let mut optional_locals: std::collections::HashMap<String, String> = std::collections::HashMap::new();
for assertion in &fixture.assertions {
if let Some(f) = &assertion.field {
if !f.is_empty() {
if !result_is_simple && !field_resolver.is_valid_for_result(f) {
continue;
}
let resolved = field_resolver.resolve(f);
if field_resolver.is_optional(resolved) && !optional_locals.contains_key(f.as_str()) {
let is_string_field = assertion.value.as_ref().is_some_and(|v| v.is_string());
let is_array_field = field_resolver.is_array(resolved);
if !is_string_field || is_array_field {
continue;
}
let field_expr = field_resolver.accessor(f, "go", &effective_result_var);
let local_var = go_param_name(&resolved.replace(['.', '[', ']'], "_"));
if field_resolver.has_map_access(f) {
let _ = writeln!(out, "\t{local_var} := {field_expr}");
} else {
let _ = writeln!(out, "\tvar {local_var} string");
let _ = writeln!(out, "\tif {field_expr} != nil {{");
let _ = writeln!(out, "\t\t{local_var} = string(*{field_expr})");
let _ = writeln!(out, "\t}}");
}
optional_locals.insert(f.clone(), local_var);
}
}
}
}
for assertion in &fixture.assertions {
if let Some(f) = &assertion.field {
if !f.is_empty() && !optional_locals.contains_key(f.as_str()) {
let parts: Vec<&str> = f.split('.').collect();
let mut guard_expr: Option<String> = None;
for i in 1..parts.len() {
let prefix = parts[..i].join(".");
let resolved_prefix = field_resolver.resolve(&prefix);
if field_resolver.is_optional(resolved_prefix) {
let guard_prefix = if let Some(bracket_pos) = resolved_prefix.rfind('[') {
let suffix = &resolved_prefix[bracket_pos + 1..];
let is_numeric_index = suffix.trim_end_matches(']').chars().all(|c| c.is_ascii_digit());
if is_numeric_index {
&resolved_prefix[..bracket_pos]
} else {
resolved_prefix
}
} else {
resolved_prefix
};
let accessor = field_resolver.accessor(guard_prefix, "go", &effective_result_var);
guard_expr = Some(accessor);
break;
}
}
if let Some(guard) = guard_expr {
if field_resolver.is_valid_for_result(f) {
let is_struct_value = !guard.contains('[') && !guard.contains('(') && !guard.contains("map");
if is_struct_value {
render_assertion(
out,
assertion,
&effective_result_var,
import_alias,
field_resolver,
&optional_locals,
result_is_simple,
result_is_array,
is_streaming,
streaming_item_type,
);
continue;
}
let _ = writeln!(out, "\tif {guard} != nil {{");
let mut nil_buf = String::new();
render_assertion(
&mut nil_buf,
assertion,
&effective_result_var,
import_alias,
field_resolver,
&optional_locals,
result_is_simple,
result_is_array,
is_streaming,
streaming_item_type,
);
for line in nil_buf.lines() {
let _ = writeln!(out, "\t{line}");
}
let _ = writeln!(out, "\t}}");
} else {
render_assertion(
out,
assertion,
&effective_result_var,
import_alias,
field_resolver,
&optional_locals,
result_is_simple,
result_is_array,
is_streaming,
streaming_item_type,
);
}
continue;
}
}
}
render_assertion(
out,
assertion,
&effective_result_var,
import_alias,
field_resolver,
&optional_locals,
result_is_simple,
result_is_array,
is_streaming,
streaming_item_type,
);
}
let _ = writeln!(out, "}}");
}
fn render_http_test_function(out: &mut String, fixture: &Fixture) {
client::http_call::render_http_test(out, &GoTestClientRenderer, fixture);
}
struct GoTestClientRenderer;
impl client::TestClientRenderer for GoTestClientRenderer {
fn language_name(&self) -> &'static str {
"go"
}
fn sanitize_test_name(&self, id: &str) -> String {
id.to_upper_camel_case()
}
fn render_test_open(&self, out: &mut String, fn_name: &str, description: &str, skip_reason: Option<&str>) {
let _ = writeln!(out, "func Test_{fn_name}(t *testing.T) {{");
let _ = writeln!(out, "\t// {description}");
if let Some(reason) = skip_reason {
let escaped = go_string_literal(reason);
let _ = writeln!(out, "\tt.Skip({escaped})");
}
}
fn render_test_close(&self, out: &mut String) {
let _ = writeln!(out, "}}");
}
fn render_call(&self, out: &mut String, ctx: &client::CallCtx<'_>) {
let method = ctx.method.to_uppercase();
let path = ctx.path;
let _ = writeln!(out, "\tbaseURL := os.Getenv(\"SUT_URL\")");
let _ = writeln!(out, "\tif baseURL == \"\" {{");
let _ = writeln!(out, "\t\tbaseURL = \"http://127.0.0.1:8012\"");
let _ = writeln!(out, "\t}}");
let body_expr = if let Some(body) = ctx.body {
let json = serde_json::to_string(body).unwrap_or_default();
let escaped = go_string_literal(&json);
format!("strings.NewReader({})", escaped)
} else {
"strings.NewReader(\"\")".to_string()
};
let _ = writeln!(out, "\tbody := {body_expr}");
let _ = writeln!(
out,
"\treq, err := http.NewRequest(\"{method}\", baseURL+\"{path}\", body)"
);
let _ = writeln!(out, "\tif err != nil {{");
let _ = writeln!(out, "\t\tt.Fatalf(\"new request failed: %v\", err)");
let _ = writeln!(out, "\t}}");
if ctx.body.is_some() {
let content_type = ctx.content_type.unwrap_or("application/json");
let _ = writeln!(out, "\treq.Header.Set(\"Content-Type\", \"{content_type}\")");
}
let mut header_names: Vec<&String> = ctx.headers.keys().collect();
header_names.sort();
for name in header_names {
let value = &ctx.headers[name];
let escaped_name = go_string_literal(name);
let escaped_value = go_string_literal(value);
let _ = writeln!(out, "\treq.Header.Set({escaped_name}, {escaped_value})");
}
if !ctx.cookies.is_empty() {
let mut cookie_names: Vec<&String> = ctx.cookies.keys().collect();
cookie_names.sort();
for name in cookie_names {
let value = &ctx.cookies[name];
let escaped_name = go_string_literal(name);
let escaped_value = go_string_literal(value);
let _ = writeln!(
out,
"\treq.AddCookie(&http.Cookie{{Name: {escaped_name}, Value: {escaped_value}}})"
);
}
}
let _ = writeln!(out, "\tnoRedirectClient := &http.Client{{");
let _ = writeln!(
out,
"\t\tCheckRedirect: func(req *http.Request, via []*http.Request) error {{"
);
let _ = writeln!(out, "\t\t\treturn http.ErrUseLastResponse");
let _ = writeln!(out, "\t\t}},");
let _ = writeln!(out, "\t}}");
let _ = writeln!(out, "\tresp, err := noRedirectClient.Do(req)");
let _ = writeln!(out, "\tif err != nil {{");
let _ = writeln!(out, "\t\tt.Fatalf(\"request failed: %v\", err)");
let _ = writeln!(out, "\t}}");
let _ = writeln!(out, "\tdefer func() {{ _ = resp.Body.Close() }}()");
let _ = writeln!(out, "\tbodyBytes, err := io.ReadAll(resp.Body)");
let _ = writeln!(out, "\tif err != nil {{");
let _ = writeln!(out, "\t\tt.Fatalf(\"read body failed: %v\", err)");
let _ = writeln!(out, "\t}}");
let _ = writeln!(out, "\t_ = bodyBytes");
}
fn render_assert_status(&self, out: &mut String, _response_var: &str, status: u16) {
let _ = writeln!(out, "\tif resp.StatusCode != {status} {{");
let _ = writeln!(out, "\t\tt.Fatalf(\"status: got %d want {status}\", resp.StatusCode)");
let _ = writeln!(out, "\t}}");
}
fn render_assert_header(&self, out: &mut String, _response_var: &str, name: &str, expected: &str) {
if matches!(expected, "<<absent>>" | "<<present>>" | "<<uuid>>") {
return;
}
if name.eq_ignore_ascii_case("connection") {
return;
}
let escaped_name = go_string_literal(name);
let escaped_value = go_string_literal(expected);
let _ = writeln!(
out,
"\tif !strings.Contains(resp.Header.Get({escaped_name}), {escaped_value}) {{"
);
let _ = writeln!(
out,
"\t\tt.Fatalf(\"header %s mismatch: got %q want to contain %q\", {escaped_name}, resp.Header.Get({escaped_name}), {escaped_value})"
);
let _ = writeln!(out, "\t}}");
}
fn render_assert_json_body(&self, out: &mut String, _response_var: &str, expected: &serde_json::Value) {
match expected {
serde_json::Value::Object(_) | serde_json::Value::Array(_) => {
let json_str = serde_json::to_string(expected).unwrap_or_default();
let escaped = go_string_literal(&json_str);
let _ = writeln!(out, "\tvar got any");
let _ = writeln!(out, "\tvar want any");
let _ = writeln!(out, "\tif err := json.Unmarshal(bodyBytes, &got); err != nil {{");
let _ = writeln!(out, "\t\tt.Fatalf(\"json unmarshal got: %v\", err)");
let _ = writeln!(out, "\t}}");
let _ = writeln!(
out,
"\tif err := json.Unmarshal([]byte({escaped}), &want); err != nil {{"
);
let _ = writeln!(out, "\t\tt.Fatalf(\"json unmarshal want: %v\", err)");
let _ = writeln!(out, "\t}}");
let _ = writeln!(out, "\tif !reflect.DeepEqual(got, want) {{");
let _ = writeln!(out, "\t\tt.Fatalf(\"body mismatch: got %v want %v\", got, want)");
let _ = writeln!(out, "\t}}");
}
serde_json::Value::String(s) => {
let escaped = go_string_literal(s);
let _ = writeln!(out, "\twant := {escaped}");
let _ = writeln!(out, "\tif strings.TrimSpace(string(bodyBytes)) != want {{");
let _ = writeln!(out, "\t\tt.Fatalf(\"body: got %q want %q\", string(bodyBytes), want)");
let _ = writeln!(out, "\t}}");
}
other => {
let escaped = go_string_literal(&other.to_string());
let _ = writeln!(out, "\twant := {escaped}");
let _ = writeln!(out, "\tif strings.TrimSpace(string(bodyBytes)) != want {{");
let _ = writeln!(out, "\t\tt.Fatalf(\"body: got %q want %q\", string(bodyBytes), want)");
let _ = writeln!(out, "\t}}");
}
}
}
fn render_assert_partial_body(&self, out: &mut String, _response_var: &str, expected: &serde_json::Value) {
if let Some(obj) = expected.as_object() {
let _ = writeln!(out, "\tvar _partialGot map[string]any");
let _ = writeln!(
out,
"\tif err := json.Unmarshal(bodyBytes, &_partialGot); err != nil {{"
);
let _ = writeln!(out, "\t\tt.Fatalf(\"json unmarshal partial: %v\", err)");
let _ = writeln!(out, "\t}}");
for (key, val) in obj {
let escaped_key = go_string_literal(key);
let json_val = serde_json::to_string(val).unwrap_or_default();
let escaped_val = go_string_literal(&json_val);
let _ = writeln!(out, "\t{{");
let _ = writeln!(out, "\t\tvar _wantVal any");
let _ = writeln!(
out,
"\t\tif err := json.Unmarshal([]byte({escaped_val}), &_wantVal); err != nil {{"
);
let _ = writeln!(out, "\t\t\tt.Fatalf(\"json unmarshal partial want: %v\", err)");
let _ = writeln!(out, "\t\t}}");
let _ = writeln!(
out,
"\t\tif !reflect.DeepEqual(_partialGot[{escaped_key}], _wantVal) {{"
);
let _ = writeln!(
out,
"\t\t\tt.Fatalf(\"partial body field {key}: got %v want %v\", _partialGot[{escaped_key}], _wantVal)"
);
let _ = writeln!(out, "\t\t}}");
let _ = writeln!(out, "\t}}");
}
}
}
fn render_assert_validation_errors(
&self,
out: &mut String,
_response_var: &str,
errors: &[crate::e2e::fixture::ValidationErrorExpectation],
) {
let _ = writeln!(out, "\tvar _veBody map[string]any");
let _ = writeln!(out, "\tif err := json.Unmarshal(bodyBytes, &_veBody); err != nil {{");
let _ = writeln!(out, "\t\tt.Fatalf(\"json unmarshal validation errors: %v\", err)");
let _ = writeln!(out, "\t}}");
let _ = writeln!(out, "\t_veErrors, _ := _veBody[\"errors\"].([]any)");
for ve in errors {
let escaped_msg = go_string_literal(&ve.msg);
let _ = writeln!(out, "\t{{");
let _ = writeln!(out, "\t\t_found := false");
let _ = writeln!(out, "\t\tfor _, _e := range _veErrors {{");
let _ = writeln!(out, "\t\t\tif _em, ok := _e.(map[string]any); ok {{");
let _ = writeln!(
out,
"\t\t\t\tif _msg, ok := _em[\"msg\"].(string); ok && strings.Contains(_msg, {escaped_msg}) {{"
);
let _ = writeln!(out, "\t\t\t\t\t_found = true");
let _ = writeln!(out, "\t\t\t\t\tbreak");
let _ = writeln!(out, "\t\t\t\t}}");
let _ = writeln!(out, "\t\t\t}}");
let _ = writeln!(out, "\t\t}}");
let _ = writeln!(out, "\t\tif !_found {{");
let _ = writeln!(
out,
"\t\t\tt.Fatalf(\"validation error with msg containing %q not found in errors\", {escaped_msg})"
);
let _ = writeln!(out, "\t\t}}");
let _ = writeln!(out, "\t}}");
}
}
}