diff --git a/docs/rest-api-vectors/absent-caps.json b/docs/rest-api-vectors/absent-caps.json new file mode 100644 index 00000000..a86bf556 --- /dev/null +++ b/docs/rest-api-vectors/absent-caps.json @@ -0,0 +1,6 @@ +{ + "contract": "docs/rest-api.md#get-apistatus", + "says": "The default build reports `[\"patterns\",\"params\",\"sleep\"]` and nothing else; each [edition](EDITIONS.md) adds its own — Audio adds `osc` and `audio`, Performance adds `weather`, `mqtt` and `shows`.", + "features": [], + "steps": [{ "send": { "http": "GET /api/status" }, "expect": { "reply": { "status": 200, "body": { "caps": ["patterns", "params", "sleep"] } } } }] +} diff --git a/docs/rest-api-vectors/absent-feature-nav.json b/docs/rest-api-vectors/absent-feature-nav.json new file mode 100644 index 00000000..50a50e9e --- /dev/null +++ b/docs/rest-api-vectors/absent-feature-nav.json @@ -0,0 +1,6 @@ +{ + "contract": "docs/rest-api.md#get-apistatus", + "says": "What the console header and home screen build their feature links from; empty on the default build.", + "features": [], + "steps": [{ "send": { "http": "GET /api/status" }, "expect": { "reply": { "status": 200, "body": { "featureNav": [] } } } }] +} diff --git a/docs/rest-api-vectors/absent-routes.json b/docs/rest-api-vectors/absent-routes.json new file mode 100644 index 00000000..ab55dec3 --- /dev/null +++ b/docs/rest-api-vectors/absent-routes.json @@ -0,0 +1,16 @@ +{ + "contract": "docs/rest-api.md#conventions-for-future-additions", + "says": "A feature's endpoints exist only when that feature is composed in — the route itself is absent otherwise, so clients probe `caps` rather than expecting a soft 404.", + "features": [], + "steps": [ + { "send": { "http": "GET /api/mqtt" }, "expect": { "reply": { "status": 404, "headers": { "content-type": "text/plain" } } } }, + { "send": { "http": "POST /api/mqtt?role=subscriber" }, "expect": { "reply": { "status": 404, "headers": { "content-type": "text/plain" } } } }, + { "send": { "http": "GET /api/midi" }, "expect": { "reply": { "status": 404, "headers": { "content-type": "text/plain" } } } }, + { "send": { "http": "POST /api/midi?outMode=rel" }, "expect": { "reply": { "status": 404, "headers": { "content-type": "text/plain" } } } }, + { "send": { "http": "GET /api/audio" }, "expect": { "reply": { "status": 404, "headers": { "content-type": "text/plain" } } } }, + { "send": { "http": "POST /api/audio?on=1" }, "expect": { "reply": { "status": 404, "headers": { "content-type": "text/plain" } } } }, + { "send": { "http": "GET /api/audio-in" }, "expect": { "reply": { "status": 404, "headers": { "content-type": "text/plain" } } } }, + { "send": { "http": "GET /api/clock" }, "expect": { "reply": { "status": 404, "headers": { "content-type": "text/plain" } } } }, + { "send": { "http": "GET /api/shows" }, "expect": { "reply": { "status": 404, "headers": { "content-type": "text/plain" } } } } + ] +} diff --git a/docs/rest-api-vectors/device-compressed.json b/docs/rest-api-vectors/device-compressed.json new file mode 100644 index 00000000..c9d82972 --- /dev/null +++ b/docs/rest-api-vectors/device-compressed.json @@ -0,0 +1,10 @@ +{ + "contract": "docs/rest-api.md#transport", + "says": "Nothing under `/api/` is compressed.", + "features": [], + "steps": [ + { "send": { "http": { "request": "GET /api/status", "headers": { "Accept-Encoding": "gzip, deflate" } } }, "expect": { "reply": { "status": 200, "headers": { "content-encoding": "" } } } }, + { "send": { "http": { "request": "GET /api/patterns", "headers": { "Accept-Encoding": "gzip, deflate" } } }, "expect": { "reply": { "status": 200, "headers": { "content-encoding": "" } } } }, + { "send": { "http": { "request": "GET /api/patterns/file?slug=wave_saw", "headers": { "Accept-Encoding": "gzip, deflate" } } }, "expect": { "reply": { "status": 200, "headers": { "content-encoding": "" } } } } + ] +} diff --git a/docs/rest-api-vectors/device-cors.json b/docs/rest-api-vectors/device-cors.json new file mode 100644 index 00000000..16254fc1 --- /dev/null +++ b/docs/rest-api-vectors/device-cors.json @@ -0,0 +1,15 @@ +{ + "contract": "docs/rest-api.md#transport", + "says": "Only `/api/patterns/select` and `/api/display` send `Access-Control-Allow-Origin: *`.", + "features": [], + "steps": [ + { "send": { "http": "GET /api/display" }, "expect": { "reply": { "status": 200, "headers": { "access-control-allow-origin": "*" } } } }, + { "send": { "http": "GET /api/patterns/select?index=0" }, "expect": { "reply": { "status": 200, "headers": { "access-control-allow-origin": "*" } } } }, + { "send": { "http": "GET /api/status" }, "expect": { "reply": { "headers": { "access-control-allow-origin": "" } } } }, + { "send": { "http": "GET /api/patterns" }, "expect": { "reply": { "headers": { "access-control-allow-origin": "" } } } }, + { "send": { "http": "GET /api/knobs" }, "expect": { "reply": { "headers": { "access-control-allow-origin": "" } } } }, + { "send": { "http": "POST /api/params?p1=500" }, "expect": { "reply": { "headers": { "access-control-allow-origin": "" } } } }, + { "send": { "http": "GET /api/patterns/file?slug=wave_saw&ext=json" }, "expect": { "reply": { "headers": { "access-control-allow-origin": "" } } } }, + { "send": { "http": "GET /api/nothing" }, "expect": { "reply": { "headers": { "access-control-allow-origin": "" } } } } + ] +} diff --git a/docs/rest-api-vectors/device-crash.json b/docs/rest-api-vectors/device-crash.json new file mode 100644 index 00000000..5f0039c9 --- /dev/null +++ b/docs/rest-api-vectors/device-crash.json @@ -0,0 +1,9 @@ +{ + "contract": "docs/rest-api.md#delete-apicrash", + "says": "`{\"ok\":true}`, or `404` with `{\"ok\":false,\"error\":\"no crash recorded\"}` when there was nothing to clear.", + "features": [], + "steps": [ + { "send": { "http": "GET /api/status" }, "expect": { "reply": { "body": { "crash": "" } } } }, + { "send": { "http": "DELETE /api/crash" }, "expect": { "reply": { "status": 404, "body": { "ok": false, "error": "no crash recorded" } } } } + ] +} diff --git a/docs/rest-api-vectors/device-display-brightness.json b/docs/rest-api-vectors/device-display-brightness.json new file mode 100644 index 00000000..7de00efc --- /dev/null +++ b/docs/rest-api-vectors/device-display-brightness.json @@ -0,0 +1,9 @@ +{ + "contract": "docs/rest-api.md#get-apistatus", + "says": "K1's long-press session and `GET /api/display?brightness=` move the same value; what the power clamp lets through is `power_applied` on `/api/display`.", + "features": [], + "steps": [ + { "send": { "http": "GET /api/display?brightness=77" }, "expect": { "reply": { "body": { "brightness": 77 } }, "status": { "brightness": 77 } } }, + { "send": { "http": "GET /api/display?brightness=255" }, "expect": { "reply": { "body": { "brightness": 255 } }, "status": { "brightness": 255 } } } + ] +} diff --git a/docs/rest-api-vectors/device-display-read-only.json b/docs/rest-api-vectors/device-display-read-only.json new file mode 100644 index 00000000..8e95976e --- /dev/null +++ b/docs/rest-api-vectors/device-display-read-only.json @@ -0,0 +1,9 @@ +{ + "contract": "docs/rest-api.md#get-apidisplay", + "says": "Read-only in the response: `calib`, `sleep`, `power_ma`, `power_demand`, `power_limiting`, `power_applied`.", + "features": [], + "steps": [ + { "send": { "http": "GET /api/display?sleep=1" }, "expect": { "reply": { "status": 200, "body": { "sleep": "" } } } }, + { "send": { "wait": 20 }, "expect": { "status": { "sleep": false } } } + ] +} diff --git a/docs/rest-api-vectors/device-display-state.json b/docs/rest-api-vectors/device-display-state.json new file mode 100644 index 00000000..f4d58076 --- /dev/null +++ b/docs/rest-api-vectors/device-display-state.json @@ -0,0 +1,15 @@ +{ + "contract": "docs/rest-api.md#get-apidisplay", + "says": "Any subset of these may be set in the query string; the response always reports the full state afterwards.", + "features": [], + "steps": [ + { + "send": { "http": "GET /api/display?sat=2&gamma_g=2.5" }, + "expect": { "reply": { "status": 200, "body": { + "wb_r": "", "wb_g": "", "wb_b": "", "gamma_r": "", "gamma_g": 2.5, "gamma_b": "", "sat": 2, + "brightness": "", "screen": "", "level": "", "power_budget": "", "power_limit": "", + "calib": "", "sleep": "", "power_ma": "", "power_demand": "", "power_limiting": "", "power_applied": "" } } } + }, + { "send": { "http": "GET /api/display?brightness=128" }, "expect": { "reply": { "body": { "gamma_g": 2.5, "sat": 2, "brightness": 128 } } } } + ] +} diff --git a/docs/rest-api-vectors/device-favicon.json b/docs/rest-api-vectors/device-favicon.json new file mode 100644 index 00000000..0b7d2e50 --- /dev/null +++ b/docs/rest-api-vectors/device-favicon.json @@ -0,0 +1,9 @@ +{ + "contract": "docs/rest-api.md#transport", + "says": "`GET /favicon.ico` answers `204`, with `Cache-Control: public, max-age=31536000, immutable` on a panel `Host` and `no-store` on any other (see Host gating); pages carry their icon inline.", + "features": [], + "steps": [ + { "send": { "http": { "request": "GET /favicon.ico", "headers": { "Host": "patternflow.local" } } }, "expect": { "reply": { "status": 204, "headers": { "cache-control": "public, max-age=31536000, immutable" } } } }, + { "send": { "http": { "request": "GET /favicon.ico", "headers": { "Host": "patternflow.fritz.box" } } }, "expect": { "reply": { "status": 204, "headers": { "cache-control": "no-store" } } } } + ] +} diff --git a/docs/rest-api-vectors/device-host.json b/docs/rest-api-vectors/device-host.json new file mode 100644 index 00000000..1d61cd37 --- /dev/null +++ b/docs/rest-api-vectors/device-host.json @@ -0,0 +1,15 @@ +{ + "contract": "docs/rest-api.md#transport", + "says": "Pages, the chrome and the favicon are cacheable only when the request's `Host` can only be the panel: an IPv4 address, a name with no dot (`patternflow`) or a `.local` name, with or without a port.", + "features": [], + "steps": [ + { "send": { "http": { "request": "GET /favicon.ico", "headers": { "Host": "192.168.1.42" } } }, "expect": { "reply": { "headers": { "cache-control": "public, max-age=31536000, immutable" } } } }, + { "send": { "http": { "request": "GET /favicon.ico", "headers": { "Host": "192.168.1.42:80" } } }, "expect": { "reply": { "headers": { "cache-control": "public, max-age=31536000, immutable" } } } }, + { "send": { "http": { "request": "GET /favicon.ico", "headers": { "Host": "patternflow" } } }, "expect": { "reply": { "headers": { "cache-control": "public, max-age=31536000, immutable" } } } }, + { "send": { "http": { "request": "GET /favicon.ico", "headers": { "Host": "patternflow:8080" } } }, "expect": { "reply": { "headers": { "cache-control": "public, max-age=31536000, immutable" } } } }, + { "send": { "http": { "request": "GET /favicon.ico", "headers": { "Host": "patternflow-a1b2.local" } } }, "expect": { "reply": { "headers": { "cache-control": "public, max-age=31536000, immutable" } } } }, + { "send": { "http": { "request": "GET /favicon.ico", "headers": { "Host": "patternflow.local:80" } } }, "expect": { "reply": { "headers": { "cache-control": "public, max-age=31536000, immutable" } } } }, + { "send": { "http": { "request": "GET /favicon.ico", "headers": { "Host": "patternflow.fritz.box" } } }, "expect": { "reply": { "headers": { "cache-control": "no-store" } } } }, + { "send": { "http": { "request": "GET /favicon.ico", "headers": { "Host": "patternflow.work:80" } } }, "expect": { "reply": { "headers": { "cache-control": "no-store" } } } } + ] +} diff --git a/docs/rest-api-vectors/device-knobs-get.json b/docs/rest-api-vectors/device-knobs-get.json new file mode 100644 index 00000000..a59190ae --- /dev/null +++ b/docs/rest-api-vectors/device-knobs-get.json @@ -0,0 +1,16 @@ +{ + "contract": "docs/rest-api.md#get-apiknobs-and-post-apiknobs", + "says": "`GET` returns `inv` (four booleans: reversed or not), `sub` (edges per click per knob: 4, 2 or 1 — most encoders are 4), `clicks` (each knob's click count as the frame sees it) and `raw` (the edge counters underneath, for telling a 2-edge part from a 4-edge one).", + "features": [], + "steps": [ + { "send": { "hand": { "knob": 1, "turn": 2 } } }, + { "send": { "hand": { "knob": 3, "turn": -1 } } }, + { "send": { "wait": 20 }, "expect": { "status": { "knobs": [2, 0, -1, 0] } } }, + { + "send": { "http": "GET /api/knobs" }, + "expect": { "reply": { "status": 200, "body": { + "inv": ["", "", "", ""], "sub": ["", "", "", ""], + "clicks": [2, 0, -1, 0], "raw": ["", "", "", ""] } } } + } + ] +} diff --git a/docs/rest-api-vectors/device-knobs-post.json b/docs/rest-api-vectors/device-knobs-post.json new file mode 100644 index 00000000..8a8a8686 --- /dev/null +++ b/docs/rest-api-vectors/device-knobs-post.json @@ -0,0 +1,15 @@ +{ + "contract": "docs/rest-api.md#get-apiknobs-and-post-apiknobs", + "says": "`POST` takes any subset of `inv0`..`inv3` (`0`/`1`) and `sub0`..`sub3` (`4`/`2`/`1`), or `inv=` / `sub=` for all four, form-encoded; a change is applied at a frame boundary without moving the knob, stored on the device (NVS `pf_knobs`) and answered as `GET`.", + "features": [], + "steps": [ + { "send": { "hand": { "knob": 2, "turn": 3 } } }, + { "send": { "wait": 20 }, "expect": { "status": { "knobs": [0, 3, 0, 0] } } }, + { "send": { "http": { "request": "POST /api/knobs", "form": "inv=0&sub=4" } }, "expect": { "reply": { "body": { "inv": [false, false, false, false], "sub": [4, 4, 4, 4], "clicks": [0, 3, 0, 0], "raw": "" } } } }, + { "send": { "http": { "request": "POST /api/knobs", "form": "inv1=1&sub2=2" } }, "expect": { "reply": { "body": { "inv": [false, true, false, false], "sub": [4, 4, 2, 4], "clicks": [0, 3, 0, 0], "raw": "" } } } }, + { "send": { "wait": 20 }, "expect": { "status": { "knobs": [0, 3, 0, 0] } } }, + { "send": { "hand": { "knob": 2, "turn": 1 } } }, + { "send": { "hand": { "knob": 3, "turn": 1 } } }, + { "send": { "wait": 20 }, "expect": { "status": { "knobs": [0, 2, 2, 0] } } } + ] +} diff --git a/docs/rest-api-vectors/device-no-store.json b/docs/rest-api-vectors/device-no-store.json new file mode 100644 index 00000000..76618bc4 --- /dev/null +++ b/docs/rest-api-vectors/device-no-store.json @@ -0,0 +1,19 @@ +{ + "contract": "docs/rest-api.md#transport", + "says": "Every JSON endpoint sends `Cache-Control: no-store`, and so does the plain-text `404`.", + "features": [], + "steps": [ + { "send": { "http": "GET /api/status" }, "expect": { "reply": { "headers": { "cache-control": "no-store" } } } }, + { "send": { "http": "GET /api/display" }, "expect": { "reply": { "headers": { "cache-control": "no-store" } } } }, + { "send": { "http": "GET /api/knobs" }, "expect": { "reply": { "headers": { "cache-control": "no-store" } } } }, + { "send": { "http": { "request": "POST /api/knobs", "form": "inv=0" } }, "expect": { "reply": { "headers": { "cache-control": "no-store" } } } }, + { "send": { "http": "POST /api/params?p1=500" }, "expect": { "reply": { "headers": { "cache-control": "no-store" } } } }, + { "send": { "http": "POST /api/sleep?on=0" }, "expect": { "reply": { "headers": { "cache-control": "no-store" } } } }, + { "send": { "http": "DELETE /api/crash" }, "expect": { "reply": { "status": 404, "headers": { "cache-control": "no-store" } } } }, + { "send": { "http": "GET /api/patterns" }, "expect": { "reply": { "headers": { "cache-control": "no-store" } } } }, + { "send": { "http": "GET /api/patterns/select?index=0" }, "expect": { "reply": { "headers": { "cache-control": "no-store" } } } }, + { "send": { "http": "GET /api/patterns/select?index=-1" }, "expect": { "reply": { "status": 404, "headers": { "cache-control": "no-store" } } } }, + { "send": { "http": "GET /api/patterns/file?slug=wave_saw&ext=json" }, "expect": { "reply": { "headers": { "cache-control": "no-store" } } } }, + { "send": { "http": "GET /api/nothing" }, "expect": { "reply": { "status": 404, "headers": { "content-type": "text/plain", "cache-control": "no-store" } } } } + ] +} diff --git a/docs/rest-api-vectors/device-removed.json b/docs/rest-api-vectors/device-removed.json new file mode 100644 index 00000000..5bc888d4 --- /dev/null +++ b/docs/rest-api-vectors/device-removed.json @@ -0,0 +1,11 @@ +{ + "contract": "docs/rest-api.md#knobs-and-parameters", + "says": "`/api/knob` and `/remote` existed and were removed as unused alongside the `/api/frame` incident; do not look for them.", + "features": [], + "steps": [ + { "send": { "http": "GET /api/knob" }, "expect": { "reply": { "status": 404 } } }, + { "send": { "http": "POST /api/knob?knob=1&delta=1" }, "expect": { "reply": { "status": 404 } } }, + { "send": { "http": "GET /remote" }, "expect": { "reply": { "status": 404 } } }, + { "send": { "http": "GET /api/frame" }, "expect": { "reply": { "status": 404 } } } + ] +} diff --git a/docs/rest-api-vectors/patterns-file-404.json b/docs/rest-api-vectors/patterns-file-404.json new file mode 100644 index 00000000..5ab7153c --- /dev/null +++ b/docs/rest-api-vectors/patterns-file-404.json @@ -0,0 +1,10 @@ +{ + "contract": "docs/rest-api.md#get-apipatternsfile", + "says": "`404` if the file is not there, `409` if storage is not mounted.", + "features": [], + "steps": [ + { "send": { "http": "GET /api/patterns/file?slug=no_such_module" }, "expect": { "reply": { "status": 404 } } }, + { "send": { "http": "GET /api/patterns/file?slug=no_such_module&ext=json" }, "expect": { "reply": { "status": 404 } } }, + { "send": { "http": "GET /api/patterns/file?slug=no_such_module&ext=thumb" }, "expect": { "reply": { "status": 404 } } } + ] +} diff --git a/docs/rest-api-vectors/patterns-file-download.json b/docs/rest-api-vectors/patterns-file-download.json new file mode 100644 index 00000000..9a6617a7 --- /dev/null +++ b/docs/rest-api-vectors/patterns-file-download.json @@ -0,0 +1,9 @@ +{ + "contract": "docs/rest-api.md#get-apipatternsfile", + "says": "This is a file download (`Content-Disposition: attachment`), one file at a time — the server is single-connection.", + "features": [], + "steps": [ + { "send": { "http": "GET /api/patterns/file?slug=wave_saw" }, "expect": { "reply": { "status": 200, "headers": { "content-disposition": "" } } } }, + { "send": { "http": "GET /api/patterns/file?slug=wave_saw&ext=json" }, "expect": { "reply": { "status": 200, "headers": { "content-disposition": "" } } } } + ] +} diff --git a/docs/rest-api-vectors/patterns-file-preset.json b/docs/rest-api-vectors/patterns-file-preset.json new file mode 100644 index 00000000..18777bd6 --- /dev/null +++ b/docs/rest-api-vectors/patterns-file-preset.json @@ -0,0 +1,6 @@ +{ + "contract": "docs/rest-api.md#get-apipatternsfile", + "says": "Presets have no sidecar.", + "features": [], + "steps": [{ "send": { "http": "GET /api/patterns/file?slug=origin&ext=json" }, "expect": { "reply": { "status": 404 } } }] +} diff --git a/docs/rest-api-vectors/patterns-file-sidecar.json b/docs/rest-api-vectors/patterns-file-sidecar.json new file mode 100644 index 00000000..210c1886 --- /dev/null +++ b/docs/rest-api-vectors/patterns-file-sidecar.json @@ -0,0 +1,9 @@ +{ + "contract": "docs/rest-api.md#get-apipatternsfile", + "says": "`ext=json` returns the module's **sidecar** — the only place per-pattern metadata is exposed over HTTP:", + "features": [], + "steps": [ + { "send": { "http": "GET /api/patterns/file?slug=wave_saw&ext=json" }, "expect": { "reply": { "status": 200, "body": { + "name": "", "abi": "", "knobs": ["", "", "", ""], "slug": "wave_saw" } } } } + ] +} diff --git a/docs/rest-api-vectors/patterns-list.json b/docs/rest-api-vectors/patterns-list.json new file mode 100644 index 00000000..41e80de7 --- /dev/null +++ b/docs/rest-api-vectors/patterns-list.json @@ -0,0 +1,15 @@ +{ + "contract": "docs/rest-api.md#get-apipatterns", + "says": "`active` is an index into `patterns`, or `-1` while nothing is loaded.", + "features": [], + "steps": [ + { + "send": { "http": "GET /api/patterns" }, + "expect": { "reply": { "status": 200, "body": { + "active": "", "presets": 1, "mounted": true, "free": "", "patterns": "", "pendingRev": "", "pending": "" } } } + }, + { "send": { "http": "GET /api/patterns/select?index=2" } }, + { "send": { "wait": 20 } }, + { "send": { "http": "GET /api/patterns" }, "expect": { "reply": { "body": { "active": 2 } } } } + ] +} diff --git a/docs/rest-api-vectors/patterns-select-404.json b/docs/rest-api-vectors/patterns-select-404.json new file mode 100644 index 00000000..5512e049 --- /dev/null +++ b/docs/rest-api-vectors/patterns-select-404.json @@ -0,0 +1,13 @@ +{ + "contract": "docs/rest-api.md#get-apipatternsselect", + "says": "`404` for an out-of-range index or an unknown name.", + "features": [], + "steps": [ + { "send": { "http": "GET /api/patterns/select?index=0" } }, + { "send": { "wait": 20 } }, + { "send": { "http": "GET /api/patterns/select?index=-1" }, "expect": { "reply": { "status": 404 } } }, + { "send": { "http": "GET /api/patterns/select?index=9999" }, "expect": { "reply": { "status": 404 } } }, + { "send": { "http": "GET /api/patterns/select?name=No%20such%20pattern" }, "expect": { "reply": { "status": 404 } } }, + { "send": { "wait": 20 }, "expect": { "status": { "active": "Origin" } } } + ] +} diff --git a/docs/rest-api-vectors/patterns-select-name.json b/docs/rest-api-vectors/patterns-select-name.json new file mode 100644 index 00000000..3dcbe1a1 --- /dev/null +++ b/docs/rest-api-vectors/patterns-select-name.json @@ -0,0 +1,12 @@ +{ + "contract": "docs/rest-api.md#get-apipatternsselect", + "says": "Display name, exact match. Only if you have no index.", + "features": [], + "steps": [ + { "send": { "http": "GET /api/patterns/select?index=1" } }, + { "send": { "wait": 20 }, "expect": { "status": { "activeIsModule": true } } }, + { "send": { "http": "GET /api/patterns/select?name=origin" }, "expect": { "reply": { "status": 404 } } }, + { "send": { "http": "GET /api/patterns/select?name=Origin" }, "expect": { "reply": { "status": 200, "body": { "ok": true, "index": 0, "name": "Origin" } } } }, + { "send": { "wait": 20 }, "expect": { "status": { "active": "Origin" } } } + ] +} diff --git a/docs/rest-api-vectors/patterns-select-step.json b/docs/rest-api-vectors/patterns-select-step.json new file mode 100644 index 00000000..6c104f82 --- /dev/null +++ b/docs/rest-api-vectors/patterns-select-step.json @@ -0,0 +1,16 @@ +{ + "contract": "docs/rest-api.md#get-apipatternsselect", + "says": "`+1` or `-1`: the next or previous pattern that is not hidden, wrapping, from wherever the panel is now.", + "features": [], + "steps": [ + { "send": { "http": "GET /api/patterns/select?index=0" } }, + { "send": { "wait": 20 } }, + { "send": { "http": "GET /api/patterns/select?step=%2B1" }, "expect": { "reply": { "status": 200, "body": { "index": 1 } } } }, + { "send": { "wait": 20 } }, + { "send": { "http": "GET /api/patterns/select?step=-1" }, "expect": { "reply": { "status": 200, "body": { "index": 0 } } } }, + { "send": { "wait": 20 } }, + { "send": { "http": "GET /api/patterns/select?step=-1" }, "expect": { "reply": { "status": 200, "body": { "index": "" } } } }, + { "send": { "wait": 20 } }, + { "send": { "http": "GET /api/patterns/select?step=%2B1" }, "expect": { "reply": { "status": 200, "body": { "index": 0 } } } } + ] +} diff --git a/docs/rest-api-vectors/patterns-select.json b/docs/rest-api-vectors/patterns-select.json new file mode 100644 index 00000000..0f147d12 --- /dev/null +++ b/docs/rest-api-vectors/patterns-select.json @@ -0,0 +1,13 @@ +{ + "contract": "docs/rest-api.md#get-apipatternsselect", + "says": "Queued and applied in `loop()`, for the same reason as sleep: activating a module reads FATFS and runs the relocator.", + "features": [], + "steps": [ + { "send": { "http": "GET /api/patterns/select?index=0" } }, + { "send": { "wait": 20 }, "expect": { "status": { "active": "Origin", "activeIsModule": false } } }, + { "send": { "http": "GET /api/patterns/select?index=1" }, "expect": { "reply": { "status": 200, "body": { "ok": true, "index": 1, "name": "" } } } }, + { "send": { "http": "GET /api/patterns" }, "expect": { "reply": { "body": { "active": 0 } } } }, + { "send": { "wait": 20 }, "expect": { "status": { "activeIsModule": true } } }, + { "send": { "http": "GET /api/patterns" }, "expect": { "reply": { "body": { "active": 1 } } } } + ] +} diff --git a/docs/rest-api-vectors/patterns-thumb.json b/docs/rest-api-vectors/patterns-thumb.json new file mode 100644 index 00000000..ab8dd6f2 --- /dev/null +++ b/docs/rest-api-vectors/patterns-thumb.json @@ -0,0 +1,14 @@ +{ + "contract": "docs/rest-api.md#get-apipatternsfile", + "says": "`404` until the pattern has been played once; presets (`origin`) get one the same way.", + "features": [], + "steps": [ + { "send": { "http": "GET /api/patterns/select?index=0" } }, + { "send": { "wait": 20 } }, + { "send": { "http": "GET /api/patterns/file?slug=origin&ext=thumb" }, "expect": { "reply": { "status": 404 } } }, + { "send": { "wait": 3000 } }, + { "send": { "http": "GET /api/patterns/select?index=1" } }, + { "send": { "wait": 20 }, "expect": { "status": { "activeIsModule": true } } }, + { "send": { "http": "GET /api/patterns/file?slug=origin&ext=thumb" }, "expect": { "reply": { "status": 200 } } } + ] +}