APIZDocs

APIZ guide

Writing Policy Fixtures

A Policy Fixture describes one synthetic request and the Policy result you expect. It is credential-free, does not contact an upstream service, and is published atomically with the Policy definition.

Every normal Fixture has three primary sections:

metadata     which Fixture this is and which Adapter normalizes it
request      the synthetic request presented to that Adapter
expectation  the Policy result that must be observed

You do not write a normalized action or normalized resource. APIZ derives them through the selected Adapter before running the Policy.

Discover complete credential-free starters and the selected Adapter vocabulary from the installed CLI:

apiz policy example list
apiz policy example show <example-id> --output json
apiz adapter explain <adapter-id>
apiz policy draft save --help

To test a saved server candidate rather than local source or the current Published Revision, compile it and name its exact Draft revision:

apiz policy draft compile <policy-id> --expected-draft-revision <draft-revision>
apiz policy draft test <policy-id> \
  --expected-draft-revision <draft-revision> \
  --fixture fixtures/deny-delete.yaml --explain

The result identifies target=working_draft, the Draft revision, content hash, compiled artifact, and test run. It does not contact upstream services.

Minimal YAML Fixture

metadata:
  key: deny-delete
  name: Deny delete requests
  enabled: true
  adapter_id: general

request:
  method: DELETE
  path: /items/1

expectation:
  decision: deny
  reason: delete_not_allowed

Store it under the project, for example fixtures/deny-delete.yaml, and reference it from apiz-policy.json:

{
  "fixtures": [
    {"file": "fixtures/deny-delete.yaml"}
  ]
}

The project manifest still contains the other required Policy Project fields; the abbreviated example above shows only the Fixture reference.

Equivalent JSON Fixture

{
  "metadata": {
    "key": "allow-read",
    "name": "Allow a read request",
    "enabled": true,
    "adapter_id": "general"
  },
  "request": {
    "method": "GET",
    "path": "/items/1",
    "query": {"include": ["summary"]},
    "headers": {"accept": ["application/json"]}
  },
  "expectation": {
    "decision": "allow",
    "reason": "read_allowed"
  }
}

JSON, YAML, and Studio Form mode are representations of the same Fixture contract. Studio may normalize formatting when a source Fixture is edited in Form mode. Resolve syntax errors before switching from source to Form.

Metadata

FieldRequiredMeaning
keyyesStable lowercase id used by CLI, Studio, results, and persisted readiness evidence.
nameyesHuman-readable case name.
enabledyesWhether Run all includes this Fixture. A disabled Fixture can still be opened and run explicitly.
adapter_idconditionalAdapter that normalizes the request. It may be omitted when inherited from a non-empty Policy adapter_scope.

If both Fixture adapter_id and Policy adapter_scope are present, they must match. An adapter-neutral Policy must select an Adapter per Fixture so APIZ knows how to normalize the request.

Request

request.method and request.path are required. The bounded request may also contain:

  • query: a key to string-list map;
  • headers: a sanitized key to string-list map;
  • content_type;
  • body.encoding: json, text, raw, base64, file, form_urlencoded, or multipart; and
  • body.value: the bounded synthetic value.

Policy Studio exposes these fields as a complete credential-free HTTP request builder. Headers and query parameters accept arbitrary keys and repeated values. Body modes cover JSON, text, raw bytes, one embedded file, URL-encoded form fields, and multipart fields/files. Selecting a local file embeds its bounded base64 content in the Fixture; running a Fixture never reads from the original workstation path.

Example JSON-RPC request:

metadata:
  key: deny-unsupported-rpc
  name: Deny an unsupported JSON-RPC method
  enabled: true
  adapter_id: ethereum

request:
  method: POST
  path: /
  content_type: application/json
  body:
    encoding: json
    value:
      jsonrpc: "2.0"
      id: 1
      method: eth_chainId
      params: []

expectation:
  decision: deny
  reason: unsupported_ethereum_rpc_method

Never put Authorization headers, cookies, Client Access Credentials, upstream API keys, ciphertext, or copied production requests in a Fixture. Use synthetic identifiers and payloads.

URL-encoded form example:

request:
  method: POST
  path: /search
  content_type: application/x-www-form-urlencoded
  body:
    encoding: form_urlencoded
    value:
      tag: [one, two]
      name: [demo]

Multipart form example:

request:
  method: POST
  path: /documents
  body:
    encoding: multipart
    value:
      fields:
        purpose: [policy-test]
      files:
        - field: document
          name: example.txt
          content_type: text/plain
          data_base64: aGVsbG8=

Adapter Normalization

On live traffic, an Adapter converts protocol details into stable Policy context. Fixture execution uses the same boundary:

DELETE /items/1
  -> General Adapter
  -> action: http:delete
  -> resource: {type: general_path, path: /items/1}

eth_sendRawTransaction request body
  -> Ethereum Adapter
  -> action: ethereum:rpc:call
  -> resource: {type: ethereum_rpc}

The derived action is available to Scripted Policies as ctx.action and to Structured Policies through descriptor-driven action matching. It is shown in sanitized test trace output for explanation, but it is not an author input.

This prevents contradictory Fixtures such as method: GET combined with a manually entered action: http:delete.

Expectations

expectation.decision is required. Supported decisions are allow, deny, reply, patch, and rate_limit according to the Policy contract. Add only the assertions relevant to the behavior being tested.

Common assertions include:

expectation:
  decision: deny
  reason: delete_not_allowed
  error_category: policy_denied

Patch behavior can assert sanitized request changes:

expectation:
  decision: patch
  reason: route_rewritten
  request:
    path: /v2/items/1
    headers:
      x-policy-mode: [rewritten]
    body:
      encoding: json
      value:
        accepted: true

Reply behavior can assert the concrete APIZ-owned response:

expectation:
  decision: reply
  reason: synthetic_response
  reply:
    status: 202
    headers:
      content-type: [application/json]
    body:
      encoding: json
      value:
        accepted: true

For expected patched requests and replies, only configured fields are checked. Header names are case-insensitive; each configured header must have exactly the listed values. JSON bodies are compared structurally, while text and base64 bodies are compared byte-for-byte.

Studio preserves the last request and reply expectation objects when you switch the Expected Decision so comparison work is not discarded. APIZ only evaluates expectation.request when the selected decision is patch, and only evaluates expectation.reply when it is reply; the inactive object is an editable draft, not an additional assertion.

Runtime-failure tests can assert stable errors without depending on raw stack text:

expectation:
  decision: deny
  reason: policy_definition_error
  error_category: script_runtime_error
  error_reason: policy_script_exception

Test results show expected versus actual values, trace, request patch, source location when available, exact content/build identity, and a redaction summary.

Advanced Context Overrides

Most Fixtures do not need context overrides. Use them only when a Policy explicitly reads allowlisted context that cannot be derived from the request:

context_overrides:
  client_instance:
    tags:
      environment: production
  api_instance:
    tags:
      owner: payments

Context overrides cannot set Adapter identity, normalized action, normalized resource, credentials, authenticated identity, persisted Policy evidence, or other server-owned security fields. Tests that need malformed internal post-normalization context belong in adapter/runtime conformance tests rather than a user Policy Project.

Running Fixtures

CLI

For a Scripted project, when the separate apiz-policy TypeScript builder is installed and on PATH:

apiz policy test --project .
apiz policy test --project . --case deny-delete --explain

The current CLI does not implement policy validate and does not run a Structured project locally with policy test --project. For either Policy type, publish through the authoritative server and run the saved published suite:

apiz -o json policy publish --project . > policy-review.json
POLICY_ID=$(jq -r '.candidate.policy_id' policy-review.json)
DRAFT_REVISION=$(jq -r '.candidate.draft_revision' policy-review.json)
POLICY_REVISION=$(jq -r '.candidate.current_published_revision' policy-review.json)
apiz policy publish "$POLICY_ID" --project . \
  --expected-draft-revision "$DRAFT_REVISION" \
  --expected-policy-revision "$POLICY_REVISION" --confirm
apiz policy test-suite "$POLICY_ID"

Web Console

In Policy Studio:

  • select a Fixture in the project Explorer and choose Run Fixture; or
  • select policy.ts, open Run, and choose one Fixture or Run all.

APIZ saves and compiles the exact Draft when needed, runs without Secret Broker or upstream access, and opens the sanitized result in the docked test panel.

For a Policy that affects authorization or request mutation, include applicable cases for:

  • expected allow behavior;
  • expected deny behavior;
  • malformed request input;
  • resource or action outside the intended boundary;
  • runtime error or timeout behavior for Scripted Policies;
  • request patch or APIZ-owned reply behavior; and
  • any advanced Client/API tag branch used by the Policy.

Use small Fixtures with one clear purpose. A Fixture should make it obvious which request is being exercised and why the expectation should pass.