9.7 KiB
9.7 KiB
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | must_haves | |||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 05-cable-test-integration | 03 | execute | 3 |
|
|
true |
|
|
Purpose: Operator can test cables and print labels from a single page without leaving the workflow. Output: web/src/pages/CableTestPage.tsx, web/src/api/test.ts, router update.
<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_context>
@.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/05-cable-test-integration/05-CONTEXT.md @.planning/phases/05-cable-test-integration/05-02-SUMMARY.md @.planning/phases/03-dashboard-intake-ui/03-01-SUMMARY.md@web/src/router.tsx @web/src/pages/DashboardPage.tsx
POST /api/test/cable Request body:
{
"cable_type": 0,
"usb_version": "USB 3.2 Gen 2",
"dp_version": "",
"hdmi_version": "",
"speed_gbps": 10.0,
"max_watts": 100,
"pin_continuity": true,
"has_emarker": true,
"resistance_ohm": 0.12,
"hw_id": "HW-00042"
}
Response 201:
{ "hw_id": "HW-00042", "netbox_id": 7, "print_skipped": false }
GET /api/test/recent Response 200: array of TestResult objects (same shape as POST body)
GET /api/test/events (SSE)
event stream: data: {"voltage":5.1,"current_amps":3.0,"power_watts":15.3,"pd_protocol":"PD3.0","timestamp":"..."}\n\n
Design system tokens (from CLAUDE.md / ClickHouse design):
- Background: #000000
- Accent: #faff69 (neon volt)
- Card background: #111111
- Border: #222222
- Text primary: #ffffff
- Text muted: #888888
- Tailwind classes: bg-black, text-[#faff69], bg-[#111], border-[#222]
export interface SubmitResponse {
hw_id: string;
netbox_id: number;
print_skipped: boolean;
}
export interface LiveReading {
voltage: number;
current_amps: number;
power_watts: number;
pd_protocol: string;
timestamp: string;
}
export async function submitCableTest(result: TestResult): Promise<SubmitResponse> {
const res = await fetch('/api/test/cable', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(result),
});
if (!res.ok) throw new Error(`submit failed: ${res.status}`);
return res.json();
}
export async function getRecentTests(): Promise<TestResult[]> {
const res = await fetch('/api/test/recent');
if (!res.ok) throw new Error(`recent tests failed: ${res.status}`);
return res.json();
}
```
Create web/src/pages/CableTestPage.tsx:
Layout: Three-column grid on lg+, single stacked column on mobile.
- Column 1 (tester readout): "Cable Test Station" heading; if liveReading state is set show Voltage/Current/Power/Protocol values with neon volt accent; else show "No tester connected" in muted text. Use EventSource in useEffect — close on component unmount. On each SSE message parse JSON into liveReading state.
- Column 2 (center — label preview + print): Show a card with current test fields as text (HW ID, type, version, speed, power, continuity, eMarker, resistance). "Print & Next" button (bg-[#faff69] text-black font-bold): calls submitCableTest mutation; on success show toast "Label printed for {hw_id}" (react-hot-toast) then reset form. If print_skipped=true toast says "Saved — printer not available". Loading state: button disabled + spinner.
- Column 3 (recent tests): "Recent Tests" heading; useQuery fetching getRecentTests() every 5000ms; render as list rows showing cable_type icon (USB/DP/HDMI), hw_id, pin_continuity chip (green tick / red X), speed_gbps.
Use lucide-react icons: Usb, Monitor, Tv2 for cable types. CheckCircle2 and XCircle for continuity.
For the "current test" form: a simple controlled form with number/text inputs for each TestResult field. Pre-populate with mock data when liveReading arrives (voltage/current into the readout; the test fields remain manual until real driver parsing is implemented).
Register /test route in web/src/router.tsx. Add "Test" nav link in the site nav (alongside Dashboard / Intake / Scan).
Design tokens: all backgrounds bg-black, cards bg-[#111] border border-[#222] rounded-lg p-4, headings text-white, muted text-[#888], accent text-[#faff69].
cd /home/mikkel/homelabby/web && npm run build 2>&1 | tail -20
npm run build exits 0; /test route exists in router; CableTestPage renders three panels; Print & Next button is present.
Task 2: Unit tests for CableTestPage
web/src/pages/CableTestPage.test.tsx
Create web/src/pages/CableTestPage.test.tsx using Vitest + React Testing Library (already in the project from Phase 3).
Mock fetch globally with vi.stubGlobal.
Tests:
1. "renders 'No tester connected' when no SSE data" — render CableTestPage, assert text present.
2. "renders recent tests list" — mock getRecentTests() returning 2 USB items; assert two rows visible.
3. "Print & Next calls submitCableTest and shows toast" — mock submitCableTest to resolve {hw_id:"HW-00001",netbox_id:1,print_skipped:false}; fill hw_id input; click Print & Next; await toast text "Label printed".
4. "Print & Next shows print_skipped toast" — mock returns print_skipped:true; assert toast says "Saved — printer not available".
Mock EventSource in test setup:
```typescript
class MockEventSource {
addEventListener = vi.fn();
removeEventListener = vi.fn();
close = vi.fn();
}
vi.stubGlobal('EventSource', MockEventSource);
```
Wrap component in necessary providers (QueryClientProvider, MemoryRouter, Toaster).
cd /home/mikkel/homelabby/web && npm test -- --run CableTestPage 2>&1 | tail -30
All 4 CableTestPage tests pass; npm run build still exits 0.
<threat_model>
Trust Boundaries
| Boundary | Description |
|---|---|
| browser → POST /api/test/cable | User-submitted form data; validated server-side in 05-02 |
| SSE stream → browser | Server push only; EventSource is read-only, no injection risk |
STRIDE Threat Register
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|---|---|---|---|---|
| T-05-07 | XSS | TestResult fields rendered in DOM | mitigate | Use React JSX (auto-escaped); no dangerouslySetInnerHTML |
| T-05-08 | DoS | EventSource leak on unmount | mitigate | useEffect cleanup closes EventSource; verified in test 1 |
| </threat_model> |
<success_criteria>
- /test route renders Cable Test Station page with three panels
- Print & Next submits POST /api/test/cable, shows success toast, resets form
- SSE live readings update the tester readout panel; EventSource closed on unmount
- Recent tests list auto-refreshes every 5s
- Layout is single-column on mobile, three-column on lg+
- All 4 unit tests pass; npm run build exits 0
- ClickHouse design tokens applied throughout (#000000 background, #faff69 accent) </success_criteria>