mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-04-28 11:30:15 +00:00
435 lines
10 KiB
Go
435 lines
10 KiB
Go
package dockeragent
|
|
|
|
import (
|
|
"reflect"
|
|
"testing"
|
|
|
|
swarmtypes "github.com/moby/moby/api/types/swarm"
|
|
agentsdocker "github.com/rcourtman/pulse-go-rewrite/pkg/agents/docker"
|
|
)
|
|
|
|
func TestServiceMode(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
mode swarmtypes.ServiceMode
|
|
want string
|
|
}{
|
|
{
|
|
name: "global mode",
|
|
mode: swarmtypes.ServiceMode{
|
|
Global: &swarmtypes.GlobalService{},
|
|
},
|
|
want: "global",
|
|
},
|
|
{
|
|
name: "replicated mode",
|
|
mode: swarmtypes.ServiceMode{
|
|
Replicated: &swarmtypes.ReplicatedService{},
|
|
},
|
|
want: "replicated",
|
|
},
|
|
{
|
|
name: "replicated-job mode",
|
|
mode: swarmtypes.ServiceMode{
|
|
ReplicatedJob: &swarmtypes.ReplicatedJob{},
|
|
},
|
|
want: "replicated-job",
|
|
},
|
|
{
|
|
name: "global-job mode",
|
|
mode: swarmtypes.ServiceMode{
|
|
GlobalJob: &swarmtypes.GlobalJob{},
|
|
},
|
|
want: "global-job",
|
|
},
|
|
{
|
|
name: "empty mode returns empty string",
|
|
mode: swarmtypes.ServiceMode{},
|
|
want: "",
|
|
},
|
|
{
|
|
name: "multiple modes set returns first match (global takes precedence)",
|
|
mode: swarmtypes.ServiceMode{
|
|
Global: &swarmtypes.GlobalService{},
|
|
Replicated: &swarmtypes.ReplicatedService{},
|
|
},
|
|
want: "global",
|
|
},
|
|
{
|
|
name: "replicated-job takes precedence over global-job",
|
|
mode: swarmtypes.ServiceMode{
|
|
ReplicatedJob: &swarmtypes.ReplicatedJob{},
|
|
GlobalJob: &swarmtypes.GlobalJob{},
|
|
},
|
|
want: "replicated-job",
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := serviceMode(tt.mode)
|
|
if got != tt.want {
|
|
t.Errorf("serviceMode() = %q, want %q", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBuildContainerIndex(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
containers []agentsdocker.Container
|
|
wantKeys []string
|
|
}{
|
|
{
|
|
name: "nil containers returns nil",
|
|
containers: nil,
|
|
wantKeys: nil,
|
|
},
|
|
{
|
|
name: "empty containers returns nil",
|
|
containers: []agentsdocker.Container{},
|
|
wantKeys: nil,
|
|
},
|
|
{
|
|
name: "single container with long ID",
|
|
containers: []agentsdocker.Container{
|
|
{ID: "abcdef1234567890"},
|
|
},
|
|
wantKeys: []string{"abcdef1234567890", "abcdef123456"},
|
|
},
|
|
{
|
|
name: "container with exactly 12 char ID",
|
|
containers: []agentsdocker.Container{
|
|
{ID: "abcdef123456"},
|
|
},
|
|
wantKeys: []string{"abcdef123456"},
|
|
},
|
|
{
|
|
name: "container with short ID (less than 12)",
|
|
containers: []agentsdocker.Container{
|
|
{ID: "abc123"},
|
|
},
|
|
wantKeys: []string{"abc123"},
|
|
},
|
|
{
|
|
name: "multiple containers",
|
|
containers: []agentsdocker.Container{
|
|
{ID: "container1234567890"},
|
|
{ID: "another1234567890"},
|
|
},
|
|
wantKeys: []string{
|
|
"container1234567890", "container123",
|
|
"another1234567890", "another12345",
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := buildContainerIndex(tt.containers)
|
|
|
|
if tt.wantKeys == nil {
|
|
if got != nil {
|
|
t.Errorf("buildContainerIndex() = %v, want nil", got)
|
|
}
|
|
return
|
|
}
|
|
|
|
if got == nil {
|
|
t.Fatal("buildContainerIndex() returned nil, want non-nil")
|
|
}
|
|
|
|
for _, key := range tt.wantKeys {
|
|
if _, exists := got[key]; !exists {
|
|
t.Errorf("buildContainerIndex() missing key %q", key)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBuildContainerIndexPreservesData(t *testing.T) {
|
|
containers := []agentsdocker.Container{
|
|
{
|
|
ID: "abcdef1234567890",
|
|
Name: "mycontainer",
|
|
Image: "nginx:latest",
|
|
State: "running",
|
|
},
|
|
}
|
|
|
|
index := buildContainerIndex(containers)
|
|
|
|
// Both full ID and short ID should return the same container
|
|
fullContainer, ok := index["abcdef1234567890"]
|
|
if !ok {
|
|
t.Fatal("full ID not found in index")
|
|
}
|
|
|
|
shortContainer, ok := index["abcdef123456"]
|
|
if !ok {
|
|
t.Fatal("short ID not found in index")
|
|
}
|
|
|
|
if fullContainer.Name != "mycontainer" {
|
|
t.Errorf("full ID container name = %q, want %q", fullContainer.Name, "mycontainer")
|
|
}
|
|
|
|
if shortContainer.Name != "mycontainer" {
|
|
t.Errorf("short ID container name = %q, want %q", shortContainer.Name, "mycontainer")
|
|
}
|
|
|
|
if fullContainer.Image != shortContainer.Image {
|
|
t.Error("full ID and short ID containers should be identical")
|
|
}
|
|
}
|
|
|
|
func TestLookupContainer(t *testing.T) {
|
|
containers := []agentsdocker.Container{
|
|
{ID: "abcdef1234567890abcd", Name: "container1"},
|
|
{ID: "xyz1234567890xyz12", Name: "container2"},
|
|
}
|
|
index := buildContainerIndex(containers)
|
|
|
|
tests := []struct {
|
|
name string
|
|
index map[string]agentsdocker.Container
|
|
id string
|
|
wantName string
|
|
wantFound bool
|
|
}{
|
|
{
|
|
name: "nil index returns not found",
|
|
index: nil,
|
|
id: "abcdef1234567890",
|
|
wantFound: false,
|
|
},
|
|
{
|
|
name: "empty index returns not found",
|
|
index: map[string]agentsdocker.Container{},
|
|
id: "abcdef1234567890",
|
|
wantFound: false,
|
|
},
|
|
{
|
|
name: "full ID lookup",
|
|
index: index,
|
|
id: "abcdef1234567890abcd",
|
|
wantName: "container1",
|
|
wantFound: true,
|
|
},
|
|
{
|
|
name: "short ID lookup (12 chars)",
|
|
index: index,
|
|
id: "abcdef123456",
|
|
wantName: "container1",
|
|
wantFound: true,
|
|
},
|
|
{
|
|
name: "longer ID truncated to 12 for fallback",
|
|
index: index,
|
|
id: "abcdef12345600000000", // different suffix but same first 12
|
|
wantName: "container1",
|
|
wantFound: true,
|
|
},
|
|
{
|
|
name: "ID not in index",
|
|
index: index,
|
|
id: "notfound1234567890",
|
|
wantFound: false,
|
|
},
|
|
{
|
|
name: "very short ID not found",
|
|
index: index,
|
|
id: "abc",
|
|
wantFound: false,
|
|
},
|
|
{
|
|
name: "second container by full ID",
|
|
index: index,
|
|
id: "xyz1234567890xyz12",
|
|
wantName: "container2",
|
|
wantFound: true,
|
|
},
|
|
{
|
|
name: "second container by short ID",
|
|
index: index,
|
|
id: "xyz123456789",
|
|
wantName: "container2",
|
|
wantFound: true,
|
|
},
|
|
{
|
|
name: "empty ID returns not found",
|
|
index: index,
|
|
id: "",
|
|
wantFound: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got, found := lookupContainer(tt.index, tt.id)
|
|
|
|
if found != tt.wantFound {
|
|
t.Errorf("lookupContainer() found = %v, want %v", found, tt.wantFound)
|
|
}
|
|
|
|
if tt.wantFound && got.Name != tt.wantName {
|
|
t.Errorf("lookupContainer() name = %q, want %q", got.Name, tt.wantName)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCopyStringMap(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
source map[string]string
|
|
want map[string]string
|
|
}{
|
|
{
|
|
name: "nil map returns nil",
|
|
source: nil,
|
|
want: nil,
|
|
},
|
|
{
|
|
name: "empty map returns nil",
|
|
source: map[string]string{},
|
|
want: nil,
|
|
},
|
|
{
|
|
name: "single entry",
|
|
source: map[string]string{
|
|
"key": "value",
|
|
},
|
|
want: map[string]string{
|
|
"key": "value",
|
|
},
|
|
},
|
|
{
|
|
name: "multiple entries",
|
|
source: map[string]string{
|
|
"key1": "value1",
|
|
"key2": "value2",
|
|
"key3": "value3",
|
|
},
|
|
want: map[string]string{
|
|
"key1": "value1",
|
|
"key2": "value2",
|
|
"key3": "value3",
|
|
},
|
|
},
|
|
{
|
|
name: "empty values",
|
|
source: map[string]string{
|
|
"key": "",
|
|
},
|
|
want: map[string]string{
|
|
"key": "",
|
|
},
|
|
},
|
|
{
|
|
name: "special characters",
|
|
source: map[string]string{
|
|
"key/with/slashes": "value=with=equals",
|
|
"com.docker.label": "test",
|
|
},
|
|
want: map[string]string{
|
|
"key/with/slashes": "value=with=equals",
|
|
"com.docker.label": "test",
|
|
},
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := copyStringMap(tt.source)
|
|
|
|
if !reflect.DeepEqual(got, tt.want) {
|
|
t.Errorf("copyStringMap() = %v, want %v", got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCopyStringMapIsDeepCopy(t *testing.T) {
|
|
source := map[string]string{
|
|
"key1": "value1",
|
|
"key2": "value2",
|
|
}
|
|
|
|
copied := copyStringMap(source)
|
|
|
|
// Modify source
|
|
source["key1"] = "modified"
|
|
source["key3"] = "new"
|
|
|
|
// Copied map should be unchanged
|
|
if copied["key1"] != "value1" {
|
|
t.Error("modifying source changed the copy")
|
|
}
|
|
|
|
if _, exists := copied["key3"]; exists {
|
|
t.Error("adding to source affected the copy")
|
|
}
|
|
|
|
// Modify copy
|
|
copied["key2"] = "also modified"
|
|
|
|
// Source should be unchanged (well, it was already modified, but check consistency)
|
|
if source["key2"] != "value2" {
|
|
t.Error("modifying copy changed the source")
|
|
}
|
|
}
|
|
|
|
func TestIsTaskCompletedState(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
state string
|
|
want bool
|
|
}{
|
|
// Completed states
|
|
{name: "completed lowercase", state: "completed", want: true},
|
|
{name: "completed uppercase", state: "COMPLETED", want: true},
|
|
{name: "completed mixed case", state: "Completed", want: true},
|
|
{name: "complete lowercase", state: "complete", want: true},
|
|
{name: "complete uppercase", state: "COMPLETE", want: true},
|
|
{name: "shutdown lowercase", state: "shutdown", want: true},
|
|
{name: "shutdown uppercase", state: "SHUTDOWN", want: true},
|
|
{name: "failed lowercase", state: "failed", want: true},
|
|
{name: "failed uppercase", state: "FAILED", want: true},
|
|
{name: "rejected lowercase", state: "rejected", want: true},
|
|
{name: "rejected uppercase", state: "REJECTED", want: true},
|
|
|
|
// Non-completed states
|
|
{name: "running", state: "running", want: false},
|
|
{name: "pending", state: "pending", want: false},
|
|
{name: "starting", state: "starting", want: false},
|
|
{name: "preparing", state: "preparing", want: false},
|
|
{name: "ready", state: "ready", want: false},
|
|
{name: "assigned", state: "assigned", want: false},
|
|
{name: "accepted", state: "accepted", want: false},
|
|
{name: "new", state: "new", want: false},
|
|
{name: "allocated", state: "allocated", want: false},
|
|
{name: "orphaned", state: "orphaned", want: false},
|
|
{name: "remove", state: "remove", want: false},
|
|
|
|
// Edge cases
|
|
{name: "empty string", state: "", want: false},
|
|
{name: "whitespace", state: " ", want: false},
|
|
{name: "unknown state", state: "unknown", want: false},
|
|
{name: "partial match - fail", state: "fail", want: false},
|
|
{name: "partial match - complete (as prefix)", state: "completing", want: false},
|
|
{name: "with leading space", state: " completed", want: false},
|
|
{name: "with trailing space", state: "completed ", want: false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := isTaskCompletedState(tt.state)
|
|
if got != tt.want {
|
|
t.Errorf("isTaskCompletedState(%q) = %v, want %v", tt.state, got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|