Skip to content

Commit 64ddff2

Browse files
arun717Arun Mauryaclaude
authored
CM-869: Add test coverage for configurable TrustNamespace (#391)
* test(trustmanager): add unit tests for trust namespace configuration Add missing unit test coverage for configurable trust namespace feature. The trust namespace configuration was already fully implemented in PR #379. This commit adds comprehensive unit tests to verify the implementation and satisfy CM-869 acceptance criteria. Tests added: - Trust namespace validation when namespace doesn't exist - Custom trust namespace configuration with successful reconciliation - Status field updates for both default and custom namespace values Without these tests, the trust namespace validation logic was not adequately covered, making it harder to catch regressions. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * test(e2e): add e2e tests for trust namespace configuration Add comprehensive end-to-end tests for trust namespace configuration. Tests verify: 1. Custom trust namespace RBAC placement - Role/RoleBinding created in trust namespace - Leader election Role/RoleBinding in operand namespace 2. Deployment configuration with correct --trust-namespace arg 3. Status field updates matching spec configuration 4. Degraded condition when trust namespace doesn't exist 5. Recovery to Ready=True after namespace creation These tests ensure the trust namespace feature works correctly in a real cluster environment and that all resources are created in the correct namespaces as required by CM-869. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com> * test(trustmanager): fix for missing namespace case. * Handling PR review comments. * Handling remaining PR review comments on trustmanager_test.go * Addressed Review comments iteration 3 * Handling CodeRabbit comment * Addressed new round of pr review comments * Undo comment for test * Addressing the latest review comment of removal of a test. * Removing duplicate method definition. --------- Co-authored-by: Arun Maurya <amaurya@amaurya-thinkpadp1gen7.bengluru.csb> Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
1 parent 7255048 commit 64ddff2

6 files changed

Lines changed: 404 additions & 10 deletions

File tree

pkg/controller/trustmanager/controller_test.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package trustmanager
22

33
import (
44
"context"
5+
"fmt"
56
"testing"
67
"time"
78

@@ -251,6 +252,86 @@ func TestProcessReconcileRequest(t *testing.T) {
251252
},
252253
wantErr: "failed to check if serviceaccount",
253254
},
255+
{
256+
name: "trust namespace does not exist sets degraded true",
257+
getTrustManager: func() *v1alpha1.TrustManager {
258+
return testTrustManager().Build()
259+
},
260+
preReq: func(r *Reconciler, m *fakes.FakeCtrlClient) {
261+
m.GetCalls(func(ctx context.Context, key client.ObjectKey, obj client.Object) error {
262+
switch o := obj.(type) {
263+
case *v1alpha1.TrustManager:
264+
testTrustManager().Build().DeepCopyInto(o)
265+
}
266+
return nil
267+
})
268+
// Namespace does not exist - validateTrustNamespace will fail
269+
m.ExistsCalls(func(ctx context.Context, key client.ObjectKey, obj client.Object) (bool, error) {
270+
switch obj.(type) {
271+
case *corev1.Namespace:
272+
return false, nil
273+
}
274+
return false, nil
275+
})
276+
},
277+
wantConditions: []metav1.Condition{
278+
{
279+
Type: v1alpha1.Degraded,
280+
Status: metav1.ConditionTrue,
281+
Reason: v1alpha1.ReasonFailed,
282+
Message: fmt.Sprintf(
283+
"reconciliation failed with irrecoverable error not retrying: trust namespace %q validation failed: trust namespace %q does not exist, create the namespace before creating TrustManager CR",
284+
defaultTrustNamespace,
285+
defaultTrustNamespace,
286+
),
287+
},
288+
{
289+
Type: v1alpha1.Ready,
290+
Status: metav1.ConditionFalse,
291+
Reason: v1alpha1.ReasonFailed,
292+
},
293+
},
294+
},
295+
{
296+
name: "custom trust namespace configures resources correctly",
297+
getTrustManager: func() *v1alpha1.TrustManager {
298+
tm := testTrustManager().Build()
299+
tm.Spec.TrustManagerConfig.TrustNamespace = "custom-trust-ns"
300+
return tm
301+
},
302+
preReq: func(r *Reconciler, m *fakes.FakeCtrlClient) {
303+
m.GetCalls(func(ctx context.Context, key client.ObjectKey, obj client.Object) error {
304+
switch o := obj.(type) {
305+
case *v1alpha1.TrustManager:
306+
tm := testTrustManager().Build()
307+
tm.Spec.TrustManagerConfig.TrustNamespace = "custom-trust-ns"
308+
tm.DeepCopyInto(o)
309+
}
310+
return nil
311+
})
312+
// Custom namespace exists; so SSA Patch will create or update all resources successfully.
313+
m.ExistsCalls(func(ctx context.Context, key client.ObjectKey, obj client.Object) (bool, error) {
314+
switch obj.(type) {
315+
case *corev1.Namespace:
316+
return true, nil
317+
}
318+
return false, nil
319+
})
320+
},
321+
wantConditions: []metav1.Condition{
322+
{
323+
Type: v1alpha1.Degraded,
324+
Status: metav1.ConditionFalse,
325+
Reason: v1alpha1.ReasonReady,
326+
},
327+
{
328+
Type: v1alpha1.Ready,
329+
Status: metav1.ConditionTrue,
330+
Reason: v1alpha1.ReasonReady,
331+
Message: "reconciliation successful",
332+
},
333+
},
334+
},
254335
}
255336

256337
for _, tt := range tests {
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
package trustmanager
2+
3+
import (
4+
"context"
5+
"reflect"
6+
"testing"
7+
8+
"github.com/google/go-cmp/cmp"
9+
"github.com/openshift/cert-manager-operator/api/operator/v1alpha1"
10+
"github.com/openshift/cert-manager-operator/pkg/controller/common/fakes"
11+
"sigs.k8s.io/controller-runtime/pkg/client"
12+
)
13+
14+
func TestUpdateStatusObservedState(t *testing.T) {
15+
t.Setenv(trustManagerImageNameEnvVarName, testImage)
16+
17+
// Observed status after sync from testTrustManager() defaults (empty status fields + default spec).
18+
wantStatusSyncedFromDefaultSpec := v1alpha1.TrustManagerStatus{
19+
TrustManagerImage: testImage,
20+
TrustNamespace: defaultTrustNamespace,
21+
SecretTargetsPolicy: "",
22+
DefaultCAPackagePolicy: "",
23+
FilterExpiredCertificatesPolicy: "",
24+
}
25+
26+
tests := []struct {
27+
name string
28+
trustManager func() *v1alpha1.TrustManager
29+
wantStatusUpdate int
30+
wantStatus v1alpha1.TrustManagerStatus
31+
}{
32+
{
33+
name: "updates all observed fields when status is empty",
34+
trustManager: func() *v1alpha1.TrustManager {
35+
return testTrustManager().Build()
36+
},
37+
wantStatusUpdate: 1,
38+
wantStatus: wantStatusSyncedFromDefaultSpec,
39+
},
40+
{
41+
name: "updates all observed fields for custom spec",
42+
trustManager: func() *v1alpha1.TrustManager {
43+
return testTrustManager().
44+
WithTrustNamespace("custom-trust-ns").
45+
WithSecretTargets(v1alpha1.SecretTargetsPolicyCustom, []string{"allowed-secret"}).
46+
WithDefaultCAPackage(v1alpha1.DefaultCAPackagePolicyEnabled).
47+
WithFilterExpiredCertificates(v1alpha1.FilterExpiredCertificatesPolicyEnabled).
48+
Build()
49+
},
50+
wantStatusUpdate: 1,
51+
wantStatus: v1alpha1.TrustManagerStatus{
52+
TrustManagerImage: testImage,
53+
TrustNamespace: "custom-trust-ns",
54+
SecretTargetsPolicy: v1alpha1.SecretTargetsPolicyCustom,
55+
DefaultCAPackagePolicy: v1alpha1.DefaultCAPackagePolicyEnabled,
56+
FilterExpiredCertificatesPolicy: v1alpha1.FilterExpiredCertificatesPolicyEnabled,
57+
},
58+
},
59+
{
60+
name: "no-op when observed state already matches spec and env",
61+
trustManager: func() *v1alpha1.TrustManager {
62+
tm := testTrustManager().Build()
63+
tm.Status.TrustManagerImage = testImage
64+
tm.Status.TrustNamespace = defaultTrustNamespace
65+
tm.Status.SecretTargetsPolicy = tm.Spec.TrustManagerConfig.SecretTargets.Policy
66+
tm.Status.DefaultCAPackagePolicy = tm.Spec.TrustManagerConfig.DefaultCAPackage.Policy
67+
tm.Status.FilterExpiredCertificatesPolicy = tm.Spec.TrustManagerConfig.FilterExpiredCertificates
68+
return tm
69+
},
70+
wantStatusUpdate: 0,
71+
wantStatus: wantStatusSyncedFromDefaultSpec,
72+
},
73+
}
74+
75+
for _, tt := range tests {
76+
t.Run(tt.name, func(t *testing.T) {
77+
r := testReconciler(t)
78+
mock := &fakes.FakeCtrlClient{}
79+
tm := tt.trustManager()
80+
81+
mock.GetCalls(func(ctx context.Context, key client.ObjectKey, obj client.Object) error {
82+
switch o := obj.(type) {
83+
case *v1alpha1.TrustManager:
84+
tm.DeepCopyInto(o)
85+
}
86+
return nil
87+
})
88+
mock.StatusUpdateCalls(func(ctx context.Context, obj client.Object, opts ...client.SubResourceUpdateOption) error {
89+
return nil
90+
})
91+
r.CtrlClient = mock
92+
93+
if err := r.updateStatusObservedState(tm); err != nil {
94+
t.Fatalf("updateStatusObservedState: %v", err)
95+
}
96+
if got := mock.StatusUpdateCallCount(); got != tt.wantStatusUpdate {
97+
t.Errorf("StatusUpdateCallCount() = %d, want %d", got, tt.wantStatusUpdate)
98+
}
99+
100+
if !reflect.DeepEqual(tm.Status, tt.wantStatus) {
101+
t.Errorf("TrustManager.Status mismatch (-want +got):\n%s", cmp.Diff(tt.wantStatus, tm.Status))
102+
}
103+
})
104+
}
105+
}

pkg/controller/trustmanager/test_utils.go

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,11 @@ func (b *trustManagerBuilder) WithFilterExpiredCertificates(policy v1alpha1.Filt
8989
return b
9090
}
9191

92+
func (b *trustManagerBuilder) WithDefaultCAPackage(policy v1alpha1.DefaultCAPackagePolicy) *trustManagerBuilder {
93+
b.Spec.TrustManagerConfig.DefaultCAPackage.Policy = policy
94+
return b
95+
}
96+
9297
func (b *trustManagerBuilder) WithSecretTargets(policy v1alpha1.SecretTargetsPolicy, authorizedSecrets []string) *trustManagerBuilder {
9398
b.Spec.TrustManagerConfig.SecretTargets = v1alpha1.SecretTargetsConfig{
9499
Policy: policy,
@@ -97,13 +102,6 @@ func (b *trustManagerBuilder) WithSecretTargets(policy v1alpha1.SecretTargetsPol
97102
return b
98103
}
99104

100-
func (b *trustManagerBuilder) WithDefaultCAPackage(policy v1alpha1.DefaultCAPackagePolicy) *trustManagerBuilder {
101-
b.Spec.TrustManagerConfig.DefaultCAPackage = v1alpha1.DefaultCAPackageConfig{
102-
Policy: policy,
103-
}
104-
return b
105-
}
106-
107105
func (b *trustManagerBuilder) Build() *v1alpha1.TrustManager {
108106
return b.TrustManager
109107
}

pkg/controller/trustmanager/webhooks.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ func getValidatingWebhookConfigObject(resourceLabels, resourceAnnotations map[st
5252
return webhookConfig
5353
}
5454

55-
// updateWebhookClientConfig sets the webhook clientConfig service name and namespace
55+
// updateWebhookClientConfig sets the webhook clientConfig service name and namespace.
5656
func updateWebhookClientConfig(webhookConfig *admissionregistrationv1.ValidatingWebhookConfiguration) {
5757
for i := range webhookConfig.Webhooks {
5858
if webhookConfig.Webhooks[i].ClientConfig.Service != nil {

0 commit comments

Comments
 (0)