246 lines
10 KiB
Markdown
246 lines
10 KiB
Markdown
---
|
|
phase: 05-cable-test-integration
|
|
plan: "01"
|
|
type: tdd
|
|
wave: 1
|
|
depends_on: []
|
|
files_modified:
|
|
- internal/tester/driver.go
|
|
- internal/tester/mock_drivers.go
|
|
- internal/tester/driver_test.go
|
|
- internal/usb/device.go
|
|
autonomous: true
|
|
requirements: [CBL-01, CBL-02, CBL-03, CBL-04, CBL-07]
|
|
|
|
must_haves:
|
|
truths:
|
|
- "TesterDriver interface exists with Connect/Read/Disconnect/StreamLive methods"
|
|
- "TestResult struct captures all cable fields (type, version, speed, power, continuity, eMarker, resistance)"
|
|
- "MockUSBDriver, MockDPDriver, MockHDMIDriver return deterministic test data without hardware"
|
|
- "MockFNB58Driver emits a stream of LiveReading samples via a channel"
|
|
- "KnownDevices in usb/device.go has placeholder entries for all 4 testers (RoleCableTester)"
|
|
- "All mock drivers satisfy the TesterDriver interface at compile time"
|
|
artifacts:
|
|
- path: "internal/tester/driver.go"
|
|
provides: "TesterDriver interface, TestResult, LiveReading, CableType constants"
|
|
exports: [TesterDriver, TestResult, LiveReading, CableType]
|
|
- path: "internal/tester/mock_drivers.go"
|
|
provides: "MockUSBDriver, MockDPDriver, MockHDMIDriver, MockFNB58Driver"
|
|
- path: "internal/tester/driver_test.go"
|
|
provides: "Tests for all four mock drivers"
|
|
- path: "internal/usb/device.go"
|
|
provides: "KnownDevices entries for all four tester VID:PIDs"
|
|
key_links:
|
|
- from: "internal/usb/device.go KnownDevices"
|
|
to: "internal/tester/driver.go CableType"
|
|
via: "VID:PID keys map to CableType constants for auto-detection"
|
|
- from: "MockFNB58Driver.Stream()"
|
|
to: "chan LiveReading"
|
|
via: "channel closed on Disconnect()"
|
|
---
|
|
|
|
<objective>
|
|
Create the `internal/tester` package: TesterDriver interface, TestResult/LiveReading types, four mock implementations (TreedixUSB/DP/HDMI + FNB58), and VID:PID placeholder entries in the USB device registry.
|
|
|
|
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.
|
|
</objective>
|
|
|
|
<execution_context>
|
|
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
|
|
@$HOME/.claude/get-shit-done/templates/summary.md
|
|
</execution_context>
|
|
|
|
<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
|
|
</context>
|
|
|
|
<interfaces>
|
|
<!-- Key types from existing packages that tester package must integrate with. -->
|
|
|
|
From internal/usb/device.go:
|
|
```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):
|
|
```go
|
|
type PrinterDriver interface {
|
|
Connect() error
|
|
Print(bitmap []byte, width, height int) error
|
|
Disconnect() error
|
|
}
|
|
```
|
|
</interfaces>
|
|
|
|
<tasks>
|
|
|
|
<task type="tdd" tdd="true">
|
|
<name>Task 1: TesterDriver interface, TestResult types, mock discrete drivers (USB/DP/HDMI)</name>
|
|
<files>internal/tester/driver.go, internal/tester/mock_drivers.go, internal/tester/driver_test.go, internal/usb/device.go</files>
|
|
<behavior>
|
|
- 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
|
|
</behavior>
|
|
<action>
|
|
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.
|
|
</action>
|
|
<verify>
|
|
<automated>cd /home/mikkel/homelabby && go test ./internal/tester/... ./internal/usb/... -v -count=1 -run "TestMock|TestKnown"</automated>
|
|
</verify>
|
|
<done>All mock discrete driver tests pass; KnownDevices has 4 entries (1 printer + 3 testers); go build ./... passes.</done>
|
|
</task>
|
|
|
|
<task type="tdd" tdd="true">
|
|
<name>Task 2: FNB58 streaming mock driver</name>
|
|
<files>internal/tester/driver.go, internal/tester/mock_drivers.go, internal/tester/driver_test.go</files>
|
|
<behavior>
|
|
- 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)
|
|
</behavior>
|
|
<action>
|
|
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.
|
|
</action>
|
|
<verify>
|
|
<automated>cd /home/mikkel/homelabby && go test ./internal/tester/... -v -count=1 -run "TestFNB58|TestStream" -timeout 30s</automated>
|
|
</verify>
|
|
<done>FNB58 streaming tests pass; Stream() emits 3 readings and closes; Disconnect() stops stream; go build ./... passes; race detector clean (add -race flag).</done>
|
|
</task>
|
|
|
|
</tasks>
|
|
|
|
<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>
|
|
|
|
<verification>
|
|
```
|
|
go build ./... # must pass
|
|
go test ./internal/tester/... -race -count=1 # all pass, race-clean
|
|
go test ./internal/usb/... -count=1 # existing tests still pass
|
|
grep -c "dead0:" internal/usb/device.go # must print 4
|
|
```
|
|
</verification>
|
|
|
|
<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>
|
|
|
|
<output>
|
|
After completion, create `.planning/phases/05-cable-test-integration/05-01-SUMMARY.md`
|
|
</output>
|