banger/internal/cli/style/style_test.go
Thales Maciel 71a332a6a1
cli: maturity polish — color, error translation, tabwriter consistency
Adds three small but high-leverage presentation tweaks for v0.1:

1. internal/cli/style is a new ~70 LOC package with Pass/Fail/Warn/
   Dim/Bold helpers. Each is TTY-gated and obeys NO_COLOR. No
   external dep. Wired into the doctor PASS/FAIL/WARN status, the
   "banger:" error prefix on stderr, and the dim 'ready in <elapsed>'
   line.
2. internal/cli/errors translates rpc.ErrorResponse into user-facing
   text. operation_failed becomes invisible (the message wins);
   not_found, already_exists, bad_request, bad_version, unauthorized,
   unknown_method get short labels; unknown codes pass through. The
   daemon-attached op_id lands in dim parens — paste into
   journalctl --grep to find the daemon log line that produced the
   failure.
3. Tabwriter config converges on (0, 8, 2, ' ', 0) across every
   list/table command. The vm prune confirmation table picked up the
   right config; system install + system status switched from bare
   "key: value\n" lines to tabular form. printVMSpecLine drops its
   Unicode middle dot for an ASCII '|' so terminals without UTF-8
   render cleanly.

Tests cover translateRPCError for every code, style helpers no-op
on non-TTY and under NO_COLOR. Smoke status greps switch from
"key: value" to "key   value" to match the new format.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-26 22:27:07 -03:00

64 lines
1.9 KiB
Go

package style
import (
"bytes"
"os"
"strings"
"testing"
)
// TestStyleNoOpsForNonTTYWriter pins that styled helpers don't emit
// ANSI escapes when the destination isn't a terminal. Buffers stand
// in for any non-TTY writer (CI, redirected stdout, log files).
func TestStyleNoOpsForNonTTYWriter(t *testing.T) {
var buf bytes.Buffer
cases := map[string]string{
"pass": Pass(&buf, "ok"),
"fail": Fail(&buf, "boom"),
"warn": Warn(&buf, "huh"),
"dim": Dim(&buf, "sub"),
"bold": Bold(&buf, "bold"),
}
for label, got := range cases {
if strings.Contains(got, "\x1b[") {
t.Errorf("%s: contains ANSI escape on non-TTY writer: %q", label, got)
}
}
}
// TestStyleSuppressedByNoColor pins https://no-color.org compliance:
// even on a "real" TTY, NO_COLOR forces plain output.
func TestStyleSuppressedByNoColor(t *testing.T) {
t.Setenv("NO_COLOR", "1")
r, w, err := os.Pipe()
if err != nil {
t.Fatalf("Pipe: %v", err)
}
defer r.Close()
defer w.Close()
// w is a pipe end, not a char device — NO_COLOR is the dominant
// gate but verifying the helper still suppresses guards against
// a future TTY-detection regression that would otherwise need a
// pty harness to surface.
if got := Pass(w, "ok"); strings.Contains(got, "\x1b[") {
t.Errorf("NO_COLOR set but Pass() emitted ANSI: %q", got)
}
if got := Fail(w, "boom"); strings.Contains(got, "\x1b[") {
t.Errorf("NO_COLOR set but Fail() emitted ANSI: %q", got)
}
}
// TestSupportsColorRespectsNoColor confirms the gate function used
// by the helpers. Required for callers that compose multi-segment
// strings and want to ask once.
func TestSupportsColorRespectsNoColor(t *testing.T) {
t.Setenv("NO_COLOR", "1")
tmp, err := os.CreateTemp(t.TempDir(), "style-*")
if err != nil {
t.Fatalf("CreateTemp: %v", err)
}
defer tmp.Close()
if SupportsColor(tmp) {
t.Fatal("SupportsColor returned true with NO_COLOR set")
}
}