# 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 ```