package main import ( "bytes" "errors" "strings" "testing" intrgb "netdome.biz/paul/impact-80/internal/rgb" "netdome.biz/paul/impact-80/internal/via" ) // verifyingProtocol records writes and reports what the keyboard actually // applied, which on real hardware is not always the requested value. type verifyingProtocol struct { reports []commandReport applied map[via.LEDType]uint8 getErr error getCalls int } func (f *verifyingProtocol) SetValue(channel via.LEDType, param, value uint8) error { f.reports = append(f.reports, commandReport{channel: channel, param: param, value: value}) return nil } func (f *verifyingProtocol) SetColor(via.LEDType, uint8, uint8) error { return nil } func (f *verifyingProtocol) GetValue(channel via.LEDType, _ uint8) ([]byte, error) { f.getCalls++ if f.getErr != nil { return nil, f.getErr } return []byte{f.applied[channel]}, nil } func (f *verifyingProtocol) Close() error { return nil } func appliedFor(p *verifyingProtocol, channel via.LEDType) uint8 { return p.applied[channel] } func TestSetBrightnessVerifiedReportsAppliedValue(t *testing.T) { tests := []struct { name string requested uint8 applied uint8 wantWarning bool }{ {"keyboard honours the value", 100, 100, false}, {"keyboard clamps to 160", 200, 160, true}, {"keyboard scales up", 100, 159, true}, {"both zones clamp", 255, 160, true}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { proto := &verifyingProtocol{ applied: map[via.LEDType]uint8{ via.RGBLight: tt.applied, via.RGBMatrix: tt.applied, via.SideLight: tt.applied, }, } zones := []intrgb.Zone{intrgb.ZoneLogo} results, err := setBrightnessVerified(proto, zones, tt.requested) if err != nil { t.Fatalf("setBrightnessVerified() error = %v", err) } if len(results) != 1 { t.Fatalf("results = %d entries, want 1", len(results)) } got := results[0] if got.Zone != intrgb.ZoneLogo { t.Errorf("results[0].Zone = %q, want %q", got.Zone, intrgb.ZoneLogo) } if got.Requested != tt.requested { t.Errorf("results[0].Requested = %d, want %d", got.Requested, tt.requested) } if got.Applied != tt.applied { t.Errorf("results[0].Applied = %d, want %d", got.Applied, tt.applied) } if got.Mismatch() != tt.wantWarning { t.Errorf("results[0].Mismatch() = %t, want %t", got.Mismatch(), tt.wantWarning) } }) } } // The read-back must happen once per written zone, on the right channel. func TestSetBrightnessVerifiedReadsBackEveryZone(t *testing.T) { proto := &verifyingProtocol{ applied: map[via.LEDType]uint8{ via.RGBLight: 160, via.RGBMatrix: 255, via.SideLight: 160, }, } results, err := setBrightnessVerified(proto, intrgb.AllZones(), 200) if err != nil { t.Fatalf("setBrightnessVerified() error = %v", err) } if len(results) != 3 { t.Fatalf("results = %d entries, want 3", len(results)) } if proto.getCalls != 3 { t.Errorf("GetValue calls = %d, want 3", proto.getCalls) } // A set that fails must not be reported as applied. if got := appliedFor(proto, via.RGBLight); got != 160 { t.Errorf("unexpected applied value %d", got) } want := map[intrgb.Zone]uint8{ intrgb.ZoneLogo: 160, intrgb.ZoneBacklight: 255, intrgb.ZoneSide: 160, } for _, r := range results { if r.Applied != want[r.Zone] { t.Errorf("%s applied = %d, want %d", r.Zone, r.Applied, want[r.Zone]) } if !r.Mismatch() { t.Errorf("%s: Mismatch() = false, want true (requested 200)", r.Zone) } } } // A failed read-back must not be silently reported as success. func TestSetBrightnessVerifiedPropagatesReadError(t *testing.T) { proto := &verifyingProtocol{getErr: errors.New("read timeout")} if _, err := setBrightnessVerified(proto, []intrgb.Zone{intrgb.ZoneLogo}, 100); err == nil { t.Fatal("setBrightnessVerified() expected read error, got nil") } } func TestBrightnessCommandStaysQuietWhenApplied(t *testing.T) { proto := &verifyingProtocol{applied: map[via.LEDType]uint8{via.RGBLight: 200}} originalOpen := openRGBProtocol originalZone := targetZone t.Cleanup(func() { openRGBProtocol = originalOpen targetZone = originalZone }) openRGBProtocol = func() (rgbProtocol, error) { return proto, nil } targetZone = "logo" var out, errOut bytes.Buffer cmd := NewBrightnessCmd() cmd.SetOut(&out) cmd.SetErr(&errOut) cmd.SetArgs([]string{"200"}) if err := cmd.Execute(); err != nil { t.Fatalf("brightness returned error: %v", err) } if errOut.Len() != 0 { t.Errorf("stderr = %q, want no warning when the keyboard applied the requested value", errOut.String()) } if !strings.Contains(out.String(), "200") { t.Errorf("stdout = %q, want the applied value reported", out.String()) } }