diff --git a/netbox/custom_fields.go b/netbox/custom_fields.go index 90e11522..7767ee1c 100644 --- a/netbox/custom_fields.go +++ b/netbox/custom_fields.go @@ -27,30 +27,28 @@ var customFieldsSchemaRead = &schema.Schema{ }, } -func customFieldsDiff(ctx context.Context, d *schema.ResourceDiff, meta interface{}) error { - cfg := d.GetRawConfig().GetAttr(customFieldsKey) - cfm, ok := d.Get(customFieldsKey).(map[string]interface{}) - - if cfg.IsNull() || !ok { - d.SetNew(customFieldsKey, nil) - return nil - } +func normalizeCustomFields(cfm map[string]interface{}) map[string]interface{} { + newcfm := make(map[string]interface{}) for k, v := range cfm { - if v == "" { - delete(cfm, k) + if v != nil && v != "" { + newcfm[k] = v } } - d.SetNew(customFieldsKey, cfm) - return nil + return newcfm } -func computeCustomFieldsModel(d *schema.ResourceData) interface{} { - oldcf, newcf := d.GetChange(customFieldsKey) +func mergeCustomFields(oldcfm, newcfm map[string]interface{}) map[string]interface{} { + if newcfm == nil { + newcfm = make(map[string]interface{}) + } - oldcfm, _ := oldcf.(map[string]interface{}) - newcfm, _ := newcf.(map[string]interface{}) + for k, v := range newcfm { + if v == nil { + newcfm[k] = "" + } + } for k := range oldcfm { if _, ok := newcfm[k]; !ok { @@ -61,15 +59,29 @@ func computeCustomFieldsModel(d *schema.ResourceData) interface{} { return newcfm } +func customFieldsDiff(ctx context.Context, d *schema.ResourceDiff, meta interface{}) error { + cfg := d.GetRawConfig().GetAttr(customFieldsKey) + cfm, ok := d.Get(customFieldsKey).(map[string]interface{}) + + if cfg.IsNull() || !ok { + d.SetNew(customFieldsKey, nil) + } else { + newcfm := normalizeCustomFields(cfm) + d.SetNew(customFieldsKey, newcfm) + } + + return nil +} + func computeCustomFieldsAttr(cf interface{}) map[string]interface{} { cfm, _ := cf.(map[string]interface{}) - newcfm := make(map[string]interface{}) + return normalizeCustomFields(cfm) +} - for k, v := range cfm { - if vs, _ := v.(string); vs != "" { - newcfm[k] = v - } - } +func computeCustomFieldsModel(d *schema.ResourceData) interface{} { + oldcf, newcf := d.GetChange(customFieldsKey) - return newcfm + oldcfm, _ := oldcf.(map[string]interface{}) + newcfm, _ := newcf.(map[string]interface{}) + return mergeCustomFields(oldcfm, newcfm) } diff --git a/netbox/custom_fields_test.go b/netbox/custom_fields_test.go new file mode 100644 index 00000000..23d9b379 --- /dev/null +++ b/netbox/custom_fields_test.go @@ -0,0 +1,185 @@ +package netbox + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestCustomFields_normalize(t *testing.T) { + tests := []struct { + name string + input map[string]interface{} + expected map[string]interface{} + }{ + { + name: "MapNil", + input: nil, + expected: map[string]interface{}{}, + }, + { + name: "MapEmpty", + input: map[string]interface{}{}, + expected: map[string]interface{}{}, + }, + { + name: "MapFull", + input: map[string]interface{}{ + "key1": "value1", + "key2": "value2", + }, + expected: map[string]interface{}{ + "key1": "value1", + "key2": "value2", + }, + }, + { + name: "MapMixed", + input: map[string]interface{}{ + "key1": "value1", + "key2": "", + "key3": nil, + }, + expected: map[string]interface{}{ + "key1": "value1", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := normalizeCustomFields(tt.input) + assert.Equal(t, tt.expected, result) + }) + } +} + +func TestCustomFields_merge(t *testing.T) { + tests := []struct { + name string + state map[string]interface{} + input map[string]interface{} + expected map[string]interface{} + }{ + { + name: "MapNilBoth", + state: nil, + input: nil, + expected: map[string]interface{}{}, + }, + { + name: "MapNilNew", + state: map[string]interface{}{}, + input: nil, + expected: map[string]interface{}{}, + }, + { + name: "MapNilOld", + state: nil, + input: map[string]interface{}{}, + expected: map[string]interface{}{}, + }, + { + name: "MapUnsetWithMissing", + state: map[string]interface{}{ + "key1": "value1", + }, + input: map[string]interface{}{}, + expected: map[string]interface{}{ + "key1": "", + }, + }, + { + name: "MapUnsetWithNil", + state: map[string]interface{}{ + "key1": "value1", + }, + input: map[string]interface{}{ + "key1": nil, + }, + expected: map[string]interface{}{ + "key1": "", + }, + }, + { + name: "MapUnsetWithEmpty", + state: map[string]interface{}{ + "key1": "value1", + }, + input: map[string]interface{}{}, + expected: map[string]interface{}{ + "key1": "", + }, + }, + { + name: "MapUnsetWithNilNotSet", + state: map[string]interface{}{}, + input: map[string]interface{}{ + "key1": nil, + }, + expected: map[string]interface{}{ + "key1": "", + }, + }, + { + name: "MapUnsetWithEmptyNotSet", + state: map[string]interface{}{}, + input: map[string]interface{}{ + "key1": "", + }, + expected: map[string]interface{}{ + "key1": "", + }, + }, + { + name: "MapSetNew", + state: map[string]interface{}{}, + input: map[string]interface{}{ + "key1": "test", + }, + expected: map[string]interface{}{ + "key1": "test", + }, + }, + { + name: "MapSetExisting", + state: map[string]interface{}{ + "key1": " test", + }, + input: map[string]interface{}{ + "key1": "testnew", + }, + expected: map[string]interface{}{ + "key1": "testnew", + }, + }, + { + name: "MapMixed", + state: map[string]interface{}{ + "key1": "value1", + "key2": "value2", + "key3": "", + "key4": nil, + }, + input: map[string]interface{}{ + "key1": "valuenew1", + "key2": nil, + "keynew": "valuenew2", + }, + expected: map[string]interface{}{ + "key1": "valuenew1", + "key2": "", + "key3": "", + "key4": "", + "keynew": "valuenew2", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := mergeCustomFields(tt.state, tt.input) + assert.Equal(t, tt.expected, result) + }) + } +}