Skip to content

Commit b68fcba

Browse files
cahillsfclaude
andcommitted
add tests for FileCAProvider, SecretCAProvider, and empty secret name guards
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Stephen Cahill <stephen.cahill@datadoghq.com>
1 parent 9e5caec commit b68fcba

2 files changed

Lines changed: 239 additions & 9 deletions

File tree

internal/initializer/tls_test.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -707,6 +707,37 @@ func TestTLSCertificateGeneratorRun(t *testing.T) {
707707
},
708708
want: want{err: nil},
709709
},
710+
"EmptyServerSecretNameIsNoOp": {
711+
reason: "Passing an empty server secret name should not set tlsServerSecretName, so Run returns nil immediately.",
712+
args: args{
713+
kube: &test.MockClient{},
714+
opts: []TLSCertificateGeneratorOption{
715+
TLSCertificateGeneratorWithServerSecretName("", []string{subject}),
716+
},
717+
},
718+
want: want{err: nil},
719+
},
720+
"EmptyClientSecretNameIsNoOp": {
721+
reason: "Passing an empty client secret name should not set tlsClientSecretName, so Run returns nil immediately.",
722+
args: args{
723+
kube: &test.MockClient{},
724+
opts: []TLSCertificateGeneratorOption{
725+
TLSCertificateGeneratorWithClientSecretName("", []string{subject}),
726+
},
727+
},
728+
want: want{err: nil},
729+
},
730+
"EmptyBothSecretNamesIsNoOp": {
731+
reason: "Passing empty secret names for both server and client should result in no work done.",
732+
args: args{
733+
kube: &test.MockClient{},
734+
opts: []TLSCertificateGeneratorOption{
735+
TLSCertificateGeneratorWithServerSecretName("", []string{subject}),
736+
TLSCertificateGeneratorWithClientSecretName("", []string{subject}),
737+
},
738+
},
739+
want: want{err: nil},
740+
},
710741
"OnlyClientCertificateSuccessfulGeneratedClientCert": {
711742
reason: "It should be successful if the client certificate is generated and put into the Secret.",
712743
args: args{

internal/initializer/webhook_configurations_test.go

Lines changed: 208 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package initializer
1919
import (
2020
"bytes"
2121
"context"
22+
"os"
2223
"testing"
2324

2425
"github.com/google/go-cmp/cmp"
@@ -37,11 +38,136 @@ import (
3738
"github.com/crossplane/crossplane-runtime/v2/pkg/test"
3839
)
3940

40-
func TestWebhookConfigurations(t *testing.T) {
41+
func TestSecretCAProvider(t *testing.T) {
4142
type args struct {
4243
kube client.Client
43-
svc admv1.ServiceReference
44-
opts []WebhookConfigurationsOption
44+
ref types.NamespacedName
45+
}
46+
47+
type want struct {
48+
ca []byte
49+
err error
50+
}
51+
52+
cases := map[string]struct {
53+
reason string
54+
args args
55+
want want
56+
}{
57+
"Success": {
58+
reason: "It should return the CA bundle from the secret.",
59+
args: args{
60+
ref: types.NamespacedName{Name: "my-secret", Namespace: "ns"},
61+
kube: &test.MockClient{
62+
MockGet: func(_ context.Context, _ client.ObjectKey, obj client.Object) error {
63+
s := &corev1.Secret{
64+
Data: map[string][]byte{"tls.crt": []byte("CABUNDLE")},
65+
}
66+
s.DeepCopyInto(obj.(*corev1.Secret))
67+
return nil
68+
},
69+
},
70+
},
71+
want: want{
72+
ca: []byte("CABUNDLE"),
73+
},
74+
},
75+
"GetError": {
76+
reason: "It should return an error if the secret cannot be retrieved.",
77+
args: args{
78+
ref: types.NamespacedName{Name: "my-secret", Namespace: "ns"},
79+
kube: &test.MockClient{MockGet: test.NewMockGetFn(errBoom)},
80+
},
81+
want: want{
82+
err: errors.Wrap(errBoom, errGetWebhookSecret),
83+
},
84+
},
85+
"EmptyCert": {
86+
reason: "It should return an error if the secret has no tls.crt data.",
87+
args: args{
88+
ref: types.NamespacedName{Name: "my-secret", Namespace: "ns"},
89+
kube: &test.MockClient{MockGet: test.NewMockGetFn(nil)},
90+
},
91+
want: want{
92+
err: errors.Errorf(errFmtNoTLSCrtInSecret, "ns/my-secret"),
93+
},
94+
},
95+
}
96+
for name, tc := range cases {
97+
t.Run(name, func(t *testing.T) {
98+
p := &SecretCAProvider{SecretRef: tc.args.ref}
99+
ca, err := p.GetCABundle(context.TODO(), tc.args.kube)
100+
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
101+
t.Errorf("\n%s\nGetCABundle(...): -want err, +got err:\n%s", tc.reason, diff)
102+
}
103+
if diff := cmp.Diff(tc.want.ca, ca); diff != "" {
104+
t.Errorf("\n%s\nGetCABundle(...): -want ca, +got ca:\n%s", tc.reason, diff)
105+
}
106+
})
107+
}
108+
}
109+
110+
func TestFileCAProvider(t *testing.T) {
111+
type want struct {
112+
ca []byte
113+
err error
114+
}
115+
116+
validDir := t.TempDir()
117+
validPath := validDir + "/ca.crt"
118+
_ = os.WriteFile(validPath, []byte("CABUNDLE"), 0o644)
119+
120+
emptyDir := t.TempDir()
121+
emptyPath := emptyDir + "/ca.crt"
122+
_ = os.WriteFile(emptyPath, []byte{}, 0o644)
123+
124+
cases := map[string]struct {
125+
reason string
126+
path string
127+
want want
128+
}{
129+
"Success": {
130+
reason: "It should return the CA bundle from the file.",
131+
path: validPath,
132+
want: want{
133+
ca: []byte("CABUNDLE"),
134+
},
135+
},
136+
"FileNotFound": {
137+
reason: "It should return an error if the file does not exist.",
138+
path: "/nonexistent/ca.crt",
139+
want: want{
140+
err: errors.Wrap(errors.New("open /nonexistent/ca.crt: no such file or directory"), errReadCABundleFile),
141+
},
142+
},
143+
"EmptyFile": {
144+
reason: "It should return an error if the file is empty.",
145+
path: emptyPath,
146+
want: want{
147+
err: errors.New(errEmptyCABundleFile),
148+
},
149+
},
150+
}
151+
for name, tc := range cases {
152+
t.Run(name, func(t *testing.T) {
153+
p := &FileCAProvider{Path: tc.path}
154+
ca, err := p.GetCABundle(context.TODO(), nil)
155+
if diff := cmp.Diff(tc.want.err, err, test.EquateErrors()); diff != "" {
156+
t.Errorf("\n%s\nGetCABundle(...): -want err, +got err:\n%s", tc.reason, diff)
157+
}
158+
if diff := cmp.Diff(tc.want.ca, ca); diff != "" {
159+
t.Errorf("\n%s\nGetCABundle(...): -want ca, +got ca:\n%s", tc.reason, diff)
160+
}
161+
})
162+
}
163+
}
164+
165+
func TestWebhookConfigurations(t *testing.T) {
166+
type args struct {
167+
kube client.Client
168+
caProvider WebhookCAProvider
169+
svc admv1.ServiceReference
170+
opts []WebhookConfigurationsOption
45171
}
46172

47173
type want struct {
@@ -71,14 +197,23 @@ func TestWebhookConfigurations(t *testing.T) {
71197
Port: &p,
72198
}
73199

200+
caDir := t.TempDir()
201+
caFilePath := caDir + "/ca.crt"
202+
_ = os.WriteFile(caFilePath, []byte("CABUNDLE"), 0o644)
203+
204+
emptyCADir := t.TempDir()
205+
emptyCAPath := emptyCADir + "/ca.crt"
206+
_ = os.WriteFile(emptyCAPath, []byte{}, 0o644)
207+
74208
cases := map[string]struct {
75209
reason string
76210
args
77211
want
78212
}{
79-
"Success": {
80-
reason: "If a proper webhook TLS is given, then webhook configurations should have the configs injected and operations should succeed",
213+
"SecretCAProviderSuccess": {
214+
reason: "If a proper webhook TLS secret is given, then webhook configurations should have the configs injected and operations should succeed",
81215
args: args{
216+
caProvider: &SecretCAProvider{SecretRef: types.NamespacedName{}},
82217
opts: []WebhookConfigurationsOption{
83218
WithWebhookConfigurationsFs(fs),
84219
},
@@ -113,9 +248,10 @@ func TestWebhookConfigurations(t *testing.T) {
113248
},
114249
},
115250
},
116-
"CertNotFound": {
251+
"SecretCAProviderCertNotFound": {
117252
reason: "If TLS Secret cannot be found, then it should not proceed",
118253
args: args{
254+
caProvider: &SecretCAProvider{SecretRef: types.NamespacedName{}},
119255
opts: []WebhookConfigurationsOption{
120256
WithWebhookConfigurationsFs(fs),
121257
},
@@ -127,9 +263,10 @@ func TestWebhookConfigurations(t *testing.T) {
127263
err: errors.Wrap(errBoom, errGetWebhookSecret),
128264
},
129265
},
130-
"CertKeyEmpty": {
266+
"SecretCAProviderCertKeyEmpty": {
131267
reason: "If the TLS Secret does not have a CA bundle, then it should not proceed",
132268
args: args{
269+
caProvider: &SecretCAProvider{SecretRef: types.NamespacedName{}},
133270
opts: []WebhookConfigurationsOption{
134271
WithWebhookConfigurationsFs(fs),
135272
},
@@ -141,9 +278,10 @@ func TestWebhookConfigurations(t *testing.T) {
141278
err: errors.Errorf(errFmtNoTLSCrtInSecret, "/"),
142279
},
143280
},
144-
"ApplyFailed": {
281+
"SecretCAProviderApplyFailed": {
145282
reason: "If it cannot apply webhook configurations, then it should not proceed",
146283
args: args{
284+
caProvider: &SecretCAProvider{SecretRef: types.NamespacedName{}},
147285
opts: []WebhookConfigurationsOption{
148286
WithWebhookConfigurationsFs(fs),
149287
},
@@ -165,6 +303,7 @@ func TestWebhookConfigurations(t *testing.T) {
165303
"NonWebhookType": {
166304
reason: "Only webhook configuration types can be processed",
167305
args: args{
306+
caProvider: &SecretCAProvider{SecretRef: types.NamespacedName{}},
168307
opts: []WebhookConfigurationsOption{
169308
WithWebhookConfigurationsFs(fsWithMixedTypes),
170309
},
@@ -183,13 +322,73 @@ func TestWebhookConfigurations(t *testing.T) {
183322
err: errors.Errorf("only MutatingWebhookConfiguration and ValidatingWebhookConfiguration kinds are accepted, got %s", "*v1.CustomResourceDefinition"),
184323
},
185324
},
325+
"FileCAProviderSuccess": {
326+
reason: "If a valid CA file is given via FileCAProvider, webhook configurations should be injected successfully",
327+
args: args{
328+
caProvider: &FileCAProvider{Path: caFilePath},
329+
opts: []WebhookConfigurationsOption{
330+
WithWebhookConfigurationsFs(fs),
331+
},
332+
svc: svc,
333+
kube: &test.MockClient{
334+
MockGet: func(_ context.Context, _ client.ObjectKey, _ client.Object) error {
335+
return kerrors.NewNotFound(schema.GroupResource{}, "")
336+
},
337+
MockCreate: func(_ context.Context, obj client.Object, _ ...client.CreateOption) error {
338+
switch c := obj.(type) {
339+
case *admv1.ValidatingWebhookConfiguration:
340+
for _, w := range c.Webhooks {
341+
if !bytes.Equal(w.ClientConfig.CABundle, []byte("CABUNDLE")) {
342+
t.Errorf("unexpected certificate bundle content: %s", string(w.ClientConfig.CABundle))
343+
}
344+
}
345+
case *admv1.MutatingWebhookConfiguration:
346+
for _, w := range c.Webhooks {
347+
if !bytes.Equal(w.ClientConfig.CABundle, []byte("CABUNDLE")) {
348+
t.Errorf("unexpected certificate bundle content: %s", string(w.ClientConfig.CABundle))
349+
}
350+
}
351+
default:
352+
t.Error("unexpected type")
353+
}
354+
return nil
355+
},
356+
},
357+
},
358+
},
359+
"FileCAProviderFileNotFound": {
360+
reason: "If the CA file does not exist, it should return an error",
361+
args: args{
362+
caProvider: &FileCAProvider{Path: "/nonexistent/ca.crt"},
363+
opts: []WebhookConfigurationsOption{
364+
WithWebhookConfigurationsFs(fs),
365+
},
366+
kube: &test.MockClient{},
367+
},
368+
want: want{
369+
err: errors.Wrap(errors.New("open /nonexistent/ca.crt: no such file or directory"), errReadCABundleFile),
370+
},
371+
},
372+
"FileCAProviderEmptyFile": {
373+
reason: "If the CA file is empty, it should return an error",
374+
args: args{
375+
caProvider: &FileCAProvider{Path: emptyCAPath},
376+
opts: []WebhookConfigurationsOption{
377+
WithWebhookConfigurationsFs(fs),
378+
},
379+
kube: &test.MockClient{},
380+
},
381+
want: want{
382+
err: errors.New(errEmptyCABundleFile),
383+
},
384+
},
186385
}
187386
for name, tc := range cases {
188387
t.Run(name, func(t *testing.T) {
189388
err := NewWebhookConfigurations(
190389
"/webhooks",
191390
sch,
192-
&SecretCAProvider{SecretRef: types.NamespacedName{}},
391+
tc.args.caProvider,
193392
tc.args.svc,
194393
tc.opts...).Run(context.TODO(), tc.kube)
195394
if diff := cmp.Diff(tc.err, err, test.EquateErrors()); diff != "" {

0 commit comments

Comments
 (0)