mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-05-02 21:40:14 +00:00
53 test cases covering 4 functions: - gatherTags: environment/flag tag merging (18 cases) - parseLogLevel: log level parsing (30 cases) - defaultLogLevel: default value resolution (10 cases) - multiValue: flag.Value interface for repeatable flags (6 cases) Key difference from pulse-host-agent: parseLogLevel in unified agent delegates directly to zerolog.ParseLevel without range validation, so trace/fatal/panic levels are accepted (unlike pulse-host-agent which restricts to debug-error). First test file for cmd/pulse-agent package.
473 lines
10 KiB
Go
473 lines
10 KiB
Go
package main
|
|
|
|
import (
|
|
"reflect"
|
|
"testing"
|
|
|
|
"github.com/rs/zerolog"
|
|
)
|
|
|
|
func TestGatherTags(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
env string
|
|
flags []string
|
|
expected []string
|
|
}{
|
|
// Empty inputs
|
|
{
|
|
name: "empty env and flags returns empty slice",
|
|
env: "",
|
|
flags: nil,
|
|
expected: []string{},
|
|
},
|
|
{
|
|
name: "empty env and empty flags returns empty slice",
|
|
env: "",
|
|
flags: []string{},
|
|
expected: []string{},
|
|
},
|
|
|
|
// Environment only
|
|
{
|
|
name: "single env tag",
|
|
env: "prod",
|
|
flags: nil,
|
|
expected: []string{"prod"},
|
|
},
|
|
{
|
|
name: "multiple env tags comma separated",
|
|
env: "prod,us-west",
|
|
flags: nil,
|
|
expected: []string{"prod", "us-west"},
|
|
},
|
|
{
|
|
name: "env tags with whitespace trimmed",
|
|
env: " prod , us-west ",
|
|
flags: nil,
|
|
expected: []string{"prod", "us-west"},
|
|
},
|
|
{
|
|
name: "env empty items filtered",
|
|
env: "prod,,us-west,",
|
|
flags: nil,
|
|
expected: []string{"prod", "us-west"},
|
|
},
|
|
{
|
|
name: "env whitespace-only items filtered",
|
|
env: "prod, ,us-west",
|
|
flags: nil,
|
|
expected: []string{"prod", "us-west"},
|
|
},
|
|
|
|
// Flags only
|
|
{
|
|
name: "single flag tag",
|
|
env: "",
|
|
flags: []string{"staging"},
|
|
expected: []string{"staging"},
|
|
},
|
|
{
|
|
name: "multiple flag tags",
|
|
env: "",
|
|
flags: []string{"staging", "eu-central"},
|
|
expected: []string{"staging", "eu-central"},
|
|
},
|
|
{
|
|
name: "flag tags with whitespace trimmed",
|
|
env: "",
|
|
flags: []string{" staging ", " eu-central "},
|
|
expected: []string{"staging", "eu-central"},
|
|
},
|
|
{
|
|
name: "flag empty items filtered",
|
|
env: "",
|
|
flags: []string{"staging", "", "eu-central"},
|
|
expected: []string{"staging", "eu-central"},
|
|
},
|
|
{
|
|
name: "flag whitespace-only items filtered",
|
|
env: "",
|
|
flags: []string{"staging", " ", "eu-central"},
|
|
expected: []string{"staging", "eu-central"},
|
|
},
|
|
|
|
// Both env and flags (env first, then flags)
|
|
{
|
|
name: "env tags come before flags",
|
|
env: "prod",
|
|
flags: []string{"app1"},
|
|
expected: []string{"prod", "app1"},
|
|
},
|
|
{
|
|
name: "multiple env and multiple flags",
|
|
env: "prod,us-west",
|
|
flags: []string{"app1", "critical"},
|
|
expected: []string{"prod", "us-west", "app1", "critical"},
|
|
},
|
|
{
|
|
name: "duplicates preserved (no dedup)",
|
|
env: "prod,prod",
|
|
flags: []string{"prod"},
|
|
expected: []string{"prod", "prod", "prod"},
|
|
},
|
|
|
|
// Edge cases
|
|
{
|
|
name: "only commas in env",
|
|
env: ",,,",
|
|
flags: nil,
|
|
expected: []string{},
|
|
},
|
|
{
|
|
name: "single comma",
|
|
env: ",",
|
|
flags: nil,
|
|
expected: []string{},
|
|
},
|
|
{
|
|
name: "env with tabs",
|
|
env: "\tprod\t,\tstaging\t",
|
|
flags: nil,
|
|
expected: []string{"prod", "staging"},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := gatherTags(tt.env, tt.flags)
|
|
if !reflect.DeepEqual(got, tt.expected) {
|
|
t.Fatalf("expected %v, got %v", tt.expected, got)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestParseLogLevel(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
input string
|
|
wantLevel zerolog.Level
|
|
wantErr bool
|
|
}{
|
|
// Valid levels
|
|
{
|
|
name: "debug level",
|
|
input: "debug",
|
|
wantLevel: zerolog.DebugLevel,
|
|
},
|
|
{
|
|
name: "info level",
|
|
input: "info",
|
|
wantLevel: zerolog.InfoLevel,
|
|
},
|
|
{
|
|
name: "warn level",
|
|
input: "warn",
|
|
wantLevel: zerolog.WarnLevel,
|
|
},
|
|
{
|
|
name: "error level",
|
|
input: "error",
|
|
wantLevel: zerolog.ErrorLevel,
|
|
},
|
|
{
|
|
name: "trace level (accepted in unified agent)",
|
|
input: "trace",
|
|
wantLevel: zerolog.TraceLevel,
|
|
},
|
|
{
|
|
name: "fatal level (accepted in unified agent)",
|
|
input: "fatal",
|
|
wantLevel: zerolog.FatalLevel,
|
|
},
|
|
{
|
|
name: "panic level (accepted in unified agent)",
|
|
input: "panic",
|
|
wantLevel: zerolog.PanicLevel,
|
|
},
|
|
|
|
// Case insensitivity
|
|
{
|
|
name: "uppercase DEBUG",
|
|
input: "DEBUG",
|
|
wantLevel: zerolog.DebugLevel,
|
|
},
|
|
{
|
|
name: "mixed case Info",
|
|
input: "Info",
|
|
wantLevel: zerolog.InfoLevel,
|
|
},
|
|
{
|
|
name: "uppercase WARN",
|
|
input: "WARN",
|
|
wantLevel: zerolog.WarnLevel,
|
|
},
|
|
{
|
|
name: "uppercase ERROR",
|
|
input: "ERROR",
|
|
wantLevel: zerolog.ErrorLevel,
|
|
},
|
|
{
|
|
name: "uppercase TRACE",
|
|
input: "TRACE",
|
|
wantLevel: zerolog.TraceLevel,
|
|
},
|
|
|
|
// Whitespace handling
|
|
{
|
|
name: "leading whitespace",
|
|
input: " debug",
|
|
wantLevel: zerolog.DebugLevel,
|
|
},
|
|
{
|
|
name: "trailing whitespace",
|
|
input: "warn ",
|
|
wantLevel: zerolog.WarnLevel,
|
|
},
|
|
{
|
|
name: "both whitespace",
|
|
input: " error ",
|
|
wantLevel: zerolog.ErrorLevel,
|
|
},
|
|
{
|
|
name: "tabs",
|
|
input: "\tinfo\t",
|
|
wantLevel: zerolog.InfoLevel,
|
|
},
|
|
|
|
// Empty string defaults to info
|
|
{
|
|
name: "empty string defaults to info",
|
|
input: "",
|
|
wantLevel: zerolog.InfoLevel,
|
|
},
|
|
{
|
|
name: "whitespace only defaults to info",
|
|
input: " ",
|
|
wantLevel: zerolog.InfoLevel,
|
|
},
|
|
{
|
|
name: "tabs only defaults to info",
|
|
input: "\t\t",
|
|
wantLevel: zerolog.InfoLevel,
|
|
},
|
|
|
|
// Numeric levels (zerolog supports these)
|
|
{
|
|
name: "numeric -1 maps to trace level",
|
|
input: "-1",
|
|
wantLevel: zerolog.TraceLevel,
|
|
},
|
|
{
|
|
name: "numeric 0 maps to debug level",
|
|
input: "0",
|
|
wantLevel: zerolog.DebugLevel,
|
|
},
|
|
{
|
|
name: "numeric 1 maps to info level",
|
|
input: "1",
|
|
wantLevel: zerolog.InfoLevel,
|
|
},
|
|
{
|
|
name: "numeric 2 maps to warn level",
|
|
input: "2",
|
|
wantLevel: zerolog.WarnLevel,
|
|
},
|
|
{
|
|
name: "numeric 3 maps to error level",
|
|
input: "3",
|
|
wantLevel: zerolog.ErrorLevel,
|
|
},
|
|
{
|
|
name: "numeric 4 maps to fatal level",
|
|
input: "4",
|
|
wantLevel: zerolog.FatalLevel,
|
|
},
|
|
{
|
|
name: "numeric 5 maps to panic level",
|
|
input: "5",
|
|
wantLevel: zerolog.PanicLevel,
|
|
},
|
|
|
|
// Invalid levels
|
|
{
|
|
name: "invalid level returns error",
|
|
input: "invalid",
|
|
wantLevel: zerolog.NoLevel, // zerolog.ParseLevel returns NoLevel on error
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "typo returns error",
|
|
input: "debuf",
|
|
wantLevel: zerolog.NoLevel,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "verbose returns error",
|
|
input: "verbose",
|
|
wantLevel: zerolog.NoLevel,
|
|
wantErr: true,
|
|
},
|
|
{
|
|
name: "numeric out of range accepted (zerolog accepts any int)",
|
|
input: "99",
|
|
wantLevel: zerolog.Level(99),
|
|
wantErr: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
level, err := parseLogLevel(tt.input)
|
|
|
|
if tt.wantErr {
|
|
if err == nil {
|
|
t.Fatalf("expected error, got nil")
|
|
}
|
|
} else {
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
}
|
|
|
|
if level != tt.wantLevel {
|
|
t.Fatalf("expected level %v, got %v", tt.wantLevel, level)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDefaultLogLevel(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
envValue string
|
|
expected string
|
|
}{
|
|
// Empty returns "info"
|
|
{
|
|
name: "empty string returns info",
|
|
envValue: "",
|
|
expected: "info",
|
|
},
|
|
{
|
|
name: "whitespace only returns info",
|
|
envValue: " ",
|
|
expected: "info",
|
|
},
|
|
{
|
|
name: "tabs only returns info",
|
|
envValue: "\t\t",
|
|
expected: "info",
|
|
},
|
|
{
|
|
name: "newline only returns info",
|
|
envValue: "\n",
|
|
expected: "info",
|
|
},
|
|
|
|
// Non-empty returns as-is (no validation)
|
|
{
|
|
name: "debug returns debug",
|
|
envValue: "debug",
|
|
expected: "debug",
|
|
},
|
|
{
|
|
name: "error returns error",
|
|
envValue: "error",
|
|
expected: "error",
|
|
},
|
|
{
|
|
name: "trace returns trace",
|
|
envValue: "trace",
|
|
expected: "trace",
|
|
},
|
|
{
|
|
name: "invalid value passed through",
|
|
envValue: "invalid",
|
|
expected: "invalid",
|
|
},
|
|
{
|
|
name: "mixed case passed through",
|
|
envValue: "DEBUG",
|
|
expected: "DEBUG",
|
|
},
|
|
{
|
|
name: "value with surrounding whitespace NOT trimmed",
|
|
envValue: " debug ",
|
|
expected: " debug ",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := defaultLogLevel(tt.envValue)
|
|
if got != tt.expected {
|
|
t.Fatalf("expected %q, got %q", tt.expected, got)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMultiValue(t *testing.T) {
|
|
t.Run("String joins with comma", func(t *testing.T) {
|
|
mv := multiValue{"a", "b", "c"}
|
|
if got := mv.String(); got != "a,b,c" {
|
|
t.Fatalf("expected %q, got %q", "a,b,c", got)
|
|
}
|
|
})
|
|
|
|
t.Run("String empty slice returns empty string", func(t *testing.T) {
|
|
mv := multiValue{}
|
|
if got := mv.String(); got != "" {
|
|
t.Fatalf("expected %q, got %q", "", got)
|
|
}
|
|
})
|
|
|
|
t.Run("String single item no comma", func(t *testing.T) {
|
|
mv := multiValue{"single"}
|
|
if got := mv.String(); got != "single" {
|
|
t.Fatalf("expected %q, got %q", "single", got)
|
|
}
|
|
})
|
|
|
|
t.Run("Set appends values", func(t *testing.T) {
|
|
mv := multiValue{}
|
|
if err := mv.Set("first"); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if err := mv.Set("second"); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if err := mv.Set("third"); err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
expected := multiValue{"first", "second", "third"}
|
|
if !reflect.DeepEqual(mv, expected) {
|
|
t.Fatalf("expected %v, got %v", expected, mv)
|
|
}
|
|
})
|
|
|
|
t.Run("Set preserves empty strings", func(t *testing.T) {
|
|
mv := multiValue{}
|
|
_ = mv.Set("")
|
|
_ = mv.Set("value")
|
|
_ = mv.Set("")
|
|
|
|
if len(mv) != 3 {
|
|
t.Fatalf("expected 3 items, got %d", len(mv))
|
|
}
|
|
})
|
|
|
|
t.Run("Set always returns nil error", func(t *testing.T) {
|
|
mv := multiValue{}
|
|
// Set always returns nil, testing various inputs
|
|
inputs := []string{"", "normal", "with spaces", "special!@#$%", "unicode日本語"}
|
|
for _, input := range inputs {
|
|
if err := mv.Set(input); err != nil {
|
|
t.Fatalf("expected nil error for input %q, got %v", input, err)
|
|
}
|
|
}
|
|
})
|
|
}
|