package e2e import ( "context" "fmt" . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" capsulev1beta2 "github.com/projectcapsule/capsule/api/v1beta2" "github.com/projectcapsule/capsule/pkg/api/meta" "github.com/projectcapsule/capsule/pkg/api/rbac" corev1 "k8s.io/api/core/v1" apierrors "k8s.io/apimachinery/pkg/api/errors" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "sigs.k8s.io/controller-runtime/pkg/client" ) var _ = Describe("NamespaceStatus objects", Ordered, Label("tenant", "rules", "status"), func() { ctx := context.Background() // Two tenants, each with one owner (reuse your existing ownerClient/NamespaceCreation helpers) tntA := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "e2e-rule-status-a", Labels: map[string]string{ "env": "e2e", }, }, Spec: capsulev1beta2.TenantSpec{ Owners: rbac.OwnerListSpec{ { CoreOwnerSpec: rbac.CoreOwnerSpec{ UserSpec: rbac.UserSpec{Name: "e2e-rule-status-a", Kind: "User"}, }, }, }, }, } tntB := &capsulev1beta2.Tenant{ ObjectMeta: metav1.ObjectMeta{ Name: "e2e-rule-status-b", Labels: map[string]string{ "env": "e2e", }, }, Spec: capsulev1beta2.TenantSpec{ Owners: rbac.OwnerListSpec{ { CoreOwnerSpec: rbac.CoreOwnerSpec{ UserSpec: rbac.UserSpec{Name: "e2e-rule-status-b", Kind: "User"}, }, }, }, }, } var ( nsA1 *corev1.Namespace nsA2 *corev1.Namespace nsB1 *corev1.Namespace ) JustBeforeEach(func() { // Create tenants EventuallyCreation(func() error { tntA.ResourceVersion = "" return k8sClient.Create(ctx, tntA) }).Should(Succeed()) TenantReady(tntA, metav1.ConditionTrue, defaultTimeoutInterval) EventuallyCreation(func() error { tntB.ResourceVersion = "" return k8sClient.Create(ctx, tntB) }).Should(Succeed()) TenantReady(tntB, metav1.ConditionTrue, defaultTimeoutInterval) nsA1 = NewNamespace("e2e-rule-status-a-1", map[string]string{ meta.TenantLabel: tntA.GetName(), }) nsA2 = NewNamespace("e2e-rule-status-a-2", map[string]string{ meta.TenantLabel: tntA.GetName(), }) nsB1 = NewNamespace("e2e-rule-status-b-1", map[string]string{ meta.TenantLabel: tntB.GetName(), }) NamespaceCreation(nsA1, tntA.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) NamespaceCreation(nsA2, tntA.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) NamespaceCreation(nsB1, tntB.Spec.Owners[0].UserSpec, defaultTimeoutInterval).Should(Succeed()) // Wait until tenants list their namespaces (optional but makes debugging easier) NamespaceIsPartOfTenant(tntA, nsA1).Should(Succeed()) NamespaceIsPartOfTenant(tntA, nsA2).Should(Succeed()) NamespaceIsPartOfTenant(tntB, nsB1).Should(Succeed()) }) JustAfterEach(func() { // Best-effort cleanup namespaces first (your env may already handle this) for _, n := range []*corev1.Namespace{nsA1, nsA2, nsB1} { if n == nil { continue } _ = k8sClient.Delete(ctx, n) } // Delete tenants if tntA != nil { EventuallyDeletion(tntA) } if tntB != nil { EventuallyDeletion(tntB) } }) expectNamespaceStatusFor := func(ns *corev1.Namespace, tenant *capsulev1beta2.Tenant) { By(fmt.Sprintf("verifying NamespaceStatus for namespace %q (tenant=%q)", ns.Name, tenant.GetName())) Eventually(func(g Gomega) { // Re-read namespace to get UID reliably (in case local object is stale) curNS := &corev1.Namespace{} g.Expect(k8sClient.Get(ctx, client.ObjectKey{Name: ns.Name}, curNS)).To(Succeed()) nsStatus := &capsulev1beta2.RuleStatus{} g.Expect(k8sClient.Get(ctx, client.ObjectKey{Name: meta.NameForManagedRuleStatus(), Namespace: ns.Name}, nsStatus)).To(Succeed()) // 2) OwnerReference must point to the Namespace and be controller owner g.Expect(nsStatus.OwnerReferences).NotTo(BeEmpty()) var found bool for _, or := range nsStatus.OwnerReferences { if or.APIVersion == capsulev1beta2.GroupVersion.String() && or.Kind == "Tenant" && or.Name == tenant.Name && or.UID == tenant.UID { found = true break } } g.Expect(found).To(BeTrue(), "expected NamespaceStatus to have Tenant controller OwnerReference") }, defaultTimeoutInterval, defaultPollInterval).Should(Succeed()) } It("creates one NamespaceStatus per namespace, with correct Status.Tenant and Namespace controller OwnerReference", func() { getTenantA := &capsulev1beta2.Tenant{} Expect(k8sClient.Get(ctx, client.ObjectKey{Name: tntA.GetName()}, getTenantA)).To(Succeed()) getTenantB := &capsulev1beta2.Tenant{} Expect(k8sClient.Get(ctx, client.ObjectKey{Name: tntB.GetName()}, getTenantB)).To(Succeed()) expectNamespaceStatusFor(nsA1, getTenantA) expectNamespaceStatusFor(nsA2, getTenantA) expectNamespaceStatusFor(nsB1, getTenantB) }) It("removes NamespaceStatus when the Namespace is deleted (ownerReference GC)", func() { getTenantA := &capsulev1beta2.Tenant{} Expect(k8sClient.Get(ctx, client.ObjectKey{Name: tntA.GetName()}, getTenantA)).To(Succeed()) // Ensure it exists first expectNamespaceStatusFor(nsA1, getTenantA) // Delete namespace Expect(k8sClient.Delete(ctx, nsA1)).To(Succeed()) // Namespace deletion can take time; once it's gone, the status should be GC'd Eventually(func() bool { // confirm namespace gone or terminating; either way, check status disappears eventually nsStatus := &capsulev1beta2.RuleStatus{} err := k8sClient.Get(ctx, client.ObjectKey{Name: meta.NameForManagedRuleStatus(), Namespace: nsA1.Name}, nsStatus) return apierrors.IsNotFound(err) }, defaultTimeoutInterval, defaultPollInterval).Should(BeTrue()) }) })