func TestAZModeValidate(t *testing.T) { template := &parse.Template{} prop := schema.Schema{} ctx := schema.NewInitialContext(template, schema.NewResourceDefinitions(nil), schema.ValidationOptions{}) singleAZCtx := schema.NewPropertyContext( schema.NewResourceContext(ctx, schema.ResourceWithDefinition{ parse.NewTemplateResource("", map[string]interface{}{ "PreferredAvailabilityZones": []interface{}{"one"}, }), schema.Resource{}, }), prop, ) multiAZCtx := schema.NewPropertyContext( schema.NewResourceContext(ctx, schema.ResourceWithDefinition{ parse.NewTemplateResource("", map[string]interface{}{ "PreferredAvailabilityZones": []interface{}{"one", "two"}, }), schema.Resource{}, }), prop) if _, errs := azModeValidate("cross-az", singleAZCtx); errs == nil { t.Error("Should fail if cross-az with single availability zone", errs) } if _, errs := azModeValidate("cross-az", multiAZCtx); errs != nil { t.Error("Should pass if cross-az with multiple availability zones", errs) } }
func TestResourcePropertyConflictValidation(t *testing.T) { template := &parse.Template{} res := Resource{ Properties: Properties{ "Option1": Schema{ Type: ValueString, Conflicts: constraints.PropertyExists("Option2"), }, "Option2": Schema{ Type: ValueString, Conflicts: constraints.PropertyExists("Option1"), }, }, } ctx := NewInitialContext(template, NewResourceDefinitions(map[string]Resource{ "TestResource": res, }), ValidationOptions{}) nothingSet := ResourceWithDefinition{ parse.NewTemplateResource("TestResource", map[string]interface{}{}), res, } option1Set := ResourceWithDefinition{ parse.NewTemplateResource("TestResource", map[string]interface{}{ "Option1": "value", }), res, } option2Set := ResourceWithDefinition{ parse.NewTemplateResource("TestResource", map[string]interface{}{ "Option2": "value", }), res, } bothSet := ResourceWithDefinition{ parse.NewTemplateResource("TestResource", map[string]interface{}{ "Option1": "value", "Option2": "value", }), res, } if _, errs := res.Validate(NewResourceContext(ctx, nothingSet)); errs != nil { t.Error("Resource should pass if both neither Option1 or Option2 are set", errs) } if _, errs := res.Validate(NewResourceContext(ctx, option1Set)); errs != nil { t.Error("Resource should pass if only Option1 set", errs) } if _, errs := res.Validate(NewResourceContext(ctx, option2Set)); errs != nil { t.Error("Resource should pass if only Option2 set", errs) } if _, errs := res.Validate(NewResourceContext(ctx, bothSet)); errs == nil { t.Error("Resource should fail if both Option1 or Option2 are set") } }
func TestGeoLocationSubdivisionCodeValidation(t *testing.T) { template := &parse.Template{} res := schema.Resource{ Properties: schema.Properties{ "GeoLocation": schema.Schema{ Type: geoLocation, }, }, } ctx := schema.NewInitialContext(template, schema.NewResourceDefinitions(map[string]schema.Resource{ "TestResource": res, }), schema.ValidationOptions{}) badCountryCtx := schema.NewResourceContext(ctx, schema.ResourceWithDefinition{ parse.NewTemplateResource("TestResource", map[string]interface{}{ "GeoLocation": map[string]interface{}{ "SubdivisionCode": "AK", "CountryCode": "AU", }, }), res, }) badSubdivisionCtx := schema.NewResourceContext(ctx, schema.ResourceWithDefinition{ parse.NewTemplateResource("TestResource", map[string]interface{}{ "GeoLocation": map[string]interface{}{ "SubdivisionCode": "NSW", "CountryCode": "US", }, }), res, }) goodCombinationCtx := schema.NewResourceContext(ctx, schema.ResourceWithDefinition{ parse.NewTemplateResource("TestResource", map[string]interface{}{ "GeoLocation": map[string]interface{}{ "SubdivisionCode": "AK", "CountryCode": "US", }, }), res, }) if _, errs := res.Validate(goodCombinationCtx); errs != nil { t.Error("Period should pass on a valid state with US as the country", errs) } if _, errs := res.Validate(badSubdivisionCtx); errs == nil { t.Error("Period should fail on an invalid subdivision with US as the country") } if _, errs := res.Validate(badCountryCtx); errs == nil { t.Error("Period should fail when subdivision set without US as the country") } }
func TestPeriodValidation(t *testing.T) { template := &parse.Template{} self := ResourceWithDefinition{ parse.NewTemplateResource("", make(map[string]interface{})), Resource{}, } ctx := NewContextShorthand(template, NewResourceDefinitions(nil), self, Schema{}, ValidationOptions{}) if _, errs := Period.Validate("", ctx); errs == nil { t.Error("Period should fail on empty string") } if _, errs := Period.Validate("abc", ctx); errs == nil { t.Error("Period should fail on anything which isn't a period") } for _, ex := range []string{"0", "10", "119", "260"} { if _, errs := Period.Validate(ex, ctx); errs == nil { t.Errorf("Period should fail on number which isn't a multiple of 60 (ex: %s)", ex) } } for _, ex := range []string{"60", "120", "240"} { if _, errs := Period.Validate(ex, ctx); errs != nil { t.Errorf("Cidr should pass with numbers which are multiples of 60 (ex: %s)", ex) } } }
func TestUnexpectedProperties(t *testing.T) { res := Resource{ Properties: Properties{ "Expected": Schema{ Type: ValueString, }, }, } template := &parse.Template{} ctx := NewInitialContext(template, NewResourceDefinitions(map[string]Resource{ "TestResource": res, }), ValidationOptions{}) unexpected := ResourceWithDefinition{ parse.NewTemplateResource("TestResource", map[string]interface{}{ "Expected": "value", "SomethingElse": "value", }), res, } if _, errs := res.Validate(NewResourceContext(ctx, unexpected)); errs == nil { t.Error("Unexpected property should fail validation") } }
func TestCidrValidation(t *testing.T) { template := &parse.Template{} self := ResourceWithDefinition{ parse.NewTemplateResource("", make(map[string]interface{})), Resource{}, } ctx := NewContextShorthand(template, NewResourceDefinitions(nil), self, Schema{}, ValidationOptions{}) if _, errs := CIDR.Validate("", ctx); errs == nil { t.Error("Cidr should fail on empty str, niling") } if _, errs := CIDR.Validate("abc", ctx); errs == nil { t.Error("Cidr should fail on anything which isn't a cidr") } if _, errs := CIDR.Validate("0.0.0.0/100", ctx); errs == nil { t.Error("Cidr should fail on an invalid mask") } if _, errs := CIDR.Validate("10.200.300.10/24", ctx); errs == nil { t.Error("Cidr should fail on an invalid IP") } if _, errs := CIDR.Validate("10.200.30.10/24", ctx); errs != nil { t.Error("Cidr should pass with a valid cidr") } }
func TestNumCacheNodesValidate(t *testing.T) { template := &parse.Template{} prop := schema.Schema{} ctx := schema.NewInitialContext(template, schema.NewResourceDefinitions(nil), schema.ValidationOptions{}) redisCtx := schema.NewPropertyContext( schema.NewResourceContext(ctx, schema.ResourceWithDefinition{ parse.NewTemplateResource("", map[string]interface{}{ "Engine": "redis", }), schema.Resource{}, }), prop) memcachedCtx := schema.NewPropertyContext( schema.NewResourceContext(ctx, schema.ResourceWithDefinition{ parse.NewTemplateResource("", map[string]interface{}{ "Engine": "memcached", }), schema.Resource{}, }), prop) if _, errs := numCacheNodesValidate(float64(1), redisCtx); errs != nil { t.Error("Should pass with 1 redis node", errs) } if _, errs := numCacheNodesValidate(float64(2), redisCtx); errs == nil { t.Error("Should fail with more than 1 redis node", errs) } if _, errs := numCacheNodesValidate(float64(1), memcachedCtx); errs != nil { t.Error("Should pass with 1 memcached node", errs) } if _, errs := numCacheNodesValidate(float64(20), memcachedCtx); errs != nil { t.Error("Should pass with 20 memcached nodes", errs) } if _, errs := numCacheNodesValidate(float64(21), memcachedCtx); errs == nil { t.Error("Should fail with 21 memcached nodes", errs) } }
func TestAllowedMethodsFixedArrays(t *testing.T) { res := Distribution template := &parse.Template{} ctx := schema.NewInitialContext(template, schema.NewResourceDefinitions(map[string]schema.Resource{ "TestResource": res, }), schema.ValidationOptions{}) testCFDistribution := func(allowedMethods []interface{}) schema.ResourceContext { return schema.NewResourceContext(ctx, schema.ResourceWithDefinition{ parse.NewTemplateResource("TestResource", map[string]interface{}{ "DistributionConfig": map[string]interface{}{ "Enabled": true, "DefaultCacheBehavior": map[string]interface{}{ "AllowedMethods": allowedMethods, "TargetOriginId": "test", "ViewerProtocolPolicy": "test", "ForwardedValues": map[string]interface{}{ "QueryString": true, }, }, "Origins": []interface{}{ map[string]interface{}{ "Id": "test", "DomainName": "test", "CustomOriginConfig": map[string]interface{}{ "OriginProtocolPolicy": "test", }, }, }, }, }), res, }) } if _, errs := res.Validate(testCFDistribution([]interface{}{"HEAD", "GET"})); errs != nil { t.Error("Should pass with expected array", errs) } if _, errs := res.Validate(testCFDistribution([]interface{}{"GET", "HEAD"})); errs != nil { t.Error("Should pass with expected array in different order", errs) } if _, errs := res.Validate(testCFDistribution([]interface{}{"DELETE", "GET", "HEAD"})); errs == nil { t.Error("Should fail with random subset") } if _, errs := res.Validate(testCFDistribution([]interface{}{"GET", "HEAD", "somethingElse"})); errs == nil { t.Error("Should fail with unexpected item") } }
// TODO: This is all a bit hairy. We shouldn't need to be creating the // TemplateNestedResource here, ideally `self` should already refer to // one and value should already be a map[string]inteface{} func (res NestedResource) Validate(value interface{}, ctx PropertyContext) (reporting.ValidateResult, reporting.Reports) { if values, ok := value.(map[string]interface{}); ok { property := ctx.Property() tnr := parse.NewTemplateResource(property.Type.Describe(), values) nestedResourceContext := NewResourceContext(ctx, ResourceWithDefinition{tnr, property.Type}) failures := res.Properties.Validate(nestedResourceContext) return reporting.ValidateOK, reporting.Safe(failures) } return reporting.ValidateOK, reporting.Reports{reporting.NewFailure(ctx, "Invalid type %T for nested resource %s", value, res.Description)} }
func TestSchemaMapToArrayCoercion(t *testing.T) { res := parse.NewTemplateResource("TestType", map[string]interface{}{ "NestedArrayProp": map[string]interface{}{ "StringProp": "blah", }, }) prop := Schema{ Type: Multiple(NestedResource{ Description: "TestType", Properties: Properties{ "StringProp": Schema{ Type: ValueString, Required: constraints.Always, }, }, }), } def := Resource{ Properties: Properties{ "NestedArrayProp": prop, }, } defaultOptionsBase := NewInitialContext(&parse.Template{}, NewResourceDefinitions(nil), ValidationOptions{}) defaultOptionsCtx := NewResourceContext(defaultOptionsBase, ResourceWithDefinition{res, def}) mapCoercionOptionsBase := NewInitialContext(&parse.Template{}, NewResourceDefinitions(nil), ValidationOptions{ OptionExperimentDisableObjectArrayCoercion: true, }) mapCoercionOptionsCtx := NewResourceContext(mapCoercionOptionsBase, ResourceWithDefinition{res, def}) value := map[string]interface{}{ "StringProp": "blah", } if _, errs := prop.Validate(value, mapCoercionOptionsCtx); errs == nil { t.Error("Shouldn't be able to coerce a map into an array property", errs) } if _, errs := prop.Validate(value, defaultOptionsCtx); errs == nil { t.Error("Shouldn't be able to coerce a map into an array property without a warning", errs, len(errs)) } else if errs[0].Level != reporting.Warning { t.Error("Shouldn't be able to coerce a map into an array property without a warning", errs, len(errs)) } }
func TestEnumValidation(t *testing.T) { template := &parse.Template{} self := ResourceWithDefinition{ parse.NewTemplateResource("", make(map[string]interface{})), Resource{}, } ctx := NewContextShorthand(template, NewResourceDefinitions(nil), self, Schema{}, ValidationOptions{}) enum := EnumValue{ Options: []string{"a", "b", "c"}, } if _, errs := enum.Validate("", ctx); errs == nil { t.Error("Enum should fail on empty string") } if _, errs := enum.Validate("d", ctx); errs == nil { t.Error("Enum should fail on anything which isn't a valid option") } if _, errs := enum.Validate("b", ctx); errs != nil { t.Error("Enum should pass on a valid option") } }
func TestDeprecatedProperties(t *testing.T) { res := Resource{ Properties: Properties{ "Deprecated": Schema{ Type: ValueString, Deprecated: deprecations.Deprecated("blah blah."), }, "DeprecatedBy": Schema{ Type: ValueString, Deprecated: deprecations.ReplacedBy("SomethingElse", "blah blah."), }, }, } template := &parse.Template{} ctx := NewInitialContext(template, NewResourceDefinitions(map[string]Resource{ "TestResource": res, }), ValidationOptions{}) unexpected := ResourceWithDefinition{ parse.NewTemplateResource("TestResource", map[string]interface{}{ "Deprecated": "value", "DeprecatedBy": "value", }), res, } if _, errs := res.Validate(NewResourceContext(ctx, unexpected)); !hasWarning(errs, "Deprecated: blah blah.") { t.Errorf("Deprecated property use should warn (errs: %s)", errs) } if _, errs := res.Validate(NewResourceContext(ctx, unexpected)); !hasWarning(errs, "Replaced by SomethingElse: blah blah.") { t.Errorf("Deprecated property use should warn (errs: %s)", errs) } }
func TestAutomaticFailoverEnabled(t *testing.T) { template := &parse.Template{} res := ReplicationGroup ctx := schema.NewInitialContext(template, schema.NewResourceDefinitions(map[string]schema.Resource{ "TestResource": res, }), schema.ValidationOptions{}) badVersionCtx := schema.NewPropertyContext( schema.NewResourceContext(ctx, schema.ResourceWithDefinition{ parse.NewTemplateResource("TestResource", map[string]interface{}{ "EngineVersion": "2.7", "CacheNodeType": "cache.m3.medium", }), res, }), schema.Schema{}) badNodeTypeT1Ctx := schema.NewPropertyContext( schema.NewResourceContext(ctx, schema.ResourceWithDefinition{ parse.NewTemplateResource("TestResource", map[string]interface{}{ "EngineVersion": "2.8", "CacheNodeType": "cache.t1.micro", }), res, }), schema.Schema{}) badNodeTypeT2Ctx := schema.NewPropertyContext( schema.NewResourceContext(ctx, schema.ResourceWithDefinition{ parse.NewTemplateResource("TestResource", map[string]interface{}{ "EngineVersion": "2.8", "CacheNodeType": "cache.t2.micro", }), res, }), schema.Schema{}) goodCtx := schema.NewPropertyContext( schema.NewResourceContext(ctx, schema.ResourceWithDefinition{ parse.NewTemplateResource("TestResource", map[string]interface{}{ "EngineVersion": "2.8", "CacheNodeType": "cache.m3.medium", }), res, }), schema.Schema{}) if _, errs := automaticFailoverEnabledValidation(true, badVersionCtx); errs == nil { t.Error("Should fail if has engine less than 2.8") } if _, errs := automaticFailoverEnabledValidation(true, badNodeTypeT1Ctx); errs == nil { t.Error("Should fail if has node type of t1 or t2") } if _, errs := automaticFailoverEnabledValidation(true, badNodeTypeT2Ctx); errs == nil { t.Error("Should fail if has node type of t1 or t2") } if _, errs := automaticFailoverEnabledValidation(true, goodCtx); errs != nil { t.Error("Should pass if engine is 2.8 or above and node type isn't t1 or t2") } }
func TestSchemaRequiredValidation(t *testing.T) { template := &parse.Template{} res := Resource{ Properties: Properties{ "Option1": Schema{ Type: ValueString, Required: constraints.Always, }, "Option2": Schema{ Type: ValueString, Required: constraints.Never, }, "Option3": Schema{ Type: ValueString, }, }, } ctx := NewInitialContext(template, NewResourceDefinitions(map[string]Resource{ "TestResource": res, }), ValidationOptions{}) nothingSet := ResourceWithDefinition{ parse.NewTemplateResource("TestResource", map[string]interface{}{}), res, } option1Set := ResourceWithDefinition{ parse.NewTemplateResource("TestResource", map[string]interface{}{ "Option1": "value", }), res, } option2Set := ResourceWithDefinition{ parse.NewTemplateResource("TestResource", map[string]interface{}{ "Option2": "value", }), res, } option3Set := ResourceWithDefinition{ parse.NewTemplateResource("TestResource", map[string]interface{}{ "Option3": "value", }), res, } allSet := ResourceWithDefinition{ parse.NewTemplateResource("TestResource", map[string]interface{}{ "Option1": "value", "Option2": "value", "Option3": "value", }), res, } if _, errs := res.Validate(NewResourceContext(ctx, nothingSet)); errs == nil { t.Error("Resource should fail if Option1 isn't set", errs) } if _, errs := res.Validate(NewResourceContext(ctx, option1Set)); errs != nil { t.Error("Resource should pass if only Option1 set", errs) } if _, errs := res.Validate(NewResourceContext(ctx, option2Set)); errs == nil { t.Error("Resource should fail if only Option2 set") } if _, errs := res.Validate(NewResourceContext(ctx, option3Set)); errs == nil { t.Error("Resource should fail if only Option3 set") } if _, errs := res.Validate(NewResourceContext(ctx, allSet)); errs != nil { t.Error("Resource should pass if Option1 is set with others", errs) } }
func TestNestedResourceConstraints(t *testing.T) { res := Resource{ Properties: Properties{ "Nested": Schema{ Type: NestedResource{ Properties: Properties{ "One": Schema{ Type: ValueString, }, "Two": Schema{ Type: ValueString, Required: constraints.PropertyExists("One"), }, }, }, }, }, } template := &parse.Template{} data := func(properties map[string]interface{}) ResourceContext { return NewContextShorthand( template, NewResourceDefinitions(map[string]Resource{ "TestResource": res, }), ResourceWithDefinition{ parse.NewTemplateResource("TestResource", properties), res, }, Schema{}, ValidationOptions{}, ) } twoMissing := map[string]interface{}{ "Nested": map[string]interface{}{ "One": "abc", }, } if _, errs := res.Validate(data(twoMissing)); errs == nil { t.Error("Should fail with missing Two parameter") } oneInWrongPace := map[string]interface{}{ "One": "abc", "Nested": map[string]interface{}{}, } if _, errs := res.Validate(data(oneInWrongPace)); errs == nil { t.Error("Should fail with missing Two parameter") } allFine := map[string]interface{}{ "Nested": map[string]interface{}{ "One": "abc", "Two": "abc", }, } if _, errs := res.Validate(data(allFine)); errs != nil { t.Error("Should pass with One and Two", errs) } }