-
Notifications
You must be signed in to change notification settings - Fork 191
chore: Define conversion function for generating API request body from Resource Model in create (non-nested) #3239
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
4c78c75
f0527b2
39dde8b
ac09871
3f17644
efbde62
5533fe9
f773342
b590a98
5dcab18
2a5577f
d6c212b
065089b
bf5e83b
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,50 +3,175 @@ package autogeneration_test | |
import ( | ||
"testing" | ||
|
||
"github.com/hashicorp/terraform-plugin-framework/attr" | ||
"github.com/hashicorp/terraform-plugin-framework/types" | ||
"github.com/mongodb/terraform-provider-mongodbatlas/internal/common/autogeneration" | ||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
) | ||
|
||
func TestMarshalBasic(t *testing.T) { | ||
model := struct { | ||
AttrFloat types.Float64 `tfsdk:"attr_float"` | ||
AttrString types.String `tfsdk:"attr_string"` | ||
// values with tag `omitjson` are not marshaled, and they don't need to be Terraform types | ||
AttrOmit types.String `tfsdk:"attr_omit" autogeneration:"omitjson"` | ||
AttrOmitNoTerraform string `autogeneration:"omitjson"` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Any particular reason why this is not types.String? Would also define it with a known string value to ensure it is never sent even if it has a value in the config There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. do you mean AttrOmitNoTerraform? It's to prove that in this case you can use "invalid" types that are not Terraform types in the model, and that's ok because it's not sent in JSON, like explained in the comment above. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Aah makes sense, I see |
||
AttrUnkown types.String `tfsdk:"attr_unknown"` | ||
AttrNull types.String `tfsdk:"attr_null"` | ||
AttrInt types.Int64 `tfsdk:"attr_int"` | ||
}{ | ||
AttrFloat: types.Float64Value(1.234), | ||
AttrString: types.StringValue("hello"), | ||
AttrOmit: types.StringValue("omit"), | ||
AttrOmitNoTerraform: "omit", | ||
AttrUnkown: types.StringUnknown(), // unknown values are not marshaled | ||
AttrNull: types.StringNull(), // null values are not marshaled | ||
AttrInt: types.Int64Value(1), | ||
} | ||
const expectedJSON = `{ "attr_string": "hello", "attr_int": 1, "attr_float": 1.234 }` | ||
raw, err := autogeneration.Marshal(&model, false) | ||
require.NoError(t, err) | ||
assert.JSONEq(t, expectedJSON, string(raw)) | ||
} | ||
|
||
func TestMarshalOmitJSONUpdate(t *testing.T) { | ||
const ( | ||
expectedCreate = `{ "attr": "val1", "attr_omit_update": "val2" }` | ||
expectedUpdate = `{ "attr": "val1" }` | ||
) | ||
model := struct { | ||
Attr types.String `tfsdk:"attr"` | ||
AttrOmitUpdate types.String `tfsdk:"attr_omit_update" autogeneration:"omitjsonupdate"` | ||
AttrOmit types.String `tfsdk:"attr_omit" autogeneration:"omitjson"` | ||
}{ | ||
Attr: types.StringValue("val1"), | ||
AttrOmitUpdate: types.StringValue("val2"), | ||
AttrOmit: types.StringValue("omit"), | ||
} | ||
create, errCreate := autogeneration.Marshal(&model, false) | ||
require.NoError(t, errCreate) | ||
assert.JSONEq(t, expectedCreate, string(create)) | ||
|
||
update, errUpdate := autogeneration.Marshal(&model, true) | ||
require.NoError(t, errUpdate) | ||
assert.JSONEq(t, expectedUpdate, string(update)) | ||
} | ||
|
||
func TestMarshalUnsupported(t *testing.T) { | ||
testCases := map[string]any{ | ||
"Object not supported yet, only no-nested types": &struct { | ||
Attr types.Object | ||
}{ | ||
Attr: types.ObjectValueMust(map[string]attr.Type{ | ||
"key": types.StringType, | ||
}, map[string]attr.Value{ | ||
"key": types.StringValue("value"), | ||
}), | ||
}, | ||
"List not supported yet, only no-nested types": &struct { | ||
Attr types.List | ||
}{ | ||
Attr: types.ListValueMust(types.StringType, []attr.Value{ | ||
types.StringValue("value"), | ||
}), | ||
}, | ||
"Map not supported yet, only no-nested types": &struct { | ||
Attr types.Map | ||
}{ | ||
Attr: types.MapValueMust(types.StringType, map[string]attr.Value{ | ||
"key": types.StringValue("value"), | ||
}), | ||
}, | ||
"Set not supported yet, only no-nested types": &struct { | ||
Attr types.Set | ||
}{ | ||
Attr: types.SetValueMust(types.StringType, []attr.Value{ | ||
types.StringValue("value"), | ||
}), | ||
}, | ||
"Int32 not supported yet as it's not being used in any model": &struct { | ||
Attr types.Int32 | ||
}{ | ||
Attr: types.Int32Value(1), | ||
}, | ||
"Float32 not supported yet as it's not being used in any model": &struct { | ||
Attr types.Float32 | ||
}{ | ||
Attr: types.Float32Value(1.0), | ||
}, | ||
} | ||
for name, model := range testCases { | ||
t.Run(name, func(t *testing.T) { | ||
raw, err := autogeneration.Marshal(model, false) | ||
require.Error(t, err) | ||
assert.Nil(t, raw) | ||
}) | ||
} | ||
} | ||
|
||
func TestMarshalPanic(t *testing.T) { | ||
str := "string" | ||
testCases := map[string]any{ | ||
"no Terraform types": &struct { | ||
Attr string | ||
}{ | ||
Attr: "a", | ||
}, | ||
"no pointer": struct { | ||
Attr types.String | ||
}{ | ||
Attr: types.StringValue("a"), | ||
}, | ||
"no struct": &str, | ||
} | ||
for name, model := range testCases { | ||
t.Run(name, func(t *testing.T) { | ||
assert.Panics(t, func() { | ||
_, _ = autogeneration.Marshal(model, false) | ||
}) | ||
}) | ||
} | ||
} | ||
|
||
func TestUnmarshalBasic(t *testing.T) { | ||
var model struct { | ||
AttributeFloat types.Float64 `tfsdk:"attribute_float"` | ||
AttributeFloatWithInt types.Float64 `tfsdk:"attribute_float_with_int"` | ||
AttributeString types.String `tfsdk:"attribute_string"` | ||
AttributeNotInJSON types.String `tfsdk:"attribute_not_in_json"` | ||
AttributeInt types.Int64 `tfsdk:"attribute_int"` | ||
AttributeIntWithFloat types.Int64 `tfsdk:"attribute_int_with_float"` | ||
AttributeTrue types.Bool `tfsdk:"attribute_true"` | ||
AttributeFalse types.Bool `tfsdk:"attribute_false"` | ||
AttrFloat types.Float64 `tfsdk:"attr_float"` | ||
AttrFloatWithInt types.Float64 `tfsdk:"attr_float_with_int"` | ||
AttrString types.String `tfsdk:"attr_string"` | ||
AttrNotInJSON types.String `tfsdk:"attr_not_in_json"` | ||
AttrInt types.Int64 `tfsdk:"attr_int"` | ||
AttrIntWithFloat types.Int64 `tfsdk:"attr_int_with_float"` | ||
AttrTrue types.Bool `tfsdk:"attr_true"` | ||
AttrFalse types.Bool `tfsdk:"attr_false"` | ||
} | ||
const ( | ||
epsilon = 10e-15 // float tolerance | ||
// attribute_not_in_model is ignored because it is not in the model, no error is thrown. | ||
// attribute_null is ignored because it is null, no error is thrown even if it is not in the model. | ||
tfResponseJSON = ` | ||
{ | ||
"attribute_string": "value_string", | ||
"attribute_true": true, | ||
"attribute_false": false, | ||
"attribute_int": 123, | ||
"attribute_int_with_float": 10.6, | ||
"attribute_float": 456.1, | ||
"attribute_float_with_int": 13, | ||
"attribute_not_in_model": "val", | ||
"attribute_null": null | ||
"attr_string": "value_string", | ||
"attr_true": true, | ||
"attr_false": false, | ||
"attr_int": 123, | ||
"attr_int_with_float": 10.6, | ||
"attr_float": 456.1, | ||
"attr_float_with_int": 13, | ||
"attr_not_in_model": "val", | ||
"attr_null": null | ||
} | ||
` | ||
) | ||
require.NoError(t, autogeneration.Unmarshal([]byte(tfResponseJSON), &model)) | ||
assert.Equal(t, "value_string", model.AttributeString.ValueString()) | ||
assert.True(t, model.AttributeTrue.ValueBool()) | ||
assert.False(t, model.AttributeFalse.ValueBool()) | ||
assert.Equal(t, int64(123), model.AttributeInt.ValueInt64()) | ||
assert.Equal(t, int64(10), model.AttributeIntWithFloat.ValueInt64()) // response floats stored in model ints have their decimals stripped. | ||
assert.InEpsilon(t, float64(456.1), model.AttributeFloat.ValueFloat64(), epsilon) | ||
assert.InEpsilon(t, float64(13), model.AttributeFloatWithInt.ValueFloat64(), epsilon) | ||
assert.True(t, model.AttributeNotInJSON.IsNull()) // attributes not in JSON response are not changed, so null is kept. | ||
assert.Equal(t, "value_string", model.AttrString.ValueString()) | ||
assert.True(t, model.AttrTrue.ValueBool()) | ||
assert.False(t, model.AttrFalse.ValueBool()) | ||
assert.Equal(t, int64(123), model.AttrInt.ValueInt64()) | ||
assert.Equal(t, int64(10), model.AttrIntWithFloat.ValueInt64()) // response floats stored in model ints have their decimals stripped. | ||
assert.InEpsilon(t, float64(456.1), model.AttrFloat.ValueFloat64(), epsilon) | ||
assert.InEpsilon(t, float64(13), model.AttrFloatWithInt.ValueFloat64(), epsilon) | ||
assert.True(t, model.AttrNotInJSON.IsNull()) // attributes not in JSON response are not changed, so null is kept. | ||
} | ||
|
||
func TestUnmarshalErrors(t *testing.T) { | ||
|
@@ -132,11 +257,11 @@ func TestUnmarshalUnsupportedResponse(t *testing.T) { | |
}{ | ||
"JSON objects not support yet": { | ||
responseJSON: `{"attr": {"key": "value"}}`, | ||
errorStr: "not supported yet type map[string]interface {} for field attr", | ||
errorStr: "unmarshal not supported yet for type map[string]interface {} for field attr", | ||
}, | ||
"JSON arrays not supported yet": { | ||
responseJSON: `{"attr": [{"key": "value"}]}`, | ||
errorStr: "not supported yet type []interface {} for field attr", | ||
errorStr: "unmarshal not supported yet for type []interface {} for field attr", | ||
}, | ||
} | ||
for name, tc := range testCases { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@lantoli
Would it be worth supporting multiple tags: "autogeneration:omitjson,{futureTag}"? Probably, it can happen at a later stage
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@EspenAlbert yes, it makes sense, but didn't want to anticipate until we don't need it, this code would be very easy to adapt when neeeded