319 lines
6.6 KiB
Go
319 lines
6.6 KiB
Go
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 nesting": {
|
|
&Block{
|
|
Attributes: map[string]*Attribute{
|
|
"foo": {
|
|
NestedType: &Object{
|
|
Nesting: NestingSingle,
|
|
MinItems: 10,
|
|
MaxItems: 10,
|
|
},
|
|
Optional: true,
|
|
},
|
|
},
|
|
},
|
|
[]string{"foo: MinItems and MaxItems must be set to either 0 or 1 in NestingSingle mode"},
|
|
},
|
|
"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}
|
|
}
|
|
}
|