mirror of
https://github.com/opentofu/opentofu.git
synced 2025-01-18 20:52:58 -06:00
31349a9c3a
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.
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}
|
|
}
|
|
}
|