// Copyright 2020-2026 Project Capsule Authors // SPDX-License-Identifier: Apache-2.0 package predicates_test import ( "testing" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "sigs.k8s.io/controller-runtime/pkg/client" "sigs.k8s.io/controller-runtime/pkg/event" "github.com/projectcapsule/capsule/pkg/api/meta" "github.com/projectcapsule/capsule/pkg/runtime/predicates" ) func TestReconcileRequestedPredicate_StaticFuncs(t *testing.T) { t.Parallel() p := predicates.ReconcileRequestedPredicate{} if got := p.Create(event.CreateEvent{}); got { t.Fatalf("Create() = %v, want false", got) } if got := p.Delete(event.DeleteEvent{}); got { t.Fatalf("Delete() = %v, want false", got) } if got := p.Generic(event.GenericEvent{}); got { t.Fatalf("Generic() = %v, want false", got) } } func TestReconcileRequestedPredicate_Update(t *testing.T) { t.Parallel() p := predicates.ReconcileRequestedPredicate{} mkObj := func(ann map[string]string) *unstructured.Unstructured { u := &unstructured.Unstructured{} u.SetAPIVersion("capsule.clastix.io/v1beta2") u.SetKind("GlobalTenantResource") u.SetName("x") // Important: nil vs empty map both behave the same for lookups, // but we keep this as-is to match real objects. u.SetAnnotations(ann) return u } type tc struct { name string old map[string]string new map[string]string want bool } tests := []tc{ { name: "annotation added triggers true", old: map[string]string{}, new: map[string]string{meta.ReconcileAnnotation: "2026-01-13T06:23:14.333872+01:00"}, want: true, }, { name: "annotation value changed triggers true", old: map[string]string{meta.ReconcileAnnotation: "2026-01-13T06:23:14.333872+01:00"}, new: map[string]string{meta.ReconcileAnnotation: "2026-01-13T06:24:14.111111+01:00"}, want: true, }, { name: "annotation unchanged does not trigger", old: map[string]string{meta.ReconcileAnnotation: "2026-01-13T06:23:14.333872+01:00"}, new: map[string]string{meta.ReconcileAnnotation: "2026-01-13T06:23:14.333872+01:00"}, want: false, }, { name: "annotation removed does not trigger", old: map[string]string{meta.ReconcileAnnotation: "2026-01-13T06:23:14.333872+01:00"}, new: map[string]string{}, // removed want: false, }, { name: "annotation absent in both does not trigger", old: map[string]string{}, new: map[string]string{}, want: false, }, { name: "annotation set to empty string does not trigger", old: map[string]string{}, new: map[string]string{meta.ReconcileAnnotation: ""}, want: false, }, { name: "annotation changed to empty string (effectively removed) does not trigger", old: map[string]string{meta.ReconcileAnnotation: "2026-01-13T06:23:14.333872+01:00"}, new: map[string]string{meta.ReconcileAnnotation: ""}, want: false, }, } for _, tt := range tests { tt := tt t.Run(tt.name, func(t *testing.T) { t.Parallel() var oldObj, newObj *unstructured.Unstructured oldObj = mkObj(tt.old) newObj = mkObj(tt.new) ev := event.UpdateEvent{ ObjectOld: client.Object(oldObj), ObjectNew: client.Object(newObj), } got := p.Update(ev) if got != tt.want { t.Fatalf("Update() = %v, want %v (old=%v new=%v)", got, tt.want, tt.old, tt.new) } }) } }