homelabby/.planning/phases/05-cable-test-integration/05-01-PLAN.md
2026-04-10 07:04:57 +00:00

10 KiB

phase plan type wave depends_on files_modified autonomous requirements must_haves
05-cable-test-integration 01 tdd 1
internal/tester/driver.go
internal/tester/mock_drivers.go
internal/tester/driver_test.go
internal/usb/device.go
true
CBL-01
CBL-02
CBL-03
CBL-04
CBL-07
truths artifacts key_links
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
path provides exports
internal/tester/driver.go TesterDriver interface, TestResult, LiveReading, CableType constants
TesterDriver
TestResult
LiveReading
CableType
path provides
internal/tester/mock_drivers.go MockUSBDriver, MockDPDriver, MockHDMIDriver, MockFNB58Driver
path provides
internal/tester/driver_test.go Tests for all four mock drivers
path provides
internal/usb/device.go KnownDevices entries for all four tester VID:PIDs
from to via
internal/usb/device.go KnownDevices internal/tester/driver.go CableType VID:PID keys map to CableType constants for auto-detection
from to via
MockFNB58Driver.Stream() chan LiveReading channel closed on Disconnect()
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.

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

<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>
After completion, create `.planning/phases/05-cable-test-integration/05-01-SUMMARY.md`