opentofu/command/jsonplan/plan_test.go
Martin Atkins d512584497 command/jsonplan: Don't panic with mixtures of known/unknown/empty
The omitUnknowns and unknownAsBool functions were previously trying hard
to preserve the same collection types in the output as they had in the
input, by attempting to keep everything matched up so that the results
would be valid.

Unfortunately, this turns out to be a harder problem than we originally
thought: it was possible for a collection value going in to produce
inconsistent element types out (and thus a panic) in the following
situations:
- when a collection with mixed known and unknown values was passed in
  to omitUnknowns.
- when a collection of collections where the inner collections are a
  mixture of empty and not empty in unknownAsNull.

The results of these functions are only used to marshal to JSON anyway,
and JSON serialization can't distinguish between the three sequence types
or the two mapping types, so in practice we can just standardize on
converting all sequences to tuple and all mappings to object here and not
change the resulting output at all, and then we don't have to worry about
making sure all of the inner types get preserved exactly.

A nice consequence of that relaxation is that we can now do what we
originally wanted to do with unknownAsBool, and omit map keys and
object attributes altogether if their values would've been false,
producing a much more compact result. This is easiest to do now when
there's only one known user of this JSON plan output, and we know that
user will treat both false and omitted as the same here.
2019-05-28 19:33:21 -07:00

265 lines
5.4 KiB
Go

package jsonplan
import (
"reflect"
"testing"
"github.com/zclconf/go-cty/cty"
)
func TestOmitUnknowns(t *testing.T) {
tests := []struct {
Input cty.Value
Want cty.Value
}{
{
cty.StringVal("hello"),
cty.StringVal("hello"),
},
{
cty.NullVal(cty.String),
cty.NullVal(cty.String),
},
{
cty.UnknownVal(cty.String),
cty.NilVal,
},
{
cty.ListValEmpty(cty.String),
cty.EmptyTupleVal,
},
{
cty.ListVal([]cty.Value{cty.StringVal("hello")}),
cty.TupleVal([]cty.Value{cty.StringVal("hello")}),
},
{
cty.ListVal([]cty.Value{cty.NullVal(cty.String)}),
cty.TupleVal([]cty.Value{cty.NullVal(cty.String)}),
},
{
cty.ListVal([]cty.Value{cty.UnknownVal(cty.String)}),
cty.TupleVal([]cty.Value{cty.NullVal(cty.String)}),
},
{
cty.ListVal([]cty.Value{cty.StringVal("hello")}),
cty.TupleVal([]cty.Value{cty.StringVal("hello")}),
},
//
{
cty.ListVal([]cty.Value{
cty.StringVal("hello"),
cty.UnknownVal(cty.String)}),
cty.TupleVal([]cty.Value{
cty.StringVal("hello"),
cty.NullVal(cty.String),
}),
},
{
cty.MapVal(map[string]cty.Value{
"hello": cty.True,
"world": cty.UnknownVal(cty.Bool),
}),
cty.ObjectVal(map[string]cty.Value{
"hello": cty.True,
}),
},
{
cty.SetVal([]cty.Value{
cty.StringVal("dev"),
cty.StringVal("foo"),
cty.StringVal("stg"),
cty.UnknownVal(cty.String),
}),
cty.TupleVal([]cty.Value{
cty.StringVal("dev"),
cty.StringVal("foo"),
cty.StringVal("stg"),
}),
},
{
cty.SetVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"a": cty.UnknownVal(cty.String),
}),
cty.ObjectVal(map[string]cty.Value{
"a": cty.StringVal("known"),
}),
}),
cty.TupleVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"a": cty.StringVal("known"),
}),
cty.EmptyObjectVal,
}),
},
}
for _, test := range tests {
got := omitUnknowns(test.Input)
if !reflect.DeepEqual(got, test.Want) {
t.Errorf(
"wrong result\ninput: %#v\ngot: %#v\nwant: %#v",
test.Input, got, test.Want,
)
}
}
}
func TestUnknownAsBool(t *testing.T) {
tests := []struct {
Input cty.Value
Want cty.Value
}{
{
cty.StringVal("hello"),
cty.False,
},
{
cty.NullVal(cty.String),
cty.False,
},
{
cty.UnknownVal(cty.String),
cty.True,
},
{
cty.NullVal(cty.DynamicPseudoType),
cty.False,
},
{
cty.NullVal(cty.Object(map[string]cty.Type{"test": cty.String})),
cty.False,
},
{
cty.DynamicVal,
cty.True,
},
{
cty.ListValEmpty(cty.String),
cty.EmptyTupleVal,
},
{
cty.ListVal([]cty.Value{cty.StringVal("hello")}),
cty.TupleVal([]cty.Value{cty.False}),
},
{
cty.ListVal([]cty.Value{cty.NullVal(cty.String)}),
cty.TupleVal([]cty.Value{cty.False}),
},
{
cty.ListVal([]cty.Value{cty.UnknownVal(cty.String)}),
cty.TupleVal([]cty.Value{cty.True}),
},
{
cty.SetValEmpty(cty.String),
cty.EmptyTupleVal,
},
{
cty.SetVal([]cty.Value{cty.StringVal("hello")}),
cty.TupleVal([]cty.Value{cty.False}),
},
{
cty.SetVal([]cty.Value{cty.NullVal(cty.String)}),
cty.TupleVal([]cty.Value{cty.False}),
},
{
cty.SetVal([]cty.Value{cty.UnknownVal(cty.String)}),
cty.TupleVal([]cty.Value{cty.True}),
},
{
cty.EmptyTupleVal,
cty.EmptyTupleVal,
},
{
cty.TupleVal([]cty.Value{cty.StringVal("hello")}),
cty.TupleVal([]cty.Value{cty.False}),
},
{
cty.TupleVal([]cty.Value{cty.NullVal(cty.String)}),
cty.TupleVal([]cty.Value{cty.False}),
},
{
cty.TupleVal([]cty.Value{cty.UnknownVal(cty.String)}),
cty.TupleVal([]cty.Value{cty.True}),
},
{
cty.MapValEmpty(cty.String),
cty.EmptyObjectVal,
},
{
cty.MapVal(map[string]cty.Value{"greeting": cty.StringVal("hello")}),
cty.EmptyObjectVal,
},
{
cty.MapVal(map[string]cty.Value{"greeting": cty.NullVal(cty.String)}),
cty.EmptyObjectVal,
},
{
cty.MapVal(map[string]cty.Value{"greeting": cty.UnknownVal(cty.String)}),
cty.ObjectVal(map[string]cty.Value{"greeting": cty.True}),
},
{
cty.EmptyObjectVal,
cty.EmptyObjectVal,
},
{
cty.ObjectVal(map[string]cty.Value{"greeting": cty.StringVal("hello")}),
cty.EmptyObjectVal,
},
{
cty.ObjectVal(map[string]cty.Value{"greeting": cty.NullVal(cty.String)}),
cty.EmptyObjectVal,
},
{
cty.ObjectVal(map[string]cty.Value{"greeting": cty.UnknownVal(cty.String)}),
cty.ObjectVal(map[string]cty.Value{"greeting": cty.True}),
},
{
cty.SetVal([]cty.Value{
cty.ObjectVal(map[string]cty.Value{
"a": cty.UnknownVal(cty.String),
}),
cty.ObjectVal(map[string]cty.Value{
"a": cty.StringVal("known"),
}),
}),
cty.TupleVal([]cty.Value{
cty.EmptyObjectVal,
cty.ObjectVal(map[string]cty.Value{
"a": cty.True,
}),
}),
},
{
cty.SetVal([]cty.Value{
cty.MapValEmpty(cty.String),
cty.MapVal(map[string]cty.Value{
"a": cty.StringVal("known"),
}),
cty.MapVal(map[string]cty.Value{
"a": cty.UnknownVal(cty.String),
}),
}),
cty.TupleVal([]cty.Value{
cty.EmptyObjectVal,
cty.ObjectVal(map[string]cty.Value{
"a": cty.True,
}),
cty.EmptyObjectVal,
}),
},
}
for _, test := range tests {
got := unknownAsBool(test.Input)
if !reflect.DeepEqual(got, test.Want) {
t.Errorf(
"wrong result\ninput: %#v\ngot: %#v\nwant: %#v",
test.Input, got, test.Want,
)
}
}
}