opentofu/internal/configs/configschema/internal_validate_test.go
Martin Atkins 31349a9c3a Move configs/ to internal/configs/
This is part of a general effort to move all of Terraform's non-library
package surface under internal in order to reinforce that these are for
internal use within Terraform only.

If you were previously importing packages under this prefix into an
external codebase, you could pin to an earlier release tag as an interim
solution until you've make a plan to achieve the same functionality some
other way.
2021-05-17 14:09:07 -07:00

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}
}
}