package main import ( "bytes" "encoding/json" "errors" "strings" "testing" intdevice "netdome.biz/paul/qmk-rgb/internal/device" ) type deviceInfoOutput struct { Devices []keyboardLine `json:"devices"` Total int `json:"total"` } func stubDiscovery(t *testing.T, devices []intdevice.Device) { t.Helper() orig := discoverAll discoverAll = func() ([]intdevice.Device, error) { return devices, nil } t.Cleanup(func() { discoverAll = orig }) } // runRealKeyboardInfo executes the shipped command so a re-added // human-readable banner is caught, not just changes inside a helper. func runRealKeyboardInfo(t *testing.T) (stdout, stderr string, err error) { t.Helper() var out, errOut bytes.Buffer origOut, origErr := keyboardInfoCmd.OutOrStdout(), keyboardInfoCmd.ErrOrStderr() keyboardInfoCmd.SetOut(&out) keyboardInfoCmd.SetErr(&errOut) t.Cleanup(func() { keyboardInfoCmd.SetOut(origOut) keyboardInfoCmd.SetErr(origErr) }) err = keyboardInfoCmd.RunE(keyboardInfoCmd, nil) return out.String(), errOut.String(), err } func TestKeyboardInfoWritesOnlyJSON(t *testing.T) { withJSON(t) stubDiscovery(t, []intdevice.Device{ {Index: 1, Path: "/dev/hidraw7", VendorID: 0x36b0, ProductID: 0x309f, Name: "Impact 80"}, }) stdout, stderr, err := runRealKeyboardInfo(t) if err != nil { t.Fatalf("keyboard info returned error: %v", err) } if stderr != "" { t.Errorf("keyboard info wrote %q to stderr, want no human-readable banner next to the JSON", stderr) } var got deviceInfoOutput if err := json.Unmarshal([]byte(stdout), &got); err != nil { t.Fatalf("stdout is not valid JSON: %v (output %q)", err, stdout) } if got.Total != 1 { t.Errorf("total = %d, want 1", got.Total) } if len(got.Devices) != 1 { t.Fatalf("devices = %d entries, want 1", len(got.Devices)) } if got.Devices[0].Index != 1 { t.Errorf("devices[0].index = %d, want 1", got.Devices[0].Index) } if got.Devices[0].Path != "/dev/hidraw7" { t.Errorf("devices[0].path = %q, want %q", got.Devices[0].Path, "/dev/hidraw7") } } func TestKeyboardInfoEmitsIndentedJSON(t *testing.T) { withJSON(t) stubDiscovery(t, []intdevice.Device{ {Index: 1, Path: "/dev/hidraw7", Name: "Impact 80"}, }) stdout, _, err := runRealKeyboardInfo(t) if err != nil { t.Fatalf("keyboard info returned error: %v", err) } if !strings.Contains(stdout, "\n \"devices\": [") { t.Errorf("stdout = %q, want two-space indented JSON like the other commands emit", stdout) } if !strings.HasSuffix(stdout, "\n") { t.Errorf("stdout = %q, want a trailing newline", stdout) } } func TestKeyboardInfoEmitsEmptyArrayNotNull(t *testing.T) { withJSON(t) stubDiscovery(t, nil) stdout, _, err := runRealKeyboardInfo(t) if err != nil { t.Fatalf("keyboard info returned error: %v", err) } if !strings.Contains(stdout, `"devices": []`) { t.Errorf("stdout = %q, want an empty array so consumers can iterate unconditionally", stdout) } } func TestKeyboardInfoPropagatesDiscoveryError(t *testing.T) { orig := discoverAll discoverAll = func() ([]intdevice.Device, error) { return nil, errStub } t.Cleanup(func() { discoverAll = orig }) _, _, err := runRealKeyboardInfo(t) if err == nil { t.Fatal("keyboard info expected discovery error, got nil") } if !strings.Contains(err.Error(), "enumerate") { t.Errorf("error = %v, want it to wrap the discovery failure", err) } } var errStub = errors.New("enumerate: stub failure")