Skip to content

Commit 0abdf8f

Browse files
test: replace ArgoCD manifest regression test with mock-based resource validation
1 parent 36809b3 commit 0abdf8f

File tree

56 files changed

+192
-90111
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

56 files changed

+192
-90111
lines changed

Makefile

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -167,15 +167,9 @@ generate: $(CONTROLLER_GEN) #EXHELP Generate code containing DeepCopy, DeepCopyI
167167
$(CONTROLLER_GEN) --load-build-tags=$(GO_BUILD_TAGS) object:headerFile="hack/boilerplate.go.txt" paths="./..."
168168

169169
.PHONY: verify
170-
verify: k8s-pin kind-verify-versions fmt generate manifests crd-ref-docs generate-test-data #HELP Verify all generated code is up-to-date. Runs k8s-pin instead of just tidy.
170+
verify: k8s-pin kind-verify-versions fmt generate manifests crd-ref-docs #HELP Verify all generated code is up-to-date. Runs k8s-pin instead of just tidy.
171171
git diff --exit-code
172172

173-
# Renders registry+v1 bundles in test/convert
174-
# Used by CI in verify to catch regressions in the registry+v1 -> plain conversion code
175-
.PHONY: generate-test-data
176-
generate-test-data:
177-
go run test/convert/generate-manifests.go
178-
179173
.PHONY: fix-lint
180174
fix-lint: $(GOLANGCI_LINT) #EXHELP Fix lint issues
181175
$(GOLANGCI_LINT) run --fix --build-tags $(GO_BUILD_TAGS) $(GOLANGCI_LINT_ARGS)

internal/operator-controller/rukpak/render/render_test.go

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,13 @@ import (
66
"reflect"
77
"testing"
88

9+
"github.com/google/go-cmp/cmp"
10+
"github.com/google/go-cmp/cmp/cmpopts"
911
"github.com/stretchr/testify/require"
1012
appsv1 "k8s.io/api/apps/v1"
1113
corev1 "k8s.io/api/core/v1"
14+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
15+
"k8s.io/apimachinery/pkg/runtime/schema"
1216
"sigs.k8s.io/controller-runtime/pkg/client"
1317

1418
"github.com/operator-framework/api/pkg/operators/v1alpha1"
@@ -267,3 +271,190 @@ func Test_BundleValidatorCallsAllValidationFnsInOrder(t *testing.T) {
267271
require.NoError(t, val.Validate(nil))
268272
require.Equal(t, "hi", actual)
269273
}
274+
275+
// Test_Render_ValidatesOutputForAllInstallModes verifies that the BundleRenderer
276+
// generates the correct set of Kubernetes resources (client.Objects) for each supported
277+
// install mode: AllNamespaces, SingleNamespace, and OwnNamespace.
278+
//
279+
// For each mode, it checks that:
280+
// - All expected resources are returned.
281+
// - The full content of each resource matches the expected values
282+
// It validates that the rendered objects are correctly rendered.
283+
func Test_Render_ValidatesOutputForAllInstallModes(t *testing.T) {
284+
testCases := []struct {
285+
name string
286+
installNamespace string
287+
watchNamespace string
288+
installModes []v1alpha1.InstallMode
289+
expectedNS string
290+
}{
291+
{
292+
name: "AllNamespaces",
293+
installNamespace: "mock-system",
294+
watchNamespace: "",
295+
installModes: []v1alpha1.InstallMode{
296+
{Type: v1alpha1.InstallModeTypeAllNamespaces, Supported: true},
297+
},
298+
expectedNS: "mock-system",
299+
},
300+
{
301+
name: "SingleNamespace",
302+
installNamespace: "mock-system",
303+
watchNamespace: "mock-watch",
304+
installModes: []v1alpha1.InstallMode{
305+
{Type: v1alpha1.InstallModeTypeSingleNamespace, Supported: true},
306+
},
307+
expectedNS: "mock-watch",
308+
},
309+
{
310+
name: "OwnNamespace",
311+
installNamespace: "mock-system",
312+
watchNamespace: "mock-system",
313+
installModes: []v1alpha1.InstallMode{
314+
{Type: v1alpha1.InstallModeTypeOwnNamespace, Supported: true},
315+
},
316+
expectedNS: "mock-system",
317+
},
318+
}
319+
320+
for _, tc := range testCases {
321+
t.Run(tc.name, func(t *testing.T) {
322+
// Given the mock scenarios
323+
expectedObjects := []client.Object{
324+
fakeUnstructured("ClusterRole", "", "mock-clusterrole"),
325+
fakeUnstructured("ClusterRoleBinding", "", "mock-clusterrolebinding"),
326+
fakeUnstructured("Role", tc.expectedNS, "mock-role"),
327+
fakeUnstructured("RoleBinding", tc.expectedNS, "mock-rolebinding"),
328+
fakeUnstructured("ConfigMap", tc.expectedNS, "mock-config"),
329+
fakeUnstructured("Secret", tc.expectedNS, "mock-secret"),
330+
fakeUnstructured("Service", tc.expectedNS, "mock-service"),
331+
fakeUnstructured("Deployment", tc.expectedNS, "mock-deployment"),
332+
fakeUnstructured("ServiceAccount", tc.expectedNS, "mock-sa"),
333+
fakeUnstructured("NetworkPolicy", tc.expectedNS, "mock-netpol"),
334+
}
335+
336+
mockGen := render.ResourceGenerator(func(_ *bundle.RegistryV1, _ render.Options) ([]client.Object, error) {
337+
return expectedObjects, nil
338+
})
339+
340+
mockBundle := bundle.RegistryV1{
341+
CSV: v1alpha1.ClusterServiceVersion{
342+
Spec: v1alpha1.ClusterServiceVersionSpec{
343+
InstallModes: tc.installModes,
344+
},
345+
},
346+
}
347+
348+
// When we call the BundleRenderer with the mock bundle
349+
renderer := render.BundleRenderer{
350+
BundleValidator: render.BundleValidator{
351+
func(_ *bundle.RegistryV1) []error { return nil },
352+
},
353+
ResourceGenerators: []render.ResourceGenerator{mockGen},
354+
}
355+
356+
opts := []render.Option{
357+
render.WithTargetNamespaces(tc.watchNamespace),
358+
render.WithUniqueNameGenerator(render.DefaultUniqueNameGenerator),
359+
}
360+
361+
// Then we expect the rendered objects to match the expected objects
362+
objs, err := renderer.Render(mockBundle, tc.installNamespace, opts...)
363+
require.NoError(t, err)
364+
require.Len(t, objs, len(expectedObjects))
365+
366+
gotMap := make(map[string]client.Object)
367+
for _, obj := range objs {
368+
gotMap[objectKey(obj)] = obj
369+
}
370+
371+
for _, exp := range expectedObjects {
372+
key := objectKey(exp)
373+
got, exists := gotMap[key]
374+
require.True(t, exists, "missing expected object: %s", key)
375+
376+
expObj := exp.(*unstructured.Unstructured)
377+
gotObj := got.(*unstructured.Unstructured)
378+
379+
if diff := cmp.Diff(expObj.Object, gotObj.Object, cmpopts.EquateEmpty()); diff != "" {
380+
t.Errorf("object content mismatch for %s (-want +got):\n%s", key, diff)
381+
}
382+
}
383+
})
384+
}
385+
}
386+
387+
// fakeUnstructured creates a fake unstructured client.Object with the specified kind, namespace, and name
388+
// to allow us mocks resources to be rendered by the BundleRenderer.
389+
func fakeUnstructured(kind, namespace, name string) client.Object {
390+
obj := &unstructured.Unstructured{}
391+
obj.Object = make(map[string]interface{})
392+
393+
group := ""
394+
version := "v1"
395+
396+
switch kind {
397+
case "NetworkPolicy":
398+
err := unstructured.SetNestedField(obj.Object, map[string]interface{}{
399+
"podSelector": map[string]interface{}{
400+
"matchLabels": map[string]interface{}{"app": "my-app"},
401+
},
402+
"policyTypes": []interface{}{"Ingress"},
403+
}, "spec")
404+
if err != nil {
405+
panic(fmt.Sprintf("failed to set spec for NetworkPolicy: %v", err))
406+
}
407+
case "Service":
408+
_ = unstructured.SetNestedField(obj.Object, map[string]interface{}{
409+
"ports": []interface{}{
410+
map[string]interface{}{
411+
"port": int64(8080),
412+
"targetPort": "http",
413+
},
414+
},
415+
"selector": map[string]interface{}{
416+
"app": "mock-app",
417+
},
418+
}, "spec")
419+
case "Deployment":
420+
_ = unstructured.SetNestedField(obj.Object, map[string]interface{}{
421+
"replicas": int64(1),
422+
"selector": map[string]interface{}{
423+
"matchLabels": map[string]interface{}{"app": "mock-app"},
424+
},
425+
"template": map[string]interface{}{
426+
"metadata": map[string]interface{}{
427+
"labels": map[string]interface{}{"app": "mock-app"},
428+
},
429+
"spec": map[string]interface{}{
430+
"containers": []interface{}{
431+
map[string]interface{}{
432+
"name": "controller",
433+
"image": "mock-controller:latest",
434+
},
435+
},
436+
},
437+
},
438+
}, "spec")
439+
case "ConfigMap":
440+
_ = unstructured.SetNestedField(obj.Object, map[string]interface{}{
441+
"controller": "enabled",
442+
}, "data")
443+
}
444+
445+
obj.SetGroupVersionKind(schema.GroupVersionKind{
446+
Group: group,
447+
Version: version,
448+
Kind: kind,
449+
})
450+
obj.SetNamespace(namespace)
451+
obj.SetName(name)
452+
453+
return obj
454+
}
455+
456+
// objectKey returns a unique key for k8s resources
457+
func objectKey(obj client.Object) string {
458+
gvk := obj.GetObjectKind().GroupVersionKind()
459+
return fmt.Sprintf("%s/%s/%s", gvk.Kind, obj.GetNamespace(), obj.GetName())
460+
}

test/convert/README.md

Lines changed: 0 additions & 7 deletions
This file was deleted.

test/convert/expected-manifests/argocd-operator.v0.6.0/all-namespaces/00_clusterrole_argocd-operator-metrics-reader.yaml

Lines changed: 0 additions & 10 deletions
This file was deleted.

0 commit comments

Comments
 (0)