mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-28 18:01:01 -06:00
0317da9911
This produces a "proposed new state", which already has prior computed values propagated into it (since that behavior is standard for all resource types) but could be customized further by the provider to make the "_planned_ new state". In the process of implementing this it became clear that our configschema DecoderSpec behavior is incorrect, since it's producing list values for NestingList and map values for NestingMap. While that seems like it should be right, we should actually be using tuple and object types respectively to allow each block to have a different runtime type in situations where an attribute is given the type cty.DynamicPseudoType. That's not fixed here, and so without a further fix list and map blocks will panic here. The DecoderSpec implementation will be fixed in a subsequent commit.
275 lines
5.4 KiB
Go
275 lines
5.4 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
|
|
ErrCount int
|
|
}{
|
|
"empty": {
|
|
&Block{},
|
|
0,
|
|
},
|
|
"valid": {
|
|
&Block{
|
|
Attributes: map[string]*Attribute{
|
|
"foo": &Attribute{
|
|
Type: cty.String,
|
|
Required: true,
|
|
},
|
|
"bar": &Attribute{
|
|
Type: cty.String,
|
|
Optional: true,
|
|
},
|
|
"baz": &Attribute{
|
|
Type: cty.String,
|
|
Computed: true,
|
|
},
|
|
"baz_maybe": &Attribute{
|
|
Type: cty.String,
|
|
Optional: true,
|
|
Computed: true,
|
|
},
|
|
},
|
|
BlockTypes: map[string]*NestedBlock{
|
|
"single": &NestedBlock{
|
|
Nesting: NestingSingle,
|
|
Block: Block{},
|
|
},
|
|
"single_required": &NestedBlock{
|
|
Nesting: NestingSingle,
|
|
Block: Block{},
|
|
MinItems: 1,
|
|
MaxItems: 1,
|
|
},
|
|
"list": &NestedBlock{
|
|
Nesting: NestingList,
|
|
Block: Block{},
|
|
},
|
|
"list_required": &NestedBlock{
|
|
Nesting: NestingList,
|
|
Block: Block{},
|
|
MinItems: 1,
|
|
},
|
|
"set": &NestedBlock{
|
|
Nesting: NestingSet,
|
|
Block: Block{},
|
|
},
|
|
"set_required": &NestedBlock{
|
|
Nesting: NestingSet,
|
|
Block: Block{},
|
|
MinItems: 1,
|
|
},
|
|
"map": &NestedBlock{
|
|
Nesting: NestingMap,
|
|
Block: Block{},
|
|
},
|
|
},
|
|
},
|
|
0,
|
|
},
|
|
"attribute with no flags set": {
|
|
&Block{
|
|
Attributes: map[string]*Attribute{
|
|
"foo": &Attribute{
|
|
Type: cty.String,
|
|
},
|
|
},
|
|
},
|
|
1, // must set one of the flags
|
|
},
|
|
"attribute required and optional": {
|
|
&Block{
|
|
Attributes: map[string]*Attribute{
|
|
"foo": &Attribute{
|
|
Type: cty.String,
|
|
Required: true,
|
|
Optional: true,
|
|
},
|
|
},
|
|
},
|
|
1, // both required and optional
|
|
},
|
|
"attribute required and computed": {
|
|
&Block{
|
|
Attributes: map[string]*Attribute{
|
|
"foo": &Attribute{
|
|
Type: cty.String,
|
|
Required: true,
|
|
Computed: true,
|
|
},
|
|
},
|
|
},
|
|
1, // both required and computed
|
|
},
|
|
"attribute optional and computed": {
|
|
&Block{
|
|
Attributes: map[string]*Attribute{
|
|
"foo": &Attribute{
|
|
Type: cty.String,
|
|
Optional: true,
|
|
Computed: true,
|
|
},
|
|
},
|
|
},
|
|
0,
|
|
},
|
|
"attribute with missing type": {
|
|
&Block{
|
|
Attributes: map[string]*Attribute{
|
|
"foo": &Attribute{
|
|
Optional: true,
|
|
},
|
|
},
|
|
},
|
|
1, // Type must be set
|
|
},
|
|
"attribute with invalid name": {
|
|
&Block{
|
|
Attributes: map[string]*Attribute{
|
|
"fooBar": &Attribute{
|
|
Type: cty.String,
|
|
Optional: true,
|
|
},
|
|
},
|
|
},
|
|
1, // name may not contain uppercase letters
|
|
},
|
|
"block type with invalid name": {
|
|
&Block{
|
|
BlockTypes: map[string]*NestedBlock{
|
|
"fooBar": &NestedBlock{
|
|
Nesting: NestingSingle,
|
|
},
|
|
},
|
|
},
|
|
1, // name may not contain uppercase letters
|
|
},
|
|
"colliding names": {
|
|
&Block{
|
|
Attributes: map[string]*Attribute{
|
|
"foo": &Attribute{
|
|
Type: cty.String,
|
|
Optional: true,
|
|
},
|
|
},
|
|
BlockTypes: map[string]*NestedBlock{
|
|
"foo": &NestedBlock{
|
|
Nesting: NestingSingle,
|
|
},
|
|
},
|
|
},
|
|
1, // "foo" is defined as both attribute and block type
|
|
},
|
|
"nested block with badness": {
|
|
&Block{
|
|
BlockTypes: map[string]*NestedBlock{
|
|
"bad": &NestedBlock{
|
|
Nesting: NestingSingle,
|
|
Block: Block{
|
|
Attributes: map[string]*Attribute{
|
|
"nested_bad": &Attribute{
|
|
Type: cty.String,
|
|
Required: true,
|
|
Optional: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
1, // nested_bad is both required and optional
|
|
},
|
|
"nested list block with dynamically-typed attribute": {
|
|
&Block{
|
|
BlockTypes: map[string]*NestedBlock{
|
|
"bad": &NestedBlock{
|
|
Nesting: NestingList,
|
|
Block: Block{
|
|
Attributes: map[string]*Attribute{
|
|
"nested_bad": &Attribute{
|
|
Type: cty.DynamicPseudoType,
|
|
Optional: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
0,
|
|
},
|
|
"nested set block with dynamically-typed attribute": {
|
|
&Block{
|
|
BlockTypes: map[string]*NestedBlock{
|
|
"bad": &NestedBlock{
|
|
Nesting: NestingSet,
|
|
Block: Block{
|
|
Attributes: map[string]*Attribute{
|
|
"nested_bad": &Attribute{
|
|
Type: cty.DynamicPseudoType,
|
|
Optional: true,
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
1, // NestingSet blocks may not contain attributes of cty.DynamicPseudoType
|
|
},
|
|
"nil": {
|
|
nil,
|
|
1, // block is nil
|
|
},
|
|
"nil attr": {
|
|
&Block{
|
|
Attributes: map[string]*Attribute{
|
|
"bad": nil,
|
|
},
|
|
},
|
|
1, // attribute schema is nil
|
|
},
|
|
"nil block type": {
|
|
&Block{
|
|
BlockTypes: map[string]*NestedBlock{
|
|
"bad": nil,
|
|
},
|
|
},
|
|
1, // 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), test.ErrCount; got != want {
|
|
t.Errorf("wrong number of errors %d; want %d", got, want)
|
|
for _, err := range errs {
|
|
t.Logf("- %s", err.Error())
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
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}
|
|
}
|
|
}
|