From 11c62868cfd07778d2a761e0db4c35f7ed1b10de Mon Sep 17 00:00:00 2001 From: Shawn Date: Sat, 13 Jun 2026 22:20:20 -0400 Subject: [PATCH] =?UTF-8?q?Add=20UX=20&=20API=20test=20plan=20=E2=80=94=20?= =?UTF-8?q?10=20sections,=2060=20tests=20covering=20block=20toggle,=20para?= =?UTF-8?q?m=20updates,=20preset=20CRUD,=20audio=20profile=20switching,=20?= =?UTF-8?q?volume/bypass,=20WebSocket=20broadcasts,=20UI=20visuals,=20edge?= =?UTF-8?q?=20cases,=20and=20channel=20isolation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/test-plan-ux-apis.md | 173 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 173 insertions(+) create mode 100644 docs/test-plan-ux-apis.md diff --git a/docs/test-plan-ux-apis.md b/docs/test-plan-ux-apis.md new file mode 100644 index 0000000..13b5942 --- /dev/null +++ b/docs/test-plan-ux-apis.md @@ -0,0 +1,173 @@ +# Test Plan: UX & API Verification + +## Scope + +Test every block-related API endpoint and the UI's interaction with it. Covers both the React SPA at `pedal.local:8080` and the REST/WebSocket APIs. + +--- + +## 1. API Health & Baseline + +| # | Test | Method | Path | Expected | Result | +|---|------|--------|------|----------|--------| +| 1.1 | State endpoint | `GET` | `/api/state` | 200 — returns current preset, bypass, volume, routing | +| 1.2 | Preset list | `GET` | `/api/presets` | 200 — Bank 0 with 4 slots (2 presets, 2 null) | +| 1.3 | Models list | `GET` | `/api/models` | 200 — both .nam files visible, loaded states | +| 1.4 | Profiles | `GET` | `/api/audio/profiles` | 200 — standard + low listed | +| 1.5 | Profile detail | `GET` | `/api/audio/profile` | 200 — period=256, jack_running=true | + +--- + +## 2. Block Toggle (`PATCH /api/blocks`) + +Core bug: previously `enabled` changed but `bypass` stayed true, so blocks remained skipped. + +| # | Test | Payload | Expected | Result | +|---|------|---------|----------|--------| +| 2.1 | Enable a disabled block | `{"id": "nam_amp", "enabled": true}` | `bypass: false` returned | +| 2.2 | Disable an enabled block | `{"id": "nam_amp", "enabled": false}` | `bypass: true` returned | +| 2.3 | Toggle enabled→disabled | GET state before & after | Chain shows block with `enabled=false, bypass=true` | +| 2.4 | Toggle disabled→enabled | GET state before & after | Chain shows block with `enabled=true, bypass=false` | +| 2.5 | Toggle nonexistent block ID | `{"id": "phaser", "enabled": true}` | 404 — "Block 'phaser' not found" | +| 2.6 | Missing ID field | `{"enabled": true}` | 400 — "Missing 'id' or 'enabled'" | +| 2.7 | Missing enabled field | `{"id": "nam_amp"}` | 400 — "Missing 'id' or 'enabled'" | +| 2.8 | Toggle and confirm audio | Toggle off → play guitar → toggle on → play guitar | Audio is muted when bypassed, flows when not | + +--- + +## 3. Block Param Update (`PATCH /api/block`) + +| # | Test | Payload | Expected | Result | +|---|------|---------|----------|--------| +| 3.1 | Update volume param | `{"id": "nam_amp", "level": 0.5}` | `params.level == 0.5` returned | +| 3.2 | Update with max value | `{"id": "nam_amp", "level": 1.0}` | Accepted, no clipping | +| 3.3 | Update with zero value | `{"id": "nam_amp", "level": 0.0}` | Accepted (mute) | +| 3.4 | Update nonexistent block | `{"id": "flanger", "rate": 0.5}` | 404 | +| 3.5 | Update multiple params | `{"id": "nam_amp", "level": 0.7, "some_custom": 0.3}` | Both params stored | +| 3.6 | Readback after save | GET preset → confirm params persisted | Matches last write | + +--- + +## 4. Preset CRUD + +| # | Test | Method | Path/Payload | Expected | Result | +|---|------|--------|-------------|----------|--------| +| 4.1 | Get preset 0 | GET | `/api/presets/0/0` | 200 — Fender Deluxe Reverb Clean | +| 4.2 | Get preset 1 | GET | `/api/presets/0/1` | 200 — Marshall JCM 900 Dirty | +| 4.3 | Get empty slot | GET | `/api/presets/0/2` | 404 — "Preset not found" | +| 4.4 | Activate preset 0 | POST | `/api/presets/0/0/activate` | 200, `preset == "Fender Deluxe Reverb Clean"` | +| 4.5 | Activate preset 1 | POST | `/api/presets/0/1/activate` | 200, `preset == "Marshall JCM 900 Dirty"` | +| 4.6 | Create preset in slot 2 | PUT | `/api/presets/0/2` with nam_amp chain | 200 | +| 4.7 | Delete preset in slot 2 | DELETE | `/api/presets/0/2` | 200 — slot returns to null | +| 4.8 | Save preset with IR + NAM | PUT | block with `ir_file_path` + `nam_model_path` | Both saved | +| 4.9 | Cross-channel isolation | GET | `/api/presets?channel=bass` | Returns bass bank, not guitar | + +--- + +## 5. Audio Profile Switching + +| # | Test | Payload | Expected | Result | +|---|------|---------|----------|--------| +| 5.1 | Switch to low latency | `{"profile": "low"}` | 200, `period: 64, restarted: true` | +| 5.2 | Verify JACK restarted | GET `/api/audio/profile` | `period == 64, jack_running == true` | +| 5.3 | Switch back to standard | `{"profile": "standard"}` | 200, `period: 256, restarted: true` | +| 5.4 | Verify JACK restarted | GET `/api/audio/profile` | `period == 256, jack_running == true` | +| 5.5 | Audio still flows after switch | Play guitar | No crackles, no silence | +| 5.6 | Switch to same profile | `{"profile": "standard"}` | `restarted: false` | +| 5.7 | Invalid profile | `{"profile": "extreme"}` | 400 — unknown profile | +| 5.8 | Xrun count not null | GET `/api/audio/profile` | `xrun_count` is int or null (not error) | + +--- + +## 6. Volume & Bypass (Pipeline level) + +| # | Test | Method/Payload | Expected | Result | +|---|------|---------------|----------|--------| +| 6.1 | Set volume | `PUT /api/volume {"volume": 0.5}` | `volume: 0.5` returned | +| 6.2 | Volume clamping | `PUT /api/volume {"volume": 2.0}` | `volume: 1.0` (clamped) | +| 6.3 | Volume clamping low | `PUT /api/volume {"volume": -1.0}` | `volume: 0.0` (clamped) | +| 6.4 | Bypass toggle | `POST /api/bypass {"bypass": true}` | `bypass: true` | +| 6.5 | Bypass toggle off | `POST /api/bypass {"bypass": false}` | `bypass: false` | +| 6.6 | Bypass flip | `POST /api/bypass` (no body) | `bypass` toggles from current state | +| 6.7 | Bypass + audio | Toggle bypass ON → play guitar | Bypassed: clean DI signal, no FX | +| 6.8 | Bypass + audio | Toggle bypass OFF → play guitar | Active: NAM model processing audible | +| 6.9 | POST alias at /api/bypass/toggle | `POST /api/bypass/toggle {"bypass": true}` | Same as 6.4 | + +--- + +## 7. WebSocket Real-Time Updates + +| # | Test | Action | Expected | Result | +|---|------|--------|----------|--------| +| 7.1 | Connect WS | `wscat -c ws://pedal.local:8080/ws` | Receives `{"type": "connected", "state": {...}}` | +| 7.2 | Ping/pong | Send `{"type": "ping"}` | Response `{"type": "pong"}` | +| 7.3 | Block toggle broadcast | Toggle block via PATCH /api/blocks | WS message `{"type": "block_toggled", ...}` | +| 7.4 | Preset change broadcast | Activate preset via API | WS message `{"type": "preset_changed", ...}` | +| 7.5 | Bypass broadcast | Toggle bypass | WS message `{"type": "bypass_changed", ...}` | +| 7.6 | Volume change broadcast | Set volume | WS message `{"type": "volume_changed", ...}` | +| 7.7 | Audio profile broadcast | Switch profile | WS message `{"type": "audio_profile_changed", ...}` | + +--- + +## 8. UI Visual Block Tests (Browser) + +| # | Test | Action | Expected | Result | +|---|------|--------|----------|--------| +| 8.1 | Page loads | Navigate to `http://pedal.local:8080/` | React SPA renders, block chain visible | +| 8.2 | NAM block visible | Check the block chain | "nam_amp" tile shown with correct icon | +| 8.3 | Block label | Read tile text | Shows "nam_amp" or friendly name | +| 8.4 | Toggle block ON/OFF | Click the block's power icon | Block appears enabled/disabled visually | +| 8.5 | State persists on refresh | Toggle OFF → F5 → check | Block still shows OFF | +| 8.6 | Reverb/delay blocks render | Check all FX types | All known FX types have unique icons | +| 8.7 | Empty slot display | Preset with empty chain | Shows "no blocks" or empty chain area | +| 8.8 | Block reorder (if supported) | Drag block | Chain updates after drop | +| 8.9 | URL root works | GET `/` | Returns React SPA (not 404 or redirect) | + +--- + +## 9. Edge Cases & Robustness + +| # | Test | Action | Expected | Result | +|---|------|--------|----------|--------| +| 9.1 | Missing preset directory | Delete `~/.pedal/presets`, restart | Pedal boots, creates one empty stub | +| 9.2 | Missing models directory | Remove `.nam` files | `/api/models` returns `[]`, preset fails gracefully | +| 9.3 | Corrupt preset JSON | Inject bad JSON into `preset_0.json` | API returns 500, pedal continues running | +| 9.4 | Web server restart | `systemctl restart pi-multifx-pedal` | 30s max downtime, then all endpoints respond | +| 9.5 | Concurrent API calls | Fire 10 rapid toggle requests | All return 200, final state is deterministic | +| 9.6 | JACK not running | Kill jackd mid-session | Audio stops, API still responds, JACK restarts on next profile switch | +| 9.7 | Rate-limited toggle | Toggle block on/off 50 times | No memory leak, state remains consistent | +| 9.8 | SD card at 95%+ | Fill disk to near-capacity | API still responds (read-only ops work) | + +--- + +## 10. Channel Isolation (if applicable) + +| # | Test | Action | Expected | Result | +|---|------|--------|----------|--------| +| 10.1 | Guitar state doesn't affect bass | Set guitar volume 0.0, check bass | Bass volume unchanged | +| 10.2 | Dual-mono routing | POST `/api/channel-mode {"channel_mode": "dual-mono"}` | 200, mode switched | +| 10.3 | Stereo mode | POST `/api/channel-mode {"channel_mode": "stereo"}` | 200, unified state | + +--- + +## Test Execution + +Run in order. Mark each cell: + +- ✅ — passes +- ❌ — fails (log issue + fix before moving on) +- ⚠️ — non-critical/optional (label reason) +- ⏭️ — skipped (label why) + +For curl tests, use this template: + +```bash +curl -sv http://pedal.local:8080/api/presets | python3 -m json.tool +``` + +For WS tests: + +```bash +# Connect, wait, then send toggle commands in another terminal +wscat -c ws://pedal.local:8080/ws +```