retain legacy behaviour of empty collection when given an object as mock

sams/fill-nested
Samsondeen Dare 1 month ago
parent 7644093280
commit da4cf6b5c1

@ -5674,21 +5674,52 @@ func TestTest_TeardownOrder(t *testing.T) {
}
}
func TestTest_OverrideDataListAttribute(t *testing.T) {
func TestTest_OverrideDataMocking(t *testing.T) {
tcs := map[string]struct {
dir string
code int
desc string
dir string
expectedCode int
expectedStdout string
expectedStderr string
}{
"plain_list_attribute": {
dir: "override_data_list_attribute",
code: 0,
desc: "override_data with a computed cty.List(cty.Object) attribute",
dir: "override_data_list_attribute",
expectedCode: 0,
expectedStdout: "1 passed, 0 failed.",
},
"nested_list_attribute": {
dir: "override_data_nested_list_attribute",
code: 0,
desc: "override_data with a computed NestedType NestingList attribute",
dir: "override_data_nested_list_attribute",
expectedCode: 0,
expectedStdout: "1 passed, 0 failed.",
},
"nested_list_attribute_with_object_value": {
dir: "override_data_nested_list_attribute_object",
expectedCode: 0,
expectedStdout: "1 passed, 0 failed.",
},
"nested_list_attribute_with_invalid_type_value": {
dir: "override_data_nested_list_attribute_invalid_type",
expectedCode: 1,
expectedStderr: "incompatible types; expected list of object, found",
},
"list_attribute_with_partial_element_values": {
dir: "override_data_list_attribute_partial_elements",
expectedCode: 0,
expectedStdout: "1 passed, 0 failed.",
},
"set_attribute_with_partial_element_values": {
dir: "override_data_complex_set_attribute_partial_elements",
expectedCode: 0,
expectedStdout: "1 passed, 0 failed.",
},
"nested_set_attribute_with_object_value": {
dir: "override_data_complex_nested_set_attribute_object",
expectedCode: 0,
expectedStdout: "1 passed, 0 failed.",
},
"nested_list_attribute_with_partial_element_values": {
dir: "override_data_nested_list_attribute_partial_elements",
expectedCode: 0,
expectedStdout: "1 passed, 0 failed.",
},
}
@ -5725,7 +5756,6 @@ func TestTest_OverrideDataListAttribute(t *testing.T) {
t.Fatalf("expected init status code 0 but got %d: %s", code, output.All())
}
// Reset the streams for the next command.
streams, done = terminal.StreamsForTesting(t)
meta.Streams = streams
meta.View = views.NewView(streams)
@ -5737,17 +5767,20 @@ func TestTest_OverrideDataListAttribute(t *testing.T) {
code := c.Run([]string{"-no-color"})
output := done(t)
if code != tc.code {
t.Errorf("expected status code %d but got %d:\n\n%s", tc.code, code, output.All())
if code != tc.expectedCode {
t.Fatalf("expected status code %d but got %d:\n\n%s", tc.expectedCode, code, output.All())
}
if tc.code == 0 {
if !strings.Contains(output.Stdout(), "1 passed, 0 failed.") {
t.Errorf("expected passing test output but got:\n\nstdout:\n%s\nstderr:\n%s", output.Stdout(), output.Stderr())
}
if output.Stderr() != "" {
t.Errorf("unexpected stderr output:\n%s", output.Stderr())
}
if tc.expectedStdout != "" && !strings.Contains(output.Stdout(), tc.expectedStdout) {
t.Errorf("expected stdout to contain %q but got:\n\nstdout:\n%s\nstderr:\n%s", tc.expectedStdout, output.Stdout(), output.Stderr())
}
if tc.expectedStderr != "" && !strings.Contains(output.Stderr(), tc.expectedStderr) {
t.Errorf("expected stderr to contain %q but got:\n\nstdout:\n%s\nstderr:\n%s", tc.expectedStderr, output.Stdout(), output.Stderr())
}
if tc.expectedCode == 0 && output.Stderr() != "" {
t.Errorf("unexpected stderr output:\n%s", output.Stderr())
}
})
}

@ -0,0 +1,15 @@
terraform {
required_providers {
test = {
source = "hashicorp/test"
}
}
}
data "test_complex_data_source" "datasource" {
id = "resource"
}
output "nested_set_value" {
value = data.test_complex_data_source.datasource.nested_set_value
}

@ -0,0 +1,19 @@
provider "test" {}
override_data {
target = data.test_complex_data_source.datasource
values = {
nested_set_value = {
name = "shared"
}
}
}
run "test_override_data_complex_nested_set_attribute_object" {
command = plan
assert {
condition = length(data.test_complex_data_source.datasource.nested_set_value) == 0
error_message = "Expected nested_set_value to be empty when overridden with an object"
}
}

@ -0,0 +1,15 @@
terraform {
required_providers {
test = {
source = "hashicorp/test"
}
}
}
data "test_complex_data_source" "datasource" {
id = "resource"
}
output "set_value" {
value = data.test_complex_data_source.datasource.set_value
}

@ -0,0 +1,40 @@
provider "test" {}
override_data {
target = data.test_complex_data_source.datasource
values = {
set_value = [
{
name = "first"
},
{
value = "two"
},
]
}
}
run "test_override_data_complex_set_attribute_partial_elements" {
command = plan
assert {
condition = length(data.test_complex_data_source.datasource.set_value) == 2
error_message = "Expected set_value to have 2 elements, got ${length(data.test_complex_data_source.datasource.set_value)}"
}
assert {
condition = length([
for item in data.test_complex_data_source.datasource.set_value : item
if item.name == "first" && item.value != null
]) == 1
error_message = "Expected one set_value element with name 'first' and a filled-in value"
}
assert {
condition = length([
for item in data.test_complex_data_source.datasource.set_value : item
if item.value == "two" && item.name != null
]) == 1
error_message = "Expected one set_value element with value 'two' and a filled-in name"
}
}

@ -0,0 +1,15 @@
terraform {
required_providers {
test = {
source = "hashicorp/test"
}
}
}
data "test_data_source" "datasource" {
id = "resource"
}
output "list_value" {
value = data.test_data_source.datasource.list_value
}

@ -0,0 +1,44 @@
provider "test" {}
override_data {
target = data.test_data_source.datasource
values = {
list_value = [
{
name = "first"
},
{
value = "two"
},
]
}
}
run "test_override_data_list_attribute_partial_elements" {
command = plan
assert {
condition = length(data.test_data_source.datasource.list_value) == 2
error_message = "Expected list_value to have 2 elements, got ${length(data.test_data_source.datasource.list_value)}"
}
assert {
condition = data.test_data_source.datasource.list_value[0].name == "first"
error_message = "Expected first element name to be 'first'"
}
assert {
condition = data.test_data_source.datasource.list_value[0].value != null
error_message = "Expected first element value to be filled in"
}
assert {
condition = data.test_data_source.datasource.list_value[1].value == "two"
error_message = "Expected second element value to be 'two'"
}
assert {
condition = data.test_data_source.datasource.list_value[1].name != null
error_message = "Expected second element name to be filled in"
}
}

@ -0,0 +1,15 @@
terraform {
required_providers {
test = {
source = "hashicorp/test"
}
}
}
data "test_data_source" "datasource" {
id = "resource"
}
output "nested_list_value" {
value = data.test_data_source.datasource.nested_list_value
}

@ -0,0 +1,12 @@
provider "test" {}
override_data {
target = data.test_data_source.datasource
values = {
nested_list_value = "wrong type"
}
}
run "test_override_data_nested_list_attribute_invalid_type" {
command = plan
}

@ -0,0 +1,15 @@
terraform {
required_providers {
test = {
source = "hashicorp/test"
}
}
}
data "test_data_source" "datasource" {
id = "resource"
}
output "nested_list_value" {
value = data.test_data_source.datasource.nested_list_value
}

@ -0,0 +1,19 @@
provider "test" {}
override_data {
target = data.test_data_source.datasource
values = {
nested_list_value = {
name = "shared"
}
}
}
run "test_override_data_nested_list_attribute_object" {
command = plan
assert {
condition = length(data.test_data_source.datasource.nested_list_value) == 0
error_message = "Expected nested_list_value to be empty when overridden with an object"
}
}

@ -0,0 +1,15 @@
terraform {
required_providers {
test = {
source = "hashicorp/test"
}
}
}
data "test_data_source" "datasource" {
id = "resource"
}
output "nested_list_value" {
value = data.test_data_source.datasource.nested_list_value
}

@ -0,0 +1,44 @@
provider "test" {}
override_data {
target = data.test_data_source.datasource
values = {
nested_list_value = [
{
name = "first"
},
{
value = "two"
},
]
}
}
run "test_override_data_nested_list_attribute_partial_elements" {
command = plan
assert {
condition = length(data.test_data_source.datasource.nested_list_value) == 2
error_message = "Expected nested_list_value to have 2 elements, got ${length(data.test_data_source.datasource.nested_list_value)}"
}
assert {
condition = data.test_data_source.datasource.nested_list_value[0].name == "first"
error_message = "Expected first element name to be 'first'"
}
assert {
condition = data.test_data_source.datasource.nested_list_value[0].value != null
error_message = "Expected first element value to be filled in"
}
assert {
condition = data.test_data_source.datasource.nested_list_value[1].value == "two"
error_message = "Expected second element value to be 'two'"
}
assert {
condition = data.test_data_source.datasource.nested_list_value[1].name != null
error_message = "Expected second element name to be filled in"
}
}

@ -17,35 +17,50 @@ import (
// attributes and/or performing conversions to make the input value correct.
//
// It is similar to FillType, except it accepts attributes instead of types.
func FillAttribute(in cty.Value, attribute *configschema.Attribute) (cty.Value, error) {
return fillAttribute(in, attribute, cty.Path{})
func FillAttribute(providedMock cty.Value, attribute *configschema.Attribute) (cty.Value, error) {
return fillAttribute(providedMock, attribute, cty.Path{})
}
func fillAttribute(in cty.Value, attribute *configschema.Attribute, path cty.Path) (cty.Value, error) {
func fillAttribute(providedMock cty.Value, attribute *configschema.Attribute, path cty.Path) (cty.Value, error) {
ty := attribute.Type
if attribute.NestedType != nil {
// For nested types, the providedMock value is interpreted in two ways:
// - If it's an object, it's treated as a single instance of the nested type,
// and because we can't know how many instances are needed, we return an empty collection.
// - If it's a collection, it's treated as the whole nested type collection,
// and then we update each element of the collection with generated values where possible.
// Note: The collection type must match the attribute's nested type.
switch attribute.NestedType.Nesting {
case configschema.NestingSingle, configschema.NestingGroup:
return fillObject(in, attribute, path)
return fillObject(providedMock, attribute, path)
case configschema.NestingSet:
return cty.SetValEmpty(attribute.ImpliedType().ElementType()), nil
if providedMock.Type().IsObjectType() {
return cty.SetValEmpty(attribute.ImpliedType().ElementType()), nil
}
return fillIterable(providedMock, attribute, path)
case configschema.NestingList:
return fillIterable(in, attribute, path)
if providedMock.Type().IsObjectType() {
return cty.ListValEmpty(attribute.ImpliedType().ElementType()), nil
}
return fillIterable(providedMock, attribute, path)
case configschema.NestingMap:
return cty.MapValEmpty(attribute.ImpliedType().ElementType()), nil
if providedMock.Type().IsObjectType() {
return cty.MapValEmpty(attribute.ImpliedType().ElementType()), nil
}
return fillIterable(providedMock, attribute, path)
default:
panic(fmt.Errorf("unknown nesting mode: %d", attribute.NestedType.Nesting))
}
}
return fillType(in, ty, path)
return fillType(providedMock, ty, path)
}
func fillObject(in cty.Value, attribute *configschema.Attribute, path cty.Path) (cty.Value, error) {
// Then the in value must be an object.
if !in.Type().IsObjectType() {
return cty.NilVal, path.NewErrorf("incompatible types; expected object type, found %s", in.Type().FriendlyName())
func fillObject(providedMock cty.Value, attribute *configschema.Attribute, path cty.Path) (cty.Value, error) {
// Then the providedMock value must be an object.
if !providedMock.Type().IsObjectType() {
return cty.NilVal, path.NewErrorf("incompatible types; expected object type, found %s", providedMock.Type().FriendlyName())
}
var names []string
@ -63,8 +78,8 @@ func fillObject(in cty.Value, attribute *configschema.Attribute, path cty.Path)
children := make(map[string]cty.Value)
for _, name := range names {
if in.Type().HasAttribute(name) {
child, err := fillAttribute(in.GetAttr(name), attribute.NestedType.Attributes[name], path.GetAttr(name))
if providedMock.Type().HasAttribute(name) {
child, err := fillAttribute(providedMock.GetAttr(name), attribute.NestedType.Attributes[name], path.GetAttr(name))
if err != nil {
return cty.NilVal, err
}
@ -76,9 +91,9 @@ func fillObject(in cty.Value, attribute *configschema.Attribute, path cty.Path)
return cty.ObjectVal(children), nil
}
func fillIterable(in cty.Value, attribute *configschema.Attribute, path cty.Path) (cty.Value, error) {
func fillIterable(providedMock cty.Value, attribute *configschema.Attribute, path cty.Path) (cty.Value, error) {
ty := attribute.NestedType.ConfigType()
out, err := fillType(in, ty, path)
out, err := fillType(providedMock, ty, path)
if err != nil {
return cty.NilVal, err
}

Loading…
Cancel
Save