github.com/myhau/pulumi/pkg/v3@v3.70.2-0.20221116134521-f2775972e587/resource/stack/deployment_test.go (about)

     1  // Copyright 2016-2022, Pulumi Corporation.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  //nolint:lll
    16  package stack
    17  
    18  import (
    19  	"context"
    20  	"encoding/json"
    21  	"fmt"
    22  	"strings"
    23  	"testing"
    24  
    25  	"github.com/stretchr/testify/assert"
    26  	"github.com/stretchr/testify/require"
    27  	"pgregory.net/rapid"
    28  
    29  	"github.com/pulumi/pulumi/pkg/v3/secrets/b64"
    30  	"github.com/pulumi/pulumi/sdk/v3/go/common/apitype"
    31  	"github.com/pulumi/pulumi/sdk/v3/go/common/resource"
    32  	"github.com/pulumi/pulumi/sdk/v3/go/common/resource/config"
    33  	resource_testing "github.com/pulumi/pulumi/sdk/v3/go/common/resource/testing"
    34  	"github.com/pulumi/pulumi/sdk/v3/go/common/tokens"
    35  	"github.com/pulumi/pulumi/sdk/v3/go/common/util/contract"
    36  )
    37  
    38  // TestDeploymentSerialization creates a basic snapshot of a given resource state.
    39  func TestDeploymentSerialization(t *testing.T) {
    40  	t.Parallel()
    41  
    42  	res := resource.NewState(
    43  		tokens.Type("Test"),
    44  		resource.NewURN(
    45  			tokens.QName("test"),
    46  			tokens.PackageName("resource/test"),
    47  			tokens.Type(""),
    48  			tokens.Type("Test"),
    49  			tokens.QName("resource-x"),
    50  		),
    51  		true,
    52  		false,
    53  		resource.ID("test-resource-x"),
    54  		resource.NewPropertyMapFromMap(map[string]interface{}{
    55  			"in-nil":         nil,
    56  			"in-bool":        true,
    57  			"in-float64":     float64(1.5),
    58  			"in-string":      "lumilumilo",
    59  			"in-array":       []interface{}{"a", true, float64(32)},
    60  			"in-empty-array": []interface{}{},
    61  			"in-map": map[string]interface{}{
    62  				"a": true,
    63  				"b": float64(88),
    64  				"c": "c-see-saw",
    65  				"d": "d-dee-daw",
    66  			},
    67  			"in-empty-map":                            map[string]interface{}{},
    68  			"in-component-resource-reference":         resource.MakeComponentResourceReference("urn", "1.2.3").V,
    69  			"in-custom-resource-reference":            resource.MakeCustomResourceReference("urn2", "id", "2.3.4").V,
    70  			"in-custom-resource-reference-unknown-id": resource.MakeCustomResourceReference("urn3", "", "3.4.5").V,
    71  		}),
    72  		resource.NewPropertyMapFromMap(map[string]interface{}{
    73  			"out-nil":         nil,
    74  			"out-bool":        false,
    75  			"out-float64":     float64(76),
    76  			"out-string":      "loyolumiloom",
    77  			"out-array":       []interface{}{false, "zzxx"},
    78  			"out-empty-array": []interface{}{},
    79  			"out-map": map[string]interface{}{
    80  				"x": false,
    81  				"y": "z-zee-zaw",
    82  				"z": float64(999.9),
    83  			},
    84  			"out-empty-map": map[string]interface{}{},
    85  		}),
    86  		"",
    87  		false,
    88  		false,
    89  		[]resource.URN{
    90  			resource.URN("foo:bar:baz"),
    91  			resource.URN("foo:bar:boo"),
    92  		},
    93  		[]string{},
    94  		"",
    95  		nil,
    96  		false,
    97  		nil,
    98  		nil,
    99  		nil,
   100  		"",
   101  		false,
   102  		"",
   103  	)
   104  
   105  	dep, err := SerializeResource(res, config.NopEncrypter, false /* showSecrets */)
   106  	assert.NoError(t, err)
   107  
   108  	// assert some things about the deployment record:
   109  	assert.NotNil(t, dep)
   110  	assert.NotNil(t, dep.ID)
   111  	assert.Equal(t, resource.ID("test-resource-x"), dep.ID)
   112  	assert.Equal(t, tokens.Type("Test"), dep.Type)
   113  	assert.Equal(t, 2, len(dep.Dependencies))
   114  	assert.Equal(t, resource.URN("foo:bar:baz"), dep.Dependencies[0])
   115  	assert.Equal(t, resource.URN("foo:bar:boo"), dep.Dependencies[1])
   116  
   117  	// assert some things about the inputs:
   118  	assert.NotNil(t, dep.Inputs)
   119  	assert.Nil(t, dep.Inputs["in-nil"])
   120  	assert.NotNil(t, dep.Inputs["in-bool"])
   121  	assert.True(t, dep.Inputs["in-bool"].(bool))
   122  	assert.NotNil(t, dep.Inputs["in-float64"])
   123  	assert.Equal(t, float64(1.5), dep.Inputs["in-float64"].(float64))
   124  	assert.NotNil(t, dep.Inputs["in-string"])
   125  	assert.Equal(t, "lumilumilo", dep.Inputs["in-string"].(string))
   126  	assert.NotNil(t, dep.Inputs["in-array"])
   127  	assert.Equal(t, 3, len(dep.Inputs["in-array"].([]interface{})))
   128  	assert.Equal(t, "a", dep.Inputs["in-array"].([]interface{})[0])
   129  	assert.Equal(t, true, dep.Inputs["in-array"].([]interface{})[1])
   130  	assert.Equal(t, float64(32), dep.Inputs["in-array"].([]interface{})[2])
   131  	assert.NotNil(t, dep.Inputs["in-empty-array"])
   132  	assert.Equal(t, 0, len(dep.Inputs["in-empty-array"].([]interface{})))
   133  	assert.NotNil(t, dep.Inputs["in-map"])
   134  	inmap := dep.Inputs["in-map"].(map[string]interface{})
   135  	assert.Equal(t, 4, len(inmap))
   136  	assert.NotNil(t, inmap["a"])
   137  	assert.Equal(t, true, inmap["a"].(bool))
   138  	assert.NotNil(t, inmap["b"])
   139  	assert.Equal(t, float64(88), inmap["b"].(float64))
   140  	assert.NotNil(t, inmap["c"])
   141  	assert.Equal(t, "c-see-saw", inmap["c"].(string))
   142  	assert.NotNil(t, inmap["d"])
   143  	assert.Equal(t, "d-dee-daw", inmap["d"].(string))
   144  	assert.NotNil(t, dep.Inputs["in-empty-map"])
   145  	assert.Equal(t, 0, len(dep.Inputs["in-empty-map"].(map[string]interface{})))
   146  	assert.Equal(t, map[string]interface{}{
   147  		resource.SigKey:  resource.ResourceReferenceSig,
   148  		"urn":            "urn",
   149  		"packageVersion": "1.2.3",
   150  	}, dep.Inputs["in-component-resource-reference"])
   151  	assert.Equal(t, map[string]interface{}{
   152  		resource.SigKey:  resource.ResourceReferenceSig,
   153  		"urn":            "urn2",
   154  		"id":             "id",
   155  		"packageVersion": "2.3.4",
   156  	}, dep.Inputs["in-custom-resource-reference"])
   157  	assert.Equal(t, map[string]interface{}{
   158  		resource.SigKey:  resource.ResourceReferenceSig,
   159  		"urn":            "urn3",
   160  		"id":             "",
   161  		"packageVersion": "3.4.5",
   162  	}, dep.Inputs["in-custom-resource-reference-unknown-id"])
   163  
   164  	// assert some things about the outputs:
   165  	assert.NotNil(t, dep.Outputs)
   166  	assert.Nil(t, dep.Outputs["out-nil"])
   167  	assert.NotNil(t, dep.Outputs["out-bool"])
   168  	assert.False(t, dep.Outputs["out-bool"].(bool))
   169  	assert.NotNil(t, dep.Outputs["out-float64"])
   170  	assert.Equal(t, float64(76), dep.Outputs["out-float64"].(float64))
   171  	assert.NotNil(t, dep.Outputs["out-string"])
   172  	assert.Equal(t, "loyolumiloom", dep.Outputs["out-string"].(string))
   173  	assert.NotNil(t, dep.Outputs["out-array"])
   174  	assert.Equal(t, 2, len(dep.Outputs["out-array"].([]interface{})))
   175  	assert.Equal(t, false, dep.Outputs["out-array"].([]interface{})[0])
   176  	assert.Equal(t, "zzxx", dep.Outputs["out-array"].([]interface{})[1])
   177  	assert.NotNil(t, dep.Outputs["out-empty-array"])
   178  	assert.Equal(t, 0, len(dep.Outputs["out-empty-array"].([]interface{})))
   179  	assert.NotNil(t, dep.Outputs["out-map"])
   180  	outmap := dep.Outputs["out-map"].(map[string]interface{})
   181  	assert.Equal(t, 3, len(outmap))
   182  	assert.NotNil(t, outmap["x"])
   183  	assert.Equal(t, false, outmap["x"].(bool))
   184  	assert.NotNil(t, outmap["y"])
   185  	assert.Equal(t, "z-zee-zaw", outmap["y"].(string))
   186  	assert.NotNil(t, outmap["z"])
   187  	assert.Equal(t, float64(999.9), outmap["z"].(float64))
   188  	assert.NotNil(t, dep.Outputs["out-empty-map"])
   189  	assert.Equal(t, 0, len(dep.Outputs["out-empty-map"].(map[string]interface{})))
   190  }
   191  
   192  func TestLoadTooNewDeployment(t *testing.T) {
   193  	t.Parallel()
   194  	ctx := context.Background()
   195  
   196  	untypedDeployment := &apitype.UntypedDeployment{
   197  		Version: apitype.DeploymentSchemaVersionCurrent + 1,
   198  	}
   199  
   200  	deployment, err := DeserializeUntypedDeployment(ctx, untypedDeployment, DefaultSecretsProvider)
   201  	assert.Nil(t, deployment)
   202  	assert.Error(t, err)
   203  	assert.Equal(t, ErrDeploymentSchemaVersionTooNew, err)
   204  }
   205  
   206  func TestLoadTooOldDeployment(t *testing.T) {
   207  	t.Parallel()
   208  	ctx := context.Background()
   209  
   210  	untypedDeployment := &apitype.UntypedDeployment{
   211  		Version: DeploymentSchemaVersionOldestSupported - 1,
   212  	}
   213  
   214  	deployment, err := DeserializeUntypedDeployment(ctx, untypedDeployment, DefaultSecretsProvider)
   215  	assert.Nil(t, deployment)
   216  	assert.Error(t, err)
   217  	assert.Equal(t, ErrDeploymentSchemaVersionTooOld, err)
   218  }
   219  
   220  func TestUnsupportedSecret(t *testing.T) {
   221  	t.Parallel()
   222  
   223  	rawProp := map[string]interface{}{
   224  		resource.SigKey: resource.SecretSig,
   225  	}
   226  	_, err := DeserializePropertyValue(rawProp, config.NewPanicCrypter(), config.NewPanicCrypter())
   227  	assert.Error(t, err)
   228  }
   229  
   230  func TestUnknownSig(t *testing.T) {
   231  	t.Parallel()
   232  
   233  	rawProp := map[string]interface{}{
   234  		resource.SigKey: "foobar",
   235  	}
   236  	_, err := DeserializePropertyValue(rawProp, config.NewPanicCrypter(), config.NewPanicCrypter())
   237  	assert.Error(t, err)
   238  }
   239  
   240  // TestDeserializeResourceReferencePropertyValueID tests the ability of the deserializer to handle resource references
   241  // that were serialized without unwrapping their ID PropertyValue due to a bug in the serializer. Such resource
   242  // references were produced by Pulumi v2.18.0.
   243  func TestDeserializeResourceReferencePropertyValueID(t *testing.T) {
   244  	t.Parallel()
   245  
   246  	// Serialize replicates Pulumi 2.18.0's buggy resource reference serializer. We round-trip the value through JSON
   247  	// in order to convert the ID property value into a plain map[string]interface{}.
   248  	serialize := func(v resource.PropertyValue) interface{} {
   249  		ref := v.ResourceReferenceValue()
   250  		bytes, err := json.Marshal(map[string]interface{}{
   251  			resource.SigKey:  resource.ResourceReferenceSig,
   252  			"urn":            ref.URN,
   253  			"id":             ref.ID,
   254  			"packageVersion": ref.PackageVersion,
   255  		})
   256  		contract.IgnoreError(err)
   257  		var sv interface{}
   258  		err = json.Unmarshal(bytes, &sv)
   259  		contract.IgnoreError(err)
   260  		return sv
   261  	}
   262  
   263  	serialized := map[string]interface{}{
   264  		"component-resource":         serialize(resource.MakeComponentResourceReference("urn", "1.2.3")),
   265  		"custom-resource":            serialize(resource.MakeCustomResourceReference("urn2", "id", "2.3.4")),
   266  		"custom-resource-unknown-id": serialize(resource.MakeCustomResourceReference("urn3", "", "3.4.5")),
   267  	}
   268  
   269  	deserialized, err := DeserializePropertyValue(serialized, config.NewPanicCrypter(), config.NewPanicCrypter())
   270  	assert.NoError(t, err)
   271  
   272  	assert.Equal(t, resource.NewPropertyValue(map[string]interface{}{
   273  		"component-resource":         resource.MakeComponentResourceReference("urn", "1.2.3").V,
   274  		"custom-resource":            resource.MakeCustomResourceReference("urn2", "id", "2.3.4").V,
   275  		"custom-resource-unknown-id": resource.MakeCustomResourceReference("urn3", "", "3.4.5").V,
   276  	}), deserialized)
   277  }
   278  
   279  func TestCustomSerialization(t *testing.T) {
   280  	t.Parallel()
   281  
   282  	textAsset, err := resource.NewTextAsset("alpha beta gamma")
   283  	assert.NoError(t, err)
   284  
   285  	strProp := resource.NewStringProperty("strProp")
   286  
   287  	computed := resource.Computed{Element: strProp}
   288  	output := resource.Output{Element: strProp}
   289  	secret := &resource.Secret{Element: strProp}
   290  
   291  	propMap := resource.NewPropertyMapFromMap(map[string]interface{}{
   292  		// Primitive types
   293  		"nil":     nil,
   294  		"bool":    true,
   295  		"int32":   int64(41),
   296  		"int64":   int64(42),
   297  		"float32": float32(2.5),
   298  		"float64": float64(1.5),
   299  		"string":  "string literal",
   300  
   301  		// Data structures
   302  		"array":       []interface{}{"a", true, float64(32)},
   303  		"array-empty": []interface{}{},
   304  
   305  		"map": map[string]interface{}{
   306  			"a": true,
   307  			"b": float64(88),
   308  			"c": "c-see-saw",
   309  			"d": "d-dee-daw",
   310  		},
   311  		"map-empty": map[string]interface{}{},
   312  
   313  		// Specialized resource types
   314  		"asset-text": textAsset,
   315  
   316  		"computed": computed,
   317  		"output":   output,
   318  		"secret":   secret,
   319  	})
   320  
   321  	assert.True(t, propMap.ContainsSecrets())
   322  	assert.True(t, propMap.ContainsUnknowns())
   323  
   324  	// Confirm the expected shape of serializing a ResourceProperty and PropertyMap using the
   325  	// reflection-based default JSON encoder. This should NOT be used when serializing resources,
   326  	// but we confirm the expected shape here while we migrate older code that relied on the
   327  	// specific format.
   328  	t.Run("SerializeToJSON", func(t *testing.T) {
   329  		t.Parallel()
   330  
   331  		b, err := json.Marshal(propMap)
   332  		if err != nil {
   333  			t.Fatalf("Marshalling PropertyMap: %v", err)
   334  		}
   335  		json := string(b)
   336  
   337  		// Look for the specific JSON serialization of the properties.
   338  		tests := []string{
   339  			// Primitives
   340  			`"nil":{"V":null}`,
   341  			`"bool":{"V":true}`,
   342  			`"string":{"V":"string literal"}}`,
   343  			`"float32":{"V":2.5}`,
   344  			`"float64":{"V":1.5}`,
   345  			`"int32":{"V":41}`,
   346  			`"int64":{"V":42}`,
   347  
   348  			// Data structures
   349  			`array":{"V":[{"V":"a"},{"V":true},{"V":32}]}`,
   350  			`"array-empty":{"V":[]}`,
   351  			`"map":{"V":{"a":{"V":true},"b":{"V":88},"c":{"V":"c-see-saw"},"d":{"V":"d-dee-daw"}}}`,
   352  			`"map-empty":{"V":{}}`,
   353  
   354  			// Specialized resource types
   355  			// nolint: lll
   356  			`"asset-text":{"V":{"4dabf18193072939515e22adb298388d":"c44067f5952c0a294b673a41bacd8c17","hash":"64989ccbf3efa9c84e2afe7cee9bc5828bf0fcb91e44f8c1e591638a2c2e90e3","text":"alpha beta gamma"}}`,
   357  
   358  			`"computed":{"V":{"Element":{"V":"strProp"}}}`,
   359  			`"output":{"V":{"Element":{"V":"strProp"}}}`,
   360  			`"secret":{"V":{"Element":{"V":"strProp"}}}`,
   361  		}
   362  
   363  		for _, want := range tests {
   364  			if !strings.Contains(json, want) {
   365  				t.Errorf("Did not find expected snippet: %v", want)
   366  			}
   367  		}
   368  
   369  		if t.Failed() {
   370  			t.Logf("Full JSON encoding:\n%v", json)
   371  		}
   372  	})
   373  
   374  	// Using stack.SerializeProperties will get the correct behavior and should be used
   375  	// whenever persisting resources into some durable form.
   376  	t.Run("SerializeProperties", func(t *testing.T) {
   377  		t.Parallel()
   378  
   379  		serializedPropMap, err := SerializeProperties(propMap, config.BlindingCrypter, false /* showSecrets */)
   380  		assert.NoError(t, err)
   381  
   382  		// Now JSON encode the results?
   383  		b, err := json.Marshal(serializedPropMap)
   384  		if err != nil {
   385  			t.Fatalf("Marshalling PropertyMap: %v", err)
   386  		}
   387  		json := string(b)
   388  
   389  		// Look for the specific JSON serialization of the properties.
   390  		tests := []string{
   391  			// Primitives
   392  			`"bool":true`,
   393  			`"string":"string literal"`,
   394  			`"float32":2.5`,
   395  			`"float64":1.5`,
   396  			`"int32":41`,
   397  			`"int64":42`,
   398  			`"nil":null`,
   399  
   400  			// Data structures
   401  			`"array":["a",true,32]`,
   402  			`"array-empty":[]`,
   403  			`"map":{"a":true,"b":88,"c":"c-see-saw","d":"d-dee-daw"}`,
   404  			`"map-empty":{}`,
   405  
   406  			// Specialized resource types
   407  			// nolint: lll
   408  			`"asset-text":{"4dabf18193072939515e22adb298388d":"c44067f5952c0a294b673a41bacd8c17","hash":"64989ccbf3efa9c84e2afe7cee9bc5828bf0fcb91e44f8c1e591638a2c2e90e3","text":"alpha beta gamma"}`,
   409  
   410  			// Computed values are replaced with a magic constant.
   411  			`"computed":"04da6b54-80e4-46f7-96ec-b56ff0331ba9"`,
   412  			`"output":"04da6b54-80e4-46f7-96ec-b56ff0331ba9"`,
   413  
   414  			// Secrets are serialized with the special sig key, and their underlying cipher text.
   415  			// Since we passed in a config.BlindingCrypter the cipher text isn't super-useful.
   416  			`"secret":{"4dabf18193072939515e22adb298388d":"1b47061264138c4ac30d75fd1eb44270","ciphertext":"[secret]"}`,
   417  		}
   418  		for _, want := range tests {
   419  			if !strings.Contains(json, want) {
   420  				t.Errorf("Did not find expected snippet: %v", want)
   421  			}
   422  		}
   423  
   424  		if t.Failed() {
   425  			t.Logf("Full JSON encoding:\n%v", json)
   426  		}
   427  	})
   428  }
   429  
   430  func TestDeserializeDeploymentSecretCache(t *testing.T) {
   431  	t.Parallel()
   432  
   433  	urn := "urn:pulumi:prod::acme::acme:erp:Backend$aws:ebs/volume:Volume::PlatformBackendDb"
   434  	ctx := context.Background()
   435  	_, err := DeserializeDeploymentV3(ctx, apitype.DeploymentV3{
   436  		SecretsProviders: &apitype.SecretsProvidersV1{Type: b64.Type},
   437  		Resources: []apitype.ResourceV3{
   438  			{
   439  				URN:    resource.URN(urn),
   440  				Type:   "aws:ebs/volume:Volume",
   441  				Custom: true,
   442  				ID:     "vol-044ba5ad2bd959bc1",
   443  			},
   444  		},
   445  	}, DefaultSecretsProvider)
   446  	assert.NoError(t, err)
   447  }
   448  
   449  func TestDeserializeInvalidResourceErrors(t *testing.T) {
   450  	t.Parallel()
   451  
   452  	ctx := context.Background()
   453  	deployment, err := DeserializeDeploymentV3(ctx, apitype.DeploymentV3{
   454  		Resources: []apitype.ResourceV3{
   455  			{},
   456  		},
   457  	}, DefaultSecretsProvider)
   458  	assert.Nil(t, deployment)
   459  	assert.Error(t, err)
   460  	assert.Equal(t, "resource missing required 'urn' field", err.Error())
   461  
   462  	urn := "urn:pulumi:prod::acme::acme:erp:Backend$aws:ebs/volume:Volume::PlatformBackendDb"
   463  
   464  	deployment, err = DeserializeDeploymentV3(ctx, apitype.DeploymentV3{
   465  		Resources: []apitype.ResourceV3{
   466  			{
   467  				URN: resource.URN(urn),
   468  			},
   469  		},
   470  	}, DefaultSecretsProvider)
   471  	assert.Nil(t, deployment)
   472  	assert.Error(t, err)
   473  	assert.Equal(t, fmt.Sprintf("resource '%s' missing required 'type' field", urn), err.Error())
   474  
   475  	deployment, err = DeserializeDeploymentV3(ctx, apitype.DeploymentV3{
   476  		Resources: []apitype.ResourceV3{
   477  			{
   478  				URN:    resource.URN(urn),
   479  				Type:   "aws:ebs/volume:Volume",
   480  				Custom: false,
   481  				ID:     "vol-044ba5ad2bd959bc1",
   482  			},
   483  		},
   484  	}, DefaultSecretsProvider)
   485  	assert.Nil(t, deployment)
   486  	assert.Error(t, err)
   487  	assert.Equal(t, fmt.Sprintf("resource '%s' has 'custom' false but non-empty ID", urn), err.Error())
   488  }
   489  
   490  func TestSerializePropertyValue(t *testing.T) {
   491  	t.Parallel()
   492  
   493  	rapid.Check(t, func(t *rapid.T) {
   494  		v := resource_testing.PropertyValueGenerator(6).Draw(t, "property value").(resource.PropertyValue)
   495  		_, err := SerializePropertyValue(v, config.NopEncrypter, false)
   496  		assert.NoError(t, err)
   497  	})
   498  }
   499  
   500  func TestDeserializePropertyValue(t *testing.T) {
   501  	t.Parallel()
   502  
   503  	rapid.Check(t, func(t *rapid.T) {
   504  		v := ObjectValueGenerator(6).Draw(t, "property value")
   505  		_, err := DeserializePropertyValue(v, config.NopDecrypter, config.NopEncrypter)
   506  		assert.NoError(t, err)
   507  	})
   508  }
   509  
   510  func wireValue(v resource.PropertyValue) (interface{}, error) {
   511  	object, err := SerializePropertyValue(v, config.NopEncrypter, false)
   512  	if err != nil {
   513  		return nil, err
   514  	}
   515  
   516  	wire, err := json.Marshal(object)
   517  	if err != nil {
   518  		return nil, err
   519  	}
   520  
   521  	var wireObject interface{}
   522  	err = json.Unmarshal(wire, &wireObject)
   523  	if err != nil {
   524  		return nil, err
   525  	}
   526  	return wireObject, nil
   527  }
   528  
   529  func TestPropertyValueSchema(t *testing.T) {
   530  	t.Parallel()
   531  
   532  	//nolint:paralleltest // uses rapid.T not golang testing.T
   533  	t.Run("serialized", rapid.MakeCheck(func(t *rapid.T) {
   534  		wireObject, err := wireValue(resource_testing.PropertyValueGenerator(6).Draw(t, "property value").(resource.PropertyValue))
   535  		require.NoError(t, err)
   536  
   537  		err = propertyValueSchema.Validate(wireObject)
   538  		assert.NoError(t, err)
   539  	}))
   540  
   541  	//nolint:paralleltest // uses rapid.T not golang testing.T
   542  	t.Run("synthetic", rapid.MakeCheck(func(t *rapid.T) {
   543  		wireObject := ObjectValueGenerator(6).Draw(t, "wire object")
   544  		err := propertyValueSchema.Validate(wireObject)
   545  		assert.NoError(t, err)
   546  	}))
   547  }
   548  
   549  func replaceOutputsWithComputed(v resource.PropertyValue) resource.PropertyValue {
   550  	switch {
   551  	case v.IsArray():
   552  		a := v.ArrayValue()
   553  		for i, v := range a {
   554  			a[i] = replaceOutputsWithComputed(v)
   555  		}
   556  	case v.IsObject():
   557  		o := v.ObjectValue()
   558  		for k, v := range o {
   559  			o[k] = replaceOutputsWithComputed(v)
   560  		}
   561  	case v.IsOutput():
   562  		return resource.MakeComputed(resource.NewStringProperty(""))
   563  	case v.IsSecret():
   564  		v.SecretValue().Element = replaceOutputsWithComputed(v.SecretValue().Element)
   565  	}
   566  	return v
   567  }
   568  
   569  func TestRoundTripPropertyValue(t *testing.T) {
   570  	t.Parallel()
   571  
   572  	rapid.Check(t, func(t *rapid.T) {
   573  		original := resource_testing.PropertyValueGenerator(6).Draw(t, "property value").(resource.PropertyValue)
   574  		wireObject, err := wireValue(original)
   575  		require.NoError(t, err)
   576  
   577  		deserialized, err := DeserializePropertyValue(wireObject, config.NopDecrypter, config.NopEncrypter)
   578  		require.NoError(t, err)
   579  
   580  		resource_testing.AssertEqualPropertyValues(t, replaceOutputsWithComputed(original), deserialized)
   581  	})
   582  }
   583  
   584  // UnknownObjectGenerator generates the unknown object value.
   585  func UnknownObjectGenerator() *rapid.Generator {
   586  	return rapid.Custom(func(t *rapid.T) interface{} {
   587  		return rapid.Just(computedValuePlaceholder).Draw(t, "unknowns")
   588  	})
   589  }
   590  
   591  // BoolObjectGenerator generates boolean object values.
   592  func BoolObjectGenerator() *rapid.Generator {
   593  	return rapid.Custom(func(t *rapid.T) interface{} {
   594  		return rapid.Bool().Draw(t, "booleans")
   595  	})
   596  }
   597  
   598  // NumberObjectGenerator generates numeric object values.
   599  func NumberObjectGenerator() *rapid.Generator {
   600  	return rapid.Custom(func(t *rapid.T) interface{} {
   601  		return rapid.Float64().Draw(t, "numbers")
   602  	})
   603  }
   604  
   605  // StringObjectGenerator generates string object values.
   606  func StringObjectGenerator() *rapid.Generator {
   607  	return rapid.Custom(func(t *rapid.T) interface{} {
   608  		return rapid.String().Draw(t, "strings")
   609  	})
   610  }
   611  
   612  // TextAssetObjectGenerator generates textual asset object values.
   613  func TextAssetObjectGenerator() *rapid.Generator {
   614  	return rapid.Custom(func(t *rapid.T) interface{} {
   615  		return map[string]interface{}{
   616  			resource.SigKey:            resource.AssetSig,
   617  			resource.AssetTextProperty: rapid.String().Draw(t, "text asset contents"),
   618  		}
   619  	})
   620  }
   621  
   622  // AssetObjectGenerator generates asset object values.
   623  func AssetObjectGenerator() *rapid.Generator {
   624  	return TextAssetObjectGenerator()
   625  }
   626  
   627  // LiteralArchiveObjectGenerator generates archive object values with literal archive contents.
   628  func LiteralArchiveObjectGenerator(maxDepth int) *rapid.Generator {
   629  	return rapid.Custom(func(t *rapid.T) map[string]interface{} {
   630  		var contentsGenerator *rapid.Generator
   631  		if maxDepth > 0 {
   632  			contentsGenerator = rapid.MapOfN(rapid.StringMatching(`^(/[^[:cntrl:]/]+)*/?[^[:cntrl:]/]+$`), rapid.OneOf(AssetObjectGenerator(), ArchiveObjectGenerator(maxDepth-1)), 0, 16)
   633  		} else {
   634  			contentsGenerator = rapid.Just(map[string]interface{}{})
   635  		}
   636  
   637  		return map[string]interface{}{
   638  			resource.SigKey:                resource.ArchiveSig,
   639  			resource.ArchiveAssetsProperty: contentsGenerator.Draw(t, "literal archive contents"),
   640  		}
   641  	})
   642  }
   643  
   644  // ArchiveObjectGenerator generates archive object values.
   645  func ArchiveObjectGenerator(maxDepth int) *rapid.Generator {
   646  	return LiteralArchiveObjectGenerator(maxDepth)
   647  }
   648  
   649  // ResourceReferenceObjectGenerator generates resource reference object values.
   650  func ResourceReferenceObjectGenerator() *rapid.Generator {
   651  	return rapid.Custom(func(t *rapid.T) interface{} {
   652  		fields := map[string]interface{}{
   653  			resource.SigKey:  resource.ResourceReferenceSig,
   654  			"urn":            string(resource_testing.URNGenerator().Draw(t, "referenced URN").(resource.URN)),
   655  			"packageVersion": resource_testing.SemverStringGenerator().Draw(t, "package version"),
   656  		}
   657  
   658  		id := rapid.OneOf(UnknownObjectGenerator(), StringObjectGenerator()).Draw(t, "referenced ID")
   659  		if idstr := id.(string); idstr != "" && idstr != computedValuePlaceholder {
   660  			fields["id"] = id
   661  		}
   662  
   663  		return fields
   664  	})
   665  }
   666  
   667  // ArrayObjectGenerator generates array object values. The maxDepth parameter controls the maximum
   668  // depth of the elements of the array.
   669  func ArrayObjectGenerator(maxDepth int) *rapid.Generator {
   670  	return rapid.Custom(func(t *rapid.T) interface{} {
   671  		return rapid.SliceOfN(ObjectValueGenerator(maxDepth-1), 0, 32).Draw(t, "array elements")
   672  	})
   673  }
   674  
   675  // MapObjectGenerator generates map object values. The maxDepth parameter controls the maximum
   676  // depth of the elements of the map.
   677  func MapObjectGenerator(maxDepth int) *rapid.Generator {
   678  	return rapid.Custom(func(t *rapid.T) interface{} {
   679  		return rapid.MapOfN(rapid.String(), ObjectValueGenerator(maxDepth-1), 0, 32).Draw(t, "map elements")
   680  	})
   681  }
   682  
   683  // SecretObjectGenerator generates secret object values. The maxDepth parameter controls the maximum
   684  // depth of the plaintext value of the secret, if any.
   685  func SecretObjectGenerator(maxDepth int) *rapid.Generator {
   686  	return rapid.Custom(func(t *rapid.T) interface{} {
   687  		value := ObjectValueGenerator(maxDepth-1).Draw(t, "secret element")
   688  		bytes, err := json.Marshal(value)
   689  		require.NoError(t, err)
   690  
   691  		return map[string]interface{}{
   692  			resource.SigKey: resource.SecretSig,
   693  			"plaintext":     string(bytes),
   694  		}
   695  	})
   696  }
   697  
   698  // ObjectValueGenerator generates arbitrary object values. The maxDepth parameter controls the maximum
   699  // number of times the generator may recur.
   700  func ObjectValueGenerator(maxDepth int) *rapid.Generator {
   701  	choices := []*rapid.Generator{
   702  		UnknownObjectGenerator(),
   703  		BoolObjectGenerator(),
   704  		NumberObjectGenerator(),
   705  		StringObjectGenerator(),
   706  		AssetObjectGenerator(),
   707  		ResourceReferenceObjectGenerator(),
   708  	}
   709  	if maxDepth > 0 {
   710  		choices = append(choices,
   711  			ArchiveObjectGenerator(maxDepth),
   712  			ArrayObjectGenerator(maxDepth),
   713  			MapObjectGenerator(maxDepth),
   714  			SecretObjectGenerator(maxDepth))
   715  	}
   716  	return rapid.OneOf(choices...)
   717  }