From 50c64f28bb39930119b65ff9a8c6af3a599da163 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bastian=20Echterh=C3=B6lter?= Date: Fri, 17 Apr 2026 18:15:08 +0200 Subject: [PATCH] test: add CRD roundtrip fuzz tests MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #584 Signed-off-by: Bastian Echterhölter On-behalf-of: @SAP --- Taskfile.yml | 7 +++ api/v1alpha1/roundtrip_fuzz_test.go | 90 +++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 api/v1alpha1/roundtrip_fuzz_test.go diff --git a/Taskfile.yml b/Taskfile.yml index ec0db931..e2c89365 100644 --- a/Taskfile.yml +++ b/Taskfile.yml @@ -132,6 +132,13 @@ tasks: build: cmds: - go build -o bin/manager main.go + fuzz: + desc: "Run fuzz tests with a configurable duration (default 30s per target)" + vars: + FUZZTIME: '{{.FUZZTIME | default "30s"}}' + cmds: + - go test ./api/v1alpha1/ -run=^$ -fuzz=FuzzPlatformMeshRoundTrip -fuzztime={{.FUZZTIME}} -count=1 + docker-build: cmds: - docker build . diff --git a/api/v1alpha1/roundtrip_fuzz_test.go b/api/v1alpha1/roundtrip_fuzz_test.go new file mode 100644 index 00000000..7513a44a --- /dev/null +++ b/api/v1alpha1/roundtrip_fuzz_test.go @@ -0,0 +1,90 @@ +package v1alpha1 + +import ( + "encoding/json" + "testing" + + "k8s.io/apimachinery/pkg/api/equality" +) + +func FuzzPlatformMeshRoundTrip(f *testing.F) { + f.Add([]byte(`{ + "spec": { + "exposure": { + "baseDomain": "example.com", + "port": 443, + "protocol": "https" + }, + "kcp": { + "providerConnections": [{ + "path": "root:orgs:default", + "secret": "provider-kubeconfig", + "external": false + }], + "extraWorkspaces": [{ + "path": "root:orgs:extra", + "type": {"name": "universal", "path": "root"} + }] + }, + "ocm": { + "repo": {"name": "platform-mesh"}, + "component": {"name": "platform-mesh"}, + "referencePath": [{"name": "ref1"}] + }, + "featureToggles": [{"name": "feature-a", "parameters": {"key": "val"}}] + } + }`)) + f.Add([]byte(`{ + "spec": { + "kcp": { + "providerConnections": [{ + "path": "root:orgs:prod", + "secret": "prod-secret", + "external": true, + "adminAuth": true + }], + "extraDefaultAPIBindings": [{ + "workspaceTypePath": "root:types", + "export": "my-export", + "path": "root:orgs" + }] + }, + "wait": { + "resourceTypes": [{ + "name": "my-deploy", + "namespace": "default" + }] + } + } + }`)) + f.Add([]byte(`{}`)) + + f.Fuzz(func(t *testing.T, data []byte) { + fuzzRoundTrip(t, data, &PlatformMesh{}, &PlatformMesh{}) + }) +} + +// fuzzRoundTrip unmarshals arbitrary JSON into obj, marshals it back, unmarshals +// into obj2, and checks semantic equality. We use equality.Semantic.DeepEqual from +// k8s.io/apimachinery which treats nil and empty slices/maps as equivalent — the +// standard Kubernetes comparison semantic for API objects. +func fuzzRoundTrip[T any](t *testing.T, data []byte, obj *T, obj2 *T) { + t.Helper() + + if err := json.Unmarshal(data, obj); err != nil { + return + } + + roundtripped, err := json.Marshal(obj) + if err != nil { + t.Fatalf("failed to marshal: %v", err) + } + + if err := json.Unmarshal(roundtripped, obj2); err != nil { + t.Fatalf("failed to unmarshal roundtripped data: %v", err) + } + + if !equality.Semantic.DeepEqual(obj, obj2) { + t.Errorf("roundtrip mismatch for %T", obj) + } +}