#!/usr/bin/env elixir
{#- Elixir app harness for server-pattern e2e tests
This harness script is spawned as a subprocess by test_helper.exs and runs the
SUT app, registering handlers per fixture. It loads all fixtures, creates
handlers that return expected responses, and serves on a configured port.
Context variables (passed from Elixir codegen):
- app_class: class name for SUT app (e.g., "App")
- register_route_method: app method to register routes (e.g., "route")
- route_builder_class: class name for route builder (e.g., "MyPkg.RouteBuilder")
- route_builder_schema_setter: method to set request schema (e.g., "request_schema_json")
- method_enum_class: class name for method enum (e.g., "MyPkg.Method")
- run_method: serve entrypoint (e.g., "run")
- host: binding host (e.g., "127.0.0.1")
- port: binding port (e.g., 8000)
- binding_path: path to binding package
- fixtures_json: raw JSON string with all fixtures (auto-serialized)
#}
# When run as a standalone script via elixir (not via mix), load dependencies
# from the calling process's ebin directories (passed via -pa flags).
# The dependencies are already compiled and accessible; this script just needs
# to ensure they're in the code path. The Application environment is not needed.
# Ensure the binding modules are loaded from code path
Code.ensure_loaded!({{ app_class }})
Code.ensure_loaded!({{ route_builder_class }})
Code.ensure_loaded!({{ method_enum_class }})
{% if not skip_app_config -%}
Code.ensure_loaded!({{ server_config_class }})
{% endif %}
# Load fixtures from the JSON payload
fixtures_json = {{ fixtures_json }}
{:ok, fixtures} = Jason.decode(fixtures_json)
# Alias binding module names for readability
# The binding module name is derived from imports
alias {{ app_class }}
alias {{ route_builder_class }}
alias {{ method_enum_class }}
{% if not skip_app_config -%}
alias {{ server_config_class }}
{% endif %}
# Create and configure the app
app = {{ app_class }}.new()
# Register a handler for each fixture
app = Enum.reduce(fixtures, app, fn {fixture_id, fixture}, app_acc ->
http = Map.get(fixture, "http")
if is_nil(http) do
app_acc
else
handler = Map.get(http, "handler", %{})
method_str = Map.get(handler, "method", "GET") |> String.upcase()
body_schema = Map.get(handler, "body_schema")
expected = Map.get(http, "expected_response", %{})
# Build handler function that returns the expected response
expected_status = Map.get(expected, "status_code", 200)
expected_body = Map.get(expected, "body")
expected_headers = Map.get(expected, "headers") || %{}
# Create handler closure that captures the expected values
handler_fn = fn _request ->
{
expected_status,
expected_body,
expected_headers
}
end
# Register the handler at /fixtures/<fixture_id>
# Note: the test sends requests to /fixtures/{fixture_id};
# the handler's route field is the intended target route the fixture simulates,
# not the path to register on the SUT.
full_route = "/fixtures/#{fixture_id}"
# Build the RouteBuilder with the method and path
method_atom = method_str |> String.downcase() |> String.to_atom()
method_enum_val = apply({{ method_enum_class }}, method_atom, [])
builder = {{ route_builder_class }}.new(method_enum_val, full_route)
# If there's a body schema, attach it to the builder
builder =
if is_nil(body_schema) do
builder
else
body_schema_json = Jason.encode!(body_schema)
{{ route_builder_class }}.{{ route_builder_schema_setter }}(builder, body_schema_json)
end
# Register the route with the handler
{{ app_class }}.{{ register_route_method }}(app_acc, builder, handler_fn)
end
end)
{% if not skip_app_config %}
# Configure and start the server
config = struct({{ server_config_class }}, %{
host: "{{ host }}",
port: {{ port }}
})
app = {{ app_class }}.config(app, config)
{%- endif %}
IO.puts("Harness listening on {{ host }}:{{ port }}")
# Run the app
{{ app_class }}.{{ run_method }}(app)