internal/lineagecontrollerwebhook/controller_test.go
Rama main · réplica de lectura del repositorio original.
package lineagecontrollerwebhook
import (
"context"
"testing"
"time"
cozyv1alpha1 "github.com/cozystack/cozystack/api/v1alpha1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
ctrl "sigs.k8s.io/controller-runtime"
"sigs.k8s.io/controller-runtime/pkg/client/fake"
)
func newWebhookScheme(t *testing.T) *runtime.Scheme {
t.Helper()
scheme := runtime.NewScheme()
if err := cozyv1alpha1.AddToScheme(scheme); err != nil {
t.Fatalf("add cozyv1alpha1: %v", err)
}
return scheme
}
// TestReconcile_EmptyClusterStoresEmptyMap pins the empty-cluster
// reconcile: with no ApplicationDefinitions present, the reconciler
// must store a fresh runtimeConfig with an empty map (not leave the
// stored config nil, not return an error).
func TestReconcile_EmptyClusterStoresEmptyMap(t *testing.T) {
scheme := newWebhookScheme(t)
fakeClient := fake.NewClientBuilder().WithScheme(scheme).Build()
w := &LineageControllerWebhook{Client: fakeClient, Scheme: scheme}
if _, err := w.Reconcile(context.TODO(), ctrl.Request{}); err != nil {
t.Fatalf("unexpected error: %v", err)
}
cfg, ok := w.config.Load().(*runtimeConfig)
if !ok {
t.Fatalf("expected *runtimeConfig stored, got %T", w.config.Load())
}
if cfg.appCRDMap == nil {
t.Errorf("expected non-nil empty map")
}
if len(cfg.appCRDMap) != 0 {
t.Errorf("expected 0 entries, got %d", len(cfg.appCRDMap))
}
}
// TestReconcile_BuildsAppCRDMapByKind pins the documented mapping:
// each ApplicationDefinition is keyed by (group=apps.cozystack.io,
// kind=Spec.Application.Kind). The map values point to the
// ApplicationDefinition itself.
func TestReconcile_BuildsAppCRDMapByKind(t *testing.T) {
scheme := newWebhookScheme(t)
harbor := &cozyv1alpha1.ApplicationDefinition{
ObjectMeta: metav1.ObjectMeta{Name: "harbor"},
Spec: cozyv1alpha1.ApplicationDefinitionSpec{
Application: cozyv1alpha1.ApplicationDefinitionApplication{Kind: "Harbor"},
},
}
bucket := &cozyv1alpha1.ApplicationDefinition{
ObjectMeta: metav1.ObjectMeta{Name: "bucket"},
Spec: cozyv1alpha1.ApplicationDefinitionSpec{
Application: cozyv1alpha1.ApplicationDefinitionApplication{Kind: "Bucket"},
},
}
fakeClient := fake.NewClientBuilder().
WithScheme(scheme).
WithObjects(harbor, bucket).
Build()
w := &LineageControllerWebhook{Client: fakeClient, Scheme: scheme}
if _, err := w.Reconcile(context.TODO(), ctrl.Request{}); err != nil {
t.Fatalf("unexpected error: %v", err)
}
cfg := w.config.Load().(*runtimeConfig)
if len(cfg.appCRDMap) != 2 {
t.Fatalf("expected 2 entries, got %d (%+v)", len(cfg.appCRDMap), cfg.appCRDMap)
}
if got := cfg.appCRDMap[appRef{"apps.cozystack.io", "Harbor"}]; got == nil || got.Name != "harbor" {
t.Errorf("expected Harbor → harbor entry, got %+v", got)
}
if got := cfg.appCRDMap[appRef{"apps.cozystack.io", "Bucket"}]; got == nil || got.Name != "bucket" {
t.Errorf("expected Bucket → bucket entry, got %+v", got)
}
}
// TestReconcile_DuplicateKindKeepsOwner pins the duplicate handling: when two
// ApplicationDefinitions declare the same Application.Kind, the lineage map
// keeps the owner under the shared rule (created first, then by name), the same
// definition cozystack-api and the chartRef reconciler treat as the owner. The
// newcomer is tried on both sides of the owner by name, so neither a first-wins
// nor a last-wins walk of the list can pass.
func TestReconcile_DuplicateKindKeepsOwner(t *testing.T) {
scheme := newWebhookScheme(t)
base := time.Date(2026, 1, 1, 0, 0, 0, 0, time.UTC)
for _, newcomerName := range []string{"a-harbor-shadow", "z-harbor-shadow"} {
t.Run(newcomerName, func(t *testing.T) {
owner := &cozyv1alpha1.ApplicationDefinition{
ObjectMeta: metav1.ObjectMeta{Name: "harbor", CreationTimestamp: metav1.NewTime(base)},
Spec: cozyv1alpha1.ApplicationDefinitionSpec{
Application: cozyv1alpha1.ApplicationDefinitionApplication{Kind: "Harbor"},
},
}
newcomer := &cozyv1alpha1.ApplicationDefinition{
ObjectMeta: metav1.ObjectMeta{Name: newcomerName, CreationTimestamp: metav1.NewTime(base.Add(time.Hour))},
Spec: cozyv1alpha1.ApplicationDefinitionSpec{
Application: cozyv1alpha1.ApplicationDefinitionApplication{Kind: "Harbor"},
},
}
fakeClient := fake.NewClientBuilder().WithScheme(scheme).WithObjects(owner, newcomer).Build()
w := &LineageControllerWebhook{Client: fakeClient, Scheme: scheme}
if _, err := w.Reconcile(context.TODO(), ctrl.Request{}); err != nil {
t.Fatalf("unexpected error: %v", err)
}
cfg := w.config.Load().(*runtimeConfig)
if len(cfg.appCRDMap) != 1 {
t.Fatalf("expected exactly one Harbor entry (duplicate dropped), got %d", len(cfg.appCRDMap))
}
if got := cfg.appCRDMap[appRef{"apps.cozystack.io", "Harbor"}]; got == nil || got.Name != "harbor" {
t.Fatalf("expected the owner harbor, got %+v", got)
}
})
}
}