mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-02-18 00:17:39 +01:00
83 test cases covering classifyStatus, isNumeric, looksLikeUUID, normalizeSegment, and normalizeRoute functions used for Prometheus metrics aggregation. Tests cover status code classification, numeric string validation, UUID format detection, URL segment normalization, and route path normalization with query param stripping.
234 lines
6.9 KiB
Go
234 lines
6.9 KiB
Go
package api
|
||
|
||
import "testing"
|
||
|
||
func TestClassifyStatus(t *testing.T) {
|
||
tests := []struct {
|
||
name string
|
||
status int
|
||
want string
|
||
}{
|
||
// Server errors (5xx)
|
||
{"500 internal server error", 500, "server_error"},
|
||
{"501 not implemented", 501, "server_error"},
|
||
{"502 bad gateway", 502, "server_error"},
|
||
{"503 service unavailable", 503, "server_error"},
|
||
{"599 edge case", 599, "server_error"},
|
||
|
||
// Client errors (4xx)
|
||
{"400 bad request", 400, "client_error"},
|
||
{"401 unauthorized", 401, "client_error"},
|
||
{"403 forbidden", 403, "client_error"},
|
||
{"404 not found", 404, "client_error"},
|
||
{"429 too many requests", 429, "client_error"},
|
||
{"499 edge case", 499, "client_error"},
|
||
|
||
// Success (2xx)
|
||
{"200 OK", 200, "none"},
|
||
{"201 created", 201, "none"},
|
||
{"204 no content", 204, "none"},
|
||
|
||
// Redirects (3xx)
|
||
{"301 moved permanently", 301, "none"},
|
||
{"302 found", 302, "none"},
|
||
{"304 not modified", 304, "none"},
|
||
|
||
// Informational (1xx)
|
||
{"100 continue", 100, "none"},
|
||
{"101 switching protocols", 101, "none"},
|
||
|
||
// Edge cases
|
||
{"0 zero status", 0, "none"},
|
||
{"399 boundary below client error", 399, "none"},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
got := classifyStatus(tt.status)
|
||
if got != tt.want {
|
||
t.Errorf("classifyStatus(%d) = %q, want %q", tt.status, got, tt.want)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestIsNumeric(t *testing.T) {
|
||
tests := []struct {
|
||
name string
|
||
input string
|
||
want bool
|
||
}{
|
||
// Valid numeric strings
|
||
{"single digit", "0", true},
|
||
{"single digit 9", "9", true},
|
||
{"multiple digits", "123", true},
|
||
{"large number", "9876543210", true},
|
||
{"leading zeros", "007", true},
|
||
|
||
// Invalid strings
|
||
{"empty string", "", false},
|
||
{"letter", "a", false},
|
||
{"mixed alphanumeric", "123abc", false},
|
||
{"space", " ", false},
|
||
{"number with space", "12 34", false},
|
||
{"negative number", "-123", false},
|
||
{"decimal number", "12.34", false},
|
||
{"hexadecimal prefix", "0x10", false},
|
||
{"special characters", "12@34", false},
|
||
{"unicode digits", "123", false}, // fullwidth digits
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
got := isNumeric(tt.input)
|
||
if got != tt.want {
|
||
t.Errorf("isNumeric(%q) = %v, want %v", tt.input, got, tt.want)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestLooksLikeUUID(t *testing.T) {
|
||
tests := []struct {
|
||
name string
|
||
input string
|
||
want bool
|
||
}{
|
||
// Valid UUIDs
|
||
{"lowercase uuid", "550e8400-e29b-41d4-a716-446655440000", true},
|
||
{"uppercase uuid", "550E8400-E29B-41D4-A716-446655440000", true},
|
||
{"mixed case uuid", "550e8400-E29B-41d4-A716-446655440000", true},
|
||
{"nil uuid", "00000000-0000-0000-0000-000000000000", true},
|
||
{"max uuid", "ffffffff-ffff-ffff-ffff-ffffffffffff", true},
|
||
|
||
// Invalid UUIDs - wrong length
|
||
{"empty string", "", false},
|
||
{"too short", "550e8400-e29b-41d4-a716", false},
|
||
{"too long", "550e8400-e29b-41d4-a716-4466554400001", false},
|
||
{"35 chars", "550e8400-e29b-41d4-a716-44665544000", false},
|
||
{"37 chars", "550e8400-e29b-41d4-a716-4466554400001", false},
|
||
|
||
// Invalid UUIDs - wrong dash positions
|
||
{"dash at wrong position 0", "-50e8400-e29b-41d4-a716-44665544000", false},
|
||
{"dash at wrong position 7", "550e840-0e29b-41d4-a716-446655440000", false},
|
||
{"no dashes", "550e8400e29b41d4a716446655440000xxxx", false},
|
||
{"extra dash", "550e8400-e29b-41d4-a716-4466-5544000", false},
|
||
|
||
// Invalid UUIDs - invalid characters
|
||
{"letter g", "g50e8400-e29b-41d4-a716-446655440000", false},
|
||
{"letter z", "550e8400-z29b-41d4-a716-446655440000", false},
|
||
{"space in uuid", "550e8400 e29b-41d4-a716-446655440000", false},
|
||
{"underscore", "550e8400_e29b-41d4-a716-446655440000", false},
|
||
{"special char", "550e8400-e29b-41d4-a716-44665544000!", false},
|
||
|
||
// Edge cases
|
||
{"all zeros no dashes wrong length", "00000000000000000000000000000000xxxx", false},
|
||
{"uuid with braces", "{550e8400-e29b-41d4-a716-446655440000}", false},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
got := looksLikeUUID(tt.input)
|
||
if got != tt.want {
|
||
t.Errorf("looksLikeUUID(%q) = %v, want %v", tt.input, got, tt.want)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestNormalizeSegment(t *testing.T) {
|
||
tests := []struct {
|
||
name string
|
||
input string
|
||
want string
|
||
}{
|
||
// Numeric segments -> :id
|
||
{"numeric id", "123", ":id"},
|
||
{"single digit", "5", ":id"},
|
||
{"large number", "9999999999", ":id"},
|
||
|
||
// UUID segments -> :uuid
|
||
{"uuid", "550e8400-e29b-41d4-a716-446655440000", ":uuid"},
|
||
{"uppercase uuid", "550E8400-E29B-41D4-A716-446655440000", ":uuid"},
|
||
|
||
// Long tokens -> :token
|
||
{"33 char string", "abcdefghijklmnopqrstuvwxyz1234567", ":token"},
|
||
{"64 char string", "abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz12", ":token"},
|
||
|
||
// Regular segments preserved
|
||
{"api", "api", "api"},
|
||
{"v1", "v1", "v1"},
|
||
{"users", "users", "users"},
|
||
{"settings", "settings", "settings"},
|
||
{"exactly 32 chars", "abcdefghijklmnopqrstuvwxyz123456", "abcdefghijklmnopqrstuvwxyz123456"},
|
||
|
||
// Edge cases
|
||
{"empty string", "", ""},
|
||
{"single letter", "a", "a"},
|
||
{"mixed alphanumeric short", "user123", "user123"},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
got := normalizeSegment(tt.input)
|
||
if got != tt.want {
|
||
t.Errorf("normalizeSegment(%q) = %q, want %q", tt.input, got, tt.want)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestNormalizeRoute(t *testing.T) {
|
||
tests := []struct {
|
||
name string
|
||
input string
|
||
want string
|
||
}{
|
||
// Root and empty paths
|
||
{"empty string", "", "/"},
|
||
{"root path", "/", "/"},
|
||
{"root with trailing slash", "//", "/"},
|
||
|
||
// Simple paths
|
||
{"api path", "/api", "/api"},
|
||
{"two segments", "/api/v1", "/api/v1"},
|
||
{"three segments", "/api/v1/users", "/api/v1/users"},
|
||
|
||
// Paths with numeric IDs
|
||
{"path with numeric id", "/api/users/123", "/api/users/:id"},
|
||
{"path with multiple ids", "/api/users/123/posts/456", "/api/users/:id/posts/:id"},
|
||
|
||
// Paths with UUIDs
|
||
{"path with uuid", "/api/users/550e8400-e29b-41d4-a716-446655440000", "/api/users/:uuid"},
|
||
|
||
// Paths with long tokens
|
||
{"path with long token", "/api/auth/abcdefghijklmnopqrstuvwxyz1234567", "/api/auth/:token"},
|
||
|
||
// Query parameters stripped
|
||
{"path with query params", "/api/users?page=1&limit=10", "/api/users"},
|
||
{"path with id and query", "/api/users/123?include=posts", "/api/users/:id"},
|
||
|
||
// Segment limit (max 5)
|
||
{"exactly 5 segments", "/a/b/c/d/e", "/a/b/c/d/e"},
|
||
{"6 segments truncated", "/a/b/c/d/e/f", "/a/b/c/d/e"},
|
||
{"7 segments truncated", "/a/b/c/d/e/f/g", "/a/b/c/d/e"},
|
||
|
||
// Mixed content
|
||
{"complex path", "/api/v1/users/123/posts/550e8400-e29b-41d4-a716-446655440000", "/api/v1/users/:id/posts"},
|
||
|
||
// Edge cases
|
||
{"double slashes", "/api//users", "/api/users"},
|
||
{"trailing slash", "/api/users/", "/api/users"},
|
||
{"leading double slash", "//api/users", "/api/users"},
|
||
}
|
||
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
got := normalizeRoute(tt.input)
|
||
if got != tt.want {
|
||
t.Errorf("normalizeRoute(%q) = %q, want %q", tt.input, got, tt.want)
|
||
}
|
||
})
|
||
}
|
||
}
|