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