174 lines
8.8 KiB
Markdown
174 lines
8.8 KiB
Markdown
# 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` 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/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/` | 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/api/presets | python3 -m json.tool
|
|
```
|
|
|
|
For WS tests:
|
|
|
|
```bash
|
|
# Connect, wait, then send toggle commands in another terminal
|
|
wscat -c ws://pedal.local/ws
|
|
```
|