test: add unit tests for utils package

- Test ID generation (uniqueness, format)
- Test JSON response writing (various types, headers)
- Test boolean parsing (truthy/falsy values)
- Test environment variable trimming
- Test data directory resolution
- Test large payload handling
This commit is contained in:
rcourtman
2025-11-26 13:56:30 +00:00
parent ed81143119
commit 2eb9e61f0e

View File

@@ -0,0 +1,282 @@
package utils
import (
"encoding/json"
"net/http"
"net/http/httptest"
"os"
"strings"
"testing"
)
func TestGenerateID(t *testing.T) {
tests := []struct {
prefix string
}{
{"test"},
{"alert"},
{"node"},
{""},
}
for _, tc := range tests {
t.Run(tc.prefix, func(t *testing.T) {
id := GenerateID(tc.prefix)
// Should start with prefix
if tc.prefix != "" && !strings.HasPrefix(id, tc.prefix+"-") {
t.Errorf("GenerateID(%q) = %q, should start with %q-", tc.prefix, id, tc.prefix)
}
// Should be non-empty
if id == "" {
t.Error("GenerateID() returned empty string")
}
})
}
// IDs should be unique
id1 := GenerateID("test")
id2 := GenerateID("test")
if id1 == id2 {
t.Error("GenerateID() returned duplicate IDs")
}
}
func TestWriteJSONResponse(t *testing.T) {
tests := []struct {
name string
data interface{}
expected string
}{
{
name: "simple object",
data: map[string]string{"key": "value"},
expected: `{"key":"value"}`,
},
{
name: "array",
data: []int{1, 2, 3},
expected: `[1,2,3]`,
},
{
name: "nested object",
data: map[string]interface{}{"outer": map[string]int{"inner": 42}},
expected: `{"outer":{"inner":42}}`,
},
{
name: "empty object",
data: map[string]string{},
expected: `{}`,
},
{
name: "null",
data: nil,
expected: `null`,
},
{
name: "struct",
data: struct {
Name string `json:"name"`
Count int `json:"count"`
}{Name: "test", Count: 5},
expected: `{"name":"test","count":5}`,
},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
w := httptest.NewRecorder()
err := WriteJSONResponse(w, tc.data)
if err != nil {
t.Fatalf("WriteJSONResponse() error: %v", err)
}
// Check content type
ct := w.Header().Get("Content-Type")
if ct != "application/json" {
t.Errorf("Content-Type = %q, want %q", ct, "application/json")
}
// Check body
body := w.Body.String()
if body != tc.expected {
t.Errorf("Body = %q, want %q", body, tc.expected)
}
})
}
}
func TestWriteJSONResponse_InvalidData(t *testing.T) {
w := httptest.NewRecorder()
// Channels cannot be marshaled to JSON
ch := make(chan int)
err := WriteJSONResponse(w, ch)
if err == nil {
t.Error("WriteJSONResponse() should fail on unmarshalable data")
}
}
func TestWriteJSONResponse_StatusCode(t *testing.T) {
w := httptest.NewRecorder()
// Set status code before writing
w.WriteHeader(http.StatusCreated)
err := WriteJSONResponse(w, map[string]string{"status": "created"})
if err != nil {
t.Fatalf("WriteJSONResponse() error: %v", err)
}
if w.Code != http.StatusCreated {
t.Errorf("Status code = %d, want %d", w.Code, http.StatusCreated)
}
}
func TestParseBool(t *testing.T) {
tests := []struct {
input string
expected bool
}{
// Truthy values
{"true", true},
{"TRUE", true},
{"True", true},
{"1", true},
{"yes", true},
{"YES", true},
{"Yes", true},
{"y", true},
{"Y", true},
{"on", true},
{"ON", true},
{"On", true},
// Falsy values
{"false", false},
{"FALSE", false},
{"0", false},
{"no", false},
{"n", false},
{"off", false},
{"", false},
{"random", false},
{"2", false},
// With whitespace
{" true ", true},
{" false ", false},
{"\ttrue\n", true},
}
for _, tc := range tests {
t.Run(tc.input, func(t *testing.T) {
result := ParseBool(tc.input)
if result != tc.expected {
t.Errorf("ParseBool(%q) = %v, want %v", tc.input, result, tc.expected)
}
})
}
}
func TestGetenvTrim(t *testing.T) {
// Set test environment variable
testKey := "TEST_GETENVTRIM_VAR"
tests := []struct {
name string
value string
expected string
}{
{"no whitespace", "value", "value"},
{"leading space", " value", "value"},
{"trailing space", "value ", "value"},
{"both sides", " value ", "value"},
{"tabs", "\tvalue\t", "value"},
{"newlines", "\nvalue\n", "value"},
{"empty", "", ""},
{"only whitespace", " ", ""},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
os.Setenv(testKey, tc.value)
defer os.Unsetenv(testKey)
result := GetenvTrim(testKey)
if result != tc.expected {
t.Errorf("GetenvTrim(%q) with value %q = %q, want %q", testKey, tc.value, result, tc.expected)
}
})
}
// Test unset variable
os.Unsetenv(testKey)
result := GetenvTrim(testKey)
if result != "" {
t.Errorf("GetenvTrim() for unset var = %q, want empty string", result)
}
}
func TestGetDataDir(t *testing.T) {
envKey := "PULSE_DATA_DIR"
originalValue := os.Getenv(envKey)
defer func() {
if originalValue != "" {
os.Setenv(envKey, originalValue)
} else {
os.Unsetenv(envKey)
}
}()
// Test with env var set
os.Setenv(envKey, "/custom/data/dir")
result := GetDataDir()
if result != "/custom/data/dir" {
t.Errorf("GetDataDir() with env = %q, want /custom/data/dir", result)
}
// Test with env var unset (default)
os.Unsetenv(envKey)
result = GetDataDir()
if result != "/etc/pulse" {
t.Errorf("GetDataDir() without env = %q, want /etc/pulse", result)
}
// Test with empty env var (should use default)
os.Setenv(envKey, "")
result = GetDataDir()
if result != "/etc/pulse" {
t.Errorf("GetDataDir() with empty env = %q, want /etc/pulse", result)
}
}
func TestWriteJSONResponse_LargePayload(t *testing.T) {
w := httptest.NewRecorder()
// Create a large payload
data := make([]map[string]interface{}, 1000)
for i := 0; i < 1000; i++ {
data[i] = map[string]interface{}{
"index": i,
"name": strings.Repeat("x", 100),
}
}
err := WriteJSONResponse(w, data)
if err != nil {
t.Fatalf("WriteJSONResponse() error on large payload: %v", err)
}
// Verify it's valid JSON
var decoded []map[string]interface{}
if err := json.Unmarshal(w.Body.Bytes(), &decoded); err != nil {
t.Errorf("Response is not valid JSON: %v", err)
}
if len(decoded) != 1000 {
t.Errorf("Decoded length = %d, want 1000", len(decoded))
}
}