# Fixtures, check and preview

Copies of your sources and what they must give, so check runs your plug-in as the Server will, offline. preview draws the days; probe tries real sources.

Fixtures prove to you, the tool and a reviewer that your plug-in works without
the network. They are outside the digest; `check` runs them every time.

```text
fixtures/
  sources/<name>        a real copy of each declared source (tides, or tides.csv)
  records.jsonl         what fetch.mjs prints for them
  context.json          optional: the moment the check runs at
  runs/<case>/          optional: a later run, or a source that must fail
    sources/<name>      the files that differ from fixtures/sources/
    context.json        its own moment
    records.jsonl       what that run must print, or
    expect.json         { "exit": 2, "reason": "…" }: how it must fail
```

Cut a real source down to the days you want rather than invent one; a source
with no fixture fails with `PLG-FIXTURE-SOURCE-MISSING`. What fetch.mjs prints is held
to `records.jsonl` byte for byte. Without `context.json`, `check` runs now, and
may print differently next week; with it, `today` must be `now`'s date in your zone,
which catches the commonest time-zone mistake:

```json file=harbour-almanac/fixtures/runs/wrong-day/context.json
{ "now": "2026-09-30T10:00:00.000Z", "today": "2026-10-01" }
```

```sh run exit=1
node kanboombo-plugin.mjs check harbour-almanac
```

```text output
Kanboombo plug-in checks: harbour-almanac
✖ PLG-FIXTURE-CONTEXT-INVALID  fixtures/runs/wrong-day: today 2026-10-01 is not 2026-09-30T10:00:00.000Z's date in Pacific/Auckland (2026-09-30)
  Fix: Write { "now": "2026-10-01T13:30:00Z", "today": "2026-10-01" }, where today is now's date in your time zone.
  https://kanboombo.com/developers/plugins/publish/submission-errors/#plg-fixture-context-invalid
```

```sh run
rm -r harbour-almanac/fixtures/runs/wrong-day
```

A case with `records.jsonl` is a later day, run with `backfill` false. A case
with `expect.json` is a source that must fail, the most useful kind, and the
portal asks for at least one: when your source changes shape, your fetcher must
stop with a reason, not print nothing or the wrong column. `exit` is the code it
must use (any non-zero if left out); `reason`, words its last stderr line must
contain. Add one for each way your source has broken, or will:

```json file=harbour-almanac/fixtures/runs/unknown-status/sources/council.json
{ "asOf": "2026-10-01", "meetings": [{ "id": "meeting-2026-10-1", "date": "2026-10-01", "status": "postponed" }] }
```

```json file=harbour-almanac/fixtures/runs/unknown-status/expect.json
{ "exit": 2, "reason": "a status this plug-in does not know" }
```

## check {#check}

```sh run
node kanboombo-plugin.mjs check harbour-almanac
```

```text output
✔ harbour-almanac: 350 records, 1 later run, 2 failure cases, contract 1
  digest …
```

The new case passes: its fetcher stops. Had it printed records, or stopped
for another reason, it would fail with `PLG-RUN-EXPECTED-FAILURE`. `check`
reads `plugin.json` against contract 1, the folder (named after the id, with a
`fetch.mjs`) and its imports; finds a fixture per source; runs `fetch.mjs` on
them; checks every record; compares with `records.jsonl`; runs each
`runs/<case>`; checks your listing if you have one; and prints the digest. It
stops at the first failing step with every problem so far (code, fix, link) and
exits 0 or 1: put it in your CI as it is.

On your machine the fetcher runs as you, behind a guard: any way Node reaches
the network throws (`PLG-RUN-NETWORK`), Node's permission model limits reads to
the folder and its sources (`PLG-RUN-READ-OUTSIDE`), and your limits kill it
(`PLG-RUN-KILLED`). That is a seat belt for your own code, not a sandbox for
anyone else's: other people's code runs only in the Server's
[locked room](/developers/plugins/concepts/how-plugins-run/#locked-room).

## preview {#preview}

`preview <id>` draws each day with `pluginDayRows`, the function the app's day
uses, so a row reads as it will for a reader
([quickstart](/developers/plugins/quickstart/#5-see-your-days),
[How records look](/developers/plugins/concepts/how-records-look/)).
`--days A..B` shows up to 62 days (three either side of today by default),
`--language es` your Spanish names (dates and figures stay English, as in the
app), `--run <case>` a `runs/<case>`,
and `--html <file>` writes a page. One run cannot show `Moved to …` or a
revision: those come from what the Server stored before.

## probe {#probe}

```sh norun="it downloads from the network; check never does"
node kanboombo-plugin.mjs probe harbour-almanac
```

`probe` downloads each source once (https, your hosts only, within
`maxBytes`), runs the fetcher and checks every record. Use it before you
refresh a fixture; it is never part of a check. This site serves the almanac's
sources, so you can probe it.

## When check fails {#troubleshooting}

Copy all of `check`'s output: its code, fix and link are what an assistant
needs ([every code](/developers/plugins/publish/submission-errors/)). The ones
everyone meets once:

| Code | Nearly always | Do |
|---|---|---|
| `PLG-RECORD-AFTER-TODAY` | A date from `toISOString()`: in the evening west of UTC, it is already tomorrow | Compare every date with the context's `today` and skip what is later |
| `PLG-RUN-NETWORK` | A `fetch()` in the fetcher | Declare the file as a source and read the path the context gives |
| `PLG-IMPORT-PACKAGE`, `PLG-IMPORT-OUTSIDE` | An npm package, or `../shared/` | Copy the code into the folder as readable `.mjs`, as Harbour Almanac does with [its CSV reader](/developers/plugins/build/fetcher/#reading-files) |
| `PLG-RUN-FAILED` | An exit you did not mean | Read the last line it wrote to stderr |
| `PLG-MANIFEST-UNKNOWN-KEY` | A misspelt key | Fix the key the message names |
