# OMD Conformance Tests

These tests check an Open Meet Display receiver or producer against the
[specification](/omd/spec). A receiver runs groups A, B, and C; a producer runs group D. Each test
has a check and an observable pass condition, so the list doubles as a test plan. A test marked
**must** is required for conformance. One marked _should_ is a strong recommendation, and one
marked _may_ is optional: if you do it, do it this way. A test's id never changes, and an id the
list skips is never reused.

Tools for running them:

- The [message schema](/omd/schemas/meet-display-v1.schema.json) and the
  [probe reply schema](/omd/schemas/probe-reply-v1.schema.json) check shapes with any JSON Schema
  2020-12 validator. They allow fields and kinds they do not define, as receivers must, so a
  misspelled key passes them: check your field names against the [field reference](/omd/reference).
- [`omd-check.mjs`](/omd/tools/omd-check.mjs) checks a message, or a probe reply with `--probe`,
  and names the test each problem fails.
- Each test that needs a crafted message names a **fixture**. The
  [fixture index](/omd/fixtures/index.json) lists them all with what a conforming receiver answers:
  `accept` (`200` or `204`, and shown), `ignore` (`200` or `204`, and the board unchanged, which
  only you can see), `refuse-400`, `refuse-422` (with `accepts`), or `accept-or-422` (either is
  conforming). The [test page](/omd/test) sends them to your receiver from your browser, as a
  browser-based producer does.

You can run every test without us. If a test is ambiguous, that is a bug in the format: write to
[jason@2timer.app](mailto:jason@2timer.app?subject=OMD%20conformance).

---

## A. Messages

What a receiver does with the messages it is sent.

**OMD-C1 — Every example is accepted** (must)

- **Check:** send each [example message](/omd/examples) to your receiver.
- **Pass:** each is answered `200` or `204`. Each kind you show appears on the board as the message
  describes it; each kind you do not show changes nothing.

**OMD-C2 — A message replaces what its target shows** (must)

- **Check:** send [`results-track-heat.json`](/omd/examples/results-track-heat.json), then
  [`c2-fewer-rows.omd.json`](/omd/fixtures/c2-fewer-rows.omd.json), the same heat with its last
  row gone.
- **Pass:** the board shows only the rows of the second message. The row that disappeared is gone
  from the board; rows are never merged across messages.

**OMD-C3 — A kind you do not show is accepted and changes nothing** (must)

- **Check:** send [`c3-unknown-kind.omd.json`](/omd/fixtures/c3-unknown-kind.omd.json), whose
  `omd.kind` is `schedule`.
- **Pass:** `200` or `204`, and the board keeps what it showed. Refusing it would show the operator
  an error on every send.

**OMD-C4 — A mode other than `replace` is accepted and changes nothing** (must)

- **Check:** send [`c4-unknown-mode.omd.json`](/omd/fixtures/c4-unknown-mode.omd.json), whose
  `display.mode` is `append`.
- **Pass:** `200` or `204`, and the board keeps what it showed.

**OMD-C5 — Fields you do not know are ignored** (must)

- **Check:** send [`c5-unknown-fields.omd.json`](/omd/fixtures/c5-unknown-fields.omd.json), which
  adds fields at every level, including one prefixed with a product's name.
- **Pass:** `200` or `204`, and the board shows the results exactly as it shows
  [`results-track-heat.json`](/omd/examples/results-track-heat.json).

**OMD-C6 — A `null` field is treated as missing** (must)

- **Check:** send [`c6-nulls.omd.json`](/omd/fixtures/c6-nulls.omd.json), which sends `null` for
  optional fields a producer would leave out.
- **Pass:** `200` or `204`, and the board shows what it shows when those fields are missing: no
  `null`, `None`, or blank label printed.

**OMD-C7 — Unknown values in open lists are shown or ignored** (must)

- **Check:** send [`c7-unknown-values.omd.json`](/omd/fixtures/c7-unknown-values.omd.json): a sport,
  gender, entry type, status, and scoring role this version does not define.
- **Pass:** `200` or `204`. Each unknown value is shown as text or ignored, and the row with the
  unknown status is shown without a place of its own. See
  [Open and closed lists](/omd/spec#open-and-closed-lists).

**OMD-C8 — Names in any script are shown whole** (must)

- **Check:** send [`c8-utf8-names.omd.json`](/omd/fixtures/c8-utf8-names.omd.json), with names
  in accented Latin, Chinese, Malay, and Arabic script.
- **Pass:** `200` or `204`, and every name is shown as sent, without replacement characters. A
  name too wide for the board may be cut, never garbled.

**OMD-C9 — Rows are shown in the order sent, as written** (must)

- **Check:** send [`results-xc-race.json`](/omd/examples/results-xc-race.json) and
  [`start-list.json`](/omd/examples/start-list.json).
- **Pass:** rows appear in the order of `rows`, and every value (place, mark, wind, points) appears
  as written. The receiver does not sort, re-place, add up, or reformat them.

**OMD-C10 — A later minor version is read** (must)

- **Check:** send [`c10-later-minor.omd.json`](/omd/fixtures/c10-later-minor.omd.json), version
  `1.7` with a field 1.0 does not define.
- **Pass:** `200` or `204`, and the board shows it as the 1.0 message it would otherwise be.

**OMD-C11 — A target you do not know is shown or refused by name** (should)

- **Check:** send [`c11-unknown-target.omd.json`](/omd/fixtures/c11-unknown-target.omd.json),
  whose `display.target` is `no-such-board`.
- **Pass:** either `200` or `204` and the message appears on your default surface, or `422` with
  `errors` naming `/display/target`. Never a refusal that does not say which target.

## B. HTTP POST

How a receiver answers over HTTP. The [test page](/omd/test) runs these from a browser on another
origin, exactly as a browser-based producer does, so passing there proves CORS too.

**OMD-C12 — The preflight is answered** (must)

- **Check:** send `OPTIONS` to your URL with `Origin`, `Access-Control-Request-Method: POST`, and
  `Access-Control-Request-Headers: content-type`.
- **Pass:** `204` (or `200`) with `Access-Control-Allow-Origin`, `Access-Control-Allow-Methods`
  listing `GET`, `POST`, and `OPTIONS`, and `Access-Control-Allow-Headers` listing `Content-Type`.

**OMD-C13 — Every reply carries the CORS header** (must)

- **Check:** from a web page on another origin, send a message you accept, one you refuse
  ([`c16-not-json.txt`](/omd/fixtures/c16-not-json.txt)), and the probe.
- **Pass:** every reply, success or error, carries `Access-Control-Allow-Origin`, so the browser
  shows the producer the status. Without it the operator sees "could not connect" for a message you
  received.

**OMD-C14 — Allowed producers are named, with `Vary: Origin`** (should)

- **Check:** set your receiver to allow `https://manager.2timer.app` only, then send the preflight
  and a message from that origin and from another.
- **Pass:** the allowed origin gets `Access-Control-Allow-Origin: https://manager.2timer.app` and
  `Vary: Origin`; the other gets no `Access-Control-Allow-Origin`. `*` is for testing only. See
  [Security](/omd/spec#security).

**OMD-C15 — Accepted messages are answered within 5 seconds** (must)

- **Check:** time the reply to each example and to
  [`c3-unknown-kind.omd.json`](/omd/fixtures/c3-unknown-kind.omd.json).
- **Pass:** `200` or `204` within 5 seconds, including for a kind you ignore. Draw after you
  answer, not before.

**OMD-C16 — A body that is not JSON gets `400`** (must)

- **Check:** send [`c16-not-json.txt`](/omd/fixtures/c16-not-json.txt) with
  `Content-Type: application/json`.
- **Pass:** `400`, optionally with `{ "errors": [{ "path": "", "message": "…" }] }`, and the board
  keeps what it showed.

**OMD-C17 — A message with no `omd` block gets `422` naming what you read** (must)

- **Check:** send [`c17-no-omd.omd.json`](/omd/fixtures/c17-no-omd.omd.json), a message in the
  shape of a draft of this format: `"format": "meet.display.v1"` and no `omd` block.
- **Pass:** `422` with a body whose `accepts` lists the versions you read, such as `["1.0"]`, and
  the board keeps what it showed.

**OMD-C18 — A major you do not read gets `422` naming what you read** (must)

- **Check:** send [`c18-major-2.omd.json`](/omd/fixtures/c18-major-2.omd.json), version `2.0`.
- **Pass:** `422` with `accepts`, and `errors` naming `/omd/version`. The board keeps what it
  showed: never show half of a message from a version you do not read.

**OMD-C19 — The receiver is reachable where the operator runs it** (must)

- **Check:** run your receiver on another computer on the network, and send from a browser-based
  producer in Chrome at that computer's address.
- **Pass:** the message arrives after the operator allows Chrome's local-network prompt once. The
  receiver listens on the network interface, not only `127.0.0.1`, and its port is open in the
  firewall. Your documentation says the address, port, and path to enter.

## C. Probe

How a receiver answers the probe: a `GET` on the URL it takes messages on, which a producer
sends before its first message.

**OMD-C20 — The probe is answered** (must)

- **Check:** send `GET` to the URL you take messages on, from a web page on another origin.
- **Pass:** `200` with JSON that validates against
  [`probe-reply-v1`](/omd/schemas/probe-reply-v1.schema.json): `accepts` lists `1.0` (or a later
  `1.x`), and `generator` has your `id` and `name`. The reply carries
  `Access-Control-Allow-Origin`. `node omd-check.mjs --probe reply.json` finds nothing.

**OMD-C21 — A probe changes nothing** (must)

- **Check:** with a message on the board, send the probe three times.
- **Pass:** the board keeps what it showed, and the probes are not recorded as messages.

**OMD-C22 — A receiver that is not ready says so** (may)

- **Check:** start your receiver before its board or output is ready, and send the probe.
- **Pass:** `503` with `Retry-After`, and `200` once it is ready. A receiver that is ready as soon
  as it answers never needs `503`.

**OMD-C23 — Listed targets are ones you show** (may)

- **Check:** read `targets` in your probe reply, and send
  [`announcement.json`](/omd/examples/announcement.json) to each, changing `display.target`.
- **Pass:** each listed target shows the message on the surface it names. A target you take but do
  not list still works.

**OMD-C24 — Your generator id never changes** (should)

- **Check:** compare `generator.id` in your probe reply across a restart and an upgrade of your
  product.
- **Pass:** the same `id`, a short name you picked once. `name` and `version` may change; `id`
  does not.

## D. Producer

What meet software that sends Open Meet Display does.

**OMD-C25 — Every message validates** (must)

- **Check:** capture what you send with the [reference receiver](/omd/tools/omd-test-receiver.mjs)
  (`node omd-test-receiver.mjs --json`), and run `node omd-check.mjs` on each message.
- **Pass:** zero errors, for every kind you send, including a relay, a field event, and a
  non-finisher.

**OMD-C26 — The producer probes and sends the newest version both read** (must)

- **Check:** point your producer at the reference receiver, which answers the probe with
  `["1.0"]`, and send.
- **Pass:** your producer sent `GET` before its first message, and the message says
  `omd.version` `1.0`. Pointed at a receiver that answers `accepts: ["2.0"]` only, it sends nothing
  and tells the operator the receiver needs a newer version.

**OMD-C27 — A receiver that does not answer the probe still gets messages** (should)

- **Check:** run the reference receiver with `--draft`, which answers the probe `404`, and send.
- **Pass:** your producer still sends: the draft message that receiver was built against, or 1.0.
  It never refuses to send because the probe went unanswered.

**OMD-C28 — Fields with no value are left out** (should)

- **Check:** send a start list and look at the message.
- **Pass:** no `result`, `place`, `status`, or `points` keys in a start list row, and no `null`
  anywhere a producer has nothing to say.

**OMD-C29 — Rows arrive in display order, paged to the board** (must)

- **Check:** send results for a heat larger than the board's rows per page.
- **Pass:** each message carries the rows that should be on screen now, sorted as they should
  appear, and no more than the board shows; the next page arrives as its own message.

**OMD-C30 — The operator sets the destination** (must)

- **Check:** open your producer's display settings.
- **Pass:** the operator can enter the receiver's address, port, path, and display target, and can
  test the connection. The test probes and says what answered, in words the operator understands.
