{#- Go app harness for server-pattern e2e tests
This harness program is spawned as a subprocess by main_test.go 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 Go codegen):
- imports: list of import paths to include (typically the package being tested)
- app_class: class name for SUT app (e.g., "App")
- route_builder_class: class name for route builder (e.g., "RouteBuilder")
- method_enum_import: import path for Method (e.g., "my_app")
- method_enum_class: class name for method enum (e.g., "GET", "POST")
- register_route_method: app method to register routes (e.g., "RegisterRoute")
- run_method: serve entrypoint (e.g., "Run")
- port: binding port (e.g., 8012)
- fixtures_json: raw JSON string with all fixtures (auto-serialized)
#}
package main
import (
"encoding/json"
"fmt"
"log"
"os"
"os/signal"
"syscall"
{%- for import_name in imports %}
{{ import_alias }} "{{ import_name }}"
{%- endfor %}
)
// Fixture describes an HTTP test fixture with its handler route and expected response.
type Fixture struct {
HTTP *HTTPFixture `json:"http"`
}
// HTTPFixture contains handler and expected response details.
type HTTPFixture struct {
Handler HandlerDetails `json:"handler"`
ExpectedResponse ResponseDetails `json:"expected_response"`
}
// HandlerDetails specifies the route and HTTP method for the handler.
type HandlerDetails struct {
Route string `json:"route"`
Method string `json:"method"`
BodySchema interface{} `json:"body_schema"`
}
// ResponseDetails defines the expected status, body, and headers.
type ResponseDetails struct {
StatusCode uint16 `json:"status_code"`
Body interface{} `json:"body"`
Headers map[string]string `json:"headers"`
}
const port = {{ port }}
const host = "127.0.0.1"
// Load fixtures from the JSON payload embedded at codegen time.
// Use a quoted string with escaped newlines to avoid backtick conflicts.
var fixturesJSON = "{{ fixtures_json }}"
func main() {
var fixtures map[string]Fixture
if err := json.Unmarshal([]byte(fixturesJSON), &fixtures); err != nil {
log.Fatalf("unmarshal fixtures: %v", err)
}
app, err := {{ import_alias }}.NewApp()
if err != nil {
log.Fatalf("new app: %v", err)
}
// Register a handler for each fixture.
for fixtureID, fixture := range fixtures {
if fixture.HTTP == nil {
continue
}
http := fixture.HTTP
route := http.Handler.Route
method := http.Handler.Method
bodySchema := http.Handler.BodySchema
expectedStatus := http.ExpectedResponse.StatusCode
expectedBody := http.ExpectedResponse.Body
expectedHeaders := http.ExpectedResponse.Headers
// Build a closure that captures the expected response for this fixture.
// The handler ignores request parameters and returns the recorded response.
handler := makeHandler(expectedStatus, expectedBody, expectedHeaders)
// Register the route with the full fixture-namespaced path: /fixtures/<fixture_id>{route}
fullRoute := fmt.Sprintf("/fixtures/%s%s", fixtureID, route)
// Convert method string (GET, POST, etc.) to the framework's method enum.
builder, err := {{ import_alias }}.RouteBuilderNew({{ import_alias }}.Method(method), fullRoute)
if err != nil {
log.Fatalf("create route builder: %v", err)
}
// If there's a body schema, attach it to the builder.
if bodySchema != nil {
bodySchemaJSON, _ := json.Marshal(bodySchema)
builder = builder.RequestSchemaJSON(json.RawMessage(bodySchemaJSON))
}
// Register the route with the handler.
if err := app.{{ register_route_method }}(handler, *builder); err != nil {
log.Fatalf("register route %s: %v", fullRoute, err)
}
builder.Free()
}
// Signal readiness to the parent test process.
fmt.Printf("Harness listening on %s:%d\n", host, port)
os.Stdout.Sync()
// Graceful shutdown on interrupt.
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
go func() {
<-sigChan
os.Exit(0)
}()
// Run the server. This is blocking — the harness stays alive until interrupted.
if err := app.{{ run_method }}(); err != nil {
log.Fatalf("run: %v", err)
}
}
// makeHandler returns a handler function that always returns the expected response.
func makeHandler(statusCode uint16, body interface{}, headers map[string]string) func([]byte) ([]byte, error) {
return func(reqBody []byte) ([]byte, error) {
// Wrap the response in the framework's Response struct.
resp := map[string]interface{}{
"status_code": statusCode,
"content": body,
"headers": headers,
}
respBody, _ := json.Marshal(resp)
return respBody, nil
}
}