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 }