From 4a5e5438c4559c534d0807a04f5c28bcbf289b6b Mon Sep 17 00:00:00 2001 From: Ian Wahbe Date: Thu, 25 Jul 2024 18:06:30 -0700 Subject: [PATCH] Add requested tests --- infer/apply_secrets_test.go | 213 ++++++++++++++++++++++++++++++++++++ infer/resource.go | 4 +- tests/config_test.go | 29 ++++- 3 files changed, 243 insertions(+), 3 deletions(-) create mode 100644 infer/apply_secrets_test.go diff --git a/infer/apply_secrets_test.go b/infer/apply_secrets_test.go new file mode 100644 index 0000000..47be5cb --- /dev/null +++ b/infer/apply_secrets_test.go @@ -0,0 +1,213 @@ +// Copyright 2022, Pulumi Corporation. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package infer + +import ( + "reflect" + "testing" + + "github.com/pulumi/pulumi/sdk/v3/go/common/resource" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestApplySecrets(t *testing.T) { + t.Parallel() + + type nested struct { + F1 string `pulumi:"f1"` + } + + tests := []struct { + name string + input, expected resource.PropertyMap + typ reflect.Type + }{ + { + name: "no-secrets", + typ: typeFor[struct { + F1 string `pulumi:"f1"` + F2 map[string]string `pulumi:"f2"` + F3 map[string]nested `pulumi:"F3"` + F4 []string `pulumi:"F4"` + F5 []nested `pulumi:"F5"` + }](), + input: resource.NewPropertyMapFromMap(map[string]any{ + "f1": "v1", + "f2": map[string]any{ + "n1": "v2", + }, + "f3": map[string]any{ + "k1": map[string]any{"f1": "v3"}, + }, + "f4": []any{ + "v4", + "v5", + }, + "f5": []any{ + map[string]any{ + "k1": map[string]any{ + "f1": "v3", + }, + }, + }, + }), + expected: resource.PropertyMap{ + "f1": resource.NewProperty("v1"), + "f2": resource.NewProperty(resource.PropertyMap{ + "n1": resource.NewProperty("v2"), + }), + "f3": resource.NewProperty(resource.PropertyMap{ + "k1": resource.NewProperty(resource.PropertyMap{ + "f1": resource.NewProperty("v3"), + }), + }), + "f4": resource.NewProperty([]resource.PropertyValue{ + resource.NewProperty("v4"), + resource.NewProperty("v5"), + }), + "f5": resource.NewProperty([]resource.PropertyValue{ + resource.NewProperty(resource.PropertyMap{ + "k1": resource.NewProperty(resource.PropertyMap{ + "f1": resource.NewProperty("v3"), + }), + }), + }), + }, + }, + { + name: "nested-secrets", + typ: typeFor[struct { + F1 struct { + F1 string `pulumi:"f1" provider:"secret"` + } `pulumi:"f1"` + F2 []struct { + F1 string `pulumi:"f1" provider:"secret"` + } `pulumi:"f2"` + F3 map[string]struct { + F1 string `pulumi:"f1" provider:"secret"` + } `pulumi:"f3"` + F4 struct { + F1 struct { + F1 string `pulumi:"f1" provider:"secret"` + } `pulumi:"f1"` + } `pulumi:"f4"` + }](), + input: resource.NewPropertyMapFromMap(map[string]any{ + "f1": map[string]any{ + "f1": "secret1", + }, + "f2": []any{ + map[string]any{ + "f1": "secret2", + }, + }, + "f3": map[string]any{ + "key1": map[string]any{ + "f1": "secret3", + }, + }, + "f4": map[string]any{ + "f1": map[string]any{ + "f1": "secret4", + }, + }, + }), + expected: resource.PropertyMap{ + "f1": resource.NewProperty(resource.PropertyMap{ + "f1": resource.MakeSecret(resource.NewProperty("secret1")), + }), + "f2": resource.NewProperty([]resource.PropertyValue{ + resource.NewProperty(resource.PropertyMap{ + "f1": resource.MakeSecret(resource.NewProperty("secret2")), + }), + }), + "f3": resource.NewProperty(resource.PropertyMap{ + "key1": resource.NewProperty(resource.PropertyMap{ + "f1": resource.MakeSecret(resource.NewProperty("secret3")), + }), + }), + "f4": resource.NewProperty(resource.PropertyMap{ + "f1": resource.NewProperty(resource.PropertyMap{ + "f1": resource.MakeSecret(resource.NewProperty("secret4")), + }), + }), + }, + }, + { + name: "already-secret", + typ: typeFor[struct { + F1 string `pulumi:"f1" provider:"secret"` + F2 string `pulumi:"f2"` + }](), + input: resource.PropertyMap{ + "f1": resource.MakeSecret(resource.NewProperty("v1")), + "f2": resource.MakeSecret(resource.NewProperty("v2")), + }, + expected: resource.PropertyMap{ + "f1": resource.MakeSecret(resource.NewProperty("v1")), + "f2": resource.MakeSecret(resource.NewProperty("v2")), + }, + }, + { + name: "computed-input", + typ: typeFor[struct { + F1 string `pulumi:"f1" provider:"secret"` + }](), + input: resource.PropertyMap{ + "f1": resource.MakeComputed(resource.NewProperty("")), + }, + expected: resource.PropertyMap{ + "f1": resource.NewProperty(resource.Output{ + Element: resource.NewProperty(""), + Secret: true, + Known: false, + }), + }, + }, + { + name: "mismatched-types", + typ: typeFor[struct { + F1 string `pulumi:"f1" provider:"secret"` + F2 []struct { + F1 string `pulumi:"f1" provider:"secret"` + } `pulumi:"f2"` + }](), + input: resource.PropertyMap{ + "f2": resource.NewProperty([]resource.PropertyValue{ + resource.NewProperty("v2"), + }), + "f3": resource.NewProperty("v3"), + }, + expected: resource.PropertyMap{ + "f2": resource.NewProperty([]resource.PropertyValue{ + resource.NewProperty("v2"), + }), + "f3": resource.NewProperty("v3"), + }, + }, + } + + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + var walker secretsWalker + result := walker.walk(tt.typ, resource.NewProperty(tt.input)) + require.Empty(t, walker.errs) + assert.Equal(t, tt.expected, result.ObjectValue()) + }) + } +} diff --git a/infer/resource.go b/infer/resource.go index ca0a2b9..372d20b 100644 --- a/infer/resource.go +++ b/infer/resource.go @@ -99,8 +99,8 @@ type CustomCreate[I, O any] interface { // CustomCheck describes a resource that understands how to check its inputs. // // By default, infer handles checks by ensuring that a inputs de-serialize correctly, -// applying default values and secrets. If to build on the default version of Check, you -// can delegate to [DefaultCheck]. +// applying default values and secrets. You can wrap the default behavior of Check by +// calling [DefaultCheck] inside of your custom Check implementation. // // This is where you can extend that behavior. The // returned input is given to subsequent calls to `Create` and `Update`. diff --git a/tests/config_test.go b/tests/config_test.go index d4753ae..b126171 100644 --- a/tests/config_test.go +++ b/tests/config_test.go @@ -95,7 +95,13 @@ func TestInferCheckConfigSecrets(t *testing.T) { Int int `pulumi:"int" provider:"secret"` NotSecret string `pulumi:"not-nested"` } `pulumi:"nested"` - NotSecret string `pulumi:"not"` + NotSecret string `pulumi:"not"` + ArrayNested []struct { + Field string `pulumi:"field" provider:"secret"` + } `pulumi:"arrayNested"` + MapNested map[string]struct { + Field string `pulumi:"field" provider:"secret"` + } `pulumi:"mapNested"` } resp, err := integration.NewServer("test", semver.MustParse("0.0.0"), infer.Provider(infer.Options{ @@ -107,6 +113,16 @@ func TestInferCheckConfigSecrets(t *testing.T) { "int": resource.NewProperty(1.0), "not-nested": resource.NewProperty("not-secret"), }), + "arrayNested": resource.NewProperty([]resource.PropertyValue{ + resource.NewProperty(resource.PropertyMap{ + "field": resource.NewProperty("123"), + }), + }), + "mapNested": resource.NewProperty(resource.PropertyMap{ + "key": resource.NewProperty(resource.PropertyMap{ + "field": resource.NewProperty("123"), + }), + }), "not": resource.NewProperty("not-secret"), }, }) @@ -118,6 +134,16 @@ func TestInferCheckConfigSecrets(t *testing.T) { "int": resource.MakeSecret(resource.NewProperty(1.0)), "not-nested": resource.NewProperty("not-secret"), }), + "arrayNested": resource.NewProperty([]resource.PropertyValue{ + resource.NewProperty(resource.PropertyMap{ + "field": resource.MakeSecret(resource.NewProperty("123")), + }), + }), + "mapNested": resource.NewProperty(resource.PropertyMap{ + "key": resource.NewProperty(resource.PropertyMap{ + "field": resource.MakeSecret(resource.NewProperty("123")), + }), + }), "not": resource.NewProperty("not-secret"), }, resp.Inputs) } @@ -150,6 +176,7 @@ func TestInferCustomCheckConfig(t *testing.T) { resp, err := integration.NewServer("test", semver.MustParse("0.0.0"), infer.Provider(infer.Options{ Config: infer.Config[*config](), })).CheckConfig(p.CheckRequest{ + Urn: resource.CreateURN("p", "pulumi:providers:test", "", "test", "dev"), News: resource.PropertyMap{ "field": resource.NewProperty("value"), "nested": resource.NewProperty(resource.PropertyMap{