Ctrl K
ComunidadGuías…

pkg/ownership/manager_test.go

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

Archivo original ↗Preguntar a KiBot
package ownership_test

import (
	"fmt"
	"testing"

	"github.com/skyhook-io/radar/pkg/ownership"
	"github.com/skyhook-io/radar/pkg/ownership/ownershiptest"
	"github.com/skyhook-io/radar/pkg/resourceid"
)

const (
	trackingID    = "argocd.argoproj.io/tracking-id"
	instanceLabel = "argocd.argoproj.io/instance"
	helmRelease   = "meta.helm.sh/release-name"
	helmNamespace = "meta.helm.sh/release-namespace"
)

func app(name string) resourceid.Ref {
	return resourceid.Ref{Group: "argoproj.io", Kind: "Application", Namespace: "argocd", Name: name}
}

// checkOf returns the check of the claim naming manager.
func checkOf(e ownership.ManagerEvidence, manager resourceid.Ref) ownership.ClaimCheck {
	for _, c := range e.Claims {
		if c.Manager == manager {
			return c.Check
		}
	}
	return ""
}

func TestManagerStates(t *testing.T) {
	recorded := ownership.MembersRecorded
	for _, tc := range []struct {
		name  string
		build func(b *ownershiptest.Builder) resourceid.Ref
		state ownership.ManagerState
		lead  *resourceid.Ref
		check func(t *testing.T, a ownership.ManagerAnswer)
	}{
		{
			name: "listed: a list names it, its metadata names no one",
			build: func(b *ownershiptest.Builder) resourceid.Ref {
				cm := b.Add("", "ConfigMap", "shop", "settings")
				b.Manager(ownership.ArgoApplication, "argocd", "shop", recorded, "shop", cm.Ref)
				return cm.Ref
			},
			state: ownership.ManagerListed, lead: ref(app("shop")),
		},
		{
			name: "agree: its tracking ID names the manager whose list names it",
			build: func(b *ownershiptest.Builder) resourceid.Ref {
				cm := b.Meta(b.Add("", "ConfigMap", "shop", "settings"), nil, map[string]string{trackingID: "argocd_shop:/ConfigMap:shop/settings"})
				b.Manager(ownership.ArgoApplication, "argocd", "shop", recorded, "shop", cm.Ref)
				return cm.Ref
			},
			state: ownership.ManagerAgree, lead: ref(app("shop")),
		},
		{
			// A label without a namespace matches the Application of its name
			// whose list names the object.
			name: "agree: an instance label without a namespace",
			build: func(b *ownershiptest.Builder) resourceid.Ref {
				cm := b.Meta(b.Add("", "ConfigMap", "shop", "settings"), map[string]string{instanceLabel: "shop"}, nil)
				b.Manager(ownership.ArgoApplication, "argocd", "shop", recorded, "shop", cm.Ref)
				return cm.Ref
			},
			state: ownership.ManagerAgree, lead: ref(app("shop")),
		},
		{
			name: "disagree: its label names a manager whose readable list omits it",
			build: func(b *ownershiptest.Builder) resourceid.Ref {
				cm := b.Meta(b.Add("", "ConfigMap", "shop", "settings"), nil, map[string]string{trackingID: "argocd_old:/ConfigMap:shop/settings"})
				b.Manager(ownership.ArgoApplication, "argocd", "old", recorded, "shop")
				b.Manager(ownership.ArgoApplication, "argocd", "shop", recorded, "shop", cm.Ref)
				return cm.Ref
			},
			state: ownership.ManagerDisagree,
			check: func(t *testing.T, a ownership.ManagerAnswer) {
				if got := checkOf(a.Direct, app("old")); got != ownership.ClaimOmitted {
					t.Errorf("claim of old: %q, want omitted", got)
				}
			},
		},
		{
			// The claimed manager's list can't be read; still a disagreement.
			name: "disagree: a list names it and its label names an unreadable manager",
			build: func(b *ownershiptest.Builder) resourceid.Ref {
				cm := b.Meta(b.Add("", "ConfigMap", "shop", "settings"), nil, map[string]string{trackingID: "argocd_old:/ConfigMap:shop/settings"})
				b.Manager(ownership.ArgoApplication, "argocd", "old", ownership.MembersUnreadable, "shop")
				b.Manager(ownership.ArgoApplication, "argocd", "shop", recorded, "shop", cm.Ref)
				return cm.Ref
			},
			state: ownership.ManagerDisagree,
			check: func(t *testing.T, a ownership.ManagerAnswer) {
				if got := checkOf(a.Direct, app("old")); got != ownership.ClaimUnreadable {
					t.Errorf("claim of old: %q, want unreadable", got)
				}
			},
		},
		{
			name: "shared: two lists name it",
			build: func(b *ownershiptest.Builder) resourceid.Ref {
				cm := b.Add("", "ConfigMap", "shop", "settings")
				b.Manager(ownership.ArgoApplication, "argocd", "a", recorded, "shop", cm.Ref)
				b.Manager(ownership.ArgoApplication, "argocd", "b", recorded, "shop", cm.Ref)
				return cm.Ref
			},
			state: ownership.ManagerShared,
		},
		{
			name: "shared: the claimed one of two leads",
			build: func(b *ownershiptest.Builder) resourceid.Ref {
				cm := b.Meta(b.Add("", "ConfigMap", "shop", "settings"), nil, map[string]string{trackingID: "argocd_b:/ConfigMap:shop/settings"})
				b.Manager(ownership.ArgoApplication, "argocd", "a", recorded, "shop", cm.Ref)
				b.Manager(ownership.ArgoApplication, "argocd", "b", recorded, "shop", cm.Ref)
				return cm.Ref
			},
			state: ownership.ManagerShared, lead: ref(app("b")),
		},
		{
			name: "claimed: the claimed manager's list can't be read",
			build: func(b *ownershiptest.Builder) resourceid.Ref {
				cm := b.Meta(b.Add("", "ConfigMap", "shop", "settings"), nil, map[string]string{trackingID: "argocd_shop:/ConfigMap:shop/settings"})
				b.Manager(ownership.ArgoApplication, "argocd", "shop", ownership.MembersUnreadable, "shop")
				return cm.Ref
			},
			state: ownership.ManagerClaimed, lead: ref(app("shop")),
			check: func(t *testing.T, a ownership.ManagerAnswer) {
				if got := checkOf(a.Direct, app("shop")); got != ownership.ClaimUnreadable {
					t.Errorf("check %q, want unreadable", got)
				}
			},
		},
		{
			name: "claimed: the claimed manager applies elsewhere",
			build: func(b *ownershiptest.Builder) resourceid.Ref {
				cm := b.Meta(b.Add("", "ConfigMap", "shop", "settings"), nil, map[string]string{trackingID: "argocd_shop:/ConfigMap:shop/settings"})
				b.Manager(ownership.ArgoApplication, "argocd", "shop", ownership.MembersRemote, "shop", cm.Ref)
				return cm.Ref
			},
			state: ownership.ManagerClaimed, lead: ref(app("shop")),
			check: func(t *testing.T, a ownership.ManagerAnswer) {
				if got := checkOf(a.Direct, app("shop")); got != ownership.ClaimRemote {
					t.Errorf("check %q, want remote", got)
				}
			},
		},
		{
			// A readable list that omits it, and no other: the claim stands
			// unconfirmed rather than turning into none.
			name: "claimed: the claimed manager's readable list omits it",
			build: func(b *ownershiptest.Builder) resourceid.Ref {
				cm := b.Meta(b.Add("", "ConfigMap", "shop", "settings"), nil, map[string]string{trackingID: "argocd_shop:/ConfigMap:shop/settings"})
				b.Manager(ownership.ArgoApplication, "argocd", "shop", recorded, "shop")
				return cm.Ref
			},
			state: ownership.ManagerClaimed, lead: ref(app("shop")),
			check: func(t *testing.T, a ownership.ManagerAnswer) {
				if got := checkOf(a.Direct, app("shop")); got != ownership.ClaimOmitted {
					t.Errorf("check %q, want omitted", got)
				}
			},
		},
		{
			name: "claimed: no such manager",
			build: func(b *ownershiptest.Builder) resourceid.Ref {
				return b.Meta(b.Add("", "ConfigMap", "shop", "settings"), nil, map[string]string{trackingID: "argocd_gone:/ConfigMap:shop/settings"}).Ref
			},
			state: ownership.ManagerClaimed, lead: ref(app("gone")),
			check: func(t *testing.T, a ownership.ManagerAnswer) {
				if got := checkOf(a.Direct, app("gone")); got != ownership.ClaimMissing {
					t.Errorf("check %q, want missing", got)
				}
			},
		},
		{
			name: "claimed: a native Helm release",
			build: func(b *ownershiptest.Builder) resourceid.Ref {
				return b.Meta(b.Add("", "ConfigMap", "shop", "settings"), nil, map[string]string{helmRelease: "web", helmNamespace: "shop"}).Ref
			},
			state: ownership.ManagerClaimed, lead: &resourceid.Ref{Kind: "HelmRelease", Namespace: "shop", Name: "web"},
		},
		{
			// Flux's helm-controller and Argo CD installs carry the Helm
			// release's annotations; they don't make a second manager.
			name: "listed: Helm annotations beside a list naming it",
			build: func(b *ownershiptest.Builder) resourceid.Ref {
				cm := b.Meta(b.Add("", "ConfigMap", "shop", "settings"), nil, map[string]string{helmRelease: "web", helmNamespace: "shop"})
				b.Manager(ownership.ArgoApplication, "argocd", "shop", recorded, "shop", cm.Ref)
				return cm.Ref
			},
			state: ownership.ManagerListed, lead: ref(app("shop")),
		},
		{
			// A tracking ID and an instance label name one Application, the
			// ID saying its namespace.
			name: "claimed: a tracking ID and an instance label naming one Application",
			build: func(b *ownershiptest.Builder) resourceid.Ref {
				return b.Meta(b.Add("", "ConfigMap", "shop", "settings"),
					map[string]string{instanceLabel: "shop"},
					map[string]string{trackingID: "argocd_shop:/ConfigMap:shop/settings"}).Ref
			},
			state: ownership.ManagerClaimed, lead: ref(app("shop")),
		},
		{
			name: "claimed: two claims naming different managers, no list",
			build: func(b *ownershiptest.Builder) resourceid.Ref {
				return b.Meta(b.Add("", "ConfigMap", "shop", "settings"),
					map[string]string{"kustomize.toolkit.fluxcd.io/name": "apps", "kustomize.toolkit.fluxcd.io/namespace": "flux-system"},
					map[string]string{trackingID: "argocd_shop:/ConfigMap:shop/settings"}).Ref
			},
			state: ownership.ManagerClaimed,
		},
		{
			name: "none: copied tracking ID only, everything read",
			build: func(b *ownershiptest.Builder) resourceid.Ref {
				return b.Meta(b.Add("", "ConfigMap", "shop", "copy"), nil, map[string]string{trackingID: "argocd_shop:/ConfigMap:shop/settings"}).Ref
			},
			state: ownership.ManagerNone,
		},
		{
			name: "unknown: no evidence, and a list Radar can't read",
			build: func(b *ownershiptest.Builder) resourceid.Ref {
				cm := b.Add("", "ConfigMap", "shop", "settings")
				b.Manager(ownership.ArgoApplication, "argocd", "broken", ownership.MembersUnreadable, "shop")
				return cm.Ref
			},
			state: ownership.ManagerUnknown,
		},
		{
			name: "none: no evidence anywhere",
			build: func(b *ownershiptest.Builder) resourceid.Ref {
				return b.Add("", "ConfigMap", "shop", "settings").Ref
			},
			state: ownership.ManagerNone,
		},
		{
			// The fleet's commonest case: the Pod inherits its template's
			// instance label; only its Deployment is listed.
			name: "via: a Pod whose Deployment a list names",
			build: func(b *ownershiptest.Builder) resourceid.Ref {
				d := b.Add("apps", "Deployment", "shop", "web")
				rs := b.Add("apps", "ReplicaSet", "shop", "web-1", d.OwnerRef(true))
				pod := b.Meta(b.Add("", "Pod", "shop", "web-1-a", rs.OwnerRef(true)), map[string]string{instanceLabel: "shop"}, nil)
				b.Manager(ownership.ArgoApplication, "argocd", "shop", recorded, "shop", d.Ref)
				return pod.Ref
			},
			state: ownership.ManagerListed, lead: ref(app("shop")),
			check: func(t *testing.T, a ownership.ManagerAnswer) {
				if a.Via == nil || a.Via.Object.Kind != "Deployment" {
					t.Fatalf("via %+v, want the Deployment", a.Via)
				}
				if len(a.Direct.Claims) != 1 || !a.Direct.Claims[0].Weak {
					t.Errorf("direct claims %+v, want the Pod's label kept as weak", a.Direct.Claims)
				}
			},
		},
		{
			// A Deployment copies its annotations, a Helm release's among them,
			// onto its ReplicaSets; the list naming the Deployment answers.
			name: "via: a ReplicaSet with its Deployment's Helm annotations",
			build: func(b *ownershiptest.Builder) resourceid.Ref {
				helm := map[string]string{helmRelease: "web", helmNamespace: "shop"}
				d := b.Meta(b.Add("apps", "Deployment", "shop", "web"), nil, helm)
				rs := b.Meta(b.Add("apps", "ReplicaSet", "shop", "web-1", d.OwnerRef(true)), nil, helm)
				b.Manager(ownership.ArgoApplication, "argocd", "shop", recorded, "shop", d.Ref)
				return rs.Ref
			},
			state: ownership.ManagerListed, lead: ref(app("shop")),
		},
		{
			// A self-referencing tracking ID on the object is its own answer,
			// whatever manages its controller.
			name: "direct: an object's own tracking ID stops the walk",
			build: func(b *ownershiptest.Builder) resourceid.Ref {
				d := b.Add("apps", "Deployment", "shop", "web")
				rs := b.Meta(b.Add("apps", "ReplicaSet", "shop", "web-1", d.OwnerRef(true)), nil, map[string]string{trackingID: "argocd_other:apps/ReplicaSet:shop/web-1"})
				b.Manager(ownership.ArgoApplication, "argocd", "shop", recorded, "shop", d.Ref)
				b.Manager(ownership.ArgoApplication, "argocd", "other", ownership.MembersUnreadable, "shop")
				return rs.Ref
			},
			state: ownership.ManagerClaimed, lead: ref(app("other")),
			check: func(t *testing.T, a ownership.ManagerAnswer) {
				if a.Via != nil {
					t.Errorf("via %+v, want none", a.Via)
				}
			},
		},
		{
			// A gone controller ends the chain with an answer: nothing above
			// it was missed.
			name: "none: the walk stops at a controller that's gone",
			build: func(b *ownershiptest.Builder) resourceid.Ref {
				gone := ownershiptest.Object{Ref: resourceid.Ref{Group: "apps", Kind: "ReplicaSet", Namespace: "shop", Name: "web-1"}, APIVersion: "apps/v1", UID: "uid-gone"}
				return b.Add("", "Pod", "shop", "web-1-a", gone.OwnerRef(true)).Ref
			},
			state: ownership.ManagerNone,
			check: func(t *testing.T, a ownership.ManagerAnswer) {
				if a.ChainEnd != ownership.ChainEndUnfollowed {
					t.Errorf("chain end %q, want unfollowed", a.ChainEnd)
				}
			},
		},
		{
			// Each manager answers for what its own list names: the parent
			// for the child Application, not for the child's members.
			name: "app of apps: a child Application is managed by its parent",
			build: func(b *ownershiptest.Builder) resourceid.Ref {
				child := b.Manager(ownership.ArgoApplication, "argocd", "child", recorded, "shop")
				b.Manager(ownership.ArgoApplication, "argocd", "parent", recorded, "argocd", child.Ref)
				return child.Ref
			},
			state: ownership.ManagerListed, lead: ref(app("parent")),
		},
	} {
		t.Run(tc.name, func(t *testing.T) {
			b := ownershiptest.NewBuilder()
			subject := tc.build(b)
			got := view(t, b.World(), ownership.Unrestricted()).Manager(subject)
			if got.State != tc.state {
				t.Fatalf("state %q, want %q (direct %+v, via %+v)", got.State, tc.state, got.Direct, got.Via)
			}
			if (got.Lead == nil) != (tc.lead == nil) || got.Lead != nil && *got.Lead != *tc.lead {
				t.Fatalf("lead %v, want %v", got.Lead, tc.lead)
			}
			if got.Narrowed {
				t.Error("narrowed for an unrestricted caller")
			}
			if tc.check != nil {
				tc.check(t, got)
			}
		})
	}
}

// A caller who may not read Applications sees the claim, but nothing of the
// manager it names, and an answer marked narrowed.
func TestManagerForACallerWhoMayNotReadManagers(t *testing.T) {
	b := ownershiptest.NewBuilder()
	cm := b.Meta(b.Add("", "ConfigMap", "shop", "settings"), nil, map[string]string{trackingID: "argocd_shop:/ConfigMap:shop/settings"})
	b.Manager(ownership.ArgoApplication, "argocd", "shop", ownership.MembersRecorded, "shop", cm.Ref)
	got := view(t, b.World(), access{denied: []resourceid.GroupKind{ownership.ArgoApplication}}).Manager(cm.Ref)
	if got.State != ownership.ManagerClaimed || len(got.Direct.Recorders) != 0 || !got.Narrowed {
		t.Fatalf("answer %+v; want claimed, no recorders, narrowed", got)
	}
	if c := checkOf(got.Direct, app("shop")); c != ownership.ClaimWithheld {
		t.Fatalf("check %q, want withheld", c)
	}
}

func ref(r resourceid.Ref) *resourceid.Ref { return &r }

// Every claim must name the one recorder for agreement: one that names
// another manager makes a disagreement, whichever source it comes from.
func TestManagerAgreementNeedsEveryClaim(t *testing.T) {
	b := ownershiptest.NewBuilder()
	cm := b.Meta(b.Add("", "ConfigMap", "shop", "settings"),
		map[string]string{"kustomize.toolkit.fluxcd.io/name": "apps", "kustomize.toolkit.fluxcd.io/namespace": "flux-system"},
		map[string]string{trackingID: "argocd_shop:/ConfigMap:shop/settings"})
	b.Manager(ownership.ArgoApplication, "argocd", "shop", ownership.MembersRecorded, "shop", cm.Ref)
	if got := view(t, b.World(), ownership.Unrestricted()).Manager(cm.Ref); got.State != ownership.ManagerDisagree || got.Lead != nil {
		t.Fatalf("answer %+v; want a disagreement with no lead", got)
	}
}

// Weak claims at each hop let the walk go on, and are kept on the way.
func TestManagerKeepsWeakEvidenceAlongTheWalk(t *testing.T) {
	b := ownershiptest.NewBuilder()
	d := b.Add("apps", "Deployment", "shop", "web")
	rs := b.Meta(b.Add("apps", "ReplicaSet", "shop", "web-1", d.OwnerRef(true)), map[string]string{instanceLabel: "other"}, nil)
	pod := b.Add("", "Pod", "shop", "web-1-a", rs.OwnerRef(true))
	b.Manager(ownership.ArgoApplication, "argocd", "shop", ownership.MembersRecorded, "shop", d.Ref)
	got := view(t, b.World(), ownership.Unrestricted()).Manager(pod.Ref)
	if got.State != ownership.ManagerListed || got.Via == nil || got.Via.Object != d.Ref {
		t.Fatalf("answer %+v; want listed via the Deployment", got)
	}
	if len(got.Between) != 1 || got.Between[0].Object != rs.Ref || !got.Between[0].Claims[0].Weak {
		t.Fatalf("between %+v; want the ReplicaSet's weak claim", got.Between)
	}
}

// When no object settles it, a claim the walk passed answers: the Pod is
// tracked through its ReplicaSet, not unmanaged.
func TestManagerFallsBackToAWeakClaimOnTheWay(t *testing.T) {
	b := ownershiptest.NewBuilder()
	d := b.Add("apps", "Deployment", "shop", "web")
	rs := b.Meta(b.Add("apps", "ReplicaSet", "shop", "web-1", d.OwnerRef(true)), map[string]string{instanceLabel: "argocd_shop"}, nil)
	pod := b.Add("", "Pod", "shop", "web-1-a", rs.OwnerRef(true))
	got := view(t, b.World(), ownership.Unrestricted()).Manager(pod.Ref)
	if got.State != ownership.ManagerClaimed || got.Via == nil || got.Via.Object != rs.Ref || len(got.Between) != 0 {
		t.Fatalf("answer %+v; want claimed via the ReplicaSet", got)
	}
}

// A controller the caller may not read stops the walk: the answer says so,
// and nothing of the controller.
func TestManagerStopsAtAWithheldController(t *testing.T) {
	b := ownershiptest.NewBuilder()
	d := b.Add("apps", "Deployment", "shop", "web")
	rs := b.Add("apps", "ReplicaSet", "shop", "web-1", d.OwnerRef(true))
	pod := b.Add("", "Pod", "shop", "web-1-a", rs.OwnerRef(true))
	b.Manager(ownership.ArgoApplication, "argocd", "shop", ownership.MembersRecorded, "shop", d.Ref)
	got := view(t, b.World(), access{denied: []resourceid.GroupKind{{Group: "apps", Kind: "ReplicaSet"}}}).Manager(pod.Ref)
	if got.State != ownership.ManagerUnknown || got.ChainEnd != ownership.ChainEndUnfollowed || got.Stop == nil || got.Stop.Owner.State != ownership.OwnerWithheld || got.Via != nil {
		t.Fatalf("answer %+v; want unknown, stopped at a withheld controller", got)
	}
}

// Radar couldn't tell whether the caller may read Applications everywhere
// they are loading: no evidence is unknown, not none.
func TestManagerWithUndecidedAccessIsUnknown(t *testing.T) {
	b := ownershiptest.NewBuilder()
	cm := b.Add("", "ConfigMap", "shop", "settings")
	w := b.World()
	c := w.Collection[ownership.ArgoApplication]
	c.State = ownershiptest.Loading
	w.Collection[ownership.ArgoApplication] = c
	got := view(t, w, wholeKindUndecided{ownership.Unrestricted()}).Manager(cm.Ref)
	if got.State != ownership.ManagerUnknown || !got.Direct.Undecided {
		t.Fatalf("answer %+v; want unknown with undecided access", got)
	}
}

// A controller of a kind discovery can't map stops the walk; the stop keeps
// the reference that names it, from the metadata of an object the caller reads.
func TestManagerStopKeepsAnUnmappedControllersReference(t *testing.T) {
	b := ownershiptest.NewBuilder()
	parent := ownershiptest.Object{Ref: resourceid.Ref{Group: "missing.example.io", Kind: "Parent", Namespace: "shop", Name: "controller-x"}, APIVersion: "missing.example.io/v1", UID: "uid-parent"}
	pod := b.Add("", "Pod", "shop", "web-1-a", parent.OwnerRef(true))
	got := view(t, b.World(), ownership.Unrestricted()).Manager(pod.Ref)
	if got.Stop == nil || got.Stop.Owner.State != ownership.OwnerUnknown || got.Stop.Reference.Kind != "Parent" || got.Stop.Reference.Name != "controller-x" {
		t.Fatalf("stop %+v; want the unmapped Parent's reference", got.Stop)
	}
}

// Checking a stale claim against a large manager reads that manager's list,
// not every object in it.
func TestManagerChecksAClaimWithoutReadingItsManagersMembers(t *testing.T) {
	b := ownershiptest.NewBuilder()
	var members []resourceid.Ref
	for i := range 50 {
		members = append(members, b.Add("", "ConfigMap", "shop", fmt.Sprint("listed-", i)).Ref)
	}
	cm := b.Meta(b.Add("", "ConfigMap", "shop", "stale"), nil, map[string]string{trackingID: "argocd_shop:/ConfigMap:shop/stale"})
	b.Manager(ownership.ArgoApplication, "argocd", "shop", ownership.MembersRecorded, "shop", members...)
	s, err := ownershiptest.Scan(b.World()).Begin()
	if err != nil {
		t.Fatal(err)
	}
	counted := &objectCounter{ReadSession: s}
	got := ownership.NewView(counted, ownership.Unrestricted()).Manager(cm.Ref)
	if got.State != ownership.ManagerClaimed || checkOf(got.Direct, app("shop")) != ownership.ClaimOmitted {
		t.Fatalf("answer %+v; want the claim checked as omitted", got)
	}
	if counted.objects > 3 {
		t.Fatalf("%d object reads; want the subject and the manager, not the manager's members", counted.objects)
	}
}

type objectCounter struct {
	ownership.ReadSession
	objects     int
	memberships int
}

func (s *objectCounter) Object(ref resourceid.Ref) ownership.ObjectRead {
	s.objects++
	return s.ReadSession.Object(ref)
}

func (s *objectCounter) Membership(ref resourceid.Ref) ownership.MembersRead {
	s.memberships++
	return s.ReadSession.Membership(ref)
}

func TestManagerSharesClaimMembershipWithinAView(t *testing.T) {
	b := ownershiptest.NewBuilder()
	a := b.Meta(b.Add("", "ConfigMap", "shop", "a"), nil, map[string]string{trackingID: "argocd_shop:/ConfigMap:shop/a"})
	bb := b.Meta(b.Add("", "ConfigMap", "shop", "b"), nil, map[string]string{trackingID: "argocd_shop:/ConfigMap:shop/b"})
	b.Manager(ownership.ArgoApplication, "argocd", "shop", ownership.MembersRecorded, "shop")
	s, err := ownershiptest.Scan(b.World()).Begin()
	if err != nil {
		t.Fatal(err)
	}
	counted := &objectCounter{ReadSession: s}
	for request := range 2 {
		v := ownership.NewView(counted, ownership.Unrestricted())
		for _, row := range []resourceid.Ref{a.Ref, bb.Ref} {
			answer := v.Manager(row)
			if answer.State != ownership.ManagerClaimed || checkOf(answer.Direct, app("shop")) != ownership.ClaimOmitted {
				t.Fatalf("answer %+v; want a checked omitted claim", answer)
			}
		}
		if counted.memberships != request+1 {
			t.Fatalf("membership reads %d; want one per request", counted.memberships)
		}
	}
}

// A Pod whose template carries the Flux origin labels of an outer
// Kustomization is managed by the HelmRelease that records its Deployment, the
// nearer manager; without that record its own labels still answer.
func TestManagerWalksPastFluxLabelsAControllerCopied(t *testing.T) {
	outer := map[string]string{"kustomize.toolkit.fluxcd.io/name": "flux-system", "kustomize.toolkit.fluxcd.io/namespace": "flux-system"}
	for _, recordedByRelease := range []bool{true, false} {
		b := ownershiptest.NewBuilder()
		d := b.Add("apps", "Deployment", "apps", "web")
		rs := b.Meta(b.Add("apps", "ReplicaSet", "apps", "web-1", d.OwnerRef(true)), outer, nil)
		pod := b.Meta(b.Add("", "Pod", "apps", "web-1-a", rs.OwnerRef(true)), outer, nil)
		b.Manager(ownership.FluxKustomization, "flux-system", "flux-system", ownership.MembersRecorded, "")
		var members []resourceid.Ref
		if recordedByRelease {
			members = append(members, d.Ref)
		}
		b.Manager(ownership.FluxHelmRelease, "apps", "web", ownership.MembersRecorded, "", members...)
		got := view(t, b.World(), ownership.Unrestricted()).Manager(pod.Ref)
		release := resourceid.Ref{Group: "helm.toolkit.fluxcd.io", Kind: "HelmRelease", Namespace: "apps", Name: "web"}
		kustomization := resourceid.Ref{Group: "kustomize.toolkit.fluxcd.io", Kind: "Kustomization", Namespace: "flux-system", Name: "flux-system"}
		switch {
		case recordedByRelease && (got.State != ownership.ManagerListed || got.Lead == nil || *got.Lead != release || got.Via == nil || got.Via.Object != d.Ref):
			t.Fatalf("recorded: answer %+v; want listed via the Deployment, led by its HelmRelease", got)
		case !recordedByRelease && (got.State != ownership.ManagerClaimed || got.Lead == nil || *got.Lead != kustomization):
			t.Fatalf("unrecorded: answer %+v; want the Pod's own claim", got)
		}
	}
}

// A Pod Flux labelled and a ReplicaSet later adopted: the HelmRelease records
// no inventory, so nothing says the labels were copied, and they answer.
func TestManagerKeepsFluxLabelsWhoseListCannotBeRead(t *testing.T) {
	b := ownershiptest.NewBuilder()
	d := b.Add("apps", "Deployment", "apps", "web")
	rs := b.Add("apps", "ReplicaSet", "apps", "web-1", d.OwnerRef(true))
	pod := b.Meta(b.Add("", "Pod", "apps", "adopted", rs.OwnerRef(true)), map[string]string{"helm.toolkit.fluxcd.io/name": "legacy", "helm.toolkit.fluxcd.io/namespace": "apps"}, nil)
	b.Manager(ownership.FluxHelmRelease, "apps", "legacy", ownership.MembersUnreadable, "")
	b.Manager(ownership.FluxKustomization, "flux-system", "apps", ownership.MembersRecorded, "", d.Ref)
	got := view(t, b.World(), ownership.Unrestricted()).Manager(pod.Ref)
	legacy := resourceid.Ref{Group: "helm.toolkit.fluxcd.io", Kind: "HelmRelease", Namespace: "apps", Name: "legacy"}
	if got.Via != nil || len(got.Direct.Claims) != 1 || got.Direct.Claims[0].Weak || got.Lead == nil || *got.Lead != legacy {
		t.Fatalf("answer %+v; want the Pod's own unreadable-list claim to answer", got)
	}
}