Ctrl K
ComunidadGuías…

internal/lineagecontrollerwebhook/controller_test.go

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
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)
			}
		})
	}
}