package config import ( "strings" "testing" ) func TestIsPasswordHashed(t *testing.T) { // Generate a valid bcrypt hash for testing (60 chars, starts with $2) validBcryptHash := "$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy" tests := []struct { name string password string expected bool }{ // Valid bcrypt hashes (60 chars, $2a/$2b/$2y prefix) {"valid bcrypt 2a", validBcryptHash, true}, {"valid bcrypt 2b", "$2b$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy", true}, {"valid bcrypt 2y", "$2y$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWy", true}, {"valid bcrypt different cost", "$2a$12$LQv3c1yqBWVHxkd0LHAkCOYz6TtxMQJqhN8/X4.BFB8hMSWy6s/FO", true}, // Invalid: wrong prefix {"no $2 prefix", "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ123456", false}, {"plain password", "mysecretpassword", false}, {"$1 prefix (MD5)", "$1$saltsalt$hashed", false}, {"$5 prefix (SHA-256)", "$5$rounds=5000$saltsalt$hash", false}, {"$6 prefix (SHA-512)", "$6$rounds=5000$saltsalt$hash", false}, // Invalid: wrong length {"too short", "$2a$10$abc", false}, {"59 chars (truncated)", "$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhW", false}, {"61 chars (too long)", "$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL17lhWyX", false}, {"55 chars (truncated, logs warning)", "$2a$10$N9qo8uLOickgx2ZMRZoMyeIjZAgcfl7p92ldGxad68LJZdL1", false}, // Edge cases {"empty string", "", false}, {"just $2", "$2", false}, {"$2a$", "$2a$", false}, {"$2a$10$", "$2a$10$", false}, {"whitespace", " ", false}, {"$2 but short", "$2a$10$short", false}, // Real-world edge cases {"hash with newline", validBcryptHash + "\n", false}, {"hash with trailing space", validBcryptHash + " ", false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := IsPasswordHashed(tt.password) if result != tt.expected { t.Errorf("IsPasswordHashed(%q) = %v, want %v", tt.password, result, tt.expected) } }) } } func TestIsValidDiscoveryEnvironment(t *testing.T) { tests := []struct { name string value string expected bool }{ // Valid values (case insensitive) {"empty string (auto)", "", true}, {"auto lowercase", "auto", true}, {"AUTO uppercase", "AUTO", true}, {"Auto mixed", "Auto", true}, {"native", "native", true}, {"NATIVE", "NATIVE", true}, {"docker_host", "docker_host", true}, {"DOCKER_HOST", "DOCKER_HOST", true}, {"docker_bridge", "docker_bridge", true}, {"DOCKER_BRIDGE", "DOCKER_BRIDGE", true}, {"lxc_privileged", "lxc_privileged", true}, {"LXC_PRIVILEGED", "LXC_PRIVILEGED", true}, {"lxc_unprivileged", "lxc_unprivileged", true}, {"LXC_UNPRIVILEGED", "LXC_UNPRIVILEGED", true}, // Valid with whitespace trimming {"auto with leading space", " auto", true}, {"auto with trailing space", "auto ", true}, {"auto with both spaces", " auto ", true}, {"native with tabs", "\tnative\t", true}, // Invalid values {"unknown value", "unknown", false}, {"typo dockerhost", "dockerhost", false}, {"typo docker-host", "docker-host", false}, {"kubernetes", "kubernetes", false}, {"vm", "vm", false}, {"baremetal", "baremetal", false}, {"container", "container", false}, {"partial match docker", "docker", false}, {"partial match lxc", "lxc", false}, {"underscore only", "_", false}, {"random string", "xyzabc", false}, {"numeric", "123", false}, {"special chars", "docker@host", false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := IsValidDiscoveryEnvironment(tt.value) if result != tt.expected { t.Errorf("IsValidDiscoveryEnvironment(%q) = %v, want %v", tt.value, result, tt.expected) } }) } } func TestSplitAndTrim(t *testing.T) { tests := []struct { name string input string expected []string }{ // Basic splitting {"single value", "one", []string{"one"}}, {"two values", "one,two", []string{"one", "two"}}, {"three values", "one,two,three", []string{"one", "two", "three"}}, // Whitespace handling {"value with leading space", " one", []string{"one"}}, {"value with trailing space", "one ", []string{"one"}}, {"values with spaces around comma", "one , two", []string{"one", "two"}}, {"values with spaces", " one , two , three ", []string{"one", "two", "three"}}, {"tabs and spaces", "\tone\t,\ttwo\t", []string{"one", "two"}}, // Empty handling {"empty string", "", []string{}}, {"only comma", ",", []string{}}, {"multiple commas", ",,,", []string{}}, {"comma with spaces", " , , ", []string{}}, {"empty between values", "one,,two", []string{"one", "two"}}, {"empty at start", ",one,two", []string{"one", "two"}}, {"empty at end", "one,two,", []string{"one", "two"}}, // Real-world examples {"CIDR list", "10.0.0.0/8, 192.168.0.0/16", []string{"10.0.0.0/8", "192.168.0.0/16"}}, {"hostnames", "node1.local, node2.local, node3.local", []string{"node1.local", "node2.local", "node3.local"}}, {"mixed content", "http://a.com, https://b.com", []string{"http://a.com", "https://b.com"}}, // Edge cases {"single space", " ", []string{}}, {"value is just spaces", " ", []string{}}, {"comma surrounded by spaces value", "one, ,two", []string{"one", "two"}}, {"long value", strings.Repeat("a", 100), []string{strings.Repeat("a", 100)}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := splitAndTrim(tt.input) if len(result) != len(tt.expected) { t.Errorf("splitAndTrim(%q) returned %d items, want %d", tt.input, len(result), len(tt.expected)) t.Errorf("got: %v, want: %v", result, tt.expected) return } for i, v := range result { if v != tt.expected[i] { t.Errorf("splitAndTrim(%q)[%d] = %q, want %q", tt.input, i, v, tt.expected[i]) } } }) } } func TestSanitizeCIDRList(t *testing.T) { tests := []struct { name string input []string expected []string }{ // Nil and empty handling {"nil input", nil, nil}, {"empty slice", []string{}, nil}, // Single entry {"single valid entry", []string{"10.0.0.0/8"}, []string{"10.0.0.0/8"}}, {"single entry with leading space", []string{" 10.0.0.0/8"}, []string{"10.0.0.0/8"}}, {"single entry with trailing space", []string{"10.0.0.0/8 "}, []string{"10.0.0.0/8"}}, {"single entry with both spaces", []string{" 10.0.0.0/8 "}, []string{"10.0.0.0/8"}}, {"single entry with tabs", []string{"\t10.0.0.0/8\t"}, []string{"10.0.0.0/8"}}, // Multiple valid entries (no duplicates) {"two valid entries", []string{"10.0.0.0/8", "192.168.0.0/16"}, []string{"10.0.0.0/8", "192.168.0.0/16"}}, {"three valid entries", []string{"10.0.0.0/8", "192.168.0.0/16", "172.16.0.0/12"}, []string{"10.0.0.0/8", "192.168.0.0/16", "172.16.0.0/12"}}, // Duplicates at various positions {"duplicate at end", []string{"10.0.0.0/8", "192.168.0.0/16", "10.0.0.0/8"}, []string{"10.0.0.0/8", "192.168.0.0/16"}}, {"duplicate at start", []string{"10.0.0.0/8", "10.0.0.0/8", "192.168.0.0/16"}, []string{"10.0.0.0/8", "192.168.0.0/16"}}, {"duplicate in middle", []string{"10.0.0.0/8", "192.168.0.0/16", "10.0.0.0/8", "172.16.0.0/12"}, []string{"10.0.0.0/8", "192.168.0.0/16", "172.16.0.0/12"}}, {"all duplicates", []string{"10.0.0.0/8", "10.0.0.0/8", "10.0.0.0/8"}, []string{"10.0.0.0/8"}}, {"multiple duplicates", []string{"a", "b", "a", "c", "b", "d", "a"}, []string{"a", "b", "c", "d"}}, // Whitespace variations {"entry with leading whitespace", []string{" 10.0.0.0/8", "192.168.0.0/16"}, []string{"10.0.0.0/8", "192.168.0.0/16"}}, {"entry with trailing whitespace", []string{"10.0.0.0/8 ", "192.168.0.0/16"}, []string{"10.0.0.0/8", "192.168.0.0/16"}}, {"mixed whitespace", []string{" 10.0.0.0/8", "192.168.0.0/16 ", " 172.16.0.0/12 "}, []string{"10.0.0.0/8", "192.168.0.0/16", "172.16.0.0/12"}}, {"tabs and spaces mixed", []string{"\t10.0.0.0/8 ", " \t192.168.0.0/16\t ", " 172.16.0.0/12"}, []string{"10.0.0.0/8", "192.168.0.0/16", "172.16.0.0/12"}}, // Entries that become duplicates after trimming {"duplicates after trim", []string{" 10.0.0.0/8", "10.0.0.0/8 ", " 10.0.0.0/8 "}, []string{"10.0.0.0/8"}}, {"duplicates after trim with others", []string{" 10.0.0.0/8", "192.168.0.0/16", "10.0.0.0/8 "}, []string{"10.0.0.0/8", "192.168.0.0/16"}}, // All empty/whitespace entries {"single empty string", []string{""}, []string{}}, {"multiple empty strings", []string{"", "", ""}, []string{}}, {"only whitespace", []string{" ", " ", "\t", " \t "}, []string{}}, {"mixed empty and whitespace", []string{"", " ", "", "\t"}, []string{}}, // Mixed valid and empty entries {"valid with empty at start", []string{"", "10.0.0.0/8", "192.168.0.0/16"}, []string{"10.0.0.0/8", "192.168.0.0/16"}}, {"valid with empty in middle", []string{"10.0.0.0/8", "", "192.168.0.0/16"}, []string{"10.0.0.0/8", "192.168.0.0/16"}}, {"valid with empty at end", []string{"10.0.0.0/8", "192.168.0.0/16", ""}, []string{"10.0.0.0/8", "192.168.0.0/16"}}, {"valid with multiple empty", []string{"", "10.0.0.0/8", "", "192.168.0.0/16", "", ""}, []string{"10.0.0.0/8", "192.168.0.0/16"}}, {"valid with whitespace entries", []string{" ", "10.0.0.0/8", " ", "192.168.0.0/16", "\t"}, []string{"10.0.0.0/8", "192.168.0.0/16"}}, // Order preservation {"order preserved basic", []string{"z", "a", "m"}, []string{"z", "a", "m"}}, {"order preserved with duplicates", []string{"z", "a", "m", "z", "a"}, []string{"z", "a", "m"}}, {"order preserved complex", []string{"third", "first", "second", "third", "first"}, []string{"third", "first", "second"}}, {"order preserved after trim", []string{" c ", "a", " b ", "c", " a "}, []string{"c", "a", "b"}}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { result := sanitizeCIDRList(tt.input) // Check nil vs empty slice distinction if tt.expected == nil { if result != nil { t.Errorf("sanitizeCIDRList(%v) = %v, want nil", tt.input, result) } return } if result == nil { t.Errorf("sanitizeCIDRList(%v) = nil, want %v", tt.input, tt.expected) return } // Check length if len(result) != len(tt.expected) { t.Errorf("sanitizeCIDRList(%v) returned %d items, want %d", tt.input, len(result), len(tt.expected)) t.Errorf("got: %v, want: %v", result, tt.expected) return } // Check each element for i, v := range result { if v != tt.expected[i] { t.Errorf("sanitizeCIDRList(%v)[%d] = %q, want %q", tt.input, i, v, tt.expected[i]) } } }) } }