From ccc248f4971d3924c91f3cf4bb1f0c30d4571217 Mon Sep 17 00:00:00 2001 From: rcourtman Date: Sun, 30 Nov 2025 04:50:51 +0000 Subject: [PATCH] Add unit tests for sanitizeNodeLabel (pulse-sensor-proxy/metrics.go) --- cmd/pulse-sensor-proxy/metrics_test.go | 227 +++++++++++++++++++++++++ 1 file changed, 227 insertions(+) create mode 100644 cmd/pulse-sensor-proxy/metrics_test.go diff --git a/cmd/pulse-sensor-proxy/metrics_test.go b/cmd/pulse-sensor-proxy/metrics_test.go new file mode 100644 index 0000000..dee2e33 --- /dev/null +++ b/cmd/pulse-sensor-proxy/metrics_test.go @@ -0,0 +1,227 @@ +package main + +import "testing" + +func TestSanitizeNodeLabel(t *testing.T) { + tests := []struct { + name string + input string + want string + }{ + // Basic cases + { + name: "simple hostname", + input: "node1", + want: "node1", + }, + { + name: "hostname with hyphen", + input: "node-1", + want: "node-1", + }, + { + name: "hostname with underscore", + input: "node_1", + want: "node_1", + }, + { + name: "hostname with dot", + input: "node.local", + want: "node.local", + }, + { + name: "fqdn", + input: "node1.example.com", + want: "node1.example.com", + }, + + // Case conversion + { + name: "uppercase converted to lowercase", + input: "Node1", + want: "node1", + }, + { + name: "mixed case", + input: "MyNode-Server", + want: "mynode-server", + }, + { + name: "all uppercase", + input: "PRODSERVER01", + want: "prodserver01", + }, + + // Special character handling + { + name: "space replaced with underscore", + input: "node 1", + want: "node_1", + }, + { + name: "colon replaced with underscore", + input: "node:1", + want: "node_1", + }, + { + name: "at sign replaced with underscore", + input: "user@node", + want: "user_node", + }, + { + name: "forward slash replaced", + input: "path/to/node", + want: "path_to_node", + }, + { + name: "multiple special chars", + input: "node!@#$%^&*()+=[]{}|\\:;<>,?/~`", + want: "node___________________________", + }, + { + name: "unicode replaced with underscore", + input: "节点1", + want: "__1", + }, + { + name: "emoji replaced", + input: "server🔥hot", + want: "server_hot", + }, + + // Length limits + { + name: "exactly 63 chars preserved", + input: "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0", + want: "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0", + }, + { + name: "longer than 63 chars truncated", + input: "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0123456789", + want: "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz0", + }, + { + name: "very long hostname truncated", + input: "this-is-a-very-long-hostname-that-exceeds-the-prometheus-label-value-limit-of-sixty-three-characters", + want: "this-is-a-very-long-hostname-that-exceeds-the-prometheus-label-", + }, + + // Edge cases + { + name: "empty string returns unknown", + input: "", + want: "unknown", + }, + { + name: "all special chars returns unknown", + input: "!@#$%", + want: "_____", + }, + { + name: "single char", + input: "a", + want: "a", + }, + { + name: "numbers only", + input: "12345", + want: "12345", + }, + { + name: "hyphen start preserved", + input: "-node", + want: "-node", + }, + { + name: "underscore start preserved", + input: "_node", + want: "_node", + }, + { + name: "dot start preserved", + input: ".hidden", + want: ".hidden", + }, + + // Realistic node names + { + name: "proxmox node style", + input: "pve-node1", + want: "pve-node1", + }, + { + name: "ip address style", + input: "192.168.1.100", + want: "192.168.1.100", + }, + { + name: "kubernetes node", + input: "k8s-worker-01.cluster.local", + want: "k8s-worker-01.cluster.local", + }, + { + name: "docker container id prefix", + input: "abc123def456", + want: "abc123def456", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := sanitizeNodeLabel(tt.input) + if got != tt.want { + t.Errorf("sanitizeNodeLabel(%q) = %q, want %q", tt.input, got, tt.want) + } + }) + } +} + +func TestSanitizeNodeLabel_MaxLength(t *testing.T) { + // Test that all outputs are <= 63 chars + longInputs := []string{ + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "very-long-hostname-with-many-hyphens-and-lots-of-segments-here", + "node123456789012345678901234567890123456789012345678901234567890", + } + + for _, input := range longInputs { + result := sanitizeNodeLabel(input) + if len(result) > 63 { + t.Errorf("sanitizeNodeLabel(%q) length = %d, want <= 63", input, len(result)) + } + } +} + +func TestSanitizeNodeLabel_NeverEmpty(t *testing.T) { + // Test that output is never empty + emptyishInputs := []string{ + "", + " ", + } + + for _, input := range emptyishInputs { + result := sanitizeNodeLabel(input) + if result == "" { + t.Errorf("sanitizeNodeLabel(%q) returned empty string, should return 'unknown' or sanitized value", input) + } + } +} + +func TestSanitizeNodeLabel_Idempotent(t *testing.T) { + // Applying sanitize twice should give same result as once + inputs := []string{ + "node1", + "Node@Server", + "test.node.local", + "mixed-CASE_name.123", + } + + for _, input := range inputs { + once := sanitizeNodeLabel(input) + twice := sanitizeNodeLabel(once) + if once != twice { + t.Errorf("sanitizeNodeLabel is not idempotent: sanitizeNodeLabel(%q) = %q, sanitizeNodeLabel(%q) = %q", + input, once, once, twice) + } + } +}