10 KiB
10 KiB
| phase | plan | type | wave | depends_on | files_modified | autonomous | requirements | must_haves | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 05-cable-test-integration | 01 | tdd | 1 |
|
true |
|
|
Purpose: All cable test code builds and tests pass without hardware. Real driver implementations are drop-in replacements when hardware arrives 2026-04-13. Output: internal/tester package (driver.go + mock_drivers.go + driver_test.go), updated internal/usb/device.go.
<execution_context> @$HOME/.claude/get-shit-done/workflows/execute-plan.md @$HOME/.claude/get-shit-done/templates/summary.md </execution_context>
@.planning/PROJECT.md @.planning/ROADMAP.md @.planning/STATE.md @.planning/phases/04-usb-manager-label-printing/04-01-SUMMARY.md @.planning/phases/05-cable-test-integration/05-CONTEXT.md@internal/usb/device.go @internal/printer/driver.go
From internal/usb/device.go:
type DeviceRole int
const (
RolePrinter DeviceRole = iota
RoleCableTester // reserved for Phase 5
RoleUnknown
)
type DeviceSpec struct {
VID string
PID string
Name string
Role DeviceRole
BaudRate int
}
// KnownDevices — add 4 tester entries here (placeholder VID:PIDs)
var KnownDevices = map[string]DeviceSpec{
"0525:a4a7": {VID: "0525", PID: "a4a7", Name: "PRT Qutie", Role: RolePrinter, BaudRate: 9600},
// TODO Phase 5: Treedix testers placeholder entries go here
}
From internal/printer/driver.go (pattern to follow):
type PrinterDriver interface {
Connect() error
Print(bitmap []byte, width, height int) error
Disconnect() error
}
Task 1: TesterDriver interface, TestResult types, mock discrete drivers (USB/DP/HDMI)
internal/tester/driver.go, internal/tester/mock_drivers.go, internal/tester/driver_test.go, internal/usb/device.go
- TestResult.CableType must be one of: CableTypeUSB, CableTypeDP, CableTypeHDMI
- MockUSBDriver.Read() returns TestResult{CableType: CableTypeUSB, USBVersion: "USB 3.2 Gen 2", SpeedGbps: 10.0, MaxWatts: 100, PinContinuity: true, HasEMarker: true, ResistanceOhm: 0.12}
- MockDPDriver.Read() returns TestResult{CableType: CableTypeDP, DPVersion: "1.4", PinContinuity: true}
- MockHDMIDriver.Read() returns TestResult{CableType: CableTypeHDMI, HDMIVersion: "2.1", PinContinuity: true}
- Read() before Connect() returns ErrNotConnected
- Disconnect() twice returns nil (idempotent)
- Compile-time interface assertion: `var _ TesterDriver = (*MockUSBDriver)(nil)` etc.
- KnownDevices in usb/device.go has entries for "dead0:0001" (TreedixUSB), "dead0:0002" (TreedixDP), "dead0:0003" (TreedixHDMI) — all Role: RoleCableTester
RED: Write driver_test.go first — compile-time assertions + behavior tests for all three mocks. Run: `go test ./internal/tester/... ./internal/usb/...` — must fail (package does not exist).
GREEN: Create internal/tester/driver.go:
```go
package tester
import "errors"
var ErrNotConnected = errors.New("tester: not connected — call Connect() first")
type CableType int
const (
CableTypeUSB CableType = iota
CableTypeDP
CableTypeHDMI
)
type TestResult struct {
CableType CableType
USBVersion string
DPVersion string
HDMIVersion string
SpeedGbps float64
MaxWatts int
PinContinuity bool
HasEMarker bool
ResistanceOhm float64
}
type TesterDriver interface {
Connect() error
Read() (TestResult, error)
Disconnect() error
}
```
Create internal/tester/mock_drivers.go with MockUSBDriver, MockDPDriver, MockHDMIDriver structs. Each has a `connected bool` field. Connect() sets connected=true. Disconnect() sets connected=false (idempotent). Read() returns ErrNotConnected if !connected, else returns the deterministic TestResult shown in the behavior block.
Update internal/usb/device.go KnownDevices map: add three Treedix entries with placeholder VID:PIDs "dead0:0001", "dead0:0002", "dead0:0003", Role: RoleCableTester, BaudRate: 115200. Add comment: `// TODO(hardware): Replace placeholder VID:PIDs after characterization 2026-04-13`.
Commit RED then GREEN separately.
cd /home/mikkel/homelabby && go test ./internal/tester/... ./internal/usb/... -v -count=1 -run "TestMock|TestKnown"
All mock discrete driver tests pass; KnownDevices has 4 entries (1 printer + 3 testers); go build ./... passes.
Task 2: FNB58 streaming mock driver
internal/tester/driver.go, internal/tester/mock_drivers.go, internal/tester/driver_test.go
- LiveReading struct: Voltage float64, CurrentAmps float64, PowerWatts float64, PDProtocol string, Timestamp time.Time
- StreamingTesterDriver interface embeds TesterDriver and adds: Stream() <-chan LiveReading
- MockFNB58Driver.Stream() returns a channel that emits 3 deterministic LiveReading samples (V=5.1, A=3.0, W=15.3, PDProtocol="PD3.0") at 100ms intervals then closes
- Stream() before Connect() returns a closed channel (not nil, not blocking)
- Disconnect() closes the stream channel and sets connected=false
- Compile-time: `var _ StreamingTesterDriver = (*MockFNB58Driver)(nil)`
- KnownDevices has "dead0:0004" for FNB58 (Role: RoleCableTester, BaudRate: 0 = HID, not serial)
RED: Extend driver_test.go with FNB58-specific tests. Run tests — fail on missing types.
GREEN: Add to internal/tester/driver.go:
```go
type LiveReading struct {
Voltage float64
CurrentAmps float64
PowerWatts float64
PDProtocol string
Timestamp time.Time
}
type StreamingTesterDriver interface {
TesterDriver
Stream() <-chan LiveReading
}
```
Add MockFNB58Driver to mock_drivers.go. Connect() sets connected=true, initialises internal streamCh (buffered 8). Disconnect() closes streamCh if open, sets connected=false. Stream() returns streamCh (or a pre-closed channel if !connected). In Connect(), launch a goroutine that sends 3 readings 100ms apart then closes the channel — use a context derived from a cancelFunc stored on the struct so Disconnect() can also stop it early.
Add "dead0:0004" to KnownDevices: {VID:"dead0", PID:"0004", Name:"FNIRSI FNB58", Role:RoleCableTester, BaudRate:0} with `// TODO(hardware): BaudRate 0 = HID protocol — replace VID:PID after characterization`.
Commit RED then GREEN.
cd /home/mikkel/homelabby && go test ./internal/tester/... -v -count=1 -run "TestFNB58|TestStream" -timeout 30s
FNB58 streaming tests pass; Stream() emits 3 readings and closes; Disconnect() stops stream; go build ./... passes; race detector clean (add -race flag).
<threat_model>
Trust Boundaries
| Boundary | Description |
|---|---|
| USB serial → tester package | Raw bytes from hardware (not applicable for mocks; real drivers must validate) |
STRIDE Threat Register
| Threat ID | Category | Component | Disposition | Mitigation Plan |
|---|---|---|---|---|
| T-05-01 | Tampering | TestResult deserialization (future real drivers) | accept | Mock phase only — add input validation when real drivers are implemented |
| T-05-02 | DoS | Stream() channel never closed | mitigate | MockFNB58Driver closes channel after N readings; Disconnect() closes channel; goroutine cancelled via context |
| </threat_model> |
<success_criteria>
- internal/tester package exists and compiles
- TesterDriver and StreamingTesterDriver interfaces defined
- MockUSBDriver, MockDPDriver, MockHDMIDriver: deterministic Read(), correct CableType
- MockFNB58Driver: Stream() emits readings, closes on Disconnect(), race-clean
- KnownDevices has 4 placeholder entries (3 Treedix + 1 FNB58), all Role: RoleCableTester
- All existing tests (phases 1-4) continue to pass </success_criteria>