mirror of
https://github.com/stakater/Reloader.git
synced 2026-08-20 04:26:28 +00:00
feat: Improve test coverage of important packages
This commit is contained in:
@@ -0,0 +1,197 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stakater/Reloader/internal/pkg/config"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"sigs.k8s.io/controller-runtime/pkg/event"
|
||||
)
|
||||
|
||||
func TestCreateEventPredicate_CreateEvent(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
reloadOnCreate bool
|
||||
syncAfterRestart bool
|
||||
initialized bool
|
||||
expectedResult bool
|
||||
}{
|
||||
{
|
||||
name: "reload on create enabled, initialized",
|
||||
reloadOnCreate: true,
|
||||
syncAfterRestart: false,
|
||||
initialized: true,
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: "reload on create disabled, initialized",
|
||||
reloadOnCreate: false,
|
||||
syncAfterRestart: false,
|
||||
initialized: true,
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
name: "not initialized, sync after restart enabled",
|
||||
reloadOnCreate: true,
|
||||
syncAfterRestart: true,
|
||||
initialized: false,
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: "not initialized, sync after restart disabled",
|
||||
reloadOnCreate: true,
|
||||
syncAfterRestart: false,
|
||||
initialized: false,
|
||||
expectedResult: false,
|
||||
},
|
||||
{
|
||||
name: "not initialized, sync after restart disabled, reload on create disabled",
|
||||
reloadOnCreate: false,
|
||||
syncAfterRestart: false,
|
||||
initialized: false,
|
||||
expectedResult: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cfg := &config.Config{
|
||||
ReloadOnCreate: tt.reloadOnCreate,
|
||||
SyncAfterRestart: tt.syncAfterRestart,
|
||||
}
|
||||
initialized := tt.initialized
|
||||
|
||||
pred := createEventPredicate(cfg, &initialized)
|
||||
|
||||
cm := &corev1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
|
||||
}
|
||||
|
||||
e := event.CreateEvent{Object: cm}
|
||||
result := pred.Create(e)
|
||||
|
||||
if result != tt.expectedResult {
|
||||
t.Errorf("CreateFunc() = %v, want %v", result, tt.expectedResult)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateEventPredicate_UpdateEvent(t *testing.T) {
|
||||
cfg := &config.Config{}
|
||||
initialized := true
|
||||
|
||||
pred := createEventPredicate(cfg, &initialized)
|
||||
|
||||
cm := &corev1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
|
||||
}
|
||||
|
||||
e := event.UpdateEvent{ObjectOld: cm, ObjectNew: cm}
|
||||
result := pred.Update(e)
|
||||
|
||||
// Update events should always return true
|
||||
if !result {
|
||||
t.Error("UpdateFunc() should always return true")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateEventPredicate_DeleteEvent(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
reloadOnDelete bool
|
||||
expectedResult bool
|
||||
}{
|
||||
{
|
||||
name: "reload on delete enabled",
|
||||
reloadOnDelete: true,
|
||||
expectedResult: true,
|
||||
},
|
||||
{
|
||||
name: "reload on delete disabled",
|
||||
reloadOnDelete: false,
|
||||
expectedResult: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cfg := &config.Config{
|
||||
ReloadOnDelete: tt.reloadOnDelete,
|
||||
}
|
||||
initialized := true
|
||||
|
||||
pred := createEventPredicate(cfg, &initialized)
|
||||
|
||||
cm := &corev1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
|
||||
}
|
||||
|
||||
e := event.DeleteEvent{Object: cm}
|
||||
result := pred.Delete(e)
|
||||
|
||||
if result != tt.expectedResult {
|
||||
t.Errorf("DeleteFunc() = %v, want %v", result, tt.expectedResult)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCreateEventPredicate_GenericEvent(t *testing.T) {
|
||||
cfg := &config.Config{}
|
||||
initialized := true
|
||||
|
||||
pred := createEventPredicate(cfg, &initialized)
|
||||
|
||||
cm := &corev1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
|
||||
}
|
||||
|
||||
e := event.GenericEvent{Object: cm}
|
||||
result := pred.Generic(e)
|
||||
|
||||
// Generic events should always return false
|
||||
if result {
|
||||
t.Error("GenericFunc() should always return false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildEventFilter(t *testing.T) {
|
||||
cfg := &config.Config{
|
||||
ReloadOnCreate: true,
|
||||
ReloadOnDelete: true,
|
||||
}
|
||||
initialized := true
|
||||
|
||||
// Create a simple always-true predicate as the resource predicate
|
||||
resourcePred := &alwaysTruePredicate{}
|
||||
|
||||
filter := BuildEventFilter(resourcePred, cfg, &initialized)
|
||||
|
||||
// The filter should be created without error
|
||||
if filter == nil {
|
||||
t.Fatal("BuildEventFilter() should return a non-nil predicate")
|
||||
}
|
||||
|
||||
// Test update event passes (since resourcePred returns true and update always returns true)
|
||||
cm := &corev1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
|
||||
}
|
||||
|
||||
e := event.UpdateEvent{ObjectOld: cm, ObjectNew: cm}
|
||||
result := filter.Update(e)
|
||||
|
||||
// Since namespace filter is empty (all namespaces allowed), this should pass
|
||||
if !result {
|
||||
t.Error("UpdateFunc() should return true when all predicates pass")
|
||||
}
|
||||
}
|
||||
|
||||
// alwaysTruePredicate is a helper predicate for testing
|
||||
type alwaysTruePredicate struct{}
|
||||
|
||||
func (p *alwaysTruePredicate) Create(_ event.CreateEvent) bool { return true }
|
||||
func (p *alwaysTruePredicate) Delete(_ event.DeleteEvent) bool { return true }
|
||||
func (p *alwaysTruePredicate) Update(_ event.UpdateEvent) bool { return true }
|
||||
func (p *alwaysTruePredicate) Generic(_ event.GenericEvent) bool { return true }
|
||||
@@ -0,0 +1,131 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stakater/Reloader/internal/pkg/reload"
|
||||
"github.com/stakater/Reloader/internal/pkg/workload"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
batchv1 "k8s.io/api/batch/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func TestUpdateWorkloadWithRetry_SwitchCases(t *testing.T) {
|
||||
// Test that the switch statement correctly identifies workload types
|
||||
// Note: Full integration tests require a fake k8s client, so we just test type detection
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
workload workload.WorkloadAccessor
|
||||
expectedKind workload.Kind
|
||||
}{
|
||||
{
|
||||
name: "deployment workload",
|
||||
workload: workload.NewDeploymentWorkload(&appsv1.Deployment{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
|
||||
}),
|
||||
expectedKind: workload.KindDeployment,
|
||||
},
|
||||
{
|
||||
name: "daemonset workload",
|
||||
workload: workload.NewDaemonSetWorkload(&appsv1.DaemonSet{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
|
||||
}),
|
||||
expectedKind: workload.KindDaemonSet,
|
||||
},
|
||||
{
|
||||
name: "statefulset workload",
|
||||
workload: workload.NewStatefulSetWorkload(&appsv1.StatefulSet{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
|
||||
}),
|
||||
expectedKind: workload.KindStatefulSet,
|
||||
},
|
||||
{
|
||||
name: "job workload",
|
||||
workload: workload.NewJobWorkload(&batchv1.Job{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
|
||||
}),
|
||||
expectedKind: workload.KindJob,
|
||||
},
|
||||
{
|
||||
name: "cronjob workload",
|
||||
workload: workload.NewCronJobWorkload(&batchv1.CronJob{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
|
||||
}),
|
||||
expectedKind: workload.KindCronJob,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
// Verify the workload kind is correctly identified
|
||||
if tt.workload.Kind() != tt.expectedKind {
|
||||
t.Errorf("workload.Kind() = %v, want %v", tt.workload.Kind(), tt.expectedKind)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestJobWorkloadTypeCast(t *testing.T) {
|
||||
// Test that JobWorkload type cast works correctly
|
||||
job := &batchv1.Job{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test-job", Namespace: "default"},
|
||||
}
|
||||
jobWl := workload.NewJobWorkload(job)
|
||||
|
||||
if jobWl.GetName() != "test-job" {
|
||||
t.Errorf("JobWorkload.GetName() = %v, want test-job", jobWl.GetName())
|
||||
}
|
||||
|
||||
// Test GetJob method
|
||||
gotJob := jobWl.GetJob()
|
||||
if gotJob.Name != "test-job" {
|
||||
t.Errorf("JobWorkload.GetJob().Name = %v, want test-job", gotJob.Name)
|
||||
}
|
||||
|
||||
// Verify it satisfies WorkloadAccessor interface
|
||||
var _ workload.WorkloadAccessor = jobWl
|
||||
}
|
||||
|
||||
func TestCronJobWorkloadTypeCast(t *testing.T) {
|
||||
// Test that CronJobWorkload type cast works correctly
|
||||
cronJob := &batchv1.CronJob{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test-cronjob", Namespace: "default"},
|
||||
Spec: batchv1.CronJobSpec{
|
||||
Schedule: "*/5 * * * *",
|
||||
},
|
||||
}
|
||||
cronJobWl := workload.NewCronJobWorkload(cronJob)
|
||||
|
||||
if cronJobWl.GetName() != "test-cronjob" {
|
||||
t.Errorf("CronJobWorkload.GetName() = %v, want test-cronjob", cronJobWl.GetName())
|
||||
}
|
||||
|
||||
// Test GetCronJob method
|
||||
gotCronJob := cronJobWl.GetCronJob()
|
||||
if gotCronJob.Name != "test-cronjob" {
|
||||
t.Errorf("CronJobWorkload.GetCronJob().Name = %v, want test-cronjob", gotCronJob.Name)
|
||||
}
|
||||
|
||||
// Verify it satisfies WorkloadAccessor interface
|
||||
var _ workload.WorkloadAccessor = cronJobWl
|
||||
}
|
||||
|
||||
func TestResourceTypeKind(t *testing.T) {
|
||||
// Test that ResourceType.Kind() returns correct values
|
||||
tests := []struct {
|
||||
resourceType reload.ResourceType
|
||||
expectedKind string
|
||||
}{
|
||||
{reload.ResourceTypeConfigMap, "ConfigMap"},
|
||||
{reload.ResourceTypeSecret, "Secret"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(string(tt.resourceType), func(t *testing.T) {
|
||||
if got := tt.resourceType.Kind(); got != tt.expectedKind {
|
||||
t.Errorf("ResourceType.Kind() = %v, want %v", got, tt.expectedKind)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package reload
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stakater/Reloader/internal/pkg/workload"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func TestFilterDecisions(t *testing.T) {
|
||||
// Create some mock workloads for testing
|
||||
wl1 := workload.NewDeploymentWorkload(&appsv1.Deployment{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "deploy1", Namespace: "default"},
|
||||
})
|
||||
wl2 := workload.NewDeploymentWorkload(&appsv1.Deployment{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "deploy2", Namespace: "default"},
|
||||
})
|
||||
wl3 := workload.NewDeploymentWorkload(&appsv1.Deployment{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "deploy3", Namespace: "default"},
|
||||
})
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
decisions []ReloadDecision
|
||||
wantCount int
|
||||
wantNames []string
|
||||
}{
|
||||
{
|
||||
name: "empty list",
|
||||
decisions: []ReloadDecision{},
|
||||
wantCount: 0,
|
||||
wantNames: nil,
|
||||
},
|
||||
{
|
||||
name: "all should reload",
|
||||
decisions: []ReloadDecision{
|
||||
{Workload: wl1, ShouldReload: true, Reason: "test"},
|
||||
{Workload: wl2, ShouldReload: true, Reason: "test"},
|
||||
},
|
||||
wantCount: 2,
|
||||
wantNames: []string{"deploy1", "deploy2"},
|
||||
},
|
||||
{
|
||||
name: "none should reload",
|
||||
decisions: []ReloadDecision{
|
||||
{Workload: wl1, ShouldReload: false, Reason: "test"},
|
||||
{Workload: wl2, ShouldReload: false, Reason: "test"},
|
||||
},
|
||||
wantCount: 0,
|
||||
wantNames: nil,
|
||||
},
|
||||
{
|
||||
name: "mixed - some should reload",
|
||||
decisions: []ReloadDecision{
|
||||
{Workload: wl1, ShouldReload: true, Reason: "test"},
|
||||
{Workload: wl2, ShouldReload: false, Reason: "test"},
|
||||
{Workload: wl3, ShouldReload: true, Reason: "test"},
|
||||
},
|
||||
wantCount: 2,
|
||||
wantNames: []string{"deploy1", "deploy3"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := FilterDecisions(tt.decisions)
|
||||
|
||||
if len(result) != tt.wantCount {
|
||||
t.Errorf("FilterDecisions() returned %d decisions, want %d", len(result), tt.wantCount)
|
||||
}
|
||||
|
||||
if tt.wantNames != nil {
|
||||
for i, d := range result {
|
||||
if d.Workload.GetName() != tt.wantNames[i] {
|
||||
t.Errorf("FilterDecisions()[%d].Workload.GetName() = %s, want %s",
|
||||
i, d.Workload.GetName(), tt.wantNames[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestReloadDecision_Fields(t *testing.T) {
|
||||
wl := workload.NewDeploymentWorkload(&appsv1.Deployment{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
|
||||
})
|
||||
|
||||
decision := ReloadDecision{
|
||||
Workload: wl,
|
||||
ShouldReload: true,
|
||||
AutoReload: true,
|
||||
Reason: "test reason",
|
||||
Hash: "abc123",
|
||||
}
|
||||
|
||||
if decision.Workload.GetName() != "test" {
|
||||
t.Errorf("ReloadDecision.Workload.GetName() = %v, want test", decision.Workload.GetName())
|
||||
}
|
||||
if !decision.ShouldReload {
|
||||
t.Error("ReloadDecision.ShouldReload should be true")
|
||||
}
|
||||
if !decision.AutoReload {
|
||||
t.Error("ReloadDecision.AutoReload should be true")
|
||||
}
|
||||
if decision.Reason != "test reason" {
|
||||
t.Errorf("ReloadDecision.Reason = %v, want 'test reason'", decision.Reason)
|
||||
}
|
||||
if decision.Hash != "abc123" {
|
||||
t.Errorf("ReloadDecision.Hash = %v, want 'abc123'", decision.Hash)
|
||||
}
|
||||
}
|
||||
@@ -607,3 +607,333 @@ func TestNamespaceFilterPredicateWithCache_NilCache(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIgnoreAnnotationPredicate_Create(t *testing.T) {
|
||||
cfg := config.NewDefault()
|
||||
predicate := IgnoreAnnotationPredicate(cfg)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
annotations map[string]string
|
||||
wantAllow bool
|
||||
}{
|
||||
{
|
||||
name: "no annotations",
|
||||
annotations: nil,
|
||||
wantAllow: true,
|
||||
},
|
||||
{
|
||||
name: "empty annotations",
|
||||
annotations: map[string]string{},
|
||||
wantAllow: true,
|
||||
},
|
||||
{
|
||||
name: "other annotations only",
|
||||
annotations: map[string]string{"other": "value"},
|
||||
wantAllow: true,
|
||||
},
|
||||
{
|
||||
name: "ignore annotation true",
|
||||
annotations: map[string]string{cfg.Annotations.Ignore: "true"},
|
||||
wantAllow: false,
|
||||
},
|
||||
{
|
||||
name: "ignore annotation false",
|
||||
annotations: map[string]string{cfg.Annotations.Ignore: "false"},
|
||||
wantAllow: true,
|
||||
},
|
||||
{
|
||||
name: "ignore annotation with other value",
|
||||
annotations: map[string]string{cfg.Annotations.Ignore: "yes"},
|
||||
wantAllow: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cm := &corev1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-cm",
|
||||
Namespace: "default",
|
||||
Annotations: tt.annotations,
|
||||
},
|
||||
}
|
||||
|
||||
e := event.CreateEvent{Object: cm}
|
||||
got := predicate.Create(e)
|
||||
|
||||
if got != tt.wantAllow {
|
||||
t.Errorf("Create() = %v, want %v", got, tt.wantAllow)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIgnoreAnnotationPredicate_AllEventTypes(t *testing.T) {
|
||||
cfg := config.NewDefault()
|
||||
predicate := IgnoreAnnotationPredicate(cfg)
|
||||
|
||||
ignoredCM := &corev1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "ignored-cm",
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{cfg.Annotations.Ignore: "true"},
|
||||
},
|
||||
}
|
||||
|
||||
allowedCM := &corev1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "allowed-cm",
|
||||
Namespace: "default",
|
||||
},
|
||||
}
|
||||
|
||||
// Test Update
|
||||
if predicate.Update(event.UpdateEvent{ObjectNew: ignoredCM}) {
|
||||
t.Error("Update() should block ignored resource")
|
||||
}
|
||||
if !predicate.Update(event.UpdateEvent{ObjectNew: allowedCM}) {
|
||||
t.Error("Update() should allow non-ignored resource")
|
||||
}
|
||||
|
||||
// Test Delete
|
||||
if predicate.Delete(event.DeleteEvent{Object: ignoredCM}) {
|
||||
t.Error("Delete() should block ignored resource")
|
||||
}
|
||||
if !predicate.Delete(event.DeleteEvent{Object: allowedCM}) {
|
||||
t.Error("Delete() should allow non-ignored resource")
|
||||
}
|
||||
|
||||
// Test Generic
|
||||
if predicate.Generic(event.GenericEvent{Object: ignoredCM}) {
|
||||
t.Error("Generic() should block ignored resource")
|
||||
}
|
||||
if !predicate.Generic(event.GenericEvent{Object: allowedCM}) {
|
||||
t.Error("Generic() should allow non-ignored resource")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCombinedPredicates(t *testing.T) {
|
||||
cfg := config.NewDefault()
|
||||
cfg.IgnoredNamespaces = []string{"kube-system"}
|
||||
|
||||
nsPredicate := NamespaceFilterPredicate(cfg)
|
||||
ignorePredicate := IgnoreAnnotationPredicate(cfg)
|
||||
|
||||
combined := CombinedPredicates(nsPredicate, ignorePredicate)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
namespace string
|
||||
annotations map[string]string
|
||||
wantAllow bool
|
||||
}{
|
||||
{
|
||||
name: "both predicates pass",
|
||||
namespace: "default",
|
||||
annotations: nil,
|
||||
wantAllow: true,
|
||||
},
|
||||
{
|
||||
name: "namespace predicate fails",
|
||||
namespace: "kube-system",
|
||||
annotations: nil,
|
||||
wantAllow: false,
|
||||
},
|
||||
{
|
||||
name: "ignore predicate fails",
|
||||
namespace: "default",
|
||||
annotations: map[string]string{cfg.Annotations.Ignore: "true"},
|
||||
wantAllow: false,
|
||||
},
|
||||
{
|
||||
name: "both predicates fail",
|
||||
namespace: "kube-system",
|
||||
annotations: map[string]string{cfg.Annotations.Ignore: "true"},
|
||||
wantAllow: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cm := &corev1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-cm",
|
||||
Namespace: tt.namespace,
|
||||
Annotations: tt.annotations,
|
||||
},
|
||||
}
|
||||
|
||||
e := event.CreateEvent{Object: cm}
|
||||
got := combined.Create(e)
|
||||
|
||||
if got != tt.wantAllow {
|
||||
t.Errorf("Create() = %v, want %v", got, tt.wantAllow)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigMapPredicates_Update(t *testing.T) {
|
||||
cfg := config.NewDefault()
|
||||
hasher := NewHasher()
|
||||
predicate := ConfigMapPredicates(cfg, hasher)
|
||||
|
||||
oldCM := &corev1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
|
||||
Data: map[string]string{"key": "value1"},
|
||||
}
|
||||
newCMSameContent := &corev1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
|
||||
Data: map[string]string{"key": "value1"},
|
||||
}
|
||||
newCMDifferentContent := &corev1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
|
||||
Data: map[string]string{"key": "value2"},
|
||||
}
|
||||
|
||||
// Same content should not trigger update
|
||||
e := event.UpdateEvent{ObjectOld: oldCM, ObjectNew: newCMSameContent}
|
||||
if predicate.Update(e) {
|
||||
t.Error("Update() should return false when content is the same")
|
||||
}
|
||||
|
||||
// Different content should trigger update
|
||||
e = event.UpdateEvent{ObjectOld: oldCM, ObjectNew: newCMDifferentContent}
|
||||
if !predicate.Update(e) {
|
||||
t.Error("Update() should return true when content changed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigMapPredicates_InvalidTypes(t *testing.T) {
|
||||
cfg := config.NewDefault()
|
||||
hasher := NewHasher()
|
||||
predicate := ConfigMapPredicates(cfg, hasher)
|
||||
|
||||
// Test with non-ConfigMap types
|
||||
secret := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
|
||||
}
|
||||
cm := &corev1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
|
||||
}
|
||||
|
||||
// Old is secret, new is configmap - should return false
|
||||
e := event.UpdateEvent{ObjectOld: secret, ObjectNew: cm}
|
||||
if predicate.Update(e) {
|
||||
t.Error("Update() should return false for mismatched types")
|
||||
}
|
||||
|
||||
// Both are secrets - should return false
|
||||
e = event.UpdateEvent{ObjectOld: secret, ObjectNew: secret}
|
||||
if predicate.Update(e) {
|
||||
t.Error("Update() should return false for non-ConfigMap types")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConfigMapPredicates_CreateDeleteGeneric(t *testing.T) {
|
||||
cfg := config.NewDefault()
|
||||
cfg.ReloadOnCreate = true
|
||||
cfg.ReloadOnDelete = true
|
||||
hasher := NewHasher()
|
||||
predicate := ConfigMapPredicates(cfg, hasher)
|
||||
|
||||
cm := &corev1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
|
||||
}
|
||||
|
||||
// Test Create
|
||||
if !predicate.Create(event.CreateEvent{Object: cm}) {
|
||||
t.Error("Create() should return true when ReloadOnCreate is true")
|
||||
}
|
||||
|
||||
// Test Delete
|
||||
if !predicate.Delete(event.DeleteEvent{Object: cm}) {
|
||||
t.Error("Delete() should return true when ReloadOnDelete is true")
|
||||
}
|
||||
|
||||
// Test Generic (should always return false)
|
||||
if predicate.Generic(event.GenericEvent{Object: cm}) {
|
||||
t.Error("Generic() should always return false")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSecretPredicates_Update(t *testing.T) {
|
||||
cfg := config.NewDefault()
|
||||
hasher := NewHasher()
|
||||
predicate := SecretPredicates(cfg, hasher)
|
||||
|
||||
oldSecret := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
|
||||
Data: map[string][]byte{"key": []byte("value1")},
|
||||
}
|
||||
newSecretSameContent := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
|
||||
Data: map[string][]byte{"key": []byte("value1")},
|
||||
}
|
||||
newSecretDifferentContent := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
|
||||
Data: map[string][]byte{"key": []byte("value2")},
|
||||
}
|
||||
|
||||
// Same content should not trigger update
|
||||
e := event.UpdateEvent{ObjectOld: oldSecret, ObjectNew: newSecretSameContent}
|
||||
if predicate.Update(e) {
|
||||
t.Error("Update() should return false when content is the same")
|
||||
}
|
||||
|
||||
// Different content should trigger update
|
||||
e = event.UpdateEvent{ObjectOld: oldSecret, ObjectNew: newSecretDifferentContent}
|
||||
if !predicate.Update(e) {
|
||||
t.Error("Update() should return true when content changed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSecretPredicates_InvalidTypes(t *testing.T) {
|
||||
cfg := config.NewDefault()
|
||||
hasher := NewHasher()
|
||||
predicate := SecretPredicates(cfg, hasher)
|
||||
|
||||
// Test with non-Secret types
|
||||
cm := &corev1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
|
||||
}
|
||||
secret := &corev1.Secret{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
|
||||
}
|
||||
|
||||
// Old is configmap, new is secret - should return false
|
||||
e := event.UpdateEvent{ObjectOld: cm, ObjectNew: secret}
|
||||
if predicate.Update(e) {
|
||||
t.Error("Update() should return false for mismatched types")
|
||||
}
|
||||
|
||||
// Both are configmaps - should return false
|
||||
e = event.UpdateEvent{ObjectOld: cm, ObjectNew: cm}
|
||||
if predicate.Update(e) {
|
||||
t.Error("Update() should return false for non-Secret types")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLabelsSet(t *testing.T) {
|
||||
ls := LabelsSet{"app": "test", "env": "prod"}
|
||||
|
||||
// Test Has
|
||||
if !ls.Has("app") {
|
||||
t.Error("Has(app) should return true")
|
||||
}
|
||||
if ls.Has("nonexistent") {
|
||||
t.Error("Has(nonexistent) should return false")
|
||||
}
|
||||
|
||||
// Test Get
|
||||
if ls.Get("app") != "test" {
|
||||
t.Errorf("Get(app) = %v, want test", ls.Get("app"))
|
||||
}
|
||||
if ls.Get("env") != "prod" {
|
||||
t.Errorf("Get(env) = %v, want prod", ls.Get("env"))
|
||||
}
|
||||
if ls.Get("nonexistent") != "" {
|
||||
t.Errorf("Get(nonexistent) = %v, want empty string", ls.Get("nonexistent"))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
package reload
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestResourceType_Kind(t *testing.T) {
|
||||
tests := []struct {
|
||||
resourceType ResourceType
|
||||
want string
|
||||
}{
|
||||
{ResourceTypeConfigMap, "ConfigMap"},
|
||||
{ResourceTypeSecret, "Secret"},
|
||||
{ResourceType("unknown"), "unknown"},
|
||||
{ResourceType("custom"), "custom"},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(string(tt.resourceType), func(t *testing.T) {
|
||||
got := tt.resourceType.Kind()
|
||||
if got != tt.want {
|
||||
t.Errorf("ResourceType(%q).Kind() = %v, want %v", tt.resourceType, got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestResourceTypeConstants(t *testing.T) {
|
||||
// Verify the constant values are as expected
|
||||
if ResourceTypeConfigMap != "configmap" {
|
||||
t.Errorf("ResourceTypeConfigMap = %v, want configmap", ResourceTypeConfigMap)
|
||||
}
|
||||
if ResourceTypeSecret != "secret" {
|
||||
t.Errorf("ResourceTypeSecret = %v, want secret", ResourceTypeSecret)
|
||||
}
|
||||
}
|
||||
@@ -587,6 +587,742 @@ func TestService_ProcessConfigMap_DifferentNamespaces(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestService_Hasher(t *testing.T) {
|
||||
cfg := config.NewDefault()
|
||||
svc := NewService(cfg)
|
||||
|
||||
hasher := svc.Hasher()
|
||||
if hasher == nil {
|
||||
t.Fatal("Expected Hasher to return non-nil hasher")
|
||||
}
|
||||
|
||||
// Verify it's functional
|
||||
cm := &corev1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test"},
|
||||
Data: map[string]string{"key": "value"},
|
||||
}
|
||||
hash := hasher.HashConfigMap(cm)
|
||||
if hash == "" {
|
||||
t.Error("Expected hasher to produce non-empty hash")
|
||||
}
|
||||
}
|
||||
|
||||
func TestService_shouldProcessEvent(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
reloadOnCreate bool
|
||||
reloadOnDelete bool
|
||||
eventType EventType
|
||||
expected bool
|
||||
}{
|
||||
{"create enabled", true, false, EventTypeCreate, true},
|
||||
{"create disabled", false, false, EventTypeCreate, false},
|
||||
{"delete enabled", false, true, EventTypeDelete, true},
|
||||
{"delete disabled", false, false, EventTypeDelete, false},
|
||||
{"update always true", false, false, EventTypeUpdate, true},
|
||||
{"unknown event", false, false, EventType("unknown"), false},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
cfg := config.NewDefault()
|
||||
cfg.ReloadOnCreate = tt.reloadOnCreate
|
||||
cfg.ReloadOnDelete = tt.reloadOnDelete
|
||||
svc := NewService(cfg)
|
||||
|
||||
result := svc.shouldProcessEvent(tt.eventType)
|
||||
if result != tt.expected {
|
||||
t.Errorf("shouldProcessEvent(%s) = %v, want %v", tt.eventType, result, tt.expected)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestService_findVolumeUsingResource_ConfigMap(t *testing.T) {
|
||||
cfg := config.NewDefault()
|
||||
svc := NewService(cfg)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
volumes []corev1.Volume
|
||||
resourceName string
|
||||
resourceType ResourceType
|
||||
wantVolume string
|
||||
}{
|
||||
{
|
||||
name: "direct configmap volume",
|
||||
volumes: []corev1.Volume{
|
||||
{
|
||||
Name: "config-vol",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
ConfigMap: &corev1.ConfigMapVolumeSource{
|
||||
LocalObjectReference: corev1.LocalObjectReference{Name: "my-cm"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
resourceName: "my-cm",
|
||||
resourceType: ResourceTypeConfigMap,
|
||||
wantVolume: "config-vol",
|
||||
},
|
||||
{
|
||||
name: "projected configmap volume",
|
||||
volumes: []corev1.Volume{
|
||||
{
|
||||
Name: "projected-vol",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
Projected: &corev1.ProjectedVolumeSource{
|
||||
Sources: []corev1.VolumeProjection{
|
||||
{
|
||||
ConfigMap: &corev1.ConfigMapProjection{
|
||||
LocalObjectReference: corev1.LocalObjectReference{Name: "projected-cm"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
resourceName: "projected-cm",
|
||||
resourceType: ResourceTypeConfigMap,
|
||||
wantVolume: "projected-vol",
|
||||
},
|
||||
{
|
||||
name: "no match",
|
||||
volumes: []corev1.Volume{
|
||||
{
|
||||
Name: "other-vol",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
ConfigMap: &corev1.ConfigMapVolumeSource{
|
||||
LocalObjectReference: corev1.LocalObjectReference{Name: "other-cm"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
resourceName: "my-cm",
|
||||
resourceType: ResourceTypeConfigMap,
|
||||
wantVolume: "",
|
||||
},
|
||||
{
|
||||
name: "empty volumes",
|
||||
volumes: []corev1.Volume{},
|
||||
resourceName: "my-cm",
|
||||
resourceType: ResourceTypeConfigMap,
|
||||
wantVolume: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := svc.findVolumeUsingResource(tt.volumes, tt.resourceName, tt.resourceType)
|
||||
if got != tt.wantVolume {
|
||||
t.Errorf("findVolumeUsingResource() = %q, want %q", got, tt.wantVolume)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestService_findVolumeUsingResource_Secret(t *testing.T) {
|
||||
cfg := config.NewDefault()
|
||||
svc := NewService(cfg)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
volumes []corev1.Volume
|
||||
resourceName string
|
||||
wantVolume string
|
||||
}{
|
||||
{
|
||||
name: "direct secret volume",
|
||||
volumes: []corev1.Volume{
|
||||
{
|
||||
Name: "secret-vol",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
Secret: &corev1.SecretVolumeSource{
|
||||
SecretName: "my-secret",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
resourceName: "my-secret",
|
||||
wantVolume: "secret-vol",
|
||||
},
|
||||
{
|
||||
name: "projected secret volume",
|
||||
volumes: []corev1.Volume{
|
||||
{
|
||||
Name: "projected-vol",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
Projected: &corev1.ProjectedVolumeSource{
|
||||
Sources: []corev1.VolumeProjection{
|
||||
{
|
||||
Secret: &corev1.SecretProjection{
|
||||
LocalObjectReference: corev1.LocalObjectReference{Name: "projected-secret"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
resourceName: "projected-secret",
|
||||
wantVolume: "projected-vol",
|
||||
},
|
||||
{
|
||||
name: "no match",
|
||||
volumes: []corev1.Volume{
|
||||
{
|
||||
Name: "other-vol",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
Secret: &corev1.SecretVolumeSource{
|
||||
SecretName: "other-secret",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
resourceName: "my-secret",
|
||||
wantVolume: "",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := svc.findVolumeUsingResource(tt.volumes, tt.resourceName, ResourceTypeSecret)
|
||||
if got != tt.wantVolume {
|
||||
t.Errorf("findVolumeUsingResource() = %q, want %q", got, tt.wantVolume)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestService_findContainerWithVolumeMount(t *testing.T) {
|
||||
cfg := config.NewDefault()
|
||||
svc := NewService(cfg)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
containers []corev1.Container
|
||||
volumeName string
|
||||
wantName string
|
||||
shouldMatch bool
|
||||
}{
|
||||
{
|
||||
name: "container with matching volume mount",
|
||||
containers: []corev1.Container{
|
||||
{
|
||||
Name: "container1",
|
||||
VolumeMounts: []corev1.VolumeMount{
|
||||
{Name: "config-vol", MountPath: "/config"},
|
||||
},
|
||||
},
|
||||
},
|
||||
volumeName: "config-vol",
|
||||
wantName: "container1",
|
||||
shouldMatch: true,
|
||||
},
|
||||
{
|
||||
name: "second container with matching mount",
|
||||
containers: []corev1.Container{
|
||||
{
|
||||
Name: "container1",
|
||||
VolumeMounts: []corev1.VolumeMount{},
|
||||
},
|
||||
{
|
||||
Name: "container2",
|
||||
VolumeMounts: []corev1.VolumeMount{
|
||||
{Name: "config-vol", MountPath: "/config"},
|
||||
},
|
||||
},
|
||||
},
|
||||
volumeName: "config-vol",
|
||||
wantName: "container2",
|
||||
shouldMatch: true,
|
||||
},
|
||||
{
|
||||
name: "no matching mount",
|
||||
containers: []corev1.Container{
|
||||
{
|
||||
Name: "container1",
|
||||
VolumeMounts: []corev1.VolumeMount{
|
||||
{Name: "other-vol", MountPath: "/other"},
|
||||
},
|
||||
},
|
||||
},
|
||||
volumeName: "config-vol",
|
||||
shouldMatch: false,
|
||||
},
|
||||
{
|
||||
name: "empty containers",
|
||||
containers: []corev1.Container{},
|
||||
volumeName: "config-vol",
|
||||
shouldMatch: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := svc.findContainerWithVolumeMount(tt.containers, tt.volumeName)
|
||||
if tt.shouldMatch {
|
||||
if got == nil {
|
||||
t.Error("Expected to find a container, got nil")
|
||||
} else if got.Name != tt.wantName {
|
||||
t.Errorf("findContainerWithVolumeMount() container name = %q, want %q", got.Name, tt.wantName)
|
||||
}
|
||||
} else {
|
||||
if got != nil {
|
||||
t.Errorf("Expected nil, got container %q", got.Name)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestService_findContainerWithEnvRef_ConfigMap(t *testing.T) {
|
||||
cfg := config.NewDefault()
|
||||
svc := NewService(cfg)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
containers []corev1.Container
|
||||
resourceName string
|
||||
wantName string
|
||||
shouldMatch bool
|
||||
}{
|
||||
{
|
||||
name: "container with ConfigMapKeyRef",
|
||||
containers: []corev1.Container{
|
||||
{
|
||||
Name: "app",
|
||||
Env: []corev1.EnvVar{
|
||||
{
|
||||
Name: "CONFIG_VALUE",
|
||||
ValueFrom: &corev1.EnvVarSource{
|
||||
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
|
||||
LocalObjectReference: corev1.LocalObjectReference{Name: "my-cm"},
|
||||
Key: "key",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
resourceName: "my-cm",
|
||||
wantName: "app",
|
||||
shouldMatch: true,
|
||||
},
|
||||
{
|
||||
name: "container with ConfigMapRef in EnvFrom",
|
||||
containers: []corev1.Container{
|
||||
{
|
||||
Name: "app",
|
||||
EnvFrom: []corev1.EnvFromSource{
|
||||
{
|
||||
ConfigMapRef: &corev1.ConfigMapEnvSource{
|
||||
LocalObjectReference: corev1.LocalObjectReference{Name: "my-cm"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
resourceName: "my-cm",
|
||||
wantName: "app",
|
||||
shouldMatch: true,
|
||||
},
|
||||
{
|
||||
name: "no matching env ref",
|
||||
containers: []corev1.Container{
|
||||
{
|
||||
Name: "app",
|
||||
Env: []corev1.EnvVar{
|
||||
{
|
||||
Name: "SIMPLE_VAR",
|
||||
Value: "value",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
resourceName: "my-cm",
|
||||
shouldMatch: false,
|
||||
},
|
||||
{
|
||||
name: "env without ValueFrom",
|
||||
containers: []corev1.Container{
|
||||
{
|
||||
Name: "app",
|
||||
Env: []corev1.EnvVar{
|
||||
{Name: "VAR1", Value: "val"},
|
||||
},
|
||||
},
|
||||
},
|
||||
resourceName: "my-cm",
|
||||
shouldMatch: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := svc.findContainerWithEnvRef(tt.containers, tt.resourceName, ResourceTypeConfigMap)
|
||||
if tt.shouldMatch {
|
||||
if got == nil {
|
||||
t.Error("Expected to find a container, got nil")
|
||||
} else if got.Name != tt.wantName {
|
||||
t.Errorf("findContainerWithEnvRef() container name = %q, want %q", got.Name, tt.wantName)
|
||||
}
|
||||
} else {
|
||||
if got != nil {
|
||||
t.Errorf("Expected nil, got container %q", got.Name)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestService_findContainerWithEnvRef_Secret(t *testing.T) {
|
||||
cfg := config.NewDefault()
|
||||
svc := NewService(cfg)
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
containers []corev1.Container
|
||||
resourceName string
|
||||
wantName string
|
||||
shouldMatch bool
|
||||
}{
|
||||
{
|
||||
name: "container with SecretKeyRef",
|
||||
containers: []corev1.Container{
|
||||
{
|
||||
Name: "app",
|
||||
Env: []corev1.EnvVar{
|
||||
{
|
||||
Name: "SECRET_VALUE",
|
||||
ValueFrom: &corev1.EnvVarSource{
|
||||
SecretKeyRef: &corev1.SecretKeySelector{
|
||||
LocalObjectReference: corev1.LocalObjectReference{Name: "my-secret"},
|
||||
Key: "password",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
resourceName: "my-secret",
|
||||
wantName: "app",
|
||||
shouldMatch: true,
|
||||
},
|
||||
{
|
||||
name: "container with SecretRef in EnvFrom",
|
||||
containers: []corev1.Container{
|
||||
{
|
||||
Name: "app",
|
||||
EnvFrom: []corev1.EnvFromSource{
|
||||
{
|
||||
SecretRef: &corev1.SecretEnvSource{
|
||||
LocalObjectReference: corev1.LocalObjectReference{Name: "my-secret"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
resourceName: "my-secret",
|
||||
wantName: "app",
|
||||
shouldMatch: true,
|
||||
},
|
||||
{
|
||||
name: "no matching env ref",
|
||||
containers: []corev1.Container{
|
||||
{
|
||||
Name: "app",
|
||||
Env: []corev1.EnvVar{
|
||||
{
|
||||
Name: "SIMPLE_VAR",
|
||||
Value: "value",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
resourceName: "my-secret",
|
||||
shouldMatch: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got := svc.findContainerWithEnvRef(tt.containers, tt.resourceName, ResourceTypeSecret)
|
||||
if tt.shouldMatch {
|
||||
if got == nil {
|
||||
t.Error("Expected to find a container, got nil")
|
||||
} else if got.Name != tt.wantName {
|
||||
t.Errorf("findContainerWithEnvRef() container name = %q, want %q", got.Name, tt.wantName)
|
||||
}
|
||||
} else {
|
||||
if got != nil {
|
||||
t.Errorf("Expected nil, got container %q", got.Name)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestService_findTargetContainer_AutoReload(t *testing.T) {
|
||||
cfg := config.NewDefault()
|
||||
svc := NewService(cfg)
|
||||
|
||||
// Test with autoReload=true and volume mount
|
||||
deploy := createTestDeployment("test", "default", nil)
|
||||
deploy.Spec.Template.Spec.Volumes = []corev1.Volume{
|
||||
{
|
||||
Name: "config-vol",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
ConfigMap: &corev1.ConfigMapVolumeSource{
|
||||
LocalObjectReference: corev1.LocalObjectReference{Name: "my-cm"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
deploy.Spec.Template.Spec.Containers = []corev1.Container{
|
||||
{
|
||||
Name: "app",
|
||||
Image: "nginx",
|
||||
VolumeMounts: []corev1.VolumeMount{
|
||||
{Name: "config-vol", MountPath: "/config"},
|
||||
},
|
||||
},
|
||||
}
|
||||
accessor := workload.NewDeploymentWorkload(deploy)
|
||||
|
||||
container := svc.findTargetContainer(accessor, "my-cm", ResourceTypeConfigMap, true)
|
||||
if container == nil {
|
||||
t.Fatal("Expected to find a container")
|
||||
}
|
||||
if container.Name != "app" {
|
||||
t.Errorf("Expected container 'app', got %q", container.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestService_findTargetContainer_AutoReload_EnvRef(t *testing.T) {
|
||||
cfg := config.NewDefault()
|
||||
svc := NewService(cfg)
|
||||
|
||||
// Test with autoReload=true and env ref (no volume)
|
||||
deploy := createTestDeployment("test", "default", nil)
|
||||
deploy.Spec.Template.Spec.Containers = []corev1.Container{
|
||||
{
|
||||
Name: "sidecar",
|
||||
Image: "busybox",
|
||||
},
|
||||
{
|
||||
Name: "app",
|
||||
Image: "nginx",
|
||||
Env: []corev1.EnvVar{
|
||||
{
|
||||
Name: "CONFIG_VAL",
|
||||
ValueFrom: &corev1.EnvVarSource{
|
||||
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
|
||||
LocalObjectReference: corev1.LocalObjectReference{Name: "my-cm"},
|
||||
Key: "key",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
accessor := workload.NewDeploymentWorkload(deploy)
|
||||
|
||||
container := svc.findTargetContainer(accessor, "my-cm", ResourceTypeConfigMap, true)
|
||||
if container == nil {
|
||||
t.Fatal("Expected to find a container")
|
||||
}
|
||||
if container.Name != "app" {
|
||||
t.Errorf("Expected container 'app', got %q", container.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestService_findTargetContainer_AutoReload_InitContainer(t *testing.T) {
|
||||
cfg := config.NewDefault()
|
||||
svc := NewService(cfg)
|
||||
|
||||
// Test with autoReload=true where init container uses the volume
|
||||
deploy := createTestDeployment("test", "default", nil)
|
||||
deploy.Spec.Template.Spec.Volumes = []corev1.Volume{
|
||||
{
|
||||
Name: "config-vol",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
ConfigMap: &corev1.ConfigMapVolumeSource{
|
||||
LocalObjectReference: corev1.LocalObjectReference{Name: "my-cm"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
deploy.Spec.Template.Spec.InitContainers = []corev1.Container{
|
||||
{
|
||||
Name: "init",
|
||||
Image: "busybox",
|
||||
VolumeMounts: []corev1.VolumeMount{
|
||||
{Name: "config-vol", MountPath: "/config"},
|
||||
},
|
||||
},
|
||||
}
|
||||
deploy.Spec.Template.Spec.Containers = []corev1.Container{
|
||||
{
|
||||
Name: "app",
|
||||
Image: "nginx",
|
||||
},
|
||||
}
|
||||
accessor := workload.NewDeploymentWorkload(deploy)
|
||||
|
||||
container := svc.findTargetContainer(accessor, "my-cm", ResourceTypeConfigMap, true)
|
||||
if container == nil {
|
||||
t.Fatal("Expected to find a container")
|
||||
}
|
||||
// Should return first main container when init container uses the volume
|
||||
if container.Name != "app" {
|
||||
t.Errorf("Expected container 'app', got %q", container.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestService_findTargetContainer_AutoReload_InitContainerEnvRef(t *testing.T) {
|
||||
cfg := config.NewDefault()
|
||||
svc := NewService(cfg)
|
||||
|
||||
// Test with autoReload=true where init container has env ref
|
||||
deploy := createTestDeployment("test", "default", nil)
|
||||
deploy.Spec.Template.Spec.InitContainers = []corev1.Container{
|
||||
{
|
||||
Name: "init",
|
||||
Image: "busybox",
|
||||
Env: []corev1.EnvVar{
|
||||
{
|
||||
Name: "CONFIG_VAL",
|
||||
ValueFrom: &corev1.EnvVarSource{
|
||||
ConfigMapKeyRef: &corev1.ConfigMapKeySelector{
|
||||
LocalObjectReference: corev1.LocalObjectReference{Name: "my-cm"},
|
||||
Key: "key",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
deploy.Spec.Template.Spec.Containers = []corev1.Container{
|
||||
{
|
||||
Name: "app",
|
||||
Image: "nginx",
|
||||
},
|
||||
}
|
||||
accessor := workload.NewDeploymentWorkload(deploy)
|
||||
|
||||
container := svc.findTargetContainer(accessor, "my-cm", ResourceTypeConfigMap, true)
|
||||
if container == nil {
|
||||
t.Fatal("Expected to find a container")
|
||||
}
|
||||
// Should return first main container when init container has the env ref
|
||||
if container.Name != "app" {
|
||||
t.Errorf("Expected container 'app', got %q", container.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestService_findTargetContainer_NoContainers(t *testing.T) {
|
||||
cfg := config.NewDefault()
|
||||
svc := NewService(cfg)
|
||||
|
||||
deploy := createTestDeployment("test", "default", nil)
|
||||
deploy.Spec.Template.Spec.Containers = []corev1.Container{}
|
||||
accessor := workload.NewDeploymentWorkload(deploy)
|
||||
|
||||
container := svc.findTargetContainer(accessor, "my-cm", ResourceTypeConfigMap, false)
|
||||
if container != nil {
|
||||
t.Error("Expected nil container for empty container list")
|
||||
}
|
||||
}
|
||||
|
||||
func TestService_findTargetContainer_NonAutoReload(t *testing.T) {
|
||||
cfg := config.NewDefault()
|
||||
svc := NewService(cfg)
|
||||
|
||||
deploy := createTestDeployment("test", "default", nil)
|
||||
deploy.Spec.Template.Spec.Containers = []corev1.Container{
|
||||
{Name: "first", Image: "nginx"},
|
||||
{Name: "second", Image: "busybox"},
|
||||
}
|
||||
accessor := workload.NewDeploymentWorkload(deploy)
|
||||
|
||||
// Without autoReload, should return first container
|
||||
container := svc.findTargetContainer(accessor, "my-cm", ResourceTypeConfigMap, false)
|
||||
if container == nil {
|
||||
t.Fatal("Expected to find a container")
|
||||
}
|
||||
if container.Name != "first" {
|
||||
t.Errorf("Expected first container, got %q", container.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestService_findTargetContainer_AutoReload_FallbackToFirst(t *testing.T) {
|
||||
cfg := config.NewDefault()
|
||||
svc := NewService(cfg)
|
||||
|
||||
// autoReload=true but no matching volume or env ref - should fallback to first container
|
||||
deploy := createTestDeployment("test", "default", nil)
|
||||
deploy.Spec.Template.Spec.Containers = []corev1.Container{
|
||||
{Name: "first", Image: "nginx"},
|
||||
{Name: "second", Image: "busybox"},
|
||||
}
|
||||
accessor := workload.NewDeploymentWorkload(deploy)
|
||||
|
||||
container := svc.findTargetContainer(accessor, "non-existent", ResourceTypeConfigMap, true)
|
||||
if container == nil {
|
||||
t.Fatal("Expected to find a container")
|
||||
}
|
||||
if container.Name != "first" {
|
||||
t.Errorf("Expected first container as fallback, got %q", container.Name)
|
||||
}
|
||||
}
|
||||
|
||||
func TestService_ProcessNilChange(t *testing.T) {
|
||||
cfg := config.NewDefault()
|
||||
svc := NewService(cfg)
|
||||
|
||||
deploy := createTestDeployment("test", "default", nil)
|
||||
workloads := []workload.WorkloadAccessor{workload.NewDeploymentWorkload(deploy)}
|
||||
|
||||
// Test with nil ConfigMap
|
||||
change := ConfigMapChange{
|
||||
ConfigMap: nil,
|
||||
EventType: EventTypeUpdate,
|
||||
}
|
||||
|
||||
decisions := svc.Process(change, workloads)
|
||||
if decisions != nil {
|
||||
t.Errorf("Expected nil decisions for nil change, got %v", decisions)
|
||||
}
|
||||
}
|
||||
|
||||
func TestService_ProcessCreateEventDisabled(t *testing.T) {
|
||||
cfg := config.NewDefault()
|
||||
cfg.ReloadOnCreate = false
|
||||
svc := NewService(cfg)
|
||||
|
||||
deploy := createTestDeployment("test", "default", map[string]string{
|
||||
"reloader.stakater.com/auto": "true",
|
||||
})
|
||||
workloads := []workload.WorkloadAccessor{workload.NewDeploymentWorkload(deploy)}
|
||||
|
||||
cm := &corev1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test-cm", Namespace: "default"},
|
||||
Data: map[string]string{"key": "value"},
|
||||
}
|
||||
|
||||
change := ConfigMapChange{
|
||||
ConfigMap: cm,
|
||||
EventType: EventTypeCreate,
|
||||
}
|
||||
|
||||
decisions := svc.Process(change, workloads)
|
||||
if decisions != nil {
|
||||
t.Errorf("Expected nil decisions when create events disabled, got %v", decisions)
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to create a test deployment
|
||||
func createTestDeployment(name, namespace string, annotations map[string]string) *appsv1.Deployment {
|
||||
replicas := int32(1)
|
||||
|
||||
@@ -0,0 +1,250 @@
|
||||
package workload
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
argorolloutv1alpha1 "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
batchv1 "k8s.io/api/batch/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
func TestNewRegistry_WithoutArgoRollouts(t *testing.T) {
|
||||
r := NewRegistry(false)
|
||||
|
||||
kinds := r.SupportedKinds()
|
||||
if len(kinds) != 5 {
|
||||
t.Errorf("SupportedKinds() = %d kinds, want 5", len(kinds))
|
||||
}
|
||||
|
||||
// Should not include ArgoRollout
|
||||
for _, k := range kinds {
|
||||
if k == KindArgoRollout {
|
||||
t.Error("SupportedKinds() should not include ArgoRollout when disabled")
|
||||
}
|
||||
}
|
||||
|
||||
// ListerFor should return nil for ArgoRollout
|
||||
if r.ListerFor(KindArgoRollout) != nil {
|
||||
t.Error("ListerFor(KindArgoRollout) should return nil when disabled")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewRegistry_WithArgoRollouts(t *testing.T) {
|
||||
r := NewRegistry(true)
|
||||
|
||||
kinds := r.SupportedKinds()
|
||||
if len(kinds) != 6 {
|
||||
t.Errorf("SupportedKinds() = %d kinds, want 6", len(kinds))
|
||||
}
|
||||
|
||||
// Should include ArgoRollout
|
||||
found := false
|
||||
for _, k := range kinds {
|
||||
if k == KindArgoRollout {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
t.Error("SupportedKinds() should include ArgoRollout when enabled")
|
||||
}
|
||||
|
||||
// ListerFor should return a function for ArgoRollout
|
||||
if r.ListerFor(KindArgoRollout) == nil {
|
||||
t.Error("ListerFor(KindArgoRollout) should return a function when enabled")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegistry_ListerFor_AllKinds(t *testing.T) {
|
||||
r := NewRegistry(true)
|
||||
|
||||
tests := []struct {
|
||||
kind Kind
|
||||
wantNil bool
|
||||
}{
|
||||
{KindDeployment, false},
|
||||
{KindDaemonSet, false},
|
||||
{KindStatefulSet, false},
|
||||
{KindJob, false},
|
||||
{KindCronJob, false},
|
||||
{KindArgoRollout, false},
|
||||
{Kind("unknown"), true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
lister := r.ListerFor(tt.kind)
|
||||
if (lister == nil) != tt.wantNil {
|
||||
t.Errorf("ListerFor(%s) = nil? %v, want nil? %v", tt.kind, lister == nil, tt.wantNil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegistry_FromObject_Deployment(t *testing.T) {
|
||||
r := NewRegistry(false)
|
||||
deploy := &appsv1.Deployment{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
|
||||
}
|
||||
|
||||
w, err := r.FromObject(deploy)
|
||||
if err != nil {
|
||||
t.Fatalf("FromObject(Deployment) error = %v", err)
|
||||
}
|
||||
if w.Kind() != KindDeployment {
|
||||
t.Errorf("FromObject(Deployment).Kind() = %v, want %v", w.Kind(), KindDeployment)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegistry_FromObject_DaemonSet(t *testing.T) {
|
||||
r := NewRegistry(false)
|
||||
ds := &appsv1.DaemonSet{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
|
||||
}
|
||||
|
||||
w, err := r.FromObject(ds)
|
||||
if err != nil {
|
||||
t.Fatalf("FromObject(DaemonSet) error = %v", err)
|
||||
}
|
||||
if w.Kind() != KindDaemonSet {
|
||||
t.Errorf("FromObject(DaemonSet).Kind() = %v, want %v", w.Kind(), KindDaemonSet)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegistry_FromObject_StatefulSet(t *testing.T) {
|
||||
r := NewRegistry(false)
|
||||
sts := &appsv1.StatefulSet{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
|
||||
}
|
||||
|
||||
w, err := r.FromObject(sts)
|
||||
if err != nil {
|
||||
t.Fatalf("FromObject(StatefulSet) error = %v", err)
|
||||
}
|
||||
if w.Kind() != KindStatefulSet {
|
||||
t.Errorf("FromObject(StatefulSet).Kind() = %v, want %v", w.Kind(), KindStatefulSet)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegistry_FromObject_Job(t *testing.T) {
|
||||
r := NewRegistry(false)
|
||||
job := &batchv1.Job{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
|
||||
}
|
||||
|
||||
w, err := r.FromObject(job)
|
||||
if err != nil {
|
||||
t.Fatalf("FromObject(Job) error = %v", err)
|
||||
}
|
||||
if w.Kind() != KindJob {
|
||||
t.Errorf("FromObject(Job).Kind() = %v, want %v", w.Kind(), KindJob)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegistry_FromObject_CronJob(t *testing.T) {
|
||||
r := NewRegistry(false)
|
||||
cj := &batchv1.CronJob{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
|
||||
}
|
||||
|
||||
w, err := r.FromObject(cj)
|
||||
if err != nil {
|
||||
t.Fatalf("FromObject(CronJob) error = %v", err)
|
||||
}
|
||||
if w.Kind() != KindCronJob {
|
||||
t.Errorf("FromObject(CronJob).Kind() = %v, want %v", w.Kind(), KindCronJob)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegistry_FromObject_Rollout_Enabled(t *testing.T) {
|
||||
r := NewRegistry(true)
|
||||
rollout := &argorolloutv1alpha1.Rollout{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
|
||||
}
|
||||
|
||||
w, err := r.FromObject(rollout)
|
||||
if err != nil {
|
||||
t.Fatalf("FromObject(Rollout) error = %v", err)
|
||||
}
|
||||
if w.Kind() != KindArgoRollout {
|
||||
t.Errorf("FromObject(Rollout).Kind() = %v, want %v", w.Kind(), KindArgoRollout)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegistry_FromObject_Rollout_Disabled(t *testing.T) {
|
||||
r := NewRegistry(false)
|
||||
rollout := &argorolloutv1alpha1.Rollout{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
|
||||
}
|
||||
|
||||
_, err := r.FromObject(rollout)
|
||||
if err == nil {
|
||||
t.Error("FromObject(Rollout) should return error when Argo Rollouts disabled")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegistry_FromObject_UnsupportedType(t *testing.T) {
|
||||
r := NewRegistry(false)
|
||||
cm := &corev1.ConfigMap{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test", Namespace: "default"},
|
||||
}
|
||||
|
||||
_, err := r.FromObject(cm)
|
||||
if err == nil {
|
||||
t.Error("FromObject(ConfigMap) should return error for unsupported type")
|
||||
}
|
||||
}
|
||||
|
||||
func TestKindFromString(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
want Kind
|
||||
wantErr bool
|
||||
}{
|
||||
// Lowercase
|
||||
{"deployment", KindDeployment, false},
|
||||
{"daemonset", KindDaemonSet, false},
|
||||
{"statefulset", KindStatefulSet, false},
|
||||
{"job", KindJob, false},
|
||||
{"cronjob", KindCronJob, false},
|
||||
{"rollout", KindArgoRollout, false},
|
||||
// Plural forms
|
||||
{"deployments", KindDeployment, false},
|
||||
{"daemonsets", KindDaemonSet, false},
|
||||
{"statefulsets", KindStatefulSet, false},
|
||||
{"jobs", KindJob, false},
|
||||
{"cronjobs", KindCronJob, false},
|
||||
{"rollouts", KindArgoRollout, false},
|
||||
// Mixed case
|
||||
{"Deployment", KindDeployment, false},
|
||||
{"DAEMONSET", KindDaemonSet, false},
|
||||
{"StatefulSet", KindStatefulSet, false},
|
||||
// Unknown
|
||||
{"unknown", "", true},
|
||||
{"replicaset", "", true},
|
||||
{"", "", true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
got, err := KindFromString(tt.input)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("KindFromString(%q) error = %v, wantErr %v", tt.input, err, tt.wantErr)
|
||||
continue
|
||||
}
|
||||
if got != tt.want {
|
||||
t.Errorf("KindFromString(%q) = %v, want %v", tt.input, got, tt.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewLister(t *testing.T) {
|
||||
r := NewRegistry(false)
|
||||
l := NewLister(nil, r, nil)
|
||||
|
||||
if l == nil {
|
||||
t.Fatal("NewLister should not return nil")
|
||||
}
|
||||
if l.Registry != r {
|
||||
t.Error("NewLister should set Registry")
|
||||
}
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
argorolloutv1alpha1 "github.com/argoproj/argo-rollouts/pkg/apis/rollouts/v1alpha1"
|
||||
appsv1 "k8s.io/api/apps/v1"
|
||||
batchv1 "k8s.io/api/batch/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
@@ -597,6 +598,9 @@ func TestDaemonSetWorkload_BasicGetters(t *testing.T) {
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-ds",
|
||||
Namespace: "test-ns",
|
||||
Annotations: map[string]string{
|
||||
"key": "value",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -608,6 +612,128 @@ func TestDaemonSetWorkload_BasicGetters(t *testing.T) {
|
||||
if w.GetName() != "test-ds" {
|
||||
t.Errorf("GetName() = %v, want test-ds", w.GetName())
|
||||
}
|
||||
if w.GetNamespace() != "test-ns" {
|
||||
t.Errorf("GetNamespace() = %v, want test-ns", w.GetNamespace())
|
||||
}
|
||||
if w.GetAnnotations()["key"] != "value" {
|
||||
t.Errorf("GetAnnotations()[key] = %v, want value", w.GetAnnotations()["key"])
|
||||
}
|
||||
if w.GetObject() != ds {
|
||||
t.Error("GetObject() should return the underlying daemonset")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDaemonSetWorkload_PodTemplateAnnotations(t *testing.T) {
|
||||
ds := &appsv1.DaemonSet{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test"},
|
||||
Spec: appsv1.DaemonSetSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Annotations: map[string]string{
|
||||
"existing": "annotation",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
w := NewDaemonSetWorkload(ds)
|
||||
|
||||
annotations := w.GetPodTemplateAnnotations()
|
||||
if annotations["existing"] != "annotation" {
|
||||
t.Errorf("GetPodTemplateAnnotations()[existing] = %v, want annotation", annotations["existing"])
|
||||
}
|
||||
|
||||
w.SetPodTemplateAnnotation("new-key", "new-value")
|
||||
if w.GetPodTemplateAnnotations()["new-key"] != "new-value" {
|
||||
t.Error("SetPodTemplateAnnotation should add new annotation")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDaemonSetWorkload_PodTemplateAnnotations_NilInit(t *testing.T) {
|
||||
ds := &appsv1.DaemonSet{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test"},
|
||||
Spec: appsv1.DaemonSetSpec{
|
||||
Template: corev1.PodTemplateSpec{},
|
||||
},
|
||||
}
|
||||
|
||||
w := NewDaemonSetWorkload(ds)
|
||||
|
||||
annotations := w.GetPodTemplateAnnotations()
|
||||
if annotations == nil {
|
||||
t.Error("GetPodTemplateAnnotations should initialize nil map")
|
||||
}
|
||||
|
||||
w.SetPodTemplateAnnotation("key", "value")
|
||||
if w.GetPodTemplateAnnotations()["key"] != "value" {
|
||||
t.Error("SetPodTemplateAnnotation should work with nil initial map")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDaemonSetWorkload_Containers(t *testing.T) {
|
||||
ds := &appsv1.DaemonSet{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test"},
|
||||
Spec: appsv1.DaemonSetSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{Name: "main", Image: "nginx"},
|
||||
},
|
||||
InitContainers: []corev1.Container{
|
||||
{Name: "init", Image: "busybox"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
w := NewDaemonSetWorkload(ds)
|
||||
|
||||
containers := w.GetContainers()
|
||||
if len(containers) != 1 || containers[0].Name != "main" {
|
||||
t.Errorf("GetContainers() = %v, want [main]", containers)
|
||||
}
|
||||
|
||||
initContainers := w.GetInitContainers()
|
||||
if len(initContainers) != 1 || initContainers[0].Name != "init" {
|
||||
t.Errorf("GetInitContainers() = %v, want [init]", initContainers)
|
||||
}
|
||||
|
||||
newContainers := []corev1.Container{{Name: "new-main", Image: "alpine"}}
|
||||
w.SetContainers(newContainers)
|
||||
if w.GetContainers()[0].Name != "new-main" {
|
||||
t.Error("SetContainers should update containers")
|
||||
}
|
||||
|
||||
newInitContainers := []corev1.Container{{Name: "new-init", Image: "alpine"}}
|
||||
w.SetInitContainers(newInitContainers)
|
||||
if w.GetInitContainers()[0].Name != "new-init" {
|
||||
t.Error("SetInitContainers should update init containers")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDaemonSetWorkload_Volumes(t *testing.T) {
|
||||
ds := &appsv1.DaemonSet{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test"},
|
||||
Spec: appsv1.DaemonSetSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
Spec: corev1.PodSpec{
|
||||
Volumes: []corev1.Volume{
|
||||
{Name: "config-vol"},
|
||||
{Name: "secret-vol"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
w := NewDaemonSetWorkload(ds)
|
||||
|
||||
volumes := w.GetVolumes()
|
||||
if len(volumes) != 2 {
|
||||
t.Errorf("GetVolumes() length = %d, want 2", len(volumes))
|
||||
}
|
||||
}
|
||||
|
||||
func TestDaemonSetWorkload_UsesConfigMap(t *testing.T) {
|
||||
@@ -638,6 +764,157 @@ func TestDaemonSetWorkload_UsesConfigMap(t *testing.T) {
|
||||
if !w.UsesConfigMap("ds-config") {
|
||||
t.Error("DaemonSet UsesConfigMap should return true for ConfigMap volume")
|
||||
}
|
||||
if w.UsesConfigMap("other-config") {
|
||||
t.Error("UsesConfigMap should return false for non-existent ConfigMap")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDaemonSetWorkload_UsesConfigMap_EnvFrom(t *testing.T) {
|
||||
ds := &appsv1.DaemonSet{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test"},
|
||||
Spec: appsv1.DaemonSetSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "main",
|
||||
EnvFrom: []corev1.EnvFromSource{
|
||||
{
|
||||
ConfigMapRef: &corev1.ConfigMapEnvSource{
|
||||
LocalObjectReference: corev1.LocalObjectReference{
|
||||
Name: "ds-env-config",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
w := NewDaemonSetWorkload(ds)
|
||||
|
||||
if !w.UsesConfigMap("ds-env-config") {
|
||||
t.Error("DaemonSet UsesConfigMap should return true for envFrom ConfigMap")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDaemonSetWorkload_UsesSecret(t *testing.T) {
|
||||
ds := &appsv1.DaemonSet{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test"},
|
||||
Spec: appsv1.DaemonSetSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
Spec: corev1.PodSpec{
|
||||
Volumes: []corev1.Volume{
|
||||
{
|
||||
Name: "secret-vol",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
Secret: &corev1.SecretVolumeSource{
|
||||
SecretName: "ds-secret",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
w := NewDaemonSetWorkload(ds)
|
||||
|
||||
if !w.UsesSecret("ds-secret") {
|
||||
t.Error("DaemonSet UsesSecret should return true for Secret volume")
|
||||
}
|
||||
if w.UsesSecret("other-secret") {
|
||||
t.Error("UsesSecret should return false for non-existent Secret")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDaemonSetWorkload_GetEnvFromSources(t *testing.T) {
|
||||
ds := &appsv1.DaemonSet{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test"},
|
||||
Spec: appsv1.DaemonSetSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "main",
|
||||
EnvFrom: []corev1.EnvFromSource{
|
||||
{ConfigMapRef: &corev1.ConfigMapEnvSource{LocalObjectReference: corev1.LocalObjectReference{Name: "cm1"}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
InitContainers: []corev1.Container{
|
||||
{
|
||||
Name: "init",
|
||||
EnvFrom: []corev1.EnvFromSource{
|
||||
{SecretRef: &corev1.SecretEnvSource{LocalObjectReference: corev1.LocalObjectReference{Name: "secret1"}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
w := NewDaemonSetWorkload(ds)
|
||||
|
||||
sources := w.GetEnvFromSources()
|
||||
if len(sources) != 2 {
|
||||
t.Errorf("GetEnvFromSources() returned %d sources, want 2", len(sources))
|
||||
}
|
||||
}
|
||||
|
||||
func TestDaemonSetWorkload_DeepCopy(t *testing.T) {
|
||||
ds := &appsv1.DaemonSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: appsv1.DaemonSetSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{Name: "main", Image: "nginx"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
w := NewDaemonSetWorkload(ds)
|
||||
copy := w.DeepCopy()
|
||||
|
||||
w.SetPodTemplateAnnotation("modified", "true")
|
||||
|
||||
copyAnnotations := copy.GetPodTemplateAnnotations()
|
||||
if copyAnnotations["modified"] == "true" {
|
||||
t.Error("DeepCopy should create independent copy")
|
||||
}
|
||||
}
|
||||
|
||||
func TestDaemonSetWorkload_GetOwnerReferences(t *testing.T) {
|
||||
ds := &appsv1.DaemonSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test",
|
||||
OwnerReferences: []metav1.OwnerReference{
|
||||
{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "DaemonSet",
|
||||
Name: "test-owner",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
w := NewDaemonSetWorkload(ds)
|
||||
|
||||
refs := w.GetOwnerReferences()
|
||||
if len(refs) != 1 || refs[0].Name != "test-owner" {
|
||||
t.Errorf("GetOwnerReferences() = %v, want owner ref to test-owner", refs)
|
||||
}
|
||||
}
|
||||
|
||||
// StatefulSet tests
|
||||
@@ -646,6 +923,9 @@ func TestStatefulSetWorkload_BasicGetters(t *testing.T) {
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-sts",
|
||||
Namespace: "test-ns",
|
||||
Annotations: map[string]string{
|
||||
"key": "value",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -657,6 +937,193 @@ func TestStatefulSetWorkload_BasicGetters(t *testing.T) {
|
||||
if w.GetName() != "test-sts" {
|
||||
t.Errorf("GetName() = %v, want test-sts", w.GetName())
|
||||
}
|
||||
if w.GetNamespace() != "test-ns" {
|
||||
t.Errorf("GetNamespace() = %v, want test-ns", w.GetNamespace())
|
||||
}
|
||||
if w.GetAnnotations()["key"] != "value" {
|
||||
t.Errorf("GetAnnotations()[key] = %v, want value", w.GetAnnotations()["key"])
|
||||
}
|
||||
if w.GetObject() != sts {
|
||||
t.Error("GetObject() should return the underlying statefulset")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStatefulSetWorkload_PodTemplateAnnotations(t *testing.T) {
|
||||
sts := &appsv1.StatefulSet{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test"},
|
||||
Spec: appsv1.StatefulSetSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Annotations: map[string]string{
|
||||
"existing": "annotation",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
w := NewStatefulSetWorkload(sts)
|
||||
|
||||
annotations := w.GetPodTemplateAnnotations()
|
||||
if annotations["existing"] != "annotation" {
|
||||
t.Errorf("GetPodTemplateAnnotations()[existing] = %v, want annotation", annotations["existing"])
|
||||
}
|
||||
|
||||
w.SetPodTemplateAnnotation("new-key", "new-value")
|
||||
if w.GetPodTemplateAnnotations()["new-key"] != "new-value" {
|
||||
t.Error("SetPodTemplateAnnotation should add new annotation")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStatefulSetWorkload_PodTemplateAnnotations_NilInit(t *testing.T) {
|
||||
sts := &appsv1.StatefulSet{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test"},
|
||||
Spec: appsv1.StatefulSetSpec{
|
||||
Template: corev1.PodTemplateSpec{},
|
||||
},
|
||||
}
|
||||
|
||||
w := NewStatefulSetWorkload(sts)
|
||||
|
||||
annotations := w.GetPodTemplateAnnotations()
|
||||
if annotations == nil {
|
||||
t.Error("GetPodTemplateAnnotations should initialize nil map")
|
||||
}
|
||||
|
||||
w.SetPodTemplateAnnotation("key", "value")
|
||||
if w.GetPodTemplateAnnotations()["key"] != "value" {
|
||||
t.Error("SetPodTemplateAnnotation should work with nil initial map")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStatefulSetWorkload_Containers(t *testing.T) {
|
||||
sts := &appsv1.StatefulSet{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test"},
|
||||
Spec: appsv1.StatefulSetSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{Name: "main", Image: "nginx"},
|
||||
},
|
||||
InitContainers: []corev1.Container{
|
||||
{Name: "init", Image: "busybox"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
w := NewStatefulSetWorkload(sts)
|
||||
|
||||
containers := w.GetContainers()
|
||||
if len(containers) != 1 || containers[0].Name != "main" {
|
||||
t.Errorf("GetContainers() = %v, want [main]", containers)
|
||||
}
|
||||
|
||||
initContainers := w.GetInitContainers()
|
||||
if len(initContainers) != 1 || initContainers[0].Name != "init" {
|
||||
t.Errorf("GetInitContainers() = %v, want [init]", initContainers)
|
||||
}
|
||||
|
||||
newContainers := []corev1.Container{{Name: "new-main", Image: "alpine"}}
|
||||
w.SetContainers(newContainers)
|
||||
if w.GetContainers()[0].Name != "new-main" {
|
||||
t.Error("SetContainers should update containers")
|
||||
}
|
||||
|
||||
newInitContainers := []corev1.Container{{Name: "new-init", Image: "alpine"}}
|
||||
w.SetInitContainers(newInitContainers)
|
||||
if w.GetInitContainers()[0].Name != "new-init" {
|
||||
t.Error("SetInitContainers should update init containers")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStatefulSetWorkload_Volumes(t *testing.T) {
|
||||
sts := &appsv1.StatefulSet{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test"},
|
||||
Spec: appsv1.StatefulSetSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
Spec: corev1.PodSpec{
|
||||
Volumes: []corev1.Volume{
|
||||
{Name: "config-vol"},
|
||||
{Name: "secret-vol"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
w := NewStatefulSetWorkload(sts)
|
||||
|
||||
volumes := w.GetVolumes()
|
||||
if len(volumes) != 2 {
|
||||
t.Errorf("GetVolumes() length = %d, want 2", len(volumes))
|
||||
}
|
||||
}
|
||||
|
||||
func TestStatefulSetWorkload_UsesConfigMap(t *testing.T) {
|
||||
sts := &appsv1.StatefulSet{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test"},
|
||||
Spec: appsv1.StatefulSetSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
Spec: corev1.PodSpec{
|
||||
Volumes: []corev1.Volume{
|
||||
{
|
||||
Name: "config-vol",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
ConfigMap: &corev1.ConfigMapVolumeSource{
|
||||
LocalObjectReference: corev1.LocalObjectReference{
|
||||
Name: "sts-config",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
w := NewStatefulSetWorkload(sts)
|
||||
|
||||
if !w.UsesConfigMap("sts-config") {
|
||||
t.Error("StatefulSet UsesConfigMap should return true for ConfigMap volume")
|
||||
}
|
||||
if w.UsesConfigMap("other-config") {
|
||||
t.Error("UsesConfigMap should return false for non-existent ConfigMap")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStatefulSetWorkload_UsesConfigMap_EnvFrom(t *testing.T) {
|
||||
sts := &appsv1.StatefulSet{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test"},
|
||||
Spec: appsv1.StatefulSetSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "main",
|
||||
EnvFrom: []corev1.EnvFromSource{
|
||||
{
|
||||
ConfigMapRef: &corev1.ConfigMapEnvSource{
|
||||
LocalObjectReference: corev1.LocalObjectReference{
|
||||
Name: "sts-env-config",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
w := NewStatefulSetWorkload(sts)
|
||||
|
||||
if !w.UsesConfigMap("sts-env-config") {
|
||||
t.Error("StatefulSet UsesConfigMap should return true for envFrom ConfigMap")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStatefulSetWorkload_UsesSecret(t *testing.T) {
|
||||
@@ -685,6 +1152,126 @@ func TestStatefulSetWorkload_UsesSecret(t *testing.T) {
|
||||
if !w.UsesSecret("sts-secret") {
|
||||
t.Error("StatefulSet UsesSecret should return true for Secret volume")
|
||||
}
|
||||
if w.UsesSecret("other-secret") {
|
||||
t.Error("UsesSecret should return false for non-existent Secret")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStatefulSetWorkload_UsesSecret_EnvFrom(t *testing.T) {
|
||||
sts := &appsv1.StatefulSet{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test"},
|
||||
Spec: appsv1.StatefulSetSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "main",
|
||||
EnvFrom: []corev1.EnvFromSource{
|
||||
{
|
||||
SecretRef: &corev1.SecretEnvSource{
|
||||
LocalObjectReference: corev1.LocalObjectReference{
|
||||
Name: "sts-env-secret",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
w := NewStatefulSetWorkload(sts)
|
||||
|
||||
if !w.UsesSecret("sts-env-secret") {
|
||||
t.Error("StatefulSet UsesSecret should return true for envFrom Secret")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStatefulSetWorkload_GetEnvFromSources(t *testing.T) {
|
||||
sts := &appsv1.StatefulSet{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test"},
|
||||
Spec: appsv1.StatefulSetSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "main",
|
||||
EnvFrom: []corev1.EnvFromSource{
|
||||
{ConfigMapRef: &corev1.ConfigMapEnvSource{LocalObjectReference: corev1.LocalObjectReference{Name: "cm1"}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
InitContainers: []corev1.Container{
|
||||
{
|
||||
Name: "init",
|
||||
EnvFrom: []corev1.EnvFromSource{
|
||||
{SecretRef: &corev1.SecretEnvSource{LocalObjectReference: corev1.LocalObjectReference{Name: "secret1"}}},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
w := NewStatefulSetWorkload(sts)
|
||||
|
||||
sources := w.GetEnvFromSources()
|
||||
if len(sources) != 2 {
|
||||
t.Errorf("GetEnvFromSources() returned %d sources, want 2", len(sources))
|
||||
}
|
||||
}
|
||||
|
||||
func TestStatefulSetWorkload_DeepCopy(t *testing.T) {
|
||||
sts := &appsv1.StatefulSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: appsv1.StatefulSetSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{Name: "main", Image: "nginx"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
w := NewStatefulSetWorkload(sts)
|
||||
copy := w.DeepCopy()
|
||||
|
||||
w.SetPodTemplateAnnotation("modified", "true")
|
||||
|
||||
copyAnnotations := copy.GetPodTemplateAnnotations()
|
||||
if copyAnnotations["modified"] == "true" {
|
||||
t.Error("DeepCopy should create independent copy")
|
||||
}
|
||||
}
|
||||
|
||||
func TestStatefulSetWorkload_GetOwnerReferences(t *testing.T) {
|
||||
sts := &appsv1.StatefulSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test",
|
||||
OwnerReferences: []metav1.OwnerReference{
|
||||
{
|
||||
APIVersion: "apps/v1",
|
||||
Kind: "StatefulSet",
|
||||
Name: "test-owner",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
w := NewStatefulSetWorkload(sts)
|
||||
|
||||
refs := w.GetOwnerReferences()
|
||||
if len(refs) != 1 || refs[0].Name != "test-owner" {
|
||||
t.Errorf("GetOwnerReferences() = %v, want owner ref to test-owner", refs)
|
||||
}
|
||||
}
|
||||
|
||||
// Test that workloads implement the interface
|
||||
@@ -915,3 +1502,325 @@ func TestToRolloutStrategy(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Job tests
|
||||
func TestJobWorkload_BasicGetters(t *testing.T) {
|
||||
job := &batchv1.Job{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-job",
|
||||
Namespace: "test-ns",
|
||||
Annotations: map[string]string{
|
||||
"key": "value",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
w := NewJobWorkload(job)
|
||||
|
||||
if w.Kind() != KindJob {
|
||||
t.Errorf("Kind() = %v, want %v", w.Kind(), KindJob)
|
||||
}
|
||||
if w.GetName() != "test-job" {
|
||||
t.Errorf("GetName() = %v, want test-job", w.GetName())
|
||||
}
|
||||
if w.GetNamespace() != "test-ns" {
|
||||
t.Errorf("GetNamespace() = %v, want test-ns", w.GetNamespace())
|
||||
}
|
||||
if w.GetAnnotations()["key"] != "value" {
|
||||
t.Errorf("GetAnnotations()[key] = %v, want value", w.GetAnnotations()["key"])
|
||||
}
|
||||
if w.GetObject() != job {
|
||||
t.Error("GetObject() should return the underlying job")
|
||||
}
|
||||
}
|
||||
|
||||
func TestJobWorkload_PodTemplateAnnotations(t *testing.T) {
|
||||
job := &batchv1.Job{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test"},
|
||||
Spec: batchv1.JobSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Annotations: map[string]string{
|
||||
"existing": "annotation",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
w := NewJobWorkload(job)
|
||||
|
||||
annotations := w.GetPodTemplateAnnotations()
|
||||
if annotations["existing"] != "annotation" {
|
||||
t.Errorf("GetPodTemplateAnnotations()[existing] = %v, want annotation", annotations["existing"])
|
||||
}
|
||||
|
||||
w.SetPodTemplateAnnotation("new-key", "new-value")
|
||||
if w.GetPodTemplateAnnotations()["new-key"] != "new-value" {
|
||||
t.Error("SetPodTemplateAnnotation should add new annotation")
|
||||
}
|
||||
}
|
||||
|
||||
func TestJobWorkload_UsesConfigMap(t *testing.T) {
|
||||
job := &batchv1.Job{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test"},
|
||||
Spec: batchv1.JobSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
Spec: corev1.PodSpec{
|
||||
Volumes: []corev1.Volume{
|
||||
{
|
||||
Name: "config-vol",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
ConfigMap: &corev1.ConfigMapVolumeSource{
|
||||
LocalObjectReference: corev1.LocalObjectReference{
|
||||
Name: "job-config",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
w := NewJobWorkload(job)
|
||||
|
||||
if !w.UsesConfigMap("job-config") {
|
||||
t.Error("Job UsesConfigMap should return true for ConfigMap volume")
|
||||
}
|
||||
if w.UsesConfigMap("other-config") {
|
||||
t.Error("Job UsesConfigMap should return false for non-existent ConfigMap")
|
||||
}
|
||||
}
|
||||
|
||||
func TestJobWorkload_UsesSecret(t *testing.T) {
|
||||
job := &batchv1.Job{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test"},
|
||||
Spec: batchv1.JobSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "main",
|
||||
EnvFrom: []corev1.EnvFromSource{
|
||||
{
|
||||
SecretRef: &corev1.SecretEnvSource{
|
||||
LocalObjectReference: corev1.LocalObjectReference{
|
||||
Name: "job-secret",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
w := NewJobWorkload(job)
|
||||
|
||||
if !w.UsesSecret("job-secret") {
|
||||
t.Error("Job UsesSecret should return true for Secret envFrom")
|
||||
}
|
||||
}
|
||||
|
||||
func TestJobWorkload_DeepCopy(t *testing.T) {
|
||||
job := &batchv1.Job{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test"},
|
||||
Spec: batchv1.JobSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Annotations: map[string]string{
|
||||
"original": "value",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
w := NewJobWorkload(job)
|
||||
copy := w.DeepCopy()
|
||||
|
||||
w.SetPodTemplateAnnotation("modified", "true")
|
||||
|
||||
copyAnnotations := copy.GetPodTemplateAnnotations()
|
||||
if copyAnnotations["modified"] == "true" {
|
||||
t.Error("DeepCopy should create independent copy")
|
||||
}
|
||||
}
|
||||
|
||||
// CronJob tests
|
||||
func TestCronJobWorkload_BasicGetters(t *testing.T) {
|
||||
cj := &batchv1.CronJob{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-cronjob",
|
||||
Namespace: "test-ns",
|
||||
Annotations: map[string]string{
|
||||
"key": "value",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
w := NewCronJobWorkload(cj)
|
||||
|
||||
if w.Kind() != KindCronJob {
|
||||
t.Errorf("Kind() = %v, want %v", w.Kind(), KindCronJob)
|
||||
}
|
||||
if w.GetName() != "test-cronjob" {
|
||||
t.Errorf("GetName() = %v, want test-cronjob", w.GetName())
|
||||
}
|
||||
if w.GetNamespace() != "test-ns" {
|
||||
t.Errorf("GetNamespace() = %v, want test-ns", w.GetNamespace())
|
||||
}
|
||||
if w.GetAnnotations()["key"] != "value" {
|
||||
t.Errorf("GetAnnotations()[key] = %v, want value", w.GetAnnotations()["key"])
|
||||
}
|
||||
if w.GetObject() != cj {
|
||||
t.Error("GetObject() should return the underlying cronjob")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCronJobWorkload_PodTemplateAnnotations(t *testing.T) {
|
||||
cj := &batchv1.CronJob{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test"},
|
||||
Spec: batchv1.CronJobSpec{
|
||||
JobTemplate: batchv1.JobTemplateSpec{
|
||||
Spec: batchv1.JobSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Annotations: map[string]string{
|
||||
"existing": "annotation",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
w := NewCronJobWorkload(cj)
|
||||
|
||||
annotations := w.GetPodTemplateAnnotations()
|
||||
if annotations["existing"] != "annotation" {
|
||||
t.Errorf("GetPodTemplateAnnotations()[existing] = %v, want annotation", annotations["existing"])
|
||||
}
|
||||
|
||||
w.SetPodTemplateAnnotation("new-key", "new-value")
|
||||
if w.GetPodTemplateAnnotations()["new-key"] != "new-value" {
|
||||
t.Error("SetPodTemplateAnnotation should add new annotation")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCronJobWorkload_UsesConfigMap(t *testing.T) {
|
||||
cj := &batchv1.CronJob{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test"},
|
||||
Spec: batchv1.CronJobSpec{
|
||||
JobTemplate: batchv1.JobTemplateSpec{
|
||||
Spec: batchv1.JobSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
Spec: corev1.PodSpec{
|
||||
Volumes: []corev1.Volume{
|
||||
{
|
||||
Name: "config-vol",
|
||||
VolumeSource: corev1.VolumeSource{
|
||||
ConfigMap: &corev1.ConfigMapVolumeSource{
|
||||
LocalObjectReference: corev1.LocalObjectReference{
|
||||
Name: "cronjob-config",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
w := NewCronJobWorkload(cj)
|
||||
|
||||
if !w.UsesConfigMap("cronjob-config") {
|
||||
t.Error("CronJob UsesConfigMap should return true for ConfigMap volume")
|
||||
}
|
||||
if w.UsesConfigMap("other-config") {
|
||||
t.Error("CronJob UsesConfigMap should return false for non-existent ConfigMap")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCronJobWorkload_UsesSecret(t *testing.T) {
|
||||
cj := &batchv1.CronJob{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test"},
|
||||
Spec: batchv1.CronJobSpec{
|
||||
JobTemplate: batchv1.JobTemplateSpec{
|
||||
Spec: batchv1.JobSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "main",
|
||||
Env: []corev1.EnvVar{
|
||||
{
|
||||
Name: "SECRET_VALUE",
|
||||
ValueFrom: &corev1.EnvVarSource{
|
||||
SecretKeyRef: &corev1.SecretKeySelector{
|
||||
LocalObjectReference: corev1.LocalObjectReference{
|
||||
Name: "cronjob-secret",
|
||||
},
|
||||
Key: "key",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
w := NewCronJobWorkload(cj)
|
||||
|
||||
if !w.UsesSecret("cronjob-secret") {
|
||||
t.Error("CronJob UsesSecret should return true for Secret envVar")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCronJobWorkload_DeepCopy(t *testing.T) {
|
||||
cj := &batchv1.CronJob{
|
||||
ObjectMeta: metav1.ObjectMeta{Name: "test"},
|
||||
Spec: batchv1.CronJobSpec{
|
||||
JobTemplate: batchv1.JobTemplateSpec{
|
||||
Spec: batchv1.JobSpec{
|
||||
Template: corev1.PodTemplateSpec{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Annotations: map[string]string{
|
||||
"original": "value",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
w := NewCronJobWorkload(cj)
|
||||
copy := w.DeepCopy()
|
||||
|
||||
w.SetPodTemplateAnnotation("modified", "true")
|
||||
|
||||
copyAnnotations := copy.GetPodTemplateAnnotations()
|
||||
if copyAnnotations["modified"] == "true" {
|
||||
t.Error("DeepCopy should create independent copy")
|
||||
}
|
||||
}
|
||||
|
||||
// Test that Job and CronJob implement the interface
|
||||
func TestJobCronJobWorkloadInterface(t *testing.T) {
|
||||
var _ WorkloadAccessor = (*JobWorkload)(nil)
|
||||
var _ WorkloadAccessor = (*CronJobWorkload)(nil)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user