| package configschema |
| |
| import ( |
| "testing" |
| |
| "github.com/zclconf/go-cty/cty" |
| |
| multierror "github.com/hashicorp/go-multierror" |
| ) |
| |
| func TestBlockInternalValidate(t *testing.T) { |
| tests := map[string]struct { |
| Block *Block |
| Errs []string |
| }{ |
| "empty": { |
| &Block{}, |
| []string{}, |
| }, |
| "valid": { |
| &Block{ |
| Attributes: map[string]*Attribute{ |
| "foo": { |
| Type: cty.String, |
| Required: true, |
| }, |
| "bar": { |
| Type: cty.String, |
| Optional: true, |
| }, |
| "baz": { |
| Type: cty.String, |
| Computed: true, |
| }, |
| "baz_maybe": { |
| Type: cty.String, |
| Optional: true, |
| Computed: true, |
| }, |
| }, |
| BlockTypes: map[string]*NestedBlock{ |
| "single": { |
| Nesting: NestingSingle, |
| Block: Block{}, |
| }, |
| "single_required": { |
| Nesting: NestingSingle, |
| Block: Block{}, |
| MinItems: 1, |
| MaxItems: 1, |
| }, |
| "list": { |
| Nesting: NestingList, |
| Block: Block{}, |
| }, |
| "list_required": { |
| Nesting: NestingList, |
| Block: Block{}, |
| MinItems: 1, |
| }, |
| "set": { |
| Nesting: NestingSet, |
| Block: Block{}, |
| }, |
| "set_required": { |
| Nesting: NestingSet, |
| Block: Block{}, |
| MinItems: 1, |
| }, |
| "map": { |
| Nesting: NestingMap, |
| Block: Block{}, |
| }, |
| }, |
| }, |
| []string{}, |
| }, |
| "attribute with no flags set": { |
| &Block{ |
| Attributes: map[string]*Attribute{ |
| "foo": { |
| Type: cty.String, |
| }, |
| }, |
| }, |
| []string{"foo: must set Optional, Required or Computed"}, |
| }, |
| "attribute required and optional": { |
| &Block{ |
| Attributes: map[string]*Attribute{ |
| "foo": { |
| Type: cty.String, |
| Required: true, |
| Optional: true, |
| }, |
| }, |
| }, |
| []string{"foo: cannot set both Optional and Required"}, |
| }, |
| "attribute required and computed": { |
| &Block{ |
| Attributes: map[string]*Attribute{ |
| "foo": { |
| Type: cty.String, |
| Required: true, |
| Computed: true, |
| }, |
| }, |
| }, |
| []string{"foo: cannot set both Computed and Required"}, |
| }, |
| "attribute optional and computed": { |
| &Block{ |
| Attributes: map[string]*Attribute{ |
| "foo": { |
| Type: cty.String, |
| Optional: true, |
| Computed: true, |
| }, |
| }, |
| }, |
| []string{}, |
| }, |
| "attribute with missing type": { |
| &Block{ |
| Attributes: map[string]*Attribute{ |
| "foo": { |
| Optional: true, |
| }, |
| }, |
| }, |
| []string{"foo: either Type or NestedType must be defined"}, |
| }, |
| /* FIXME: This caused errors when applied to existing providers (oci) |
| and cannot be enforced without coordination. |
| |
| "attribute with invalid name": {&Block{Attributes: |
| map[string]*Attribute{"fooBar": {Type: cty.String, Optional: |
| true, |
| }, |
| }, |
| }, |
| []string{"fooBar: name may contain only lowercase letters, digits and underscores"}, |
| }, |
| */ |
| "attribute with invalid NestedType attribute": { |
| &Block{ |
| Attributes: map[string]*Attribute{ |
| "foo": { |
| NestedType: &Object{ |
| Nesting: NestingSingle, |
| Attributes: map[string]*Attribute{ |
| "foo": { |
| Type: cty.String, |
| Required: true, |
| Optional: true, |
| }, |
| }, |
| }, |
| Optional: true, |
| }, |
| }, |
| }, |
| []string{"foo: cannot set both Optional and Required"}, |
| }, |
| "block type with invalid name": { |
| &Block{ |
| BlockTypes: map[string]*NestedBlock{ |
| "fooBar": { |
| Nesting: NestingSingle, |
| }, |
| }, |
| }, |
| []string{"fooBar: name may contain only lowercase letters, digits and underscores"}, |
| }, |
| "colliding names": { |
| &Block{ |
| Attributes: map[string]*Attribute{ |
| "foo": { |
| Type: cty.String, |
| Optional: true, |
| }, |
| }, |
| BlockTypes: map[string]*NestedBlock{ |
| "foo": { |
| Nesting: NestingSingle, |
| }, |
| }, |
| }, |
| []string{"foo: name defined as both attribute and child block type"}, |
| }, |
| "nested block with badness": { |
| &Block{ |
| BlockTypes: map[string]*NestedBlock{ |
| "bad": { |
| Nesting: NestingSingle, |
| Block: Block{ |
| Attributes: map[string]*Attribute{ |
| "nested_bad": { |
| Type: cty.String, |
| Required: true, |
| Optional: true, |
| }, |
| }, |
| }, |
| }, |
| }, |
| }, |
| []string{"bad.nested_bad: cannot set both Optional and Required"}, |
| }, |
| "nested list block with dynamically-typed attribute": { |
| &Block{ |
| BlockTypes: map[string]*NestedBlock{ |
| "bad": { |
| Nesting: NestingList, |
| Block: Block{ |
| Attributes: map[string]*Attribute{ |
| "nested_bad": { |
| Type: cty.DynamicPseudoType, |
| Optional: true, |
| }, |
| }, |
| }, |
| }, |
| }, |
| }, |
| []string{}, |
| }, |
| "nested set block with dynamically-typed attribute": { |
| &Block{ |
| BlockTypes: map[string]*NestedBlock{ |
| "bad": { |
| Nesting: NestingSet, |
| Block: Block{ |
| Attributes: map[string]*Attribute{ |
| "nested_bad": { |
| Type: cty.DynamicPseudoType, |
| Optional: true, |
| }, |
| }, |
| }, |
| }, |
| }, |
| }, |
| []string{"bad: NestingSet blocks may not contain attributes of cty.DynamicPseudoType"}, |
| }, |
| "nil": { |
| nil, |
| []string{"top-level block schema is nil"}, |
| }, |
| "nil attr": { |
| &Block{ |
| Attributes: map[string]*Attribute{ |
| "bad": nil, |
| }, |
| }, |
| []string{"bad: attribute schema is nil"}, |
| }, |
| "nil block type": { |
| &Block{ |
| BlockTypes: map[string]*NestedBlock{ |
| "bad": nil, |
| }, |
| }, |
| []string{"bad: block schema is nil"}, |
| }, |
| } |
| |
| for name, test := range tests { |
| t.Run(name, func(t *testing.T) { |
| errs := multierrorErrors(test.Block.InternalValidate()) |
| if got, want := len(errs), len(test.Errs); got != want { |
| t.Errorf("wrong number of errors %d; want %d", got, want) |
| for _, err := range errs { |
| t.Logf("- %s", err.Error()) |
| } |
| } else { |
| if len(errs) > 0 { |
| for i := range errs { |
| if errs[i].Error() != test.Errs[i] { |
| t.Errorf("wrong error: got %s, want %s", errs[i].Error(), test.Errs[i]) |
| } |
| } |
| } |
| } |
| }) |
| } |
| } |
| |
| func multierrorErrors(err error) []error { |
| // A function like this should really be part of the multierror package... |
| |
| if err == nil { |
| return nil |
| } |
| |
| switch terr := err.(type) { |
| case *multierror.Error: |
| return terr.Errors |
| default: |
| return []error{err} |
| } |
| } |