Wanderland

Wanderland Core: --type test

Third adapter alongside http and cli. Walks the scenarios: block in config, synthesises a request per scenario through normal dispatch with X-Wanderland-Verify set, and interprets the returned crossing. Exits 0 when every matched scenario passes, non-zero otherwise.

Same binary, same config, same chain. The only thing test mode adds on top of the verify_route probe is iteration and an exit code — the shape comparison is still the verify_route boundary doing its one job.

Invocation

wanderland --type test config.yml
wanderland --type test config.yml --route greet
wanderland --type test config.yml --scenario alice
wanderland --type test config.yml --route greet --scenario alice

Arguments:

Filters combine with AND semantics. An unknown filter reports the configured routes with scenarios and exits non-zero.

Scenario shape for test mode

Live probes via X-Wanderland-Verify only need expected: — the actual comes from the real request. Test mode synthesises the request, so scenarios need both input: and expected::

scenarios:
  greet:
    alice:
      description: "Canonical greeting format"
      input:
        params:
          name: alice
      expected:
        greeting: "Hello, alice!"
    default_output:
      description: "Empty name defaults to world"
      input:
        params:
          name:
      expected:
        greeting: "Hello, world!"
    polite_tone:
      description: "Greeting always starts with Hello"
      input:
        params:
          name: bob
      expected:
        greeting:
          matches: "^Hello, "

input.params is the merged params hash — path captures, query, and body folded into one. Path captures expand through the route pattern when the runner resolves the synthetic path: /greet/:name with params.name: alice becomes /greet/alice in the dispatched request.

Optional input.headers attaches custom request headers; the runner adds X-Wanderland-Verify: <scenario-name> on top so the verify_route boundary fires.

A scenario missing input: runs with empty params. Whatever the route's default behaviour is, that's the "actual" the expected shape matches against.

Output

  greet
    ✓ alice  — Canonical greeting format
    ✓ default_output  — Empty name defaults to world
    ✓ polite_tone  — Greeting always starts with Hello

  3 scenarios, 3 passed, 0 failed

Failures list the ShapeMatcher output verbatim:

  greet
    ✓ alice  — Canonical greeting format
    ✗ polite_tone  — Greeting always starts with Hello
        greeting: expected to match /^Hello, /, got "Howdy alice!"

  2 scenarios, 1 passed, 1 failed

How it works under the hood

for each scenario in config.scenarios.<route>.<name>:
    compiled = runtime.engine.named_routes[route]
    path     = compiled.pattern.expand(params)
    headers  = input.headers.merge("X-Wanderland-Verify" => name)
    crossing = Dispatch.invoke(runtime, compiled,
                               params:, headers:, path:, adapter: "http")

    if type_addr is :signals:stop:*
        fail with result.scenarios[].failures
    else
        pass

The dispatch call exercises the whole chain — adapter announcement, denial enforcement, every user slot, verify_route, trace_emit, format, seal. The test runner watches the final crossing's type_addr: a blocking address means verify_route halted the chain because the shape didn't match. A non-blocking address means everything ran through cleanly.

Because the synthetic request carries a real verify header, the existing verify_route boundary does the comparison. No duplication of the match logic in the test runner.

The three lenses

One scenarios: block, three ways to run it:

Lens Trigger Input source Exit signal
--type test wanderland --type test config.yml scenario.input (synthetic) exit code
HTTP probe X-Wanderland-Verify: <name> real request 422 on mismatch
RSpec Wanderland::Scenario.rspec_describe!(runtime) scenario.input (synthetic) expect(failures).to be_empty

Dev exercises scenarios under rspec. CI runs --type test against the same config. Staging and prod attach probes via the verify header for synthetic monitoring. The scenarios don't care which lens is reading them.

Exit codes

Relationship to other tools

Source

Site Audit

wanderland.dev

oculus-view: fence: fence execute HTTP 404