mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-02-18 00:17:39 +01:00
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:
282
internal/utils/utils_test.go
Normal file
282
internal/utils/utils_test.go
Normal 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))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user