> ## Documentation Index
> Fetch the complete documentation index at: https://docs.vetrasuite.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Testing your adapter

> Run the contract from the console, then the checks no runner can do for you.

Test against a domain that **has something in it**. On an empty server almost nothing you
declare is exercised, and a run where nothing failed is not a run where anything passed.

## Run the contract

```text theme={null}
vetra_blueprints_adapter test acme.turrets         the read-only contract
vetra_blueprints_adapter test all                  the same, for every adapter
vetra_blueprints_adapter test acme.turrets full    the whole contract
```

One runner, for every adapter, built entirely on the public contract. You write no test
harness and ship no test hooks. It works out which checks apply from what you declare, and it
ships in the build customers install.

### Two modes, and what each one touches

|                                    | read-only (the default)         | `full`                                             |
| ---------------------------------- | ------------------------------- | -------------------------------------------------- |
| Calls                              | `Collect`, `PrepareMaterialize` | every capability function you declared             |
| Creates, moves, conforms, removes  | nothing                         | one fixture of its own, through your `Materialize` |
| Writes to your addon's storage     | no                              | yes, through your own functions                    |
| Who may run it                     | the server console, or an admin | the server console only                            |
| How many adapters                  | one, or `all`                   | one                                                |
| While a restore or deployment runs | refused                         | refused                                            |

**Read-only** is no more invasive than Blueprints' own use of your adapter. It calls the
functions Blueprints already calls to capture a Version or preview a migration, which are the
functions the contract forbids from writing.

**Full** writes. No check ever targets an object that was already on the server: every
destructive check works on a fixture the runner built seconds earlier through your own
`Materialize`, and the run ends by releasing it. But what your adapter does to reach that
fixture is real. An adapter backed by a database writes to it, and one whose storage is not
object-scoped may touch neighbouring records to act on one: Vetra's own DarkRP adapter
releases a spawn by rewriting that job's spawn set, ids preserved.

<Warning>
  Run `full` on a development server. That is why it is refused to players, admins included.
</Warning>

### Reading the report

```text theme={null}
VETRA BLUEPRINTS: ADAPTER CONTRACT (READ-ONLY)

  adapter   acme.turrets  (Acme Turrets)
  version   1   data format 1
  identity  persistent
  declares  materialize properties remove snapshot transform

  [PASS   ] every declared capability is callable
  [PASS   ] Collect returns valid normalized records
  [PASS   ] every record has a unique id within the capture
  [PASS   ] no record holds an engine handle or an unserializable value
  [PASS   ] every record's identity is at least as strong as the adapter declared (persistent)
  [PASS   ] two consecutive captures name the same objects
  [PASS   ] record content is stable between captures
  [PASS   ] PrepareMaterialize accepts a record the adapter itself produced
  [PASS   ]   and declares an orientation this build understands
  [PASS   ]   and its preview descriptor is well formed
  [PASS   ]   preview kind: point
  [PASS   ] PrepareMaterialize wrote nothing
  [PASS   ] PrepareMaterialize did not write to the record it was given
  [NOT RUN] Materialize creates exactly one object
            read-only run: the runner creates, moves, conforms and removes nothing
  [NOT RUN] transform contract
            read-only run: the runner creates, moves, conforms and removes nothing
  [NOT RUN] properties contract
            read-only run: the runner creates, moves, conforms and removes nothing
  [NOT RUN] remove contract
            read-only run: the runner creates, moves, conforms and removes nothing

  13 passed, 0 failed, 4 not run

  ADAPTER CONTRACT (READ-ONLY): PASS
```

**NOT RUN is never a pass.** Every skipped check carries its reason, and a read-only run says
so in its verdict. A full run against an empty domain prints `DECLARED BUT NOT EXERCISED` for
every capability it could not reach, because with no record to copy there is no fixture to
work on.

The full run adds the fixture checks: that `Materialize` builds exactly one object with a
**new** identity, that a move and your properties read back, that `ReleaseMaterialized` and
`Remove` leave the world as they found it, and that no test artifact is left behind.

## The order to test in

<Steps>
  <Step title="Registration">
    ```text theme={null}
    vetra_blueprints_adapter show acme.turrets
    ```

    Your adapter is listed, available, with the capabilities you declared. If it is not
    listed at all, it did not register: the server console logs every refusal and names what
    was wrong.

    Then deliberately break your dependency (rename the addon folder, stop DarkRP, whatever
    applies) and confirm the status flips to unavailable **with your sentence**, and that
    Blueprints still starts normally.
  </Step>

  <Step title="The read-only contract">
    `vetra_blueprints_adapter test acme.turrets`, from any console. It checks your records,
    your identity floor and your determinism, and writes nothing.
  </Step>

  <Step title="The full contract, on a development server">
    `vetra_blueprints_adapter test acme.turrets full`, from the server console. Every
    capability you declared is exercised on a fixture of its own.
  </Step>

  <Step title="The real diff">
    Change one object: move it, or change one property. Capture, and compare against the
    earlier Version.

    You want **one changed row**. If you get one removed plus one added, your identity is
    derived from something the diff compares, and it will destroy builds. Stop and read
    [Identity](/sdk/identity).
  </Step>

  <Step title="Restart, if you declared persistent">
    <Warning>
      The runner cannot restart the server. This is the check that matters most, and it is yours.
    </Warning>

    Restart the **server process**, not a Lua refresh. Capture again and compare against the
    Version from before the restart.

    An adapter that declares `persistent` must produce an empty diff. If objects come back with
    different ids, you are `session`, not `persistent`, and declaring otherwise turns an
    honest "we cannot match these" into a silent wrong match.
  </Step>

  <Step title="The real workflows">
    The contract proves the terms; the product proves the experience. Migrate a group
    containing your objects and check the ghost preview lands where you expect. Restore an
    earlier Version and confirm your objects and their properties come back. Force a partial
    failure (one member that cannot be built) and confirm nothing is left behind.
  </Step>

  <Step title="Missing adapter">
    Uninstall your addon, restart, and open a Version that contains your records.

    Your domain must be reported as `adapter_missing` and **your records must still be there**.
    Export that Version, reinstall your addon, and import it back: your records survive the
    round trip. `vetra_blueprints_adapter compat acme.turrets` shows the same from the console.
  </Step>

  <Step title="Persistence, if applicable">
    If your domain is backed by a saved record rather than by a live entity, run the full
    contract and the real workflows, then **restart the server** and capture again.

    The fixture must still be gone and every change must still be there. If something
    reverted, you wrote the entity and not the record. See
    [Persistence integrations](/sdk/persistence).
  </Step>
</Steps>

## The checks a first adapter usually fails

The runner reports each of these by name. They are contract terms rather than bugs.

<AccordionGroup>
  <Accordion title="ApplyTransform does not refuse an unknown id properly" icon="x">
    It must return `false` plus a non-empty reason. Returning `nil`, or `true`, is a failure.
  </Accordion>

  <Accordion title="Materialize does not return an identity table" icon="x">
    `{ ok = true, localId = ... }` is not enough. `identity` is required, with `source` and
    `scope` from the declared vocabularies.
  </Accordion>

  <Accordion title="A phase-1 refusal carries no code or reason" icon="x">
    A bare `{ ok = false }` is a contract failure, not a refusal. Both `code` and a non-empty
    `reason` are required.
  </Accordion>

  <Accordion title="ReleaseMaterialized does not restore the object count" icon="x">
    An adapter that creates one thing and releases something else leaves the world different
    from how it found it.
  </Accordion>

  <Accordion title="A Prepare function writes" icon="x">
    Phase 1 answers. It does not write, and it does not throw.
  </Accordion>

  <Accordion title="Two consecutive captures disagree" icon="x">
    A timestamp, an uptime, a recomputed float, or an id that depends on iteration order.
  </Accordion>

  <Accordion title="The adapter reaches for source.localId" icon="x">
    It is `source.id`, namespaced. Your `label` is `source.metadata.label`. This only bites on
    a path that runs during a real migration or restore, which is why it survives casual
    testing. See [Records](/sdk/records#what-you-get-back-is-a-different-shape).
  </Accordion>
</AccordionGroup>

## What no amount of testing will tell you

* **That your domain is complete.** Nothing can know whether `Collect` returned every turret.
  Only you can.
* **That your identity is really persistent**, unless you actually restart the process. A Lua
  refresh is not a restart.
* **Anything at all, on an empty domain.** The runner says `DECLARED BUT NOT EXERCISED`.
  Create a few objects first.

<Note>
  Do not ship test hooks, debug convars or fault-injection seams in your adapter. Blueprints
  does not need them, and a runtime conditional that weakens a contract is a bypass somebody
  will find.
</Note>
