pkg/ownership/catalog_test.go
Rama main · réplica de lectura del repositorio original.
package ownership
import (
"reflect"
"slices"
"testing"
"github.com/skyhook-io/radar/pkg/resourceid"
)
func TestCatalogMergesVersionsAndKeepsTheGarbageCollectorsKinds(t *testing.T) {
gc := []string{"create", "delete", "get", "list", "watch"}
widget := resourceid.GroupKind{Group: "example.io", Kind: "Widget"}
c := NewCatalog([]ServedResource{
{Group: "example.io", Version: "v1beta1", Kind: "Widget", Resource: "widgets", Namespaced: true, Verbs: []string{"get", "list"}},
{Group: "example.io", Version: "v1", Kind: "Widget", Resource: "widgets", Namespaced: true, Verbs: gc},
{Group: "example.io", Version: "v1", Kind: "Gadget", Resource: "gadgets", Namespaced: true, Verbs: []string{"get", "list", "watch"}},
{Group: "metrics.k8s.io", Version: "v1beta1", Kind: "PodMetrics", Resource: "pods", Namespaced: true, Verbs: []string{"get", "list"}},
{Group: "", Version: "v1", Kind: "Event", Resource: "events", Namespaced: true, Verbs: gc},
{Group: "events.k8s.io", Version: "v1", Kind: "Event", Resource: "events", Namespaced: true, Verbs: gc},
{Group: "", Version: "v1", Kind: "Node", Resource: "nodes", Verbs: gc},
}, true)
got, ok := c.Kind(widget)
want := CatalogKind{GroupKind: widget, Resource: "widgets", Namespaced: true, Versions: []string{"v1", "v1beta1"}, HasGCVerbs: true}
if !ok || !reflect.DeepEqual(got, want) {
t.Fatalf("Widget = %+v, %v; want %+v", got, ok, want)
}
if !c.Serves(widget, "v1beta1") || c.Serves(widget, "v2") || c.Serves(resourceid.GroupKind{Kind: "Widget"}, "v1") {
t.Fatal("Serves must name exactly the group, kind and versions discovery listed")
}
var dependents []string
for _, k := range c.DependentKinds() {
dependents = append(dependents, k.GroupKind.String())
}
// Gadget lacks delete and PodMetrics lacks watch, so the garbage collector
// never acts on them; both Events are left out by design.
if want := []string{"Node", "Widget.example.io"}; !slices.Equal(dependents, want) {
t.Fatalf("DependentKinds = %v, want %v", dependents, want)
}
if !c.Complete() || NewCatalog(nil, false).Complete() {
t.Fatal("Complete must be what discovery said")
}
}
// A reference names a kind, not a resource, so a kind two resources of one
// group serve can't be read or searched as either.
func TestCatalogMarksAKindServedByTwoResourcesAmbiguous(t *testing.T) {
widget := resourceid.GroupKind{Group: "example.io", Kind: "Widget"}
getOnly := ServedResource{Group: "example.io", Version: "v1", Kind: "Widget", Resource: "awidgets", Verbs: []string{"get"}}
collected := ServedResource{Group: "example.io", Version: "v1", Kind: "Widget", Resource: "widgets", Namespaced: true, Verbs: []string{"delete", "list", "watch"}}
for _, order := range [][]ServedResource{{getOnly, collected}, {collected, getOnly}} {
c := NewCatalog(order, true)
if _, found := c.Kind(widget); found || !c.Ambiguous(widget) || c.Serves(widget, "v1") {
t.Fatalf("Widget must be ambiguous, never resolved to one resource: found=%v ambiguous=%v", found, c.Ambiguous(widget))
}
if got := c.AmbiguousDependentKinds(); !slices.Equal(got, []AmbiguousKind{{GroupKind: widget, Namespaced: true}}) {
t.Fatalf("AmbiguousDependentKinds = %+v; want Widget, collected through its namespaced resource", got)
}
if len(c.DependentKinds()) != 0 {
t.Fatal("an ambiguous kind must not join the dependent kinds as one of its resources")
}
}
if NewCatalog([]ServedResource{collected}, true).Ambiguous(widget) {
t.Fatal("one resource serving a kind is not ambiguous")
}
}
// An aggregated API can serve one kind namespaced at one version and
// cluster-scoped at another, so which scope a reference or a search means is
// as open as which resource.
func TestCatalogMarksAKindServedAtTwoScopesAmbiguous(t *testing.T) {
widget := resourceid.GroupKind{Group: "example.io", Kind: "Widget"}
gc := []string{"delete", "list", "watch"}
v1 := ServedResource{Group: "example.io", Version: "v1", Kind: "Widget", Resource: "widgets", Namespaced: true, Verbs: gc}
v1beta1 := ServedResource{Group: "example.io", Version: "v1beta1", Kind: "Widget", Resource: "widgets", Verbs: gc}
for _, order := range [][]ServedResource{{v1, v1beta1}, {v1beta1, v1}} {
c := NewCatalog(order, true)
if _, found := c.Kind(widget); found || !c.Ambiguous(widget) {
t.Fatalf("Widget at two scopes must be ambiguous: found=%v", found)
}
if got := c.AmbiguousDependentKinds(); !slices.Equal(got, []AmbiguousKind{{GroupKind: widget, Namespaced: true, ClusterScoped: true}}) {
t.Fatalf("AmbiguousDependentKinds = %+v; want Widget in both scopes", got)
}
}
}
// Aggregated APIs can serve one kind through several query resources that
// nothing can list, watch or delete, as Kueue's visibility API serves
// PendingWorkloadsSummary through clusterqueues and localqueues. The kind is
// ambiguous, but no object of it can be a dependent.
func TestCatalogKeepsAnAmbiguousKindNothingCollectsOutOfDependents(t *testing.T) {
summary := resourceid.GroupKind{Group: "visibility.kueue.x-k8s.io", Kind: "PendingWorkloadsSummary"}
c := NewCatalog([]ServedResource{
{Group: summary.Group, Version: "v1beta2", Kind: summary.Kind, Resource: "clusterqueues"},
{Group: summary.Group, Version: "v1beta2", Kind: summary.Kind, Resource: "localqueues", Namespaced: true},
}, true)
if !c.Ambiguous(summary) {
t.Fatal("two resources serving the kind must still make it ambiguous")
}
if got := c.AmbiguousDependentKinds(); len(got) != 0 {
t.Fatalf("AmbiguousDependentKinds = %+v; nothing can collect a summary", got)
}
scoped := NewCatalog([]ServedResource{
{Group: "example.io", Version: "v1", Kind: "Widget", Resource: "widgets", Namespaced: true, Verbs: []string{"get"}},
{Group: "example.io", Version: "v1", Kind: "Widget", Resource: "clusterwidgets", Verbs: []string{"delete", "list", "watch"}},
}, true)
if got := scoped.AmbiguousDependentKinds(); !slices.Equal(got, []AmbiguousKind{{GroupKind: resourceid.GroupKind{Group: "example.io", Kind: "Widget"}, ClusterScoped: true}}) {
t.Fatalf("AmbiguousDependentKinds = %+v; want Widget, collected only cluster-wide", got)
}
}
func TestIsEventNamesBothEventKinds(t *testing.T) {
for gk, want := range map[resourceid.GroupKind]bool{
{Kind: "Event"}: true,
{Group: "events.k8s.io", Kind: "Event"}: true,
{Group: "example.io", Kind: "Event"}: false,
{Kind: "Pod"}: false,
} {
if IsEvent(gk) != want {
t.Errorf("IsEvent(%v) = %v, want %v", gk, !want, want)
}
}
}
func TestZeroReadIsUnavailable(t *testing.T) {
var r ObjectRead
if r.State != Unavailable || r.State.String() != "unavailable" {
t.Fatalf("zero read = %v; an unset read must never claim an answer", r.State)
}
}