internal/lineagecontrollerwebhook/matcher_test.go
Rama main · réplica de lectura del repositorio original.
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")
}
}