From 7577ab9af65b5a6d9365ce8f1c2d63afef389ed7 Mon Sep 17 00:00:00 2001 From: Pavel Okhlopkov Date: Wed, 11 Jun 2025 19:06:13 +0300 Subject: [PATCH 1/7] init Signed-off-by: Pavel Okhlopkov --- testing/framework/init.go | 87 +++++++++++++++++++++++++++ testing/framework/init_test.go | 106 +++++++++++++++++++++++++++++++++ 2 files changed, 193 insertions(+) create mode 100644 testing/framework/init.go create mode 100644 testing/framework/init_test.go diff --git a/testing/framework/init.go b/testing/framework/init.go new file mode 100644 index 00000000..5e0310c2 --- /dev/null +++ b/testing/framework/init.go @@ -0,0 +1,87 @@ +package framework + +import ( + "context" + "encoding/json" + "fmt" + "testing" + + "github.com/deckhouse/deckhouse/pkg/log" + objectpatch "github.com/deckhouse/module-sdk/internal/object-patch" + "github.com/deckhouse/module-sdk/pkg" + "github.com/deckhouse/module-sdk/pkg/jq" + "github.com/deckhouse/module-sdk/testing/mock" + "github.com/stretchr/testify/assert" + "gopkg.in/yaml.v3" +) + +type HookFramework struct { + t *testing.T + Config *pkg.HookConfig + ReconcileFunc pkg.ReconcileFunc + HookInput *pkg.HookInput +} + +func NewHookFramework(t *testing.T, config *pkg.HookConfig, f pkg.ReconcileFunc) *HookFramework { + return &HookFramework{ + t: t, + Config: config, + ReconcileFunc: f, + HookInput: &pkg.HookInput{ + Snapshots: mock.NewSnapshotsMock(t), + Values: mock.NewPatchableValuesCollectorMock(t), + ConfigValues: mock.NewPatchableValuesCollectorMock(t), + PatchCollector: mock.NewPatchCollectorMock(t), + MetricsCollector: mock.NewMetricsCollectorMock(t), + DC: mock.NewDependencyContainerMock(t), + Logger: log.NewNop(), + }, + } +} + +func (f *HookFramework) GetInput() *pkg.HookInput { + if f.HookInput == nil { + f.t.Fatal("HookInput is not initialized") + } + return f.HookInput +} + +type InputSnapshots map[string][]string + +func (f *HookFramework) PrepareHookSnapshots(config *pkg.HookConfig, inputSnapshots InputSnapshots) { + formattedSnapshots := make(objectpatch.Snapshots, len(inputSnapshots)) + for snapBindingName, snaps := range inputSnapshots { + var ( + err error + query *jq.Query + ) + + for _, v := range config.Kubernetes { + if v.Name == snapBindingName { + fmt.Println("Using JQ filter:", v.JqFilter) + query, err = jq.NewQuery(v.JqFilter) + assert.NoError(f.t, err, "Failed to create JQ query from filter: %s", v.JqFilter) + } + } + + for _, snap := range snaps { + var yml map[string]interface{} + + err := yaml.Unmarshal([]byte(snap), &yml) + assert.NoError(f.t, err, "Failed to unmarshal snapshot YAML: %s", snap) + + jsonSnap, err := json.Marshal(yml) + assert.NoError(f.t, err, "Failed to marshal snapshot to JSON: %s", snap) + + fmt.Println("JSON Snapshot:", string(jsonSnap)) + + res, err := query.FilterStringObject(context.TODO(), string(jsonSnap)) + assert.NoError(f.t, err, "Failed to filter snapshot with JQ query: %s", jsonSnap) + fmt.Println("JSON Snapshot:", string(res.String())) + + formattedSnapshots[snapBindingName] = append(formattedSnapshots[snapBindingName], objectpatch.Snapshot(res.String())) + } + } + + f.HookInput.Snapshots = formattedSnapshots +} diff --git a/testing/framework/init_test.go b/testing/framework/init_test.go new file mode 100644 index 00000000..e056f8d0 --- /dev/null +++ b/testing/framework/init_test.go @@ -0,0 +1,106 @@ +package framework_test + +import ( + "context" + "testing" + + "github.com/deckhouse/module-sdk/pkg" + "github.com/deckhouse/module-sdk/testing/framework" +) + +const node1YAML = ` +--- +apiVersion: v1 +kind: Node +metadata: + name: kube-worker-1 + labels: + node-role: "testrole1" +` +const node2YAML = ` +--- +apiVersion: v1 +kind: Node +metadata: + name: kube-worker-2 + labels: + node-role: "testrole1" +` + +func Test_PrepareHookInput(t *testing.T) { + config := &pkg.HookConfig{ + Metadata: pkg.HookMetadata{ + Name: "test-hook", + }, + Kubernetes: []pkg.KubernetesConfig{ + { + Name: "nodes", + APIVersion: "v1", + Kind: "Node", + JqFilter: ".metadata.name", + }, + }, + } + + f := framework.NewHookFramework(t, config, func(ctx context.Context, input *pkg.HookInput) error { + return nil + }) + + // Test with custom context and snapshots + f.PrepareHookSnapshots(config, framework.InputSnapshots{ + "nodes": { + node1YAML, + node2YAML, + }, + }) + + // Test snapshots are correctly set + if len(f.GetInput().Snapshots.Get("nodes")) != 2 { + t.Errorf("Expected 2 node snapshots, got %d", len(f.GetInput().Snapshots.Get("nodes"))) + } + + // Test with multiple binding types + multiConfig := &pkg.HookConfig{ + Metadata: pkg.HookMetadata{ + Name: "multi-test-hook", + }, + Kubernetes: []pkg.KubernetesConfig{ + { + Name: "nodes", + APIVersion: "v1", + Kind: "Node", + JqFilter: ".metadata.name", + }, + { + Name: "node_roles", + APIVersion: "v1", + Kind: "Node", + JqFilter: ".metadata.labels", + }, + }, + } + + f = framework.NewHookFramework(t, multiConfig, func(ctx context.Context, input *pkg.HookInput) error { + return nil + }) + + f.PrepareHookSnapshots(multiConfig, framework.InputSnapshots{ + "nodes": { + node1YAML, + }, + "node_roles": { + node2YAML, + }, + }) + + // Verify collectors are initialized + if f.GetInput().Values == nil || f.GetInput().ConfigValues == nil || + f.GetInput().PatchCollector == nil || f.GetInput().MetricsCollector == nil { + t.Error("One or more collectors were not initialized") + } + + // Verify logger is initialized + if f.GetInput().Logger == nil { + t.Error("Logger not initialized") + } +} From f73dde412aed6b1830376e7b1fdc597faadc83e6 Mon Sep 17 00:00:00 2001 From: Pavel Okhlopkov Date: Tue, 24 Jun 2025 15:07:49 +0300 Subject: [PATCH 2/7] add snapshots and patch helper Signed-off-by: Pavel Okhlopkov --- .../tls-certificate/internal_tls_test.go | 62 ++++++ testing/framework/init.go | 190 ++++++++++++++++++ 2 files changed, 252 insertions(+) diff --git a/common-hooks/tls-certificate/internal_tls_test.go b/common-hooks/tls-certificate/internal_tls_test.go index 007827b9..948fc42e 100644 --- a/common-hooks/tls-certificate/internal_tls_test.go +++ b/common-hooks/tls-certificate/internal_tls_test.go @@ -32,6 +32,7 @@ import ( "github.com/deckhouse/module-sdk/pkg" "github.com/deckhouse/module-sdk/pkg/certificate" "github.com/deckhouse/module-sdk/pkg/jq" + tframework "github.com/deckhouse/module-sdk/testing/framework" mock "github.com/deckhouse/module-sdk/testing/mock" ) @@ -430,3 +431,64 @@ func Test_GenSelfSignedTLS(t *testing.T) { assert.NoError(t, err) }) } + +func Test_GenSelfSignedTLS_NewFramework(t *testing.T) { + t.Run("outdated certificate in snapshot", func(t *testing.T) { + tlsConfig := tlscertificate.GenSelfSignedTLSHookConf{ + CN: "cert-name", + TLSSecretName: "secret-webhook-cert", + Namespace: "some-namespace", + BeforeHookCheck: func(_ *pkg.HookInput) bool { + return true + }, + SANs: tlscertificate.DefaultSANs([]string{ + "example-webhook", + "example-webhook.d8-example-module", + "example-webhook.d8-example-module.svc", + "%CLUSTER_DOMAIN%://example-webhook.d8-example-module.svc", + "%PUBLIC_DOMAIN%://example-webhook.d8-example-module.svc", + }), + FullValuesPathPrefix: "d8-example-module.internal.webhookCert", + } + + hookConfig := tlscertificate.GenSelfSignedTLSConfig(tlsConfig) + + f := tframework.NewHookFramework(t, hookConfig, tlscertificate.GenSelfSignedTLS(tlsConfig)) + + f.PrepareHookSnapshots(hookConfig, map[string][]string{ + tlscertificate.InternalTLSSnapshotKey: { + ` +apiVersion: v1 +data: + ca.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJpRENDQVM2Z0F3SUJBZ0lVV3VXcVhMQ1ZnWXVSaVNmZVZvT3RHMG9vU3pZd0NnWUlLb1pJemowRUF3SXcKSWpFZ01CNEdBMVVFQXhNWFpHVmphMmh2ZFhObExtUTRMWE41YzNSbGJTNXpkbU13SGhjTk1qVXdOakU0TVRnMApOREF3V2hjTk16VXdOakUyTVRnME5EQXdXakFpTVNBd0hnWURWUVFERXhka1pXTnJhRzkxYzJVdVpEZ3RjM2x6CmRHVnRMbk4yWXpCWk1CTUdCeXFHU000OUFnRUdDQ3FHU000OUF3RUhBMElBQkM0N3h1WCs2VkhvVVVpaG9VSUsKbzY1QzR2OVU5UjV5dXZLQUN3SlJ3bFoxUGs1MGR2aXFFNHJjbXRsdTRsZkRPSW9qaFlJN3ZUS1piMVByVTY3MgpTSHVqUWpCQU1BNEdBMVVkRHdFQi93UUVBd0lCQmpBUEJnTlZIUk1CQWY4RUJUQURBUUgvTUIwR0ExVWREZ1FXCkJCVDF1U3JvYjNJeHpaNlJOc042dEFjTGlyUGt3REFLQmdncWhrak9QUVFEQWdOSUFEQkZBaUJ6YTVSS3p0RDYKRmJuT2NOTm5ncjhQazhrME4vcGtzTGNiemZXd3NCN0lVQUloQU5tMjNMSzczNVJ0c3F4TGhGNmtyTCtlZmJicgpBbU9jSmpWdGwvNWc5aEhhCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUIwakNDQVhpZ0F3SUJBZ0lVVk5uZTJHaE1vV2k2RUdSVlh3bW1Kak1OdU40d0NnWUlLb1pJemowRUF3SXcKSWpFZ01CNEdBMVVFQXhNWFpHVmphMmh2ZFhObExtUTRMWE41YzNSbGJTNXpkbU13SGhjTk1qVXdOakU0TVRnMApOREF3V2hjTk16VXdOakUyTVRnME5EQXdXakFpTVNBd0hnWURWUVFERXhka1pXTnJhRzkxYzJVdVpEZ3RjM2x6CmRHVnRMbk4yWXpCWk1CTUdCeXFHU000OUFnRUdDQ3FHU000OUF3RUhBMElBQlBSdWduRk1yMlFZM0lKSFhvNlAKSEN3ZnYxRVJyS0dQdXZMYVovNHI1QWNmUkhJT3AzYUNvR1pwT0JRbFRUejBSaTE3VDRVeStHdmZxRWg0MHVCNQowYldqZ1lzd2dZZ3dEZ1lEVlIwUEFRSC9CQVFEQWdXZ01Bd0dBMVVkRXdFQi93UUNNQUF3SFFZRFZSME9CQllFCkZPMmpYOXE5MDc0WHdkNU90RFVhOE9vaXJiNEtNRWtHQTFVZEVRUkNNRUNDRjJSbFkydG9iM1Z6WlM1a09DMXoKZVhOMFpXMHVjM1pqZ2lWa1pXTnJhRzkxYzJVdVpEZ3RjM2x6ZEdWdExuTjJZeTVqYkhWemRHVnlMbXh2WTJGcwpNQW9HQ0NxR1NNNDlCQU1DQTBnQU1FVUNJUUN6dEJGbEY2eEpueHYyS3hrNHNqam5mQjQ1YjRmdjNsTFJYVkp6CmZmL2lsZ0lnTXQvM3pHSXRqVndlV3B1eDdyZnN0RkxxalhtZmkwRk4xL3ZwWGtOTEljZz0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= + tls.key: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSUdIUUdieWlDdlV2WDdiUUhBbmZ2YkExbVdBLy9ESjlUdC83WW94akMvZ2dvQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFOUc2Q2NVeXZaQmpjZ2tkZWpvOGNMQisvVVJHc29ZKzY4dHBuL2l2a0J4OUVjZzZuZG9LZwpabWs0RkNWTlBQUkdMWHRQaFRMNGE5K29TSGpTNEhuUnRRPT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQo= +kind: Secret +metadata: + creationTimestamp: "2025-06-18T18:48:49Z" + labels: + app: deckhouse + heritage: deckhouse + module: deckhouse + name: admission-webhook-certs + namespace: d8-system +`, + }, + }) + + values := mock.NewPatchableValuesCollectorMock(t) + + values.GetMock.When("global.discovery.clusterDomain").Then(gjson.Result{Type: gjson.String, Str: "cluster.local"}) + values.GetMock.When("global.modules.publicDomainTemplate").Then(gjson.Result{Type: gjson.String, Str: "%s.127.0.0.1.sslip.io"}) + + values.SetMock.Set(func(path string, value any) { + assert.Equal(t, "d8-example-module.internal.webhookCert", path) + assert.NotEmpty(t, value) + }) + + f.HookInput.Values = values + + err := f.Execute(context.TODO()) + assert.NoError(t, err) + }) +} diff --git a/testing/framework/init.go b/testing/framework/init.go index 5e0310c2..1d56b082 100644 --- a/testing/framework/init.go +++ b/testing/framework/init.go @@ -85,3 +85,193 @@ func (f *HookFramework) PrepareHookSnapshots(config *pkg.HookConfig, inputSnapsh f.HookInput.Snapshots = formattedSnapshots } + +func (f *HookFramework) PreparePatchCollector(patches ...any) { + pc := mock.NewPatchCollectorMock(f.t) + + for _, patch := range patches { + switch p := patch.(type) { + case Create: + pc.CreateMock.Set(func(obj any) { + assert.Equal(f.t, p.Object, obj, "Create object mismatch") + }) + case CreateIfNotExists: + pc.CreateIfNotExistsMock.Set(func(obj any) { + assert.Equal(f.t, p.Object, obj, "CreateIfNotExists object mismatch") + }) + case CreateOrUpdate: + pc.CreateOrUpdateMock.Set(func(obj any) { + assert.Equal(f.t, p.Object, obj, "CreateOrUpdate object mismatch") + }) + case Delete: + pc.DeleteMock.Set(func(apiVersion, kind, namespace, name string) { + assert.Equal(f.t, p.ApiVersion, apiVersion, "API version mismatch") + assert.Equal(f.t, p.Kind, kind, "Kind mismatch") + assert.Equal(f.t, p.Namespace, namespace, "Namespace mismatch") + assert.Equal(f.t, p.Name, name, "Name mismatch") + }) + case DeleteInBackground: + pc.DeleteInBackgroundMock.Set(func(apiVersion, kind, namespace, name string) { + assert.Equal(f.t, p.ApiVersion, apiVersion, "API version mismatch") + assert.Equal(f.t, p.Kind, kind, "Kind mismatch") + assert.Equal(f.t, p.Namespace, namespace, "Namespace mismatch") + assert.Equal(f.t, p.Name, name, "Name mismatch") + }) + case DeleteNonCascading: + pc.DeleteNonCascadingMock.Set(func(apiVersion, kind, namespace, name string) { + assert.Equal(f.t, p.ApiVersion, apiVersion, "API version mismatch") + assert.Equal(f.t, p.Kind, kind, "Kind mismatch") + assert.Equal(f.t, p.Namespace, namespace, "Namespace mismatch") + assert.Equal(f.t, p.Name, name, "Name mismatch") + }) + case JSONPatch: + pc.JSONPatchMock.Set(func(jsonPatch any, apiVersion, kind, namespace, name string, opts ...pkg.PatchCollectorOption) { + assert.Equal(f.t, p.JsonPatch, jsonPatch, "JSON patch mismatch") + assert.Equal(f.t, p.ApiVersion, apiVersion, "API version mismatch") + assert.Equal(f.t, p.Kind, kind, "Kind mismatch") + assert.Equal(f.t, p.Namespace, namespace, "Namespace mismatch") + assert.Equal(f.t, p.Name, name, "Name mismatch") + }) + case MergePatch: + pc.MergePatchMock.Set(func(mergePatch any, apiVersion, kind, namespace, name string, opts ...pkg.PatchCollectorOption) { + assert.Equal(f.t, p.MergePatch, mergePatch, "Merge patch mismatch") + assert.Equal(f.t, p.ApiVersion, apiVersion, "API version mismatch") + assert.Equal(f.t, p.Kind, kind, "Kind mismatch") + assert.Equal(f.t, p.Namespace, namespace, "Namespace mismatch") + assert.Equal(f.t, p.Name, name, "Name mismatch") + }) + case JQFilter: + pc.JQFilterMock.Set(func(jqFilter string, apiVersion, kind, namespace, name string, opts ...pkg.PatchCollectorOption) { + assert.Equal(f.t, p.JQFilter, jqFilter, "JQ filter mismatch") + assert.Equal(f.t, p.ApiVersion, apiVersion, "API version mismatch") + assert.Equal(f.t, p.Kind, kind, "Kind mismatch") + assert.Equal(f.t, p.Namespace, namespace, "Namespace mismatch") + assert.Equal(f.t, p.Name, name, "Name mismatch") + }) + case PatchWithJQ: + pc.PatchWithJQMock.Set(func(jqfilter, apiVersion, kind, namespace, name string, opts ...pkg.PatchCollectorOption) { + assert.Equal(f.t, p.JQFilter, jqfilter, "JQ filter mismatch") + assert.Equal(f.t, p.ApiVersion, apiVersion, "API version mismatch") + assert.Equal(f.t, p.Kind, kind, "Kind mismatch") + assert.Equal(f.t, p.Namespace, namespace, "Namespace mismatch") + assert.Equal(f.t, p.Name, name, "Name mismatch") + }) + case PatchWithJSON: + pc.PatchWithJSONMock.Set(func(jsonPatch any, apiVersion, kind, namespace, name string, opts ...pkg.PatchCollectorOption) { + assert.Equal(f.t, p.JsonPatch, jsonPatch, "JSON patch mismatch") + assert.Equal(f.t, p.ApiVersion, apiVersion, "API version mismatch") + assert.Equal(f.t, p.Kind, kind, "Kind mismatch") + assert.Equal(f.t, p.Namespace, namespace, "Namespace mismatch") + assert.Equal(f.t, p.Name, name, "Name mismatch") + }) + case PatchWithMerge: + pc.PatchWithMergeMock.Set(func(mergePatch any, apiVersion, kind, namespace, name string, opts ...pkg.PatchCollectorOption) { + assert.Equal(f.t, p.MergePatch, mergePatch, "Merge patch mismatch") + assert.Equal(f.t, p.ApiVersion, apiVersion, "API version mismatch") + assert.Equal(f.t, p.Kind, kind, "Kind mismatch") + assert.Equal(f.t, p.Namespace, namespace, "Namespace mismatch") + assert.Equal(f.t, p.Name, name, "Name mismatch") + }) + default: + f.t.Fatalf("Unsupported patch type: %T", p) + } + } + + f.HookInput.PatchCollector = pc +} + +// Patch type definitions for different patching operations +type Create struct { + Object any +} + +type CreateIfNotExists struct { + Object any +} + +type CreateOrUpdate struct { + Object any +} + +type Delete struct { + ApiVersion string + Kind string + Namespace string + Name string +} + +type DeleteInBackground struct { + ApiVersion string + Kind string + Namespace string + Name string +} + +type DeleteNonCascading struct { + ApiVersion string + Kind string + Namespace string + Name string +} + +type JSONPatch struct { + JsonPatch any + ApiVersion string + Kind string + Namespace string + Name string + Options []pkg.PatchCollectorOption +} + +type MergePatch struct { + MergePatch any + ApiVersion string + Kind string + Namespace string + Name string + Options []pkg.PatchCollectorOption +} + +type JQFilter struct { + JQFilter string + ApiVersion string + Kind string + Namespace string + Name string + Options []pkg.PatchCollectorOption +} + +type PatchWithJQ struct { + JQFilter string + ApiVersion string + Kind string + Namespace string + Name string +} + +type PatchWithJSON struct { + JsonPatch any + ApiVersion string + Kind string + Namespace string + Name string + Options []pkg.PatchCollectorOption +} + +type PatchWithMerge struct { + MergePatch any + ApiVersion string + Kind string + Namespace string + Name string + Options []pkg.PatchCollectorOption +} + +func (f *HookFramework) Execute(ctx context.Context) error { + err := f.ReconcileFunc(ctx, f.HookInput) + if err != nil { + return fmt.Errorf("execute: %w", err) + } + + return nil +} From 455401b50ac95cc536fe4cfbfd1e532c98f76090 Mon Sep 17 00:00:00 2001 From: Pavel Okhlopkov Date: Tue, 24 Jun 2025 15:12:10 +0300 Subject: [PATCH 3/7] add helpers Signed-off-by: Pavel Okhlopkov --- testing/helpers/patch_collector.go | 190 +++++++++++++++++++++++++++++ testing/helpers/snapshots.go | 53 ++++++++ 2 files changed, 243 insertions(+) create mode 100644 testing/helpers/patch_collector.go create mode 100644 testing/helpers/snapshots.go diff --git a/testing/helpers/patch_collector.go b/testing/helpers/patch_collector.go new file mode 100644 index 00000000..b3f966f5 --- /dev/null +++ b/testing/helpers/patch_collector.go @@ -0,0 +1,190 @@ +package helpers + +import ( + "testing" + + "github.com/deckhouse/module-sdk/pkg" + "github.com/deckhouse/module-sdk/testing/mock" + "github.com/stretchr/testify/assert" +) + +func PreparePatchCollector(t *testing.T, patches ...any) pkg.OutputPatchCollector { + pc := mock.NewPatchCollectorMock(t) + + for _, patch := range patches { + switch p := patch.(type) { + case Create: + pc.CreateMock.Set(func(obj any) { + assert.Equal(t, p.Object, obj, "Create object mismatch") + }) + case CreateIfNotExists: + pc.CreateIfNotExistsMock.Set(func(obj any) { + assert.Equal(t, p.Object, obj, "CreateIfNotExists object mismatch") + }) + case CreateOrUpdate: + pc.CreateOrUpdateMock.Set(func(obj any) { + assert.Equal(t, p.Object, obj, "CreateOrUpdate object mismatch") + }) + case Delete: + pc.DeleteMock.Set(func(apiVersion, kind, namespace, name string) { + assert.Equal(t, p.ApiVersion, apiVersion, "API version mismatch") + assert.Equal(t, p.Kind, kind, "Kind mismatch") + assert.Equal(t, p.Namespace, namespace, "Namespace mismatch") + assert.Equal(t, p.Name, name, "Name mismatch") + }) + case DeleteInBackground: + pc.DeleteInBackgroundMock.Set(func(apiVersion, kind, namespace, name string) { + assert.Equal(t, p.ApiVersion, apiVersion, "API version mismatch") + assert.Equal(t, p.Kind, kind, "Kind mismatch") + assert.Equal(t, p.Namespace, namespace, "Namespace mismatch") + assert.Equal(t, p.Name, name, "Name mismatch") + }) + case DeleteNonCascading: + pc.DeleteNonCascadingMock.Set(func(apiVersion, kind, namespace, name string) { + assert.Equal(t, p.ApiVersion, apiVersion, "API version mismatch") + assert.Equal(t, p.Kind, kind, "Kind mismatch") + assert.Equal(t, p.Namespace, namespace, "Namespace mismatch") + assert.Equal(t, p.Name, name, "Name mismatch") + }) + case JSONPatch: + pc.JSONPatchMock.Set(func(jsonPatch any, apiVersion, kind, namespace, name string, opts ...pkg.PatchCollectorOption) { + assert.Equal(t, p.JsonPatch, jsonPatch, "JSON patch mismatch") + assert.Equal(t, p.ApiVersion, apiVersion, "API version mismatch") + assert.Equal(t, p.Kind, kind, "Kind mismatch") + assert.Equal(t, p.Namespace, namespace, "Namespace mismatch") + assert.Equal(t, p.Name, name, "Name mismatch") + }) + case MergePatch: + pc.MergePatchMock.Set(func(mergePatch any, apiVersion, kind, namespace, name string, opts ...pkg.PatchCollectorOption) { + assert.Equal(t, p.MergePatch, mergePatch, "Merge patch mismatch") + assert.Equal(t, p.ApiVersion, apiVersion, "API version mismatch") + assert.Equal(t, p.Kind, kind, "Kind mismatch") + assert.Equal(t, p.Namespace, namespace, "Namespace mismatch") + assert.Equal(t, p.Name, name, "Name mismatch") + }) + case JQFilter: + pc.JQFilterMock.Set(func(jqFilter string, apiVersion, kind, namespace, name string, opts ...pkg.PatchCollectorOption) { + assert.Equal(t, p.JQFilter, jqFilter, "JQ filter mismatch") + assert.Equal(t, p.ApiVersion, apiVersion, "API version mismatch") + assert.Equal(t, p.Kind, kind, "Kind mismatch") + assert.Equal(t, p.Namespace, namespace, "Namespace mismatch") + assert.Equal(t, p.Name, name, "Name mismatch") + }) + case PatchWithJQ: + pc.PatchWithJQMock.Set(func(jqfilter, apiVersion, kind, namespace, name string, opts ...pkg.PatchCollectorOption) { + assert.Equal(t, p.JQFilter, jqfilter, "JQ filter mismatch") + assert.Equal(t, p.ApiVersion, apiVersion, "API version mismatch") + assert.Equal(t, p.Kind, kind, "Kind mismatch") + assert.Equal(t, p.Namespace, namespace, "Namespace mismatch") + assert.Equal(t, p.Name, name, "Name mismatch") + }) + case PatchWithJSON: + pc.PatchWithJSONMock.Set(func(jsonPatch any, apiVersion, kind, namespace, name string, opts ...pkg.PatchCollectorOption) { + assert.Equal(t, p.JsonPatch, jsonPatch, "JSON patch mismatch") + assert.Equal(t, p.ApiVersion, apiVersion, "API version mismatch") + assert.Equal(t, p.Kind, kind, "Kind mismatch") + assert.Equal(t, p.Namespace, namespace, "Namespace mismatch") + assert.Equal(t, p.Name, name, "Name mismatch") + }) + case PatchWithMerge: + pc.PatchWithMergeMock.Set(func(mergePatch any, apiVersion, kind, namespace, name string, opts ...pkg.PatchCollectorOption) { + assert.Equal(t, p.MergePatch, mergePatch, "Merge patch mismatch") + assert.Equal(t, p.ApiVersion, apiVersion, "API version mismatch") + assert.Equal(t, p.Kind, kind, "Kind mismatch") + assert.Equal(t, p.Namespace, namespace, "Namespace mismatch") + assert.Equal(t, p.Name, name, "Name mismatch") + }) + default: + t.Fatalf("Unsupported patch type: %T", p) + } + } + + return pc +} + +// Patch type definitions for different patching operations +type Create struct { + Object any +} + +type CreateIfNotExists struct { + Object any +} + +type CreateOrUpdate struct { + Object any +} + +type Delete struct { + ApiVersion string + Kind string + Namespace string + Name string +} + +type DeleteInBackground struct { + ApiVersion string + Kind string + Namespace string + Name string +} + +type DeleteNonCascading struct { + ApiVersion string + Kind string + Namespace string + Name string +} + +type JSONPatch struct { + JsonPatch any + ApiVersion string + Kind string + Namespace string + Name string + Options []pkg.PatchCollectorOption +} + +type MergePatch struct { + MergePatch any + ApiVersion string + Kind string + Namespace string + Name string + Options []pkg.PatchCollectorOption +} + +type JQFilter struct { + JQFilter string + ApiVersion string + Kind string + Namespace string + Name string + Options []pkg.PatchCollectorOption +} + +type PatchWithJQ struct { + JQFilter string + ApiVersion string + Kind string + Namespace string + Name string +} + +type PatchWithJSON struct { + JsonPatch any + ApiVersion string + Kind string + Namespace string + Name string + Options []pkg.PatchCollectorOption +} + +type PatchWithMerge struct { + MergePatch any + ApiVersion string + Kind string + Namespace string + Name string + Options []pkg.PatchCollectorOption +} diff --git a/testing/helpers/snapshots.go b/testing/helpers/snapshots.go new file mode 100644 index 00000000..447c0631 --- /dev/null +++ b/testing/helpers/snapshots.go @@ -0,0 +1,53 @@ +package helpers + +import ( + "context" + "encoding/json" + "fmt" + "testing" + + objectpatch "github.com/deckhouse/module-sdk/internal/object-patch" + "github.com/deckhouse/module-sdk/pkg" + "github.com/deckhouse/module-sdk/pkg/jq" + "github.com/stretchr/testify/assert" + "gopkg.in/yaml.v3" +) + +// input snapshots must be in yaml format +func PrepareHookSnapshots(t *testing.T, config *pkg.HookConfig, inputSnapshots map[string][]string) pkg.Snapshots { + formattedSnapshots := make(objectpatch.Snapshots, len(inputSnapshots)) + for snapBindingName, snaps := range inputSnapshots { + var ( + err error + query *jq.Query + ) + + for _, v := range config.Kubernetes { + if v.Name == snapBindingName { + fmt.Println("Using JQ filter:", v.JqFilter) + query, err = jq.NewQuery(v.JqFilter) + assert.NoError(t, err, "Failed to create JQ query from filter: %s", v.JqFilter) + } + } + + for _, snap := range snaps { + var yml map[string]interface{} + + err := yaml.Unmarshal([]byte(snap), &yml) + assert.NoError(t, err, "Failed to unmarshal snapshot YAML: %s", snap) + + jsonSnap, err := json.Marshal(yml) + assert.NoError(t, err, "Failed to marshal snapshot to JSON: %s", snap) + + fmt.Println("JSON Snapshot:", string(jsonSnap)) + + res, err := query.FilterStringObject(context.TODO(), string(jsonSnap)) + assert.NoError(t, err, "Failed to filter snapshot with JQ query: %s", jsonSnap) + fmt.Println("JSON Snapshot:", string(res.String())) + + formattedSnapshots[snapBindingName] = append(formattedSnapshots[snapBindingName], objectpatch.Snapshot(res.String())) + } + } + + return formattedSnapshots +} From 0ba39d64fe9fdb55e5bdf43bae646bcc7ad7ed7a Mon Sep 17 00:00:00 2001 From: Pavel Okhlopkov Date: Tue, 24 Jun 2025 15:26:51 +0300 Subject: [PATCH 4/7] bump Signed-off-by: Pavel Okhlopkov --- .../tls-certificate/internal_tls_test.go | 45 +++++++++++++++---- testing/helpers/hook_input.go | 21 +++++++++ testing/helpers/snapshots.go | 7 ++- 3 files changed, 62 insertions(+), 11 deletions(-) create mode 100644 testing/helpers/hook_input.go diff --git a/common-hooks/tls-certificate/internal_tls_test.go b/common-hooks/tls-certificate/internal_tls_test.go index 948fc42e..b40cdfb4 100644 --- a/common-hooks/tls-certificate/internal_tls_test.go +++ b/common-hooks/tls-certificate/internal_tls_test.go @@ -32,7 +32,7 @@ import ( "github.com/deckhouse/module-sdk/pkg" "github.com/deckhouse/module-sdk/pkg/certificate" "github.com/deckhouse/module-sdk/pkg/jq" - tframework "github.com/deckhouse/module-sdk/testing/framework" + "github.com/deckhouse/module-sdk/testing/helpers" mock "github.com/deckhouse/module-sdk/testing/mock" ) @@ -453,11 +453,23 @@ func Test_GenSelfSignedTLS_NewFramework(t *testing.T) { hookConfig := tlscertificate.GenSelfSignedTLSConfig(tlsConfig) - f := tframework.NewHookFramework(t, hookConfig, tlscertificate.GenSelfSignedTLS(tlsConfig)) - - f.PrepareHookSnapshots(hookConfig, map[string][]string{ - tlscertificate.InternalTLSSnapshotKey: { - ` + snaps := helpers.PrepareHookSnapshots(t, hookConfig, map[string]string{ + tlscertificate.InternalTLSSnapshotKey: ` +apiVersion: v1 +data: + ca.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJpRENDQVM2Z0F3SUJBZ0lVV3VXcVhMQ1ZnWXVSaVNmZVZvT3RHMG9vU3pZd0NnWUlLb1pJemowRUF3SXcKSWpFZ01CNEdBMVVFQXhNWFpHVmphMmh2ZFhObExtUTRMWE41YzNSbGJTNXpkbU13SGhjTk1qVXdOakU0TVRnMApOREF3V2hjTk16VXdOakUyTVRnME5EQXdXakFpTVNBd0hnWURWUVFERXhka1pXTnJhRzkxYzJVdVpEZ3RjM2x6CmRHVnRMbk4yWXpCWk1CTUdCeXFHU000OUFnRUdDQ3FHU000OUF3RUhBMElBQkM0N3h1WCs2VkhvVVVpaG9VSUsKbzY1QzR2OVU5UjV5dXZLQUN3SlJ3bFoxUGs1MGR2aXFFNHJjbXRsdTRsZkRPSW9qaFlJN3ZUS1piMVByVTY3MgpTSHVqUWpCQU1BNEdBMVVkRHdFQi93UUVBd0lCQmpBUEJnTlZIUk1CQWY4RUJUQURBUUgvTUIwR0ExVWREZ1FXCkJCVDF1U3JvYjNJeHpaNlJOc042dEFjTGlyUGt3REFLQmdncWhrak9QUVFEQWdOSUFEQkZBaUJ6YTVSS3p0RDYKRmJuT2NOTm5ncjhQazhrME4vcGtzTGNiemZXd3NCN0lVQUloQU5tMjNMSzczNVJ0c3F4TGhGNmtyTCtlZmJicgpBbU9jSmpWdGwvNWc5aEhhCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K + tls.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUIwakNDQVhpZ0F3SUJBZ0lVVk5uZTJHaE1vV2k2RUdSVlh3bW1Kak1OdU40d0NnWUlLb1pJemowRUF3SXcKSWpFZ01CNEdBMVVFQXhNWFpHVmphMmh2ZFhObExtUTRMWE41YzNSbGJTNXpkbU13SGhjTk1qVXdOakU0TVRnMApOREF3V2hjTk16VXdOakUyTVRnME5EQXdXakFpTVNBd0hnWURWUVFERXhka1pXTnJhRzkxYzJVdVpEZ3RjM2x6CmRHVnRMbk4yWXpCWk1CTUdCeXFHU000OUFnRUdDQ3FHU000OUF3RUhBMElBQlBSdWduRk1yMlFZM0lKSFhvNlAKSEN3ZnYxRVJyS0dQdXZMYVovNHI1QWNmUkhJT3AzYUNvR1pwT0JRbFRUejBSaTE3VDRVeStHdmZxRWg0MHVCNQowYldqZ1lzd2dZZ3dEZ1lEVlIwUEFRSC9CQVFEQWdXZ01Bd0dBMVVkRXdFQi93UUNNQUF3SFFZRFZSME9CQllFCkZPMmpYOXE5MDc0WHdkNU90RFVhOE9vaXJiNEtNRWtHQTFVZEVRUkNNRUNDRjJSbFkydG9iM1Z6WlM1a09DMXoKZVhOMFpXMHVjM1pqZ2lWa1pXTnJhRzkxYzJVdVpEZ3RjM2x6ZEdWdExuTjJZeTVqYkhWemRHVnlMbXh2WTJGcwpNQW9HQ0NxR1NNNDlCQU1DQTBnQU1FVUNJUUN6dEJGbEY2eEpueHYyS3hrNHNqam5mQjQ1YjRmdjNsTFJYVkp6CmZmL2lsZ0lnTXQvM3pHSXRqVndlV3B1eDdyZnN0RkxxalhtZmkwRk4xL3ZwWGtOTEljZz0KLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo= + tls.key: LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSUdIUUdieWlDdlV2WDdiUUhBbmZ2YkExbVdBLy9ESjlUdC83WW94akMvZ2dvQW9HQ0NxR1NNNDkKQXdFSG9VUURRZ0FFOUc2Q2NVeXZaQmpjZ2tkZWpvOGNMQisvVVJHc29ZKzY4dHBuL2l2a0J4OUVjZzZuZG9LZwpabWs0RkNWTlBQUkdMWHRQaFRMNGE5K29TSGpTNEhuUnRRPT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQo= +kind: Secret +metadata: + creationTimestamp: "2025-06-18T18:48:49Z" + labels: + app: deckhouse + heritage: deckhouse + module: deckhouse + name: admission-webhook-certs + namespace: d8-system +--- apiVersion: v1 data: ca.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJpRENDQVM2Z0F3SUJBZ0lVV3VXcVhMQ1ZnWXVSaVNmZVZvT3RHMG9vU3pZd0NnWUlLb1pJemowRUF3SXcKSWpFZ01CNEdBMVVFQXhNWFpHVmphMmh2ZFhObExtUTRMWE41YzNSbGJTNXpkbU13SGhjTk1qVXdOakU0TVRnMApOREF3V2hjTk16VXdOakUyTVRnME5EQXdXakFpTVNBd0hnWURWUVFERXhka1pXTnJhRzkxYzJVdVpEZ3RjM2x6CmRHVnRMbk4yWXpCWk1CTUdCeXFHU000OUFnRUdDQ3FHU000OUF3RUhBMElBQkM0N3h1WCs2VkhvVVVpaG9VSUsKbzY1QzR2OVU5UjV5dXZLQUN3SlJ3bFoxUGs1MGR2aXFFNHJjbXRsdTRsZkRPSW9qaFlJN3ZUS1piMVByVTY3MgpTSHVqUWpCQU1BNEdBMVVkRHdFQi93UUVBd0lCQmpBUEJnTlZIUk1CQWY4RUJUQURBUUgvTUIwR0ExVWREZ1FXCkJCVDF1U3JvYjNJeHpaNlJOc042dEFjTGlyUGt3REFLQmdncWhrak9QUVFEQWdOSUFEQkZBaUJ6YTVSS3p0RDYKRmJuT2NOTm5ncjhQazhrME4vcGtzTGNiemZXd3NCN0lVQUloQU5tMjNMSzczNVJ0c3F4TGhGNmtyTCtlZmJicgpBbU9jSmpWdGwvNWc5aEhhCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K @@ -473,7 +485,6 @@ metadata: name: admission-webhook-certs namespace: d8-system `, - }, }) values := mock.NewPatchableValuesCollectorMock(t) @@ -486,9 +497,25 @@ metadata: assert.NotEmpty(t, value) }) - f.HookInput.Values = values + input := helpers.NewHookInput(t) + input.Snapshots = snaps + input.Values = values - err := f.Execute(context.TODO()) + config := tlscertificate.GenSelfSignedTLSHookConf{ + CN: "cert-name", + TLSSecretName: "secret-webhook-cert", + Namespace: "some-namespace", + SANs: tlscertificate.DefaultSANs([]string{ + "example-webhook", + "example-webhook.d8-example-module", + "example-webhook.d8-example-module.svc", + "%CLUSTER_DOMAIN%://example-webhook.d8-example-module.svc", + "%PUBLIC_DOMAIN%://example-webhook.d8-example-module.svc", + }), + FullValuesPathPrefix: "d8-example-module.internal.webhookCert", + } + + err := tlscertificate.GenSelfSignedTLS(config)(context.Background(), input) assert.NoError(t, err) }) } diff --git a/testing/helpers/hook_input.go b/testing/helpers/hook_input.go new file mode 100644 index 00000000..a076bd77 --- /dev/null +++ b/testing/helpers/hook_input.go @@ -0,0 +1,21 @@ +package helpers + +import ( + "testing" + + "github.com/deckhouse/deckhouse/pkg/log" + "github.com/deckhouse/module-sdk/pkg" + "github.com/deckhouse/module-sdk/testing/mock" +) + +func NewHookInput(t *testing.T) *pkg.HookInput { + return &pkg.HookInput{ + Snapshots: mock.NewSnapshotsMock(t), + Values: mock.NewPatchableValuesCollectorMock(t), + ConfigValues: mock.NewPatchableValuesCollectorMock(t), + PatchCollector: mock.NewPatchCollectorMock(t), + MetricsCollector: mock.NewMetricsCollectorMock(t), + DC: mock.NewDependencyContainerMock(t), + Logger: log.NewNop(), + } +} diff --git a/testing/helpers/snapshots.go b/testing/helpers/snapshots.go index 447c0631..c15227fd 100644 --- a/testing/helpers/snapshots.go +++ b/testing/helpers/snapshots.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "strings" "testing" objectpatch "github.com/deckhouse/module-sdk/internal/object-patch" @@ -14,9 +15,9 @@ import ( ) // input snapshots must be in yaml format -func PrepareHookSnapshots(t *testing.T, config *pkg.HookConfig, inputSnapshots map[string][]string) pkg.Snapshots { +func PrepareHookSnapshots(t *testing.T, config *pkg.HookConfig, inputSnapshots map[string]string) pkg.Snapshots { formattedSnapshots := make(objectpatch.Snapshots, len(inputSnapshots)) - for snapBindingName, snaps := range inputSnapshots { + for snapBindingName, rawSnaps := range inputSnapshots { var ( err error query *jq.Query @@ -30,6 +31,8 @@ func PrepareHookSnapshots(t *testing.T, config *pkg.HookConfig, inputSnapshots m } } + snaps := strings.Split(rawSnaps, "---") + for _, snap := range snaps { var yml map[string]interface{} From 156fc03ec3c0bbb22595567e9be1a945aa4c2040 Mon Sep 17 00:00:00 2001 From: Pavel Okhlopkov Date: Wed, 25 Jun 2025 11:38:08 +0300 Subject: [PATCH 5/7] proto Signed-off-by: Pavel Okhlopkov --- .../tls-certificate/internal_tls_test.go | 7 +- examples/example-module/hooks/go.mod | 3 + examples/example-module/hooks/go.sum | 4 + .../hooks/subfolder/patch_hook_test.go | 121 ++++++++---------- testing/helpers/patch_collector.go | 109 +++++++++++----- testing/helpers/snapshots.go | 33 +++-- 6 files changed, 161 insertions(+), 116 deletions(-) diff --git a/common-hooks/tls-certificate/internal_tls_test.go b/common-hooks/tls-certificate/internal_tls_test.go index b40cdfb4..2bba7d39 100644 --- a/common-hooks/tls-certificate/internal_tls_test.go +++ b/common-hooks/tls-certificate/internal_tls_test.go @@ -453,8 +453,9 @@ func Test_GenSelfSignedTLS_NewFramework(t *testing.T) { hookConfig := tlscertificate.GenSelfSignedTLSConfig(tlsConfig) - snaps := helpers.PrepareHookSnapshots(t, hookConfig, map[string]string{ - tlscertificate.InternalTLSSnapshotKey: ` + snaps := helpers.PrepareHookSnapshots(t, hookConfig, map[string][]string{ + tlscertificate.InternalTLSSnapshotKey: { + ` apiVersion: v1 data: ca.crt: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJpRENDQVM2Z0F3SUJBZ0lVV3VXcVhMQ1ZnWXVSaVNmZVZvT3RHMG9vU3pZd0NnWUlLb1pJemowRUF3SXcKSWpFZ01CNEdBMVVFQXhNWFpHVmphMmh2ZFhObExtUTRMWE41YzNSbGJTNXpkbU13SGhjTk1qVXdOakU0TVRnMApOREF3V2hjTk16VXdOakUyTVRnME5EQXdXakFpTVNBd0hnWURWUVFERXhka1pXTnJhRzkxYzJVdVpEZ3RjM2x6CmRHVnRMbk4yWXpCWk1CTUdCeXFHU000OUFnRUdDQ3FHU000OUF3RUhBMElBQkM0N3h1WCs2VkhvVVVpaG9VSUsKbzY1QzR2OVU5UjV5dXZLQUN3SlJ3bFoxUGs1MGR2aXFFNHJjbXRsdTRsZkRPSW9qaFlJN3ZUS1piMVByVTY3MgpTSHVqUWpCQU1BNEdBMVVkRHdFQi93UUVBd0lCQmpBUEJnTlZIUk1CQWY4RUJUQURBUUgvTUIwR0ExVWREZ1FXCkJCVDF1U3JvYjNJeHpaNlJOc042dEFjTGlyUGt3REFLQmdncWhrak9QUVFEQWdOSUFEQkZBaUJ6YTVSS3p0RDYKRmJuT2NOTm5ncjhQazhrME4vcGtzTGNiemZXd3NCN0lVQUloQU5tMjNMSzczNVJ0c3F4TGhGNmtyTCtlZmJicgpBbU9jSmpWdGwvNWc5aEhhCi0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K @@ -485,7 +486,7 @@ metadata: name: admission-webhook-certs namespace: d8-system `, - }) + }}) values := mock.NewPatchableValuesCollectorMock(t) diff --git a/examples/example-module/hooks/go.mod b/examples/example-module/hooks/go.mod index 88f91c33..479f2831 100644 --- a/examples/example-module/hooks/go.mod +++ b/examples/example-module/hooks/go.mod @@ -41,6 +41,8 @@ require ( github.com/google/uuid v1.6.0 // indirect github.com/imdario/mergo v0.3.16 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/itchyny/gojq v0.12.17 // indirect + github.com/itchyny/timefmt-go v0.1.6 // indirect github.com/jonboulle/clockwork v0.4.0 // indirect github.com/josharian/intern v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect @@ -62,6 +64,7 @@ require ( github.com/sirupsen/logrus v1.9.3 // indirect github.com/spf13/cobra v1.9.1 // indirect github.com/spf13/pflag v1.0.6 // indirect + github.com/stretchr/testify v1.10.0 // indirect github.com/sylabs/oci-tools v0.7.0 // indirect github.com/tidwall/match v1.1.1 // indirect github.com/tidwall/pretty v1.2.0 // indirect diff --git a/examples/example-module/hooks/go.sum b/examples/example-module/hooks/go.sum index 37d22e7b..9ae37590 100644 --- a/examples/example-module/hooks/go.sum +++ b/examples/example-module/hooks/go.sum @@ -88,6 +88,10 @@ github.com/imdario/mergo v0.3.16 h1:wwQJbIsHYGMUyLSPrEq1CT16AhnhNJQ51+4fdHUnCl4= github.com/imdario/mergo v0.3.16/go.mod h1:WBLT9ZmE3lPoWsEzCh9LPo3TiwVN+ZKEjmz+hD27ysY= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/itchyny/gojq v0.12.17 h1:8av8eGduDb5+rvEdaOO+zQUjA04MS0m3Ps8HiD+fceg= +github.com/itchyny/gojq v0.12.17/go.mod h1:WBrEMkgAfAGO1LUcGOckBl5O726KPp+OlkKug0I/FEY= +github.com/itchyny/timefmt-go v0.1.6 h1:ia3s54iciXDdzWzwaVKXZPbiXzxxnv1SPGFfM/myJ5Q= +github.com/itchyny/timefmt-go v0.1.6/go.mod h1:RRDZYC5s9ErkjQvTvvU7keJjxUYzIISJGxm9/mAERQg= github.com/jonboulle/clockwork v0.4.0 h1:p4Cf1aMWXnXAUh8lVfewRBx1zaTSYKrKMF2g3ST4RZ4= github.com/jonboulle/clockwork v0.4.0/go.mod h1:xgRqUGwRcjKCO1vbZUEtSLrqKoPSsUpK7fnezOII0kc= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= diff --git a/examples/example-module/hooks/subfolder/patch_hook_test.go b/examples/example-module/hooks/subfolder/patch_hook_test.go index 022a199c..eed2c09c 100644 --- a/examples/example-module/hooks/subfolder/patch_hook_test.go +++ b/examples/example-module/hooks/subfolder/patch_hook_test.go @@ -4,16 +4,18 @@ import ( "bytes" "context" "strings" + "testing" "time" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "github.com/deckhouse/deckhouse/pkg/log" "github.com/deckhouse/module-sdk/pkg" - "github.com/deckhouse/module-sdk/testing/mock" + testinghelpers "github.com/deckhouse/module-sdk/testing/helpers" subfolder "example-module/subfolder" ) @@ -21,13 +23,13 @@ import ( var _ = Describe("patch hook", func() { Context("HandlerHookPatch function", func() { var ( - patchCollector *mock.PatchCollectorMock - buf *bytes.Buffer - input *pkg.HookInput + patchCollector pkg.OutputPatchCollector + + buf *bytes.Buffer + input *pkg.HookInput ) BeforeEach(func() { - patchCollector = mock.NewPatchCollectorMock(GinkgoT()) buf = bytes.NewBuffer([]byte{}) input = &pkg.HookInput{ @@ -45,68 +47,53 @@ var _ = Describe("patch hook", func() { }) It("logs hello message and executes patch collector operations", func() { - // Set expectations for Create - patchCollector.CreateMock.Set(func(obj any) { - pod, ok := obj.(*corev1.Pod) - Expect(ok).To(BeTrue()) - Expect(pod.Name).To(Equal("my-first-pod")) - Expect(pod.Namespace).To(Equal("default")) - Expect(pod.Status.Phase).To(Equal(corev1.PodRunning)) - }) - - // Set expectations for CreateOrUpdate - patchCollector.CreateOrUpdateMock.Set(func(obj any) { - pod, ok := obj.(*corev1.Pod) - Expect(ok).To(BeTrue()) - Expect(pod.Name).To(Equal("my-second-pod")) - Expect(pod.Namespace).To(Equal("default")) - Expect(pod.Status.Phase).To(Equal(corev1.PodRunning)) - }) - - // Set expectations for CreateIfNotExists - patchCollector.CreateIfNotExistsMock.Set(func(obj any) { - pod, ok := obj.(*corev1.Pod) - Expect(ok).To(BeTrue()) - Expect(pod.Name).To(Equal("my-third-pod")) - Expect(pod.Namespace).To(Equal("default")) - Expect(pod.Status.Phase).To(Equal(corev1.PodRunning)) - }) - - // Set expectations for Delete - patchCollector.DeleteMock.Set(func(apiVersion, kind, namespace, name string) { - Expect(apiVersion).To(Equal("v1")) - Expect(kind).To(Equal("Pod")) - Expect(namespace).To(Equal("default")) - Expect(name).To(Equal("my-first-pod")) - }) - - // Set expectations for DeleteInBackground - patchCollector.DeleteInBackgroundMock.Set(func(apiVersion, kind, namespace, name string) { - Expect(apiVersion).To(Equal("v1")) - Expect(kind).To(Equal("Pod")) - Expect(namespace).To(Equal("default")) - Expect(name).To(Equal("my-second-pod")) - }) - - // Set expectations for DeleteNonCascading - patchCollector.DeleteNonCascadingMock.Set(func(apiVersion, kind, namespace, name string) { - Expect(apiVersion).To(Equal("v1")) - Expect(kind).To(Equal("Pod")) - Expect(namespace).To(Equal("default")) - Expect(name).To(Equal("my-third-pod")) - }) - - // Set expectations for PatchWithMerge - patchCollector.PatchWithMergeMock.Set(func(mergePatch any, apiVersion, kind, namespace, name string, opts ...pkg.PatchCollectorOption) { - patchMap, ok := mergePatch.(map[string]any) - Expect(ok).To(BeTrue()) - Expect(patchMap).To(HaveKeyWithValue("/status", "newStatus")) - Expect(apiVersion).To(Equal("v1")) - Expect(kind).To(Equal("Pod")) - Expect(namespace).To(Equal("default")) - Expect(name).To(Equal("my-third-pod")) - Expect(len(opts)).To(Equal(2)) - }) + input.PatchCollector = testinghelpers.PreparePatchCollector(&testing.T{}, + testinghelpers.NewCreate( + &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-first-pod", + Namespace: "default", + }, + Status: corev1.PodStatus{ + Phase: corev1.PodRunning, + }, + }), + testinghelpers.NewCreateOrUpdate( + &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-second-pod", + Namespace: "default", + }, + Status: corev1.PodStatus{ + Phase: corev1.PodRunning, + }, + }, + ), + testinghelpers.NewCreateIfNotExists( + &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "my-third-pod", + Namespace: "default", + }, + Status: corev1.PodStatus{ + Phase: corev1.PodRunning, + }, + }, + ), + testinghelpers.NewDelete( + "v1", "Pod", "default", "my-first-pod", + ), + testinghelpers.NewDeleteInBackground( + "v1", "Pod", "default", "my-second-pod", + ), + testinghelpers.NewDeleteNonCascading( + "v1", "Pod", "default", "my-third-pod", + ), + testinghelpers.NewPatchWithMerge( + map[string]any{"/status": "newStatus"}, + "v1", "Pod", "default", "my-third-pod", + ), + ) // Execute the handler function err := subfolder.HandlerHookPatch(context.Background(), input) diff --git a/testing/helpers/patch_collector.go b/testing/helpers/patch_collector.go index b3f966f5..16ae036b 100644 --- a/testing/helpers/patch_collector.go +++ b/testing/helpers/patch_collector.go @@ -8,45 +8,49 @@ import ( "github.com/stretchr/testify/assert" ) -func PreparePatchCollector(t *testing.T, patches ...any) pkg.OutputPatchCollector { +type typedPatch struct { + wrapped any +} + +func PreparePatchCollector(t *testing.T, patches ...*typedPatch) pkg.OutputPatchCollector { pc := mock.NewPatchCollectorMock(t) for _, patch := range patches { - switch p := patch.(type) { - case Create: + switch p := patch.wrapped.(type) { + case *create: pc.CreateMock.Set(func(obj any) { assert.Equal(t, p.Object, obj, "Create object mismatch") }) - case CreateIfNotExists: + case *createIfNotExists: pc.CreateIfNotExistsMock.Set(func(obj any) { assert.Equal(t, p.Object, obj, "CreateIfNotExists object mismatch") }) - case CreateOrUpdate: + case *createOrUpdate: pc.CreateOrUpdateMock.Set(func(obj any) { assert.Equal(t, p.Object, obj, "CreateOrUpdate object mismatch") }) - case Delete: + case *delete: pc.DeleteMock.Set(func(apiVersion, kind, namespace, name string) { assert.Equal(t, p.ApiVersion, apiVersion, "API version mismatch") assert.Equal(t, p.Kind, kind, "Kind mismatch") assert.Equal(t, p.Namespace, namespace, "Namespace mismatch") assert.Equal(t, p.Name, name, "Name mismatch") }) - case DeleteInBackground: + case *deleteInBackground: pc.DeleteInBackgroundMock.Set(func(apiVersion, kind, namespace, name string) { assert.Equal(t, p.ApiVersion, apiVersion, "API version mismatch") assert.Equal(t, p.Kind, kind, "Kind mismatch") assert.Equal(t, p.Namespace, namespace, "Namespace mismatch") assert.Equal(t, p.Name, name, "Name mismatch") }) - case DeleteNonCascading: + case *deleteNonCascading: pc.DeleteNonCascadingMock.Set(func(apiVersion, kind, namespace, name string) { assert.Equal(t, p.ApiVersion, apiVersion, "API version mismatch") assert.Equal(t, p.Kind, kind, "Kind mismatch") assert.Equal(t, p.Namespace, namespace, "Namespace mismatch") assert.Equal(t, p.Name, name, "Name mismatch") }) - case JSONPatch: + case *jsonPatch: pc.JSONPatchMock.Set(func(jsonPatch any, apiVersion, kind, namespace, name string, opts ...pkg.PatchCollectorOption) { assert.Equal(t, p.JsonPatch, jsonPatch, "JSON patch mismatch") assert.Equal(t, p.ApiVersion, apiVersion, "API version mismatch") @@ -54,7 +58,7 @@ func PreparePatchCollector(t *testing.T, patches ...any) pkg.OutputPatchCollecto assert.Equal(t, p.Namespace, namespace, "Namespace mismatch") assert.Equal(t, p.Name, name, "Name mismatch") }) - case MergePatch: + case *mergePatch: pc.MergePatchMock.Set(func(mergePatch any, apiVersion, kind, namespace, name string, opts ...pkg.PatchCollectorOption) { assert.Equal(t, p.MergePatch, mergePatch, "Merge patch mismatch") assert.Equal(t, p.ApiVersion, apiVersion, "API version mismatch") @@ -62,7 +66,7 @@ func PreparePatchCollector(t *testing.T, patches ...any) pkg.OutputPatchCollecto assert.Equal(t, p.Namespace, namespace, "Namespace mismatch") assert.Equal(t, p.Name, name, "Name mismatch") }) - case JQFilter: + case *jqFilter: pc.JQFilterMock.Set(func(jqFilter string, apiVersion, kind, namespace, name string, opts ...pkg.PatchCollectorOption) { assert.Equal(t, p.JQFilter, jqFilter, "JQ filter mismatch") assert.Equal(t, p.ApiVersion, apiVersion, "API version mismatch") @@ -70,7 +74,7 @@ func PreparePatchCollector(t *testing.T, patches ...any) pkg.OutputPatchCollecto assert.Equal(t, p.Namespace, namespace, "Namespace mismatch") assert.Equal(t, p.Name, name, "Name mismatch") }) - case PatchWithJQ: + case *patchWithJQ: pc.PatchWithJQMock.Set(func(jqfilter, apiVersion, kind, namespace, name string, opts ...pkg.PatchCollectorOption) { assert.Equal(t, p.JQFilter, jqfilter, "JQ filter mismatch") assert.Equal(t, p.ApiVersion, apiVersion, "API version mismatch") @@ -78,7 +82,7 @@ func PreparePatchCollector(t *testing.T, patches ...any) pkg.OutputPatchCollecto assert.Equal(t, p.Namespace, namespace, "Namespace mismatch") assert.Equal(t, p.Name, name, "Name mismatch") }) - case PatchWithJSON: + case *patchWithJSON: pc.PatchWithJSONMock.Set(func(jsonPatch any, apiVersion, kind, namespace, name string, opts ...pkg.PatchCollectorOption) { assert.Equal(t, p.JsonPatch, jsonPatch, "JSON patch mismatch") assert.Equal(t, p.ApiVersion, apiVersion, "API version mismatch") @@ -86,7 +90,7 @@ func PreparePatchCollector(t *testing.T, patches ...any) pkg.OutputPatchCollecto assert.Equal(t, p.Namespace, namespace, "Namespace mismatch") assert.Equal(t, p.Name, name, "Name mismatch") }) - case PatchWithMerge: + case *patchWithMerge: pc.PatchWithMergeMock.Set(func(mergePatch any, apiVersion, kind, namespace, name string, opts ...pkg.PatchCollectorOption) { assert.Equal(t, p.MergePatch, mergePatch, "Merge patch mismatch") assert.Equal(t, p.ApiVersion, apiVersion, "API version mismatch") @@ -101,69 +105,104 @@ func PreparePatchCollector(t *testing.T, patches ...any) pkg.OutputPatchCollecto return pc } +func NewCreate(obj any) *typedPatch { + return &typedPatch{wrapped: &create{Object: obj}} +} -// Patch type definitions for different patching operations -type Create struct { +type create struct { Object any } -type CreateIfNotExists struct { +func NewCreateIfNotExists(obj any) *typedPatch { + return &typedPatch{wrapped: &createIfNotExists{Object: obj}} +} + +type createIfNotExists struct { Object any } -type CreateOrUpdate struct { +func NewCreateOrUpdate(obj any) *typedPatch { + return &typedPatch{wrapped: &createOrUpdate{Object: obj}} +} + +type createOrUpdate struct { Object any } -type Delete struct { +func NewDelete(apiVersion, kind, namespace, name string) *typedPatch { + return &typedPatch{wrapped: &delete{ApiVersion: apiVersion, Kind: kind, Namespace: namespace, Name: name}} +} + +type delete struct { ApiVersion string Kind string Namespace string Name string } -type DeleteInBackground struct { +func NewDeleteInBackground(apiVersion, kind, namespace, name string) *typedPatch { + return &typedPatch{wrapped: &deleteInBackground{ApiVersion: apiVersion, Kind: kind, Namespace: namespace, Name: name}} +} + +type deleteInBackground struct { ApiVersion string Kind string Namespace string Name string } -type DeleteNonCascading struct { +func NewDeleteNonCascading(apiVersion, kind, namespace, name string) *typedPatch { + return &typedPatch{wrapped: &deleteNonCascading{ApiVersion: apiVersion, Kind: kind, Namespace: namespace, Name: name}} +} + +type deleteNonCascading struct { ApiVersion string Kind string Namespace string Name string } -type JSONPatch struct { +func NewJSONPatch(JSONPatch any, apiVersion, kind, namespace, name string) *typedPatch { + return &typedPatch{wrapped: &jsonPatch{JsonPatch: JSONPatch, ApiVersion: apiVersion, Kind: kind, Namespace: namespace, Name: name}} +} + +type jsonPatch struct { JsonPatch any ApiVersion string Kind string Namespace string Name string - Options []pkg.PatchCollectorOption } -type MergePatch struct { +func NewMergePatch(MergePatch any, apiVersion, kind, namespace, name string) *typedPatch { + return &typedPatch{wrapped: &mergePatch{MergePatch: MergePatch, ApiVersion: apiVersion, Kind: kind, Namespace: namespace, Name: name}} +} + +type mergePatch struct { MergePatch any ApiVersion string Kind string Namespace string Name string - Options []pkg.PatchCollectorOption } -type JQFilter struct { +func NewJQFilter(JQFilter, apiVersion, kind, namespace, name string) *typedPatch { + return &typedPatch{wrapped: &jqFilter{JQFilter: JQFilter, ApiVersion: apiVersion, Kind: kind, Namespace: namespace, Name: name}} +} + +type jqFilter struct { JQFilter string ApiVersion string Kind string Namespace string Name string - Options []pkg.PatchCollectorOption } -type PatchWithJQ struct { +func NewPatchWithJQ(jqFilter, apiVersion, kind, namespace, name string) *typedPatch { + return &typedPatch{wrapped: &patchWithJQ{JQFilter: jqFilter, ApiVersion: apiVersion, Kind: kind, Namespace: namespace, Name: name}} +} + +type patchWithJQ struct { JQFilter string ApiVersion string Kind string @@ -171,20 +210,26 @@ type PatchWithJQ struct { Name string } -type PatchWithJSON struct { +func NewPatchWithJSON(jsonPatch any, apiVersion, kind, namespace, name string) *typedPatch { + return &typedPatch{wrapped: &patchWithJSON{JsonPatch: jsonPatch, ApiVersion: apiVersion, Kind: kind, Namespace: namespace, Name: name}} +} + +type patchWithJSON struct { JsonPatch any ApiVersion string Kind string Namespace string Name string - Options []pkg.PatchCollectorOption } -type PatchWithMerge struct { +func NewPatchWithMerge(mergePatch any, apiVersion, kind, namespace, name string) *typedPatch { + return &typedPatch{wrapped: &patchWithMerge{MergePatch: mergePatch, ApiVersion: apiVersion, Kind: kind, Namespace: namespace, Name: name}} +} + +type patchWithMerge struct { MergePatch any ApiVersion string Kind string Namespace string Name string - Options []pkg.PatchCollectorOption } diff --git a/testing/helpers/snapshots.go b/testing/helpers/snapshots.go index c15227fd..bdd78c39 100644 --- a/testing/helpers/snapshots.go +++ b/testing/helpers/snapshots.go @@ -14,8 +14,11 @@ import ( "gopkg.in/yaml.v3" ) -// input snapshots must be in yaml format -func PrepareHookSnapshots(t *testing.T, config *pkg.HookConfig, inputSnapshots map[string]string) pkg.Snapshots { +// input snapshots must be in the format: snapshotName: ["yaml1", "yaml2", ...] +// it can handle multiple YAML documents in a single string, separated by "---" +// it will apply the JQ filter from the hook config to each snapshot +// and return a map of snapshot names to their filtered JSON representations +func PrepareHookSnapshots(t *testing.T, config *pkg.HookConfig, inputSnapshots map[string][]string) pkg.Snapshots { formattedSnapshots := make(objectpatch.Snapshots, len(inputSnapshots)) for snapBindingName, rawSnaps := range inputSnapshots { var ( @@ -31,24 +34,26 @@ func PrepareHookSnapshots(t *testing.T, config *pkg.HookConfig, inputSnapshots m } } - snaps := strings.Split(rawSnaps, "---") + for _, rawSnap := range rawSnaps { + snaps := strings.Split(rawSnap, "---") - for _, snap := range snaps { - var yml map[string]interface{} + for _, snap := range snaps { + var yml map[string]interface{} - err := yaml.Unmarshal([]byte(snap), &yml) - assert.NoError(t, err, "Failed to unmarshal snapshot YAML: %s", snap) + err := yaml.Unmarshal([]byte(snap), &yml) + assert.NoError(t, err, "Failed to unmarshal snapshot YAML: %s", snap) - jsonSnap, err := json.Marshal(yml) - assert.NoError(t, err, "Failed to marshal snapshot to JSON: %s", snap) + jsonSnap, err := json.Marshal(yml) + assert.NoError(t, err, "Failed to marshal snapshot to JSON: %s", snap) - fmt.Println("JSON Snapshot:", string(jsonSnap)) + fmt.Println("JSON Snapshot:", string(jsonSnap)) - res, err := query.FilterStringObject(context.TODO(), string(jsonSnap)) - assert.NoError(t, err, "Failed to filter snapshot with JQ query: %s", jsonSnap) - fmt.Println("JSON Snapshot:", string(res.String())) + res, err := query.FilterStringObject(context.TODO(), string(jsonSnap)) + assert.NoError(t, err, "Failed to filter snapshot with JQ query: %s", jsonSnap) + fmt.Println("JSON Snapshot:", string(res.String())) - formattedSnapshots[snapBindingName] = append(formattedSnapshots[snapBindingName], objectpatch.Snapshot(res.String())) + formattedSnapshots[snapBindingName] = append(formattedSnapshots[snapBindingName], objectpatch.Snapshot(res.String())) + } } } From d8784aad21bfba0f52433151c8d7838dc96eeb4a Mon Sep 17 00:00:00 2001 From: Pavel Okhlopkov Date: Wed, 25 Jun 2025 17:15:53 +0300 Subject: [PATCH 6/7] lint Signed-off-by: Pavel Okhlopkov --- testing/framework/init.go | 64 +++++++++++++------------- testing/framework/init_test.go | 4 +- testing/helpers/hook_input.go | 1 + testing/helpers/patch_collector.go | 72 +++++++++++++++--------------- testing/helpers/snapshots.go | 7 +-- 5 files changed, 77 insertions(+), 71 deletions(-) diff --git a/testing/framework/init.go b/testing/framework/init.go index 1d56b082..d2191e5d 100644 --- a/testing/framework/init.go +++ b/testing/framework/init.go @@ -6,13 +6,15 @@ import ( "fmt" "testing" + "github.com/stretchr/testify/assert" + "gopkg.in/yaml.v3" + "github.com/deckhouse/deckhouse/pkg/log" + objectpatch "github.com/deckhouse/module-sdk/internal/object-patch" "github.com/deckhouse/module-sdk/pkg" "github.com/deckhouse/module-sdk/pkg/jq" "github.com/deckhouse/module-sdk/testing/mock" - "github.com/stretchr/testify/assert" - "gopkg.in/yaml.v3" ) type HookFramework struct { @@ -77,7 +79,7 @@ func (f *HookFramework) PrepareHookSnapshots(config *pkg.HookConfig, inputSnapsh res, err := query.FilterStringObject(context.TODO(), string(jsonSnap)) assert.NoError(f.t, err, "Failed to filter snapshot with JQ query: %s", jsonSnap) - fmt.Println("JSON Snapshot:", string(res.String())) + fmt.Println("JSON Snapshot:", res.String()) formattedSnapshots[snapBindingName] = append(formattedSnapshots[snapBindingName], objectpatch.Snapshot(res.String())) } @@ -105,69 +107,69 @@ func (f *HookFramework) PreparePatchCollector(patches ...any) { }) case Delete: pc.DeleteMock.Set(func(apiVersion, kind, namespace, name string) { - assert.Equal(f.t, p.ApiVersion, apiVersion, "API version mismatch") + assert.Equal(f.t, p.APIVersion, apiVersion, "API version mismatch") assert.Equal(f.t, p.Kind, kind, "Kind mismatch") assert.Equal(f.t, p.Namespace, namespace, "Namespace mismatch") assert.Equal(f.t, p.Name, name, "Name mismatch") }) case DeleteInBackground: pc.DeleteInBackgroundMock.Set(func(apiVersion, kind, namespace, name string) { - assert.Equal(f.t, p.ApiVersion, apiVersion, "API version mismatch") + assert.Equal(f.t, p.APIVersion, apiVersion, "API version mismatch") assert.Equal(f.t, p.Kind, kind, "Kind mismatch") assert.Equal(f.t, p.Namespace, namespace, "Namespace mismatch") assert.Equal(f.t, p.Name, name, "Name mismatch") }) case DeleteNonCascading: pc.DeleteNonCascadingMock.Set(func(apiVersion, kind, namespace, name string) { - assert.Equal(f.t, p.ApiVersion, apiVersion, "API version mismatch") + assert.Equal(f.t, p.APIVersion, apiVersion, "API version mismatch") assert.Equal(f.t, p.Kind, kind, "Kind mismatch") assert.Equal(f.t, p.Namespace, namespace, "Namespace mismatch") assert.Equal(f.t, p.Name, name, "Name mismatch") }) case JSONPatch: - pc.JSONPatchMock.Set(func(jsonPatch any, apiVersion, kind, namespace, name string, opts ...pkg.PatchCollectorOption) { - assert.Equal(f.t, p.JsonPatch, jsonPatch, "JSON patch mismatch") - assert.Equal(f.t, p.ApiVersion, apiVersion, "API version mismatch") + pc.JSONPatchMock.Set(func(jsonPatch any, apiVersion, kind, namespace, name string, _ ...pkg.PatchCollectorOption) { + assert.Equal(f.t, p.JSONPatch, jsonPatch, "JSON patch mismatch") + assert.Equal(f.t, p.APIVersion, apiVersion, "API version mismatch") assert.Equal(f.t, p.Kind, kind, "Kind mismatch") assert.Equal(f.t, p.Namespace, namespace, "Namespace mismatch") assert.Equal(f.t, p.Name, name, "Name mismatch") }) case MergePatch: - pc.MergePatchMock.Set(func(mergePatch any, apiVersion, kind, namespace, name string, opts ...pkg.PatchCollectorOption) { + pc.MergePatchMock.Set(func(mergePatch any, apiVersion, kind, namespace, name string, _ ...pkg.PatchCollectorOption) { assert.Equal(f.t, p.MergePatch, mergePatch, "Merge patch mismatch") - assert.Equal(f.t, p.ApiVersion, apiVersion, "API version mismatch") + assert.Equal(f.t, p.APIVersion, apiVersion, "API version mismatch") assert.Equal(f.t, p.Kind, kind, "Kind mismatch") assert.Equal(f.t, p.Namespace, namespace, "Namespace mismatch") assert.Equal(f.t, p.Name, name, "Name mismatch") }) case JQFilter: - pc.JQFilterMock.Set(func(jqFilter string, apiVersion, kind, namespace, name string, opts ...pkg.PatchCollectorOption) { + pc.JQFilterMock.Set(func(jqFilter string, apiVersion, kind, namespace, name string, _ ...pkg.PatchCollectorOption) { assert.Equal(f.t, p.JQFilter, jqFilter, "JQ filter mismatch") - assert.Equal(f.t, p.ApiVersion, apiVersion, "API version mismatch") + assert.Equal(f.t, p.APIVersion, apiVersion, "API version mismatch") assert.Equal(f.t, p.Kind, kind, "Kind mismatch") assert.Equal(f.t, p.Namespace, namespace, "Namespace mismatch") assert.Equal(f.t, p.Name, name, "Name mismatch") }) case PatchWithJQ: - pc.PatchWithJQMock.Set(func(jqfilter, apiVersion, kind, namespace, name string, opts ...pkg.PatchCollectorOption) { + pc.PatchWithJQMock.Set(func(jqfilter, apiVersion, kind, namespace, name string, _ ...pkg.PatchCollectorOption) { assert.Equal(f.t, p.JQFilter, jqfilter, "JQ filter mismatch") - assert.Equal(f.t, p.ApiVersion, apiVersion, "API version mismatch") + assert.Equal(f.t, p.APIVersion, apiVersion, "API version mismatch") assert.Equal(f.t, p.Kind, kind, "Kind mismatch") assert.Equal(f.t, p.Namespace, namespace, "Namespace mismatch") assert.Equal(f.t, p.Name, name, "Name mismatch") }) case PatchWithJSON: - pc.PatchWithJSONMock.Set(func(jsonPatch any, apiVersion, kind, namespace, name string, opts ...pkg.PatchCollectorOption) { - assert.Equal(f.t, p.JsonPatch, jsonPatch, "JSON patch mismatch") - assert.Equal(f.t, p.ApiVersion, apiVersion, "API version mismatch") + pc.PatchWithJSONMock.Set(func(jsonPatch any, apiVersion, kind, namespace, name string, _ ...pkg.PatchCollectorOption) { + assert.Equal(f.t, p.JSONPatch, jsonPatch, "JSON patch mismatch") + assert.Equal(f.t, p.APIVersion, apiVersion, "API version mismatch") assert.Equal(f.t, p.Kind, kind, "Kind mismatch") assert.Equal(f.t, p.Namespace, namespace, "Namespace mismatch") assert.Equal(f.t, p.Name, name, "Name mismatch") }) case PatchWithMerge: - pc.PatchWithMergeMock.Set(func(mergePatch any, apiVersion, kind, namespace, name string, opts ...pkg.PatchCollectorOption) { + pc.PatchWithMergeMock.Set(func(mergePatch any, apiVersion, kind, namespace, name string, _ ...pkg.PatchCollectorOption) { assert.Equal(f.t, p.MergePatch, mergePatch, "Merge patch mismatch") - assert.Equal(f.t, p.ApiVersion, apiVersion, "API version mismatch") + assert.Equal(f.t, p.APIVersion, apiVersion, "API version mismatch") assert.Equal(f.t, p.Kind, kind, "Kind mismatch") assert.Equal(f.t, p.Namespace, namespace, "Namespace mismatch") assert.Equal(f.t, p.Name, name, "Name mismatch") @@ -194,29 +196,29 @@ type CreateOrUpdate struct { } type Delete struct { - ApiVersion string + APIVersion string Kind string Namespace string Name string } type DeleteInBackground struct { - ApiVersion string + APIVersion string Kind string Namespace string Name string } type DeleteNonCascading struct { - ApiVersion string + APIVersion string Kind string Namespace string Name string } type JSONPatch struct { - JsonPatch any - ApiVersion string + JSONPatch any + APIVersion string Kind string Namespace string Name string @@ -225,7 +227,7 @@ type JSONPatch struct { type MergePatch struct { MergePatch any - ApiVersion string + APIVersion string Kind string Namespace string Name string @@ -234,7 +236,7 @@ type MergePatch struct { type JQFilter struct { JQFilter string - ApiVersion string + APIVersion string Kind string Namespace string Name string @@ -243,15 +245,15 @@ type JQFilter struct { type PatchWithJQ struct { JQFilter string - ApiVersion string + APIVersion string Kind string Namespace string Name string } type PatchWithJSON struct { - JsonPatch any - ApiVersion string + JSONPatch any + APIVersion string Kind string Namespace string Name string @@ -260,7 +262,7 @@ type PatchWithJSON struct { type PatchWithMerge struct { MergePatch any - ApiVersion string + APIVersion string Kind string Namespace string Name string diff --git a/testing/framework/init_test.go b/testing/framework/init_test.go index e056f8d0..3c4d1f83 100644 --- a/testing/framework/init_test.go +++ b/testing/framework/init_test.go @@ -42,7 +42,7 @@ func Test_PrepareHookInput(t *testing.T) { }, } - f := framework.NewHookFramework(t, config, func(ctx context.Context, input *pkg.HookInput) error { + f := framework.NewHookFramework(t, config, func(_ context.Context, _ *pkg.HookInput) error { return nil }) @@ -80,7 +80,7 @@ func Test_PrepareHookInput(t *testing.T) { }, } - f = framework.NewHookFramework(t, multiConfig, func(ctx context.Context, input *pkg.HookInput) error { + f = framework.NewHookFramework(t, multiConfig, func(_ context.Context, _ *pkg.HookInput) error { return nil }) diff --git a/testing/helpers/hook_input.go b/testing/helpers/hook_input.go index a076bd77..47ea045b 100644 --- a/testing/helpers/hook_input.go +++ b/testing/helpers/hook_input.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/deckhouse/deckhouse/pkg/log" + "github.com/deckhouse/module-sdk/pkg" "github.com/deckhouse/module-sdk/testing/mock" ) diff --git a/testing/helpers/patch_collector.go b/testing/helpers/patch_collector.go index 16ae036b..86b5e696 100644 --- a/testing/helpers/patch_collector.go +++ b/testing/helpers/patch_collector.go @@ -1,11 +1,13 @@ +// nolint: revive package helpers import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/deckhouse/module-sdk/pkg" "github.com/deckhouse/module-sdk/testing/mock" - "github.com/stretchr/testify/assert" ) type typedPatch struct { @@ -31,29 +33,29 @@ func PreparePatchCollector(t *testing.T, patches ...*typedPatch) pkg.OutputPatch }) case *delete: pc.DeleteMock.Set(func(apiVersion, kind, namespace, name string) { - assert.Equal(t, p.ApiVersion, apiVersion, "API version mismatch") + assert.Equal(t, p.APIVersion, apiVersion, "API version mismatch") assert.Equal(t, p.Kind, kind, "Kind mismatch") assert.Equal(t, p.Namespace, namespace, "Namespace mismatch") assert.Equal(t, p.Name, name, "Name mismatch") }) case *deleteInBackground: pc.DeleteInBackgroundMock.Set(func(apiVersion, kind, namespace, name string) { - assert.Equal(t, p.ApiVersion, apiVersion, "API version mismatch") + assert.Equal(t, p.APIVersion, apiVersion, "API version mismatch") assert.Equal(t, p.Kind, kind, "Kind mismatch") assert.Equal(t, p.Namespace, namespace, "Namespace mismatch") assert.Equal(t, p.Name, name, "Name mismatch") }) case *deleteNonCascading: pc.DeleteNonCascadingMock.Set(func(apiVersion, kind, namespace, name string) { - assert.Equal(t, p.ApiVersion, apiVersion, "API version mismatch") + assert.Equal(t, p.APIVersion, apiVersion, "API version mismatch") assert.Equal(t, p.Kind, kind, "Kind mismatch") assert.Equal(t, p.Namespace, namespace, "Namespace mismatch") assert.Equal(t, p.Name, name, "Name mismatch") }) case *jsonPatch: pc.JSONPatchMock.Set(func(jsonPatch any, apiVersion, kind, namespace, name string, opts ...pkg.PatchCollectorOption) { - assert.Equal(t, p.JsonPatch, jsonPatch, "JSON patch mismatch") - assert.Equal(t, p.ApiVersion, apiVersion, "API version mismatch") + assert.Equal(t, p.JSONPatch, jsonPatch, "JSON patch mismatch") + assert.Equal(t, p.APIVersion, apiVersion, "API version mismatch") assert.Equal(t, p.Kind, kind, "Kind mismatch") assert.Equal(t, p.Namespace, namespace, "Namespace mismatch") assert.Equal(t, p.Name, name, "Name mismatch") @@ -61,7 +63,7 @@ func PreparePatchCollector(t *testing.T, patches ...*typedPatch) pkg.OutputPatch case *mergePatch: pc.MergePatchMock.Set(func(mergePatch any, apiVersion, kind, namespace, name string, opts ...pkg.PatchCollectorOption) { assert.Equal(t, p.MergePatch, mergePatch, "Merge patch mismatch") - assert.Equal(t, p.ApiVersion, apiVersion, "API version mismatch") + assert.Equal(t, p.APIVersion, apiVersion, "API version mismatch") assert.Equal(t, p.Kind, kind, "Kind mismatch") assert.Equal(t, p.Namespace, namespace, "Namespace mismatch") assert.Equal(t, p.Name, name, "Name mismatch") @@ -69,7 +71,7 @@ func PreparePatchCollector(t *testing.T, patches ...*typedPatch) pkg.OutputPatch case *jqFilter: pc.JQFilterMock.Set(func(jqFilter string, apiVersion, kind, namespace, name string, opts ...pkg.PatchCollectorOption) { assert.Equal(t, p.JQFilter, jqFilter, "JQ filter mismatch") - assert.Equal(t, p.ApiVersion, apiVersion, "API version mismatch") + assert.Equal(t, p.APIVersion, apiVersion, "API version mismatch") assert.Equal(t, p.Kind, kind, "Kind mismatch") assert.Equal(t, p.Namespace, namespace, "Namespace mismatch") assert.Equal(t, p.Name, name, "Name mismatch") @@ -77,15 +79,15 @@ func PreparePatchCollector(t *testing.T, patches ...*typedPatch) pkg.OutputPatch case *patchWithJQ: pc.PatchWithJQMock.Set(func(jqfilter, apiVersion, kind, namespace, name string, opts ...pkg.PatchCollectorOption) { assert.Equal(t, p.JQFilter, jqfilter, "JQ filter mismatch") - assert.Equal(t, p.ApiVersion, apiVersion, "API version mismatch") + assert.Equal(t, p.APIVersion, apiVersion, "API version mismatch") assert.Equal(t, p.Kind, kind, "Kind mismatch") assert.Equal(t, p.Namespace, namespace, "Namespace mismatch") assert.Equal(t, p.Name, name, "Name mismatch") }) case *patchWithJSON: pc.PatchWithJSONMock.Set(func(jsonPatch any, apiVersion, kind, namespace, name string, opts ...pkg.PatchCollectorOption) { - assert.Equal(t, p.JsonPatch, jsonPatch, "JSON patch mismatch") - assert.Equal(t, p.ApiVersion, apiVersion, "API version mismatch") + assert.Equal(t, p.JSONPatch, jsonPatch, "JSON patch mismatch") + assert.Equal(t, p.APIVersion, apiVersion, "API version mismatch") assert.Equal(t, p.Kind, kind, "Kind mismatch") assert.Equal(t, p.Namespace, namespace, "Namespace mismatch") assert.Equal(t, p.Name, name, "Name mismatch") @@ -93,7 +95,7 @@ func PreparePatchCollector(t *testing.T, patches ...*typedPatch) pkg.OutputPatch case *patchWithMerge: pc.PatchWithMergeMock.Set(func(mergePatch any, apiVersion, kind, namespace, name string, opts ...pkg.PatchCollectorOption) { assert.Equal(t, p.MergePatch, mergePatch, "Merge patch mismatch") - assert.Equal(t, p.ApiVersion, apiVersion, "API version mismatch") + assert.Equal(t, p.APIVersion, apiVersion, "API version mismatch") assert.Equal(t, p.Kind, kind, "Kind mismatch") assert.Equal(t, p.Namespace, namespace, "Namespace mismatch") assert.Equal(t, p.Name, name, "Name mismatch") @@ -130,105 +132,105 @@ type createOrUpdate struct { } func NewDelete(apiVersion, kind, namespace, name string) *typedPatch { - return &typedPatch{wrapped: &delete{ApiVersion: apiVersion, Kind: kind, Namespace: namespace, Name: name}} + return &typedPatch{wrapped: &delete{APIVersion: apiVersion, Kind: kind, Namespace: namespace, Name: name}} } type delete struct { - ApiVersion string + APIVersion string Kind string Namespace string Name string } func NewDeleteInBackground(apiVersion, kind, namespace, name string) *typedPatch { - return &typedPatch{wrapped: &deleteInBackground{ApiVersion: apiVersion, Kind: kind, Namespace: namespace, Name: name}} + return &typedPatch{wrapped: &deleteInBackground{APIVersion: apiVersion, Kind: kind, Namespace: namespace, Name: name}} } type deleteInBackground struct { - ApiVersion string + APIVersion string Kind string Namespace string Name string } func NewDeleteNonCascading(apiVersion, kind, namespace, name string) *typedPatch { - return &typedPatch{wrapped: &deleteNonCascading{ApiVersion: apiVersion, Kind: kind, Namespace: namespace, Name: name}} + return &typedPatch{wrapped: &deleteNonCascading{APIVersion: apiVersion, Kind: kind, Namespace: namespace, Name: name}} } type deleteNonCascading struct { - ApiVersion string + APIVersion string Kind string Namespace string Name string } -func NewJSONPatch(JSONPatch any, apiVersion, kind, namespace, name string) *typedPatch { - return &typedPatch{wrapped: &jsonPatch{JsonPatch: JSONPatch, ApiVersion: apiVersion, Kind: kind, Namespace: namespace, Name: name}} +func NewJSONPatch(jsonPatchRaw any, apiVersion, kind, namespace, name string) *typedPatch { + return &typedPatch{wrapped: &jsonPatch{JSONPatch: jsonPatchRaw, APIVersion: apiVersion, Kind: kind, Namespace: namespace, Name: name}} } type jsonPatch struct { - JsonPatch any - ApiVersion string + JSONPatch any + APIVersion string Kind string Namespace string Name string } -func NewMergePatch(MergePatch any, apiVersion, kind, namespace, name string) *typedPatch { - return &typedPatch{wrapped: &mergePatch{MergePatch: MergePatch, ApiVersion: apiVersion, Kind: kind, Namespace: namespace, Name: name}} +func NewMergePatch(mergePatchRaw any, apiVersion, kind, namespace, name string) *typedPatch { + return &typedPatch{wrapped: &mergePatch{MergePatch: mergePatchRaw, APIVersion: apiVersion, Kind: kind, Namespace: namespace, Name: name}} } type mergePatch struct { MergePatch any - ApiVersion string + APIVersion string Kind string Namespace string Name string } -func NewJQFilter(JQFilter, apiVersion, kind, namespace, name string) *typedPatch { - return &typedPatch{wrapped: &jqFilter{JQFilter: JQFilter, ApiVersion: apiVersion, Kind: kind, Namespace: namespace, Name: name}} +func NewJQFilter(jqFilterStr, apiVersion, kind, namespace, name string) *typedPatch { + return &typedPatch{wrapped: &jqFilter{JQFilter: jqFilterStr, APIVersion: apiVersion, Kind: kind, Namespace: namespace, Name: name}} } type jqFilter struct { JQFilter string - ApiVersion string + APIVersion string Kind string Namespace string Name string } func NewPatchWithJQ(jqFilter, apiVersion, kind, namespace, name string) *typedPatch { - return &typedPatch{wrapped: &patchWithJQ{JQFilter: jqFilter, ApiVersion: apiVersion, Kind: kind, Namespace: namespace, Name: name}} + return &typedPatch{wrapped: &patchWithJQ{JQFilter: jqFilter, APIVersion: apiVersion, Kind: kind, Namespace: namespace, Name: name}} } type patchWithJQ struct { JQFilter string - ApiVersion string + APIVersion string Kind string Namespace string Name string } func NewPatchWithJSON(jsonPatch any, apiVersion, kind, namespace, name string) *typedPatch { - return &typedPatch{wrapped: &patchWithJSON{JsonPatch: jsonPatch, ApiVersion: apiVersion, Kind: kind, Namespace: namespace, Name: name}} + return &typedPatch{wrapped: &patchWithJSON{JSONPatch: jsonPatch, APIVersion: apiVersion, Kind: kind, Namespace: namespace, Name: name}} } type patchWithJSON struct { - JsonPatch any - ApiVersion string + JSONPatch any + APIVersion string Kind string Namespace string Name string } func NewPatchWithMerge(mergePatch any, apiVersion, kind, namespace, name string) *typedPatch { - return &typedPatch{wrapped: &patchWithMerge{MergePatch: mergePatch, ApiVersion: apiVersion, Kind: kind, Namespace: namespace, Name: name}} + return &typedPatch{wrapped: &patchWithMerge{MergePatch: mergePatch, APIVersion: apiVersion, Kind: kind, Namespace: namespace, Name: name}} } type patchWithMerge struct { MergePatch any - ApiVersion string + APIVersion string Kind string Namespace string Name string diff --git a/testing/helpers/snapshots.go b/testing/helpers/snapshots.go index bdd78c39..fdb6aa4f 100644 --- a/testing/helpers/snapshots.go +++ b/testing/helpers/snapshots.go @@ -7,11 +7,12 @@ import ( "strings" "testing" + "github.com/stretchr/testify/assert" + "gopkg.in/yaml.v3" + objectpatch "github.com/deckhouse/module-sdk/internal/object-patch" "github.com/deckhouse/module-sdk/pkg" "github.com/deckhouse/module-sdk/pkg/jq" - "github.com/stretchr/testify/assert" - "gopkg.in/yaml.v3" ) // input snapshots must be in the format: snapshotName: ["yaml1", "yaml2", ...] @@ -50,7 +51,7 @@ func PrepareHookSnapshots(t *testing.T, config *pkg.HookConfig, inputSnapshots m res, err := query.FilterStringObject(context.TODO(), string(jsonSnap)) assert.NoError(t, err, "Failed to filter snapshot with JQ query: %s", jsonSnap) - fmt.Println("JSON Snapshot:", string(res.String())) + fmt.Println("JSON Snapshot:", res.String()) formattedSnapshots[snapBindingName] = append(formattedSnapshots[snapBindingName], objectpatch.Snapshot(res.String())) } From 628c691dea7200f64a17aa8afc0f0c8189ea78a3 Mon Sep 17 00:00:00 2001 From: Pavel Okhlopkov Date: Wed, 29 Oct 2025 11:15:10 +0300 Subject: [PATCH 7/7] bump Signed-off-by: Pavel Okhlopkov --- common-hooks/tls-certificate/internal_tls_test.go | 2 +- testing/framework/init.go | 4 ++-- testing/helpers/hook_input.go | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/common-hooks/tls-certificate/internal_tls_test.go b/common-hooks/tls-certificate/internal_tls_test.go index 9bc38f47..c192329c 100644 --- a/common-hooks/tls-certificate/internal_tls_test.go +++ b/common-hooks/tls-certificate/internal_tls_test.go @@ -488,7 +488,7 @@ metadata: `, }}) - values := mock.NewPatchableValuesCollectorMock(t) + values := mock.NewOutputPatchableValuesCollectorMock(t) values.GetMock.When("global.discovery.clusterDomain").Then(gjson.Result{Type: gjson.String, Str: "cluster.local"}) values.GetMock.When("global.modules.publicDomainTemplate").Then(gjson.Result{Type: gjson.String, Str: "%s.127.0.0.1.sslip.io"}) diff --git a/testing/framework/init.go b/testing/framework/init.go index d2191e5d..844fb822 100644 --- a/testing/framework/init.go +++ b/testing/framework/init.go @@ -31,8 +31,8 @@ func NewHookFramework(t *testing.T, config *pkg.HookConfig, f pkg.ReconcileFunc) ReconcileFunc: f, HookInput: &pkg.HookInput{ Snapshots: mock.NewSnapshotsMock(t), - Values: mock.NewPatchableValuesCollectorMock(t), - ConfigValues: mock.NewPatchableValuesCollectorMock(t), + Values: mock.NewOutputPatchableValuesCollectorMock(t), + ConfigValues: mock.NewOutputPatchableValuesCollectorMock(t), PatchCollector: mock.NewPatchCollectorMock(t), MetricsCollector: mock.NewMetricsCollectorMock(t), DC: mock.NewDependencyContainerMock(t), diff --git a/testing/helpers/hook_input.go b/testing/helpers/hook_input.go index 47ea045b..9b85bb54 100644 --- a/testing/helpers/hook_input.go +++ b/testing/helpers/hook_input.go @@ -12,8 +12,8 @@ import ( func NewHookInput(t *testing.T) *pkg.HookInput { return &pkg.HookInput{ Snapshots: mock.NewSnapshotsMock(t), - Values: mock.NewPatchableValuesCollectorMock(t), - ConfigValues: mock.NewPatchableValuesCollectorMock(t), + Values: mock.NewOutputPatchableValuesCollectorMock(t), + ConfigValues: mock.NewOutputPatchableValuesCollectorMock(t), PatchCollector: mock.NewPatchCollectorMock(t), MetricsCollector: mock.NewMetricsCollectorMock(t), DC: mock.NewDependencyContainerMock(t),