test: Add unit tests for docker_host_identity.go pure functions
Tests for all 9 functions handling Docker host identification: - uniqueNonEmptyStrings: dedupe with order preservation - sanitizeDockerHostSuffix: string cleaning for IDs - tokenHintFromRecord: redacted token hints - dockerHostIDExists: ID existence check - dockerHostSuffixCandidates: candidate suffix generation - fallbackDockerHostID: hash-based ID generation - findMatchingDockerHost: host matching by various criteria - generateDockerHostIdentifier: unique ID generation with suffixes - resolveDockerHostIdentifier: main resolver orchestration 86 test cases covering edge cases (nil, empty, whitespace), Unicode handling, and complex matching scenarios.
This commit is contained in:
parent
19bc5f7977
commit
611e606513
1 changed files with 969 additions and 0 deletions
969
internal/monitoring/docker_host_identity_test.go
Normal file
969
internal/monitoring/docker_host_identity_test.go
Normal file
|
|
@ -0,0 +1,969 @@
|
|||
package monitoring
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/config"
|
||||
"github.com/rcourtman/pulse-go-rewrite/internal/models"
|
||||
agentsdocker "github.com/rcourtman/pulse-go-rewrite/pkg/agents/docker"
|
||||
)
|
||||
|
||||
func TestUniqueNonEmptyStrings(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
input []string
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
name: "empty input",
|
||||
input: []string{},
|
||||
expected: []string{},
|
||||
},
|
||||
{
|
||||
name: "all empty strings",
|
||||
input: []string{"", "", ""},
|
||||
expected: []string{},
|
||||
},
|
||||
{
|
||||
name: "all whitespace strings",
|
||||
input: []string{" ", "\t", "\n"},
|
||||
expected: []string{},
|
||||
},
|
||||
{
|
||||
name: "duplicates removed",
|
||||
input: []string{"foo", "bar", "foo", "baz"},
|
||||
expected: []string{"foo", "bar", "baz"},
|
||||
},
|
||||
{
|
||||
name: "order preserved",
|
||||
input: []string{"first", "second", "third"},
|
||||
expected: []string{"first", "second", "third"},
|
||||
},
|
||||
{
|
||||
name: "whitespace trimmed",
|
||||
input: []string{" foo ", "bar", " foo"},
|
||||
expected: []string{"foo", "bar"},
|
||||
},
|
||||
{
|
||||
name: "mixed empty and non-empty",
|
||||
input: []string{"", "foo", "", "bar", "foo"},
|
||||
expected: []string{"foo", "bar"},
|
||||
},
|
||||
{
|
||||
name: "single value",
|
||||
input: []string{"value"},
|
||||
expected: []string{"value"},
|
||||
},
|
||||
{
|
||||
name: "nil input",
|
||||
input: nil,
|
||||
expected: []string{},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
result := uniqueNonEmptyStrings(tt.input...)
|
||||
if len(result) != len(tt.expected) {
|
||||
t.Errorf("length mismatch: got %d, want %d", len(result), len(tt.expected))
|
||||
return
|
||||
}
|
||||
for i := range result {
|
||||
if result[i] != tt.expected[i] {
|
||||
t.Errorf("index %d: got %q, want %q", i, result[i], tt.expected[i])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSanitizeDockerHostSuffix(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "empty string",
|
||||
input: "",
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "whitespace only",
|
||||
input: " ",
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "lowercase conversion",
|
||||
input: "UPPERCASE",
|
||||
expected: "uppercase",
|
||||
},
|
||||
{
|
||||
name: "special chars to hyphens",
|
||||
input: "foo@bar!baz",
|
||||
expected: "foo-bar-baz",
|
||||
},
|
||||
{
|
||||
name: "consecutive special chars become single hyphen",
|
||||
input: "foo@@@bar",
|
||||
expected: "foo-bar",
|
||||
},
|
||||
{
|
||||
name: "leading hyphen trimmed",
|
||||
input: "@@@foo",
|
||||
expected: "foo",
|
||||
},
|
||||
{
|
||||
name: "trailing hyphen trimmed",
|
||||
input: "foo@@@",
|
||||
expected: "foo",
|
||||
},
|
||||
{
|
||||
name: "leading and trailing hyphens trimmed",
|
||||
input: "@@foo@@",
|
||||
expected: "foo",
|
||||
},
|
||||
{
|
||||
name: "truncation at 48 runes",
|
||||
input: "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijklmnopqrstuvwxyz",
|
||||
expected: "abcdefghijklmnopqrstuvwxyz0123456789abcdefghijkl",
|
||||
},
|
||||
{
|
||||
name: "unicode letters preserved",
|
||||
input: "café",
|
||||
expected: "café",
|
||||
},
|
||||
{
|
||||
name: "digits preserved",
|
||||
input: "host123",
|
||||
expected: "host123",
|
||||
},
|
||||
{
|
||||
name: "all special chars",
|
||||
input: "@@@@",
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "mixed case with spaces",
|
||||
input: "My Docker Host",
|
||||
expected: "my-docker-host",
|
||||
},
|
||||
{
|
||||
name: "truncation with trailing special char",
|
||||
input: "abcdefghijklmnopqrstuvwxyz0123456789abcdefghij@@@",
|
||||
expected: "abcdefghijklmnopqrstuvwxyz0123456789abcdefghij",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
result := sanitizeDockerHostSuffix(tt.input)
|
||||
if result != tt.expected {
|
||||
t.Errorf("got %q, want %q", result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestTokenHintFromRecord(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
record *config.APITokenRecord
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "nil record",
|
||||
record: nil,
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "both prefix and suffix",
|
||||
record: &config.APITokenRecord{
|
||||
Prefix: "ps_abc",
|
||||
Suffix: "xyz",
|
||||
},
|
||||
expected: "ps_abc…xyz",
|
||||
},
|
||||
{
|
||||
name: "only prefix",
|
||||
record: &config.APITokenRecord{
|
||||
Prefix: "ps_abc",
|
||||
},
|
||||
expected: "ps_abc…",
|
||||
},
|
||||
{
|
||||
name: "only suffix",
|
||||
record: &config.APITokenRecord{
|
||||
Suffix: "xyz",
|
||||
},
|
||||
expected: "…xyz",
|
||||
},
|
||||
{
|
||||
name: "neither prefix nor suffix",
|
||||
record: &config.APITokenRecord{},
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "empty strings",
|
||||
record: &config.APITokenRecord{
|
||||
Prefix: "",
|
||||
Suffix: "",
|
||||
},
|
||||
expected: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
result := tokenHintFromRecord(tt.record)
|
||||
if result != tt.expected {
|
||||
t.Errorf("got %q, want %q", result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDockerHostIDExists(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
hosts := []models.DockerHost{
|
||||
{ID: "host1"},
|
||||
{ID: "host2"},
|
||||
{ID: "host3"},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
id string
|
||||
hosts []models.DockerHost
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "empty id",
|
||||
id: "",
|
||||
hosts: hosts,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "whitespace id",
|
||||
id: " ",
|
||||
hosts: hosts,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "id found",
|
||||
id: "host2",
|
||||
hosts: hosts,
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: "id not found",
|
||||
id: "host4",
|
||||
hosts: hosts,
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "empty hosts list",
|
||||
id: "host1",
|
||||
hosts: []models.DockerHost{},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "nil hosts list",
|
||||
id: "host1",
|
||||
hosts: nil,
|
||||
expected: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
result := dockerHostIDExists(tt.id, tt.hosts)
|
||||
if result != tt.expected {
|
||||
t.Errorf("got %v, want %v", result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDockerHostSuffixCandidates(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
report agentsdocker.Report
|
||||
tokenRecord *config.APITokenRecord
|
||||
expected []string
|
||||
}{
|
||||
{
|
||||
name: "nil tokenRecord, empty report",
|
||||
report: agentsdocker.Report{},
|
||||
tokenRecord: nil,
|
||||
expected: []string{},
|
||||
},
|
||||
{
|
||||
name: "tokenRecord with ID",
|
||||
report: agentsdocker.Report{
|
||||
Agent: agentsdocker.AgentInfo{ID: "agent-123"},
|
||||
},
|
||||
tokenRecord: &config.APITokenRecord{ID: "token-456"},
|
||||
expected: []string{"token-token-456", "agent-agent-123"},
|
||||
},
|
||||
{
|
||||
name: "agent ID only",
|
||||
report: agentsdocker.Report{
|
||||
Agent: agentsdocker.AgentInfo{ID: "agent-abc"},
|
||||
},
|
||||
tokenRecord: nil,
|
||||
expected: []string{"agent-agent-abc"},
|
||||
},
|
||||
{
|
||||
name: "machine ID only",
|
||||
report: agentsdocker.Report{
|
||||
Host: agentsdocker.HostInfo{MachineID: "machine-xyz"},
|
||||
},
|
||||
tokenRecord: nil,
|
||||
expected: []string{"machine-machine-xyz"},
|
||||
},
|
||||
{
|
||||
name: "hostname only",
|
||||
report: agentsdocker.Report{
|
||||
Host: agentsdocker.HostInfo{Hostname: "my-host"},
|
||||
},
|
||||
tokenRecord: nil,
|
||||
expected: []string{"host-my-host"},
|
||||
},
|
||||
{
|
||||
name: "display name different from hostname",
|
||||
report: agentsdocker.Report{
|
||||
Host: agentsdocker.HostInfo{
|
||||
Hostname: "host1",
|
||||
Name: "Custom Name",
|
||||
},
|
||||
},
|
||||
tokenRecord: nil,
|
||||
expected: []string{"host-host1", "name-custom-name"},
|
||||
},
|
||||
{
|
||||
name: "display name same as hostname",
|
||||
report: agentsdocker.Report{
|
||||
Host: agentsdocker.HostInfo{
|
||||
Hostname: "my-host",
|
||||
Name: "my-host",
|
||||
},
|
||||
},
|
||||
tokenRecord: nil,
|
||||
expected: []string{"host-my-host"},
|
||||
},
|
||||
{
|
||||
name: "all fields present",
|
||||
report: agentsdocker.Report{
|
||||
Agent: agentsdocker.AgentInfo{ID: "agent-1"},
|
||||
Host: agentsdocker.HostInfo{
|
||||
MachineID: "machine-1",
|
||||
Hostname: "host-1",
|
||||
Name: "Display-1",
|
||||
},
|
||||
},
|
||||
tokenRecord: &config.APITokenRecord{ID: "token-1"},
|
||||
expected: []string{"token-token-1", "agent-agent-1", "machine-machine-1", "host-host-1", "name-display-1"},
|
||||
},
|
||||
{
|
||||
name: "sanitization applies",
|
||||
report: agentsdocker.Report{
|
||||
Agent: agentsdocker.AgentInfo{ID: "Agent@123"},
|
||||
},
|
||||
tokenRecord: nil,
|
||||
expected: []string{"agent-agent-123"},
|
||||
},
|
||||
{
|
||||
name: "duplicates removed after sanitization",
|
||||
report: agentsdocker.Report{
|
||||
Host: agentsdocker.HostInfo{
|
||||
Hostname: "my-host",
|
||||
Name: "MY-HOST",
|
||||
},
|
||||
},
|
||||
tokenRecord: nil,
|
||||
expected: []string{"host-my-host"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
result := dockerHostSuffixCandidates(tt.report, tt.tokenRecord)
|
||||
if len(result) != len(tt.expected) {
|
||||
t.Errorf("length mismatch: got %d, want %d\ngot: %v\nwant: %v", len(result), len(tt.expected), result, tt.expected)
|
||||
return
|
||||
}
|
||||
for i := range result {
|
||||
if result[i] != tt.expected[i] {
|
||||
t.Errorf("index %d: got %q, want %q", i, result[i], tt.expected[i])
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFallbackDockerHostID(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
report agentsdocker.Report
|
||||
tokenRecord *config.APITokenRecord
|
||||
expectEmpty bool
|
||||
expectHash bool
|
||||
}{
|
||||
{
|
||||
name: "empty candidates",
|
||||
report: agentsdocker.Report{},
|
||||
tokenRecord: nil,
|
||||
expectEmpty: true,
|
||||
},
|
||||
{
|
||||
name: "has candidates",
|
||||
report: agentsdocker.Report{
|
||||
Agent: agentsdocker.AgentInfo{ID: "agent-123"},
|
||||
},
|
||||
tokenRecord: nil,
|
||||
expectHash: true,
|
||||
},
|
||||
{
|
||||
name: "multiple candidates",
|
||||
report: agentsdocker.Report{
|
||||
Agent: agentsdocker.AgentInfo{ID: "agent-123"},
|
||||
Host: agentsdocker.HostInfo{
|
||||
MachineID: "machine-456",
|
||||
Hostname: "my-host",
|
||||
},
|
||||
},
|
||||
tokenRecord: nil,
|
||||
expectHash: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
result := fallbackDockerHostID(tt.report, tt.tokenRecord)
|
||||
if tt.expectEmpty {
|
||||
if result != "" {
|
||||
t.Errorf("expected empty string, got %q", result)
|
||||
}
|
||||
}
|
||||
if tt.expectHash {
|
||||
if result == "" {
|
||||
t.Errorf("expected non-empty hash, got empty string")
|
||||
}
|
||||
if len(result) < len("docker-host-") {
|
||||
t.Errorf("expected docker-host- prefix, got %q", result)
|
||||
}
|
||||
if result[:12] != "docker-host-" {
|
||||
t.Errorf("expected docker-host- prefix, got %q", result[:12])
|
||||
}
|
||||
// Should be 12 hex chars after prefix (6 bytes)
|
||||
if len(result) != 12+12 {
|
||||
t.Errorf("expected length %d, got %d: %q", 12+12, len(result), result)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestFindMatchingDockerHost(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
hosts := []models.DockerHost{
|
||||
{
|
||||
ID: "host1",
|
||||
AgentID: "agent-1",
|
||||
TokenID: "token-1",
|
||||
MachineID: "machine-1",
|
||||
Hostname: "hostname-1",
|
||||
},
|
||||
{
|
||||
ID: "host2",
|
||||
AgentID: "agent-2",
|
||||
TokenID: "",
|
||||
MachineID: "machine-2",
|
||||
Hostname: "hostname-2",
|
||||
},
|
||||
{
|
||||
ID: "host3",
|
||||
AgentID: "",
|
||||
TokenID: "",
|
||||
MachineID: "machine-3",
|
||||
Hostname: "hostname-3",
|
||||
},
|
||||
{
|
||||
ID: "host4",
|
||||
AgentID: "agent-4",
|
||||
TokenID: "token-4",
|
||||
MachineID: "machine-4",
|
||||
Hostname: "hostname-4",
|
||||
},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
hosts []models.DockerHost
|
||||
report agentsdocker.Report
|
||||
tokenRecord *config.APITokenRecord
|
||||
expectedID string
|
||||
expectMatch bool
|
||||
}{
|
||||
{
|
||||
name: "empty hosts",
|
||||
hosts: []models.DockerHost{},
|
||||
report: agentsdocker.Report{},
|
||||
tokenRecord: nil,
|
||||
expectMatch: false,
|
||||
},
|
||||
{
|
||||
name: "match by agentID with matching token",
|
||||
hosts: hosts,
|
||||
report: agentsdocker.Report{
|
||||
Agent: agentsdocker.AgentInfo{ID: "agent-1"},
|
||||
},
|
||||
tokenRecord: &config.APITokenRecord{ID: "token-1"},
|
||||
expectedID: "host1",
|
||||
expectMatch: true,
|
||||
},
|
||||
{
|
||||
name: "match by agentID with no token",
|
||||
hosts: hosts,
|
||||
report: agentsdocker.Report{
|
||||
Agent: agentsdocker.AgentInfo{ID: "agent-2"},
|
||||
},
|
||||
tokenRecord: nil,
|
||||
expectedID: "host2",
|
||||
expectMatch: true,
|
||||
},
|
||||
{
|
||||
name: "match by machineID and hostname with matching token",
|
||||
hosts: hosts,
|
||||
report: agentsdocker.Report{
|
||||
Host: agentsdocker.HostInfo{
|
||||
MachineID: "machine-4",
|
||||
Hostname: "hostname-4",
|
||||
},
|
||||
},
|
||||
tokenRecord: &config.APITokenRecord{ID: "token-4"},
|
||||
expectedID: "host4",
|
||||
expectMatch: true,
|
||||
},
|
||||
{
|
||||
name: "match by machineID and hostname with no token",
|
||||
hosts: hosts,
|
||||
report: agentsdocker.Report{
|
||||
Host: agentsdocker.HostInfo{
|
||||
MachineID: "machine-3",
|
||||
Hostname: "hostname-3",
|
||||
},
|
||||
},
|
||||
tokenRecord: nil,
|
||||
expectedID: "host3",
|
||||
expectMatch: true,
|
||||
},
|
||||
{
|
||||
name: "match by machineID only (no token)",
|
||||
hosts: hosts,
|
||||
report: agentsdocker.Report{
|
||||
Host: agentsdocker.HostInfo{
|
||||
MachineID: "machine-3",
|
||||
},
|
||||
},
|
||||
tokenRecord: nil,
|
||||
expectedID: "host3",
|
||||
expectMatch: true,
|
||||
},
|
||||
{
|
||||
name: "match by hostname only (no token)",
|
||||
hosts: hosts,
|
||||
report: agentsdocker.Report{
|
||||
Host: agentsdocker.HostInfo{
|
||||
Hostname: "hostname-3",
|
||||
},
|
||||
},
|
||||
tokenRecord: nil,
|
||||
expectedID: "host3",
|
||||
expectMatch: true,
|
||||
},
|
||||
{
|
||||
name: "no match - token mismatch on agentID",
|
||||
hosts: hosts,
|
||||
report: agentsdocker.Report{
|
||||
Agent: agentsdocker.AgentInfo{ID: "agent-1"},
|
||||
},
|
||||
tokenRecord: &config.APITokenRecord{ID: "wrong-token"},
|
||||
expectMatch: false,
|
||||
},
|
||||
{
|
||||
name: "no match - token mismatch on machineID+hostname",
|
||||
hosts: hosts,
|
||||
report: agentsdocker.Report{
|
||||
Host: agentsdocker.HostInfo{
|
||||
MachineID: "machine-1",
|
||||
Hostname: "hostname-1",
|
||||
},
|
||||
},
|
||||
tokenRecord: &config.APITokenRecord{ID: "wrong-token"},
|
||||
expectMatch: false,
|
||||
},
|
||||
{
|
||||
name: "no match - unknown agent",
|
||||
hosts: hosts,
|
||||
report: agentsdocker.Report{
|
||||
Agent: agentsdocker.AgentInfo{ID: "agent-unknown"},
|
||||
},
|
||||
tokenRecord: nil,
|
||||
expectMatch: false,
|
||||
},
|
||||
{
|
||||
name: "whitespace trimming on agentID",
|
||||
hosts: hosts,
|
||||
report: agentsdocker.Report{
|
||||
Agent: agentsdocker.AgentInfo{ID: " agent-1 "},
|
||||
},
|
||||
tokenRecord: &config.APITokenRecord{ID: "token-1"},
|
||||
expectedID: "host1",
|
||||
expectMatch: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
result, found := findMatchingDockerHost(tt.hosts, tt.report, tt.tokenRecord)
|
||||
if found != tt.expectMatch {
|
||||
t.Errorf("found mismatch: got %v, want %v", found, tt.expectMatch)
|
||||
}
|
||||
if tt.expectMatch && result.ID != tt.expectedID {
|
||||
t.Errorf("ID mismatch: got %q, want %q", result.ID, tt.expectedID)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateDockerHostIdentifier(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
base string
|
||||
report agentsdocker.Report
|
||||
tokenRecord *config.APITokenRecord
|
||||
hosts []models.DockerHost
|
||||
expectedContains string
|
||||
expectFormat string // "base", "suffix", "hash", "numeric"
|
||||
}{
|
||||
{
|
||||
name: "empty base uses fallback",
|
||||
base: "",
|
||||
report: agentsdocker.Report{Agent: agentsdocker.AgentInfo{ID: "agent-1"}},
|
||||
tokenRecord: nil,
|
||||
hosts: []models.DockerHost{},
|
||||
expectFormat: "suffix",
|
||||
},
|
||||
{
|
||||
name: "base available without suffix",
|
||||
base: "my-host",
|
||||
report: agentsdocker.Report{
|
||||
Agent: agentsdocker.AgentInfo{ID: "agent-1"},
|
||||
},
|
||||
tokenRecord: nil,
|
||||
hosts: []models.DockerHost{
|
||||
{ID: "other-host"},
|
||||
},
|
||||
expectedContains: "my-host::agent-agent-1",
|
||||
expectFormat: "suffix",
|
||||
},
|
||||
{
|
||||
name: "tries multiple suffixes",
|
||||
base: "my-host",
|
||||
report: agentsdocker.Report{
|
||||
Agent: agentsdocker.AgentInfo{ID: "agent-1"},
|
||||
Host: agentsdocker.HostInfo{MachineID: "machine-1"},
|
||||
},
|
||||
tokenRecord: nil,
|
||||
hosts: []models.DockerHost{
|
||||
{ID: "my-host::agent-agent-1"},
|
||||
},
|
||||
expectedContains: "my-host::machine-machine-1",
|
||||
expectFormat: "suffix",
|
||||
},
|
||||
{
|
||||
name: "uses hash suffix when all suffixes taken",
|
||||
base: "my-host",
|
||||
report: agentsdocker.Report{
|
||||
Agent: agentsdocker.AgentInfo{ID: "agent-1"},
|
||||
},
|
||||
tokenRecord: nil,
|
||||
hosts: []models.DockerHost{
|
||||
{ID: "my-host::agent-agent-1"},
|
||||
},
|
||||
expectFormat: "hash",
|
||||
},
|
||||
{
|
||||
name: "uses numeric suffix when hash taken",
|
||||
base: "my-host",
|
||||
report: agentsdocker.Report{
|
||||
Agent: agentsdocker.AgentInfo{ID: "agent-1"},
|
||||
},
|
||||
tokenRecord: nil,
|
||||
hosts: []models.DockerHost{
|
||||
{ID: "my-host::agent-agent-1"},
|
||||
{ID: "my-host::hash-" + func() string {
|
||||
candidates := dockerHostSuffixCandidates(agentsdocker.Report{Agent: agentsdocker.AgentInfo{ID: "agent-1"}}, nil)
|
||||
if len(candidates) > 0 {
|
||||
// This will be computed during test
|
||||
return "placeholder"
|
||||
}
|
||||
return "placeholder"
|
||||
}()},
|
||||
},
|
||||
expectFormat: "numeric",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
result := generateDockerHostIdentifier(tt.base, tt.report, tt.tokenRecord, tt.hosts)
|
||||
if result == "" {
|
||||
t.Errorf("expected non-empty result")
|
||||
}
|
||||
if tt.expectedContains != "" && result != tt.expectedContains {
|
||||
t.Errorf("got %q, want %q", result, tt.expectedContains)
|
||||
}
|
||||
switch tt.expectFormat {
|
||||
case "suffix":
|
||||
if len(result) < 2 || result[len(result)-2:len(result)-1] != ":" {
|
||||
// Should contain ::
|
||||
hasDoubleColon := false
|
||||
for i := 0; i < len(result)-1; i++ {
|
||||
if result[i] == ':' && result[i+1] == ':' {
|
||||
hasDoubleColon = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hasDoubleColon {
|
||||
t.Errorf("expected suffix format with ::, got %q", result)
|
||||
}
|
||||
}
|
||||
case "hash":
|
||||
if len(result) < 7 || result[len(result)-7:len(result)-6] != ":" {
|
||||
// Should contain ::hash-
|
||||
hasHash := false
|
||||
for i := 0; i < len(result)-6; i++ {
|
||||
if result[i:i+7] == "::hash-" {
|
||||
hasHash = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !hasHash {
|
||||
t.Errorf("expected hash format with ::hash-, got %q", result)
|
||||
}
|
||||
}
|
||||
case "numeric":
|
||||
if len(result) < 3 {
|
||||
t.Errorf("result too short for numeric format: %q", result)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveDockerHostIdentifier(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
report agentsdocker.Report
|
||||
tokenRecord *config.APITokenRecord
|
||||
hosts []models.DockerHost
|
||||
expectMatch bool
|
||||
expectFallbacks int
|
||||
expectedID string
|
||||
checkIDFormat bool
|
||||
}{
|
||||
{
|
||||
name: "existing host match",
|
||||
report: agentsdocker.Report{
|
||||
Agent: agentsdocker.AgentInfo{ID: "agent-1"},
|
||||
},
|
||||
tokenRecord: &config.APITokenRecord{ID: "token-1"},
|
||||
hosts: []models.DockerHost{
|
||||
{ID: "existing-host", AgentID: "agent-1", TokenID: "token-1"},
|
||||
},
|
||||
expectMatch: true,
|
||||
expectedID: "existing-host",
|
||||
expectFallbacks: 1,
|
||||
},
|
||||
{
|
||||
name: "no match, base available",
|
||||
report: agentsdocker.Report{
|
||||
Agent: agentsdocker.AgentInfo{ID: "new-agent"},
|
||||
},
|
||||
tokenRecord: nil,
|
||||
hosts: []models.DockerHost{{ID: "other-host"}},
|
||||
expectMatch: false,
|
||||
expectedID: "new-agent",
|
||||
expectFallbacks: 1,
|
||||
},
|
||||
{
|
||||
name: "no match, base taken, generates unique",
|
||||
report: agentsdocker.Report{
|
||||
Agent: agentsdocker.AgentInfo{ID: "agent-1"},
|
||||
Host: agentsdocker.HostInfo{MachineID: "machine-1"},
|
||||
},
|
||||
tokenRecord: nil,
|
||||
hosts: []models.DockerHost{
|
||||
{ID: "agent-1"},
|
||||
},
|
||||
expectMatch: false,
|
||||
checkIDFormat: true,
|
||||
expectFallbacks: 2,
|
||||
},
|
||||
{
|
||||
name: "empty report uses fallback",
|
||||
report: agentsdocker.Report{
|
||||
Host: agentsdocker.HostInfo{Hostname: "my-host"},
|
||||
},
|
||||
tokenRecord: nil,
|
||||
hosts: []models.DockerHost{},
|
||||
expectMatch: false,
|
||||
expectedID: "my-host",
|
||||
expectFallbacks: 1,
|
||||
},
|
||||
{
|
||||
name: "completely empty report",
|
||||
report: agentsdocker.Report{},
|
||||
tokenRecord: nil,
|
||||
hosts: []models.DockerHost{},
|
||||
expectMatch: false,
|
||||
expectedID: "docker-host",
|
||||
expectFallbacks: 0,
|
||||
},
|
||||
{
|
||||
name: "multiple fallback candidates",
|
||||
report: agentsdocker.Report{
|
||||
Agent: agentsdocker.AgentInfo{ID: "agent-1"},
|
||||
Host: agentsdocker.HostInfo{
|
||||
MachineID: "machine-1",
|
||||
Hostname: "host-1",
|
||||
},
|
||||
},
|
||||
tokenRecord: nil,
|
||||
hosts: []models.DockerHost{},
|
||||
expectMatch: false,
|
||||
expectedID: "agent-1",
|
||||
expectFallbacks: 3,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
id, fallbacks, existing, found := resolveDockerHostIdentifier(tt.report, tt.tokenRecord, tt.hosts)
|
||||
if found != tt.expectMatch {
|
||||
t.Errorf("found mismatch: got %v, want %v", found, tt.expectMatch)
|
||||
}
|
||||
if tt.expectMatch && existing.ID != tt.expectedID {
|
||||
t.Errorf("existing ID mismatch: got %q, want %q", existing.ID, tt.expectedID)
|
||||
}
|
||||
if !tt.expectMatch && existing.ID != "" {
|
||||
t.Errorf("expected empty existing host, got %q", existing.ID)
|
||||
}
|
||||
if tt.expectedID != "" && !tt.checkIDFormat && id != tt.expectedID {
|
||||
t.Errorf("ID mismatch: got %q, want %q", id, tt.expectedID)
|
||||
}
|
||||
if tt.checkIDFormat && id == "" {
|
||||
t.Errorf("expected non-empty ID")
|
||||
}
|
||||
if len(fallbacks) != tt.expectFallbacks {
|
||||
t.Errorf("fallback count mismatch: got %d, want %d", len(fallbacks), tt.expectFallbacks)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGenerateDockerHostIdentifier_NumericSuffix(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Test that numeric suffix increments correctly
|
||||
report := agentsdocker.Report{
|
||||
Agent: agentsdocker.AgentInfo{ID: "agent-1"},
|
||||
}
|
||||
|
||||
// Create hosts that occupy all suffix candidates
|
||||
suffixes := dockerHostSuffixCandidates(report, nil)
|
||||
hosts := []models.DockerHost{}
|
||||
|
||||
// Take all normal suffixes
|
||||
for _, suffix := range suffixes {
|
||||
hosts = append(hosts, models.DockerHost{
|
||||
ID: fmt.Sprintf("my-host::%s", suffix),
|
||||
})
|
||||
}
|
||||
|
||||
// Take the hash suffix
|
||||
hashID := fallbackDockerHostID(report, nil)
|
||||
if hashID != "" {
|
||||
hashSuffix := hashID[len("docker-host-"):]
|
||||
hosts = append(hosts, models.DockerHost{
|
||||
ID: fmt.Sprintf("my-host::hash-%s", hashSuffix),
|
||||
})
|
||||
}
|
||||
|
||||
result := generateDockerHostIdentifier("my-host", report, nil, hosts)
|
||||
|
||||
// Should be my-host::2
|
||||
expected := "my-host::2"
|
||||
if result != expected {
|
||||
t.Errorf("got %q, want %q", result, expected)
|
||||
}
|
||||
|
||||
// Test that it increments further
|
||||
hosts = append(hosts, models.DockerHost{ID: "my-host::2"})
|
||||
result = generateDockerHostIdentifier("my-host", report, nil, hosts)
|
||||
expected = "my-host::3"
|
||||
if result != expected {
|
||||
t.Errorf("got %q, want %q", result, expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSanitizeDockerHostSuffix_UnicodeRunes(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
// Test that rune counting works correctly (not byte counting)
|
||||
input := "🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀🚀" // 12 emoji (48+ bytes but 12 runes)
|
||||
result := sanitizeDockerHostSuffix(input)
|
||||
|
||||
// All emojis should become single hyphen since they're not letters/digits
|
||||
expected := ""
|
||||
if result != expected {
|
||||
t.Errorf("got %q, want %q", result, expected)
|
||||
}
|
||||
|
||||
// Test with mix of emoji and letters
|
||||
input = "abc🚀def"
|
||||
result = sanitizeDockerHostSuffix(input)
|
||||
expected = "abc-def"
|
||||
if result != expected {
|
||||
t.Errorf("got %q, want %q", result, expected)
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue