Ctrl K
ComunidadGuías…

internal/lineagecontrollerwebhook/matcher_test.go

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

Archivo original ↗Preguntar a KiBot
package lineagecontrollerwebhook

import (
	"context"
	"testing"

	cozyv1alpha1 "github.com/cozystack/cozystack/api/v1alpha1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)

// TestMatchName_NilMatchesEverything pins the documented behaviour:
// resourceNames=nil means "match any name", so we don't accidentally
// regress to require an explicit list.
func TestMatchName_NilMatchesEverything(t *testing.T) {
	if !matchName(context.Background(), "anything", nil, nil) {
		t.Errorf("matchName(name=anything, resourceNames=nil) = false, want true")
	}
}

// TestMatchName_EmptySliceMatchesNothing pins the edge between "no
// names" (block all) and "no list" (allow all). Empty slice is "no
// names allowed", which evaluates to false on every input.
func TestMatchName_EmptySliceMatchesNothing(t *testing.T) {
	if matchName(context.Background(), "anything", nil, []string{}) {
		t.Errorf("matchName(name=anything, resourceNames=[]) = true, want false")
	}
}

// TestMatchName_ExactLiteral pins literal matching with no template
// variables.
func TestMatchName_ExactLiteral(t *testing.T) {
	cases := []struct {
		name      string
		input     string
		templates []string
		want      bool
	}{
		{"matches one of", "harbor", []string{"foo", "harbor", "bucket"}, true},
		{"no match", "registry", []string{"foo", "harbor"}, false},
		{"single match", "only", []string{"only"}, true},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			got := matchName(context.Background(), tc.input, nil, tc.templates)
			if got != tc.want {
				t.Errorf("matchName(%q, %v) = %v, want %v", tc.input, tc.templates, got, tc.want)
			}
		})
	}
}

// TestMatchName_TemplateVariables pins go-template substitution from
// the templateContext map. This is the documented mechanism that
// resourceNames entries like "{{ .name }}-secret" rely on.
func TestMatchName_TemplateVariables(t *testing.T) {
	ctx := map[string]string{
		"name":      "harbor",
		"kind":      "harbor",
		"namespace": "tenant-foo",
	}
	templates := []string{
		"{{ .name }}-secret",
		"{{ .kind }}-{{ .name }}-tls",
		"specificname",
	}
	cases := []struct {
		name  string
		input string
		want  bool
	}{
		{"first template", "harbor-secret", true},
		{"second template", "harbor-harbor-tls", true},
		{"literal", "specificname", true},
		{"none", "registry-secret", false},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			got := matchName(context.Background(), tc.input, ctx, templates)
			if got != tc.want {
				t.Errorf("matchName(%q) = %v, want %v", tc.input, got, tc.want)
			}
		})
	}
}

// TestMatchName_BadTemplateContinues pins error tolerance: a malformed
// go-template entry must be logged and skipped, not break the loop.
// Subsequent valid entries should still match.
func TestMatchName_BadTemplateContinues(t *testing.T) {
	templates := []string{
		"{{ .unclosed",      // bad — parse error
		"{{ .missingfield }}", // bad — execute error (no such field)
		"valid-target",        // good
	}
	got := matchName(context.Background(), "valid-target", map[string]string{"name": "x"}, templates)
	if !got {
		t.Errorf("expected fallthrough to literal entry to match, got false")
	}
}

// TestMatchResourceToSelector_LabelsAndName pins both halves of the
// selector match: labels must satisfy the LabelSelector AND name must
// satisfy resourceNames (when set).
func TestMatchResourceToSelector_LabelsAndName(t *testing.T) {
	sel := &cozyv1alpha1.ApplicationDefinitionResourceSelector{
		LabelSelector: metav1.LabelSelector{
			MatchLabels: map[string]string{"app": "harbor"},
		},
		ResourceNames: []string{"harbor-secret"},
	}
	cases := []struct {
		name      string
		objName   string
		objLabels map[string]string
		want      bool
	}{
		{"both match", "harbor-secret", map[string]string{"app": "harbor"}, true},
		{"name match, labels mismatch", "harbor-secret", map[string]string{"app": "registry"}, false},
		{"labels match, name mismatch", "registry-secret", map[string]string{"app": "harbor"}, false},
		{"neither", "registry-secret", map[string]string{"app": "registry"}, false},
	}
	for _, tc := range cases {
		t.Run(tc.name, func(t *testing.T) {
			got := matchResourceToSelector(context.Background(), tc.objName, nil, tc.objLabels, sel)
			if got != tc.want {
				t.Errorf("matchResourceToSelector = %v, want %v", got, tc.want)
			}
		})
	}
}

// TestMatchResourceToSelector_NilResourceNamesAllowsAnyName pins that
// when ResourceNames is nil the selector reduces to label-only
// matching.
func TestMatchResourceToSelector_NilResourceNamesAllowsAnyName(t *testing.T) {
	sel := &cozyv1alpha1.ApplicationDefinitionResourceSelector{
		LabelSelector: metav1.LabelSelector{
			MatchLabels: map[string]string{"app": "harbor"},
		},
	}
	if !matchResourceToSelector(context.Background(), "any-name", nil, map[string]string{"app": "harbor"}, sel) {
		t.Errorf("expected label-only match, got false")
	}
	if matchResourceToSelector(context.Background(), "any-name", nil, map[string]string{"app": "registry"}, sel) {
		t.Errorf("expected mismatch on labels, got true")
	}
}

// TestMatchResourceToSelectorArray_AnyMatch pins OR semantics across
// the selector array — at least one selector must match.
func TestMatchResourceToSelectorArray_AnyMatch(t *testing.T) {
	selectors := []*cozyv1alpha1.ApplicationDefinitionResourceSelector{
		{LabelSelector: metav1.LabelSelector{MatchLabels: map[string]string{"role": "primary"}}},
		{LabelSelector: metav1.LabelSelector{MatchLabels: map[string]string{"role": "replica"}}},
	}
	if !matchResourceToSelectorArray(context.Background(), "x", nil, map[string]string{"role": "replica"}, selectors) {
		t.Errorf("expected match on second selector, got false")
	}
	if matchResourceToSelectorArray(context.Background(), "x", nil, map[string]string{"role": "other"}, selectors) {
		t.Errorf("expected no match, got true")
	}
}

// TestMatchResourceToSelectorArray_EmptyArrayIsFalse pins the contract:
// no selectors means no match (deliberately not "match anything").
func TestMatchResourceToSelectorArray_EmptyArrayIsFalse(t *testing.T) {
	if matchResourceToSelectorArray(context.Background(), "x", nil, nil, nil) {
		t.Errorf("expected empty selector array to never match, got true")
	}
}

// TestMatchResourceToExcludeInclude_NilResourcesIsFalse pins the
// guard: nil resources block always evaluates false.
func TestMatchResourceToExcludeInclude_NilResourcesIsFalse(t *testing.T) {
	if matchResourceToExcludeInclude(context.Background(), "x", nil, nil, nil) {
		t.Errorf("expected nil resources to never match, got true")
	}
}

// TestMatchResourceToExcludeInclude_ExcludeWins pins precedence: an
// exclude match short-circuits to false even if include also matches.
func TestMatchResourceToExcludeInclude_ExcludeWins(t *testing.T) {
	resources := &cozyv1alpha1.ApplicationDefinitionResources{
		Exclude: []*cozyv1alpha1.ApplicationDefinitionResourceSelector{
			{LabelSelector: metav1.LabelSelector{MatchLabels: map[string]string{"sensitive": "true"}}},
		},
		Include: []*cozyv1alpha1.ApplicationDefinitionResourceSelector{
			{LabelSelector: metav1.LabelSelector{MatchLabels: map[string]string{"app": "harbor"}}},
		},
	}
	got := matchResourceToExcludeInclude(context.Background(), "x", nil,
		map[string]string{"app": "harbor", "sensitive": "true"}, resources)
	if got {
		t.Errorf("expected exclude to win over include, got true")
	}
}

// TestMatchResourceToExcludeInclude_OnlyInclude pins the simple case:
// no excludes, include matches → true.
func TestMatchResourceToExcludeInclude_OnlyInclude(t *testing.T) {
	resources := &cozyv1alpha1.ApplicationDefinitionResources{
		Include: []*cozyv1alpha1.ApplicationDefinitionResourceSelector{
			{LabelSelector: metav1.LabelSelector{MatchLabels: map[string]string{"app": "harbor"}}},
		},
	}
	if !matchResourceToExcludeInclude(context.Background(), "x", nil, map[string]string{"app": "harbor"}, resources) {
		t.Errorf("expected include match, got false")
	}
}