github.com/terramate-io/tf@v0.0.0-20230830114523-fce866b4dfcd/legacy/helper/schema/resource_timeout_test.go (about)

     1  // Copyright (c) HashiCorp, Inc.
     2  // SPDX-License-Identifier: MPL-2.0
     3  
     4  package schema
     5  
     6  import (
     7  	"fmt"
     8  	"reflect"
     9  	"testing"
    10  	"time"
    11  
    12  	"github.com/terramate-io/tf/legacy/terraform"
    13  )
    14  
    15  func TestResourceTimeout_ConfigDecode_badkey(t *testing.T) {
    16  	cases := []struct {
    17  		Name string
    18  		// what the resource has defined in source
    19  		ResourceDefaultTimeout *ResourceTimeout
    20  		// configuration provider by user in tf file
    21  		Config map[string]interface{}
    22  		// what we expect the parsed ResourceTimeout to be
    23  		Expected *ResourceTimeout
    24  		// Should we have an error (key not defined in source)
    25  		ShouldErr bool
    26  	}{
    27  		{
    28  			Name:                   "Source does not define 'delete' key",
    29  			ResourceDefaultTimeout: timeoutForValues(10, 0, 5, 0, 0),
    30  			Config:                 expectedConfigForValues(2, 0, 0, 1, 0),
    31  			Expected:               timeoutForValues(10, 0, 5, 0, 0),
    32  			ShouldErr:              true,
    33  		},
    34  		{
    35  			Name:                   "Config overrides create",
    36  			ResourceDefaultTimeout: timeoutForValues(10, 0, 5, 0, 0),
    37  			Config:                 expectedConfigForValues(2, 0, 7, 0, 0),
    38  			Expected:               timeoutForValues(2, 0, 7, 0, 0),
    39  			ShouldErr:              false,
    40  		},
    41  		{
    42  			Name:                   "Config overrides create, default provided. Should still have zero values",
    43  			ResourceDefaultTimeout: timeoutForValues(10, 0, 5, 0, 3),
    44  			Config:                 expectedConfigForValues(2, 0, 7, 0, 0),
    45  			Expected:               timeoutForValues(2, 0, 7, 0, 3),
    46  			ShouldErr:              false,
    47  		},
    48  		{
    49  			Name:                   "Use something besides 'minutes'",
    50  			ResourceDefaultTimeout: timeoutForValues(10, 0, 5, 0, 3),
    51  			Config: map[string]interface{}{
    52  				"create": "2h",
    53  			},
    54  			Expected:  timeoutForValues(120, 0, 5, 0, 3),
    55  			ShouldErr: false,
    56  		},
    57  	}
    58  
    59  	for i, c := range cases {
    60  		t.Run(fmt.Sprintf("%d-%s", i, c.Name), func(t *testing.T) {
    61  			r := &Resource{
    62  				Timeouts: c.ResourceDefaultTimeout,
    63  			}
    64  
    65  			conf := terraform.NewResourceConfigRaw(
    66  				map[string]interface{}{
    67  					"foo":             "bar",
    68  					TimeoutsConfigKey: c.Config,
    69  				},
    70  			)
    71  
    72  			timeout := &ResourceTimeout{}
    73  			decodeErr := timeout.ConfigDecode(r, conf)
    74  			if c.ShouldErr {
    75  				if decodeErr == nil {
    76  					t.Fatalf("ConfigDecode case (%d): Expected bad timeout key: %s", i, decodeErr)
    77  				}
    78  				// should error, err was not nil, continue
    79  				return
    80  			} else {
    81  				if decodeErr != nil {
    82  					// should not error, error was not nil, fatal
    83  					t.Fatalf("decodeError was not nil: %s", decodeErr)
    84  				}
    85  			}
    86  
    87  			if !reflect.DeepEqual(c.Expected, timeout) {
    88  				t.Fatalf("ConfigDecode match error case (%d).\nExpected:\n%#v\nGot:\n%#v", i, c.Expected, timeout)
    89  			}
    90  		})
    91  	}
    92  }
    93  
    94  func TestResourceTimeout_ConfigDecode(t *testing.T) {
    95  	r := &Resource{
    96  		Timeouts: &ResourceTimeout{
    97  			Create: DefaultTimeout(10 * time.Minute),
    98  			Update: DefaultTimeout(5 * time.Minute),
    99  		},
   100  	}
   101  
   102  	c := terraform.NewResourceConfigRaw(
   103  		map[string]interface{}{
   104  			"foo": "bar",
   105  			TimeoutsConfigKey: map[string]interface{}{
   106  				"create": "2m",
   107  				"update": "1m",
   108  			},
   109  		},
   110  	)
   111  
   112  	timeout := &ResourceTimeout{}
   113  	err := timeout.ConfigDecode(r, c)
   114  	if err != nil {
   115  		t.Fatalf("Expected good timeout returned:, %s", err)
   116  	}
   117  
   118  	expected := &ResourceTimeout{
   119  		Create: DefaultTimeout(2 * time.Minute),
   120  		Update: DefaultTimeout(1 * time.Minute),
   121  	}
   122  
   123  	if !reflect.DeepEqual(timeout, expected) {
   124  		t.Fatalf("bad timeout decode.\nExpected:\n%#v\nGot:\n%#v\n", expected, timeout)
   125  	}
   126  }
   127  
   128  func TestResourceTimeout_legacyConfigDecode(t *testing.T) {
   129  	r := &Resource{
   130  		Timeouts: &ResourceTimeout{
   131  			Create: DefaultTimeout(10 * time.Minute),
   132  			Update: DefaultTimeout(5 * time.Minute),
   133  		},
   134  	}
   135  
   136  	c := terraform.NewResourceConfigRaw(
   137  		map[string]interface{}{
   138  			"foo": "bar",
   139  			TimeoutsConfigKey: []interface{}{
   140  				map[string]interface{}{
   141  					"create": "2m",
   142  					"update": "1m",
   143  				},
   144  			},
   145  		},
   146  	)
   147  
   148  	timeout := &ResourceTimeout{}
   149  	err := timeout.ConfigDecode(r, c)
   150  	if err != nil {
   151  		t.Fatalf("Expected good timeout returned:, %s", err)
   152  	}
   153  
   154  	expected := &ResourceTimeout{
   155  		Create: DefaultTimeout(2 * time.Minute),
   156  		Update: DefaultTimeout(1 * time.Minute),
   157  	}
   158  
   159  	if !reflect.DeepEqual(timeout, expected) {
   160  		t.Fatalf("bad timeout decode.\nExpected:\n%#v\nGot:\n%#v\n", expected, timeout)
   161  	}
   162  }
   163  
   164  func TestResourceTimeout_DiffEncode_basic(t *testing.T) {
   165  	cases := []struct {
   166  		Timeout  *ResourceTimeout
   167  		Expected map[string]interface{}
   168  		// Not immediately clear when an error would hit
   169  		ShouldErr bool
   170  	}{
   171  		// Two fields
   172  		{
   173  			Timeout:   timeoutForValues(10, 0, 5, 0, 0),
   174  			Expected:  map[string]interface{}{TimeoutKey: expectedForValues(10, 0, 5, 0, 0)},
   175  			ShouldErr: false,
   176  		},
   177  		// Two fields, one is Default
   178  		{
   179  			Timeout:   timeoutForValues(10, 0, 0, 0, 7),
   180  			Expected:  map[string]interface{}{TimeoutKey: expectedForValues(10, 0, 0, 0, 7)},
   181  			ShouldErr: false,
   182  		},
   183  		// All fields
   184  		{
   185  			Timeout:   timeoutForValues(10, 3, 4, 1, 7),
   186  			Expected:  map[string]interface{}{TimeoutKey: expectedForValues(10, 3, 4, 1, 7)},
   187  			ShouldErr: false,
   188  		},
   189  		// No fields
   190  		{
   191  			Timeout:   &ResourceTimeout{},
   192  			Expected:  nil,
   193  			ShouldErr: false,
   194  		},
   195  	}
   196  
   197  	for _, c := range cases {
   198  		state := &terraform.InstanceDiff{}
   199  		err := c.Timeout.DiffEncode(state)
   200  		if err != nil && !c.ShouldErr {
   201  			t.Fatalf("Error, expected:\n%#v\n got:\n%#v\n", c.Expected, state.Meta)
   202  		}
   203  
   204  		// should maybe just compare [TimeoutKey] but for now we're assuming only
   205  		// that in Meta
   206  		if !reflect.DeepEqual(state.Meta, c.Expected) {
   207  			t.Fatalf("Encode not equal, expected:\n%#v\n\ngot:\n%#v\n", c.Expected, state.Meta)
   208  		}
   209  	}
   210  	// same test cases but for InstanceState
   211  	for _, c := range cases {
   212  		state := &terraform.InstanceState{}
   213  		err := c.Timeout.StateEncode(state)
   214  		if err != nil && !c.ShouldErr {
   215  			t.Fatalf("Error, expected:\n%#v\n got:\n%#v\n", c.Expected, state.Meta)
   216  		}
   217  
   218  		// should maybe just compare [TimeoutKey] but for now we're assuming only
   219  		// that in Meta
   220  		if !reflect.DeepEqual(state.Meta, c.Expected) {
   221  			t.Fatalf("Encode not equal, expected:\n%#v\n\ngot:\n%#v\n", c.Expected, state.Meta)
   222  		}
   223  	}
   224  }
   225  
   226  func TestResourceTimeout_MetaDecode_basic(t *testing.T) {
   227  	cases := []struct {
   228  		State    *terraform.InstanceDiff
   229  		Expected *ResourceTimeout
   230  		// Not immediately clear when an error would hit
   231  		ShouldErr bool
   232  	}{
   233  		// Two fields
   234  		{
   235  			State:     &terraform.InstanceDiff{Meta: map[string]interface{}{TimeoutKey: expectedForValues(10, 0, 5, 0, 0)}},
   236  			Expected:  timeoutForValues(10, 0, 5, 0, 0),
   237  			ShouldErr: false,
   238  		},
   239  		// Two fields, one is Default
   240  		{
   241  			State:     &terraform.InstanceDiff{Meta: map[string]interface{}{TimeoutKey: expectedForValues(10, 0, 0, 0, 7)}},
   242  			Expected:  timeoutForValues(10, 7, 7, 7, 7),
   243  			ShouldErr: false,
   244  		},
   245  		// All fields
   246  		{
   247  			State:     &terraform.InstanceDiff{Meta: map[string]interface{}{TimeoutKey: expectedForValues(10, 3, 4, 1, 7)}},
   248  			Expected:  timeoutForValues(10, 3, 4, 1, 7),
   249  			ShouldErr: false,
   250  		},
   251  		// No fields
   252  		{
   253  			State:     &terraform.InstanceDiff{},
   254  			Expected:  &ResourceTimeout{},
   255  			ShouldErr: false,
   256  		},
   257  	}
   258  
   259  	for _, c := range cases {
   260  		rt := &ResourceTimeout{}
   261  		err := rt.DiffDecode(c.State)
   262  		if err != nil && !c.ShouldErr {
   263  			t.Fatalf("Error, expected:\n%#v\n got:\n%#v\n", c.Expected, rt)
   264  		}
   265  
   266  		// should maybe just compare [TimeoutKey] but for now we're assuming only
   267  		// that in Meta
   268  		if !reflect.DeepEqual(rt, c.Expected) {
   269  			t.Fatalf("Encode not equal, expected:\n%#v\n\ngot:\n%#v\n", c.Expected, rt)
   270  		}
   271  	}
   272  }
   273  
   274  func timeoutForValues(create, read, update, del, def int) *ResourceTimeout {
   275  	rt := ResourceTimeout{}
   276  
   277  	if create != 0 {
   278  		rt.Create = DefaultTimeout(time.Duration(create) * time.Minute)
   279  	}
   280  	if read != 0 {
   281  		rt.Read = DefaultTimeout(time.Duration(read) * time.Minute)
   282  	}
   283  	if update != 0 {
   284  		rt.Update = DefaultTimeout(time.Duration(update) * time.Minute)
   285  	}
   286  	if del != 0 {
   287  		rt.Delete = DefaultTimeout(time.Duration(del) * time.Minute)
   288  	}
   289  
   290  	if def != 0 {
   291  		rt.Default = DefaultTimeout(time.Duration(def) * time.Minute)
   292  	}
   293  
   294  	return &rt
   295  }
   296  
   297  // Generates a ResourceTimeout struct that should reflect the
   298  // d.Timeout("key") results
   299  func expectedTimeoutForValues(create, read, update, del, def int) *ResourceTimeout {
   300  	rt := ResourceTimeout{}
   301  
   302  	defaultValues := []*int{&create, &read, &update, &del, &def}
   303  	for _, v := range defaultValues {
   304  		if *v == 0 {
   305  			*v = 20
   306  		}
   307  	}
   308  
   309  	if create != 0 {
   310  		rt.Create = DefaultTimeout(time.Duration(create) * time.Minute)
   311  	}
   312  	if read != 0 {
   313  		rt.Read = DefaultTimeout(time.Duration(read) * time.Minute)
   314  	}
   315  	if update != 0 {
   316  		rt.Update = DefaultTimeout(time.Duration(update) * time.Minute)
   317  	}
   318  	if del != 0 {
   319  		rt.Delete = DefaultTimeout(time.Duration(del) * time.Minute)
   320  	}
   321  
   322  	if def != 0 {
   323  		rt.Default = DefaultTimeout(time.Duration(def) * time.Minute)
   324  	}
   325  
   326  	return &rt
   327  }
   328  
   329  func expectedForValues(create, read, update, del, def int) map[string]interface{} {
   330  	ex := make(map[string]interface{})
   331  
   332  	if create != 0 {
   333  		ex["create"] = DefaultTimeout(time.Duration(create) * time.Minute).Nanoseconds()
   334  	}
   335  	if read != 0 {
   336  		ex["read"] = DefaultTimeout(time.Duration(read) * time.Minute).Nanoseconds()
   337  	}
   338  	if update != 0 {
   339  		ex["update"] = DefaultTimeout(time.Duration(update) * time.Minute).Nanoseconds()
   340  	}
   341  	if del != 0 {
   342  		ex["delete"] = DefaultTimeout(time.Duration(del) * time.Minute).Nanoseconds()
   343  	}
   344  
   345  	if def != 0 {
   346  		defNano := DefaultTimeout(time.Duration(def) * time.Minute).Nanoseconds()
   347  		ex["default"] = defNano
   348  
   349  		for _, k := range timeoutKeys() {
   350  			if _, ok := ex[k]; !ok {
   351  				ex[k] = defNano
   352  			}
   353  		}
   354  	}
   355  
   356  	return ex
   357  }
   358  
   359  func expectedConfigForValues(create, read, update, delete, def int) map[string]interface{} {
   360  	ex := make(map[string]interface{}, 0)
   361  
   362  	if create != 0 {
   363  		ex["create"] = fmt.Sprintf("%dm", create)
   364  	}
   365  	if read != 0 {
   366  		ex["read"] = fmt.Sprintf("%dm", read)
   367  	}
   368  	if update != 0 {
   369  		ex["update"] = fmt.Sprintf("%dm", update)
   370  	}
   371  	if delete != 0 {
   372  		ex["delete"] = fmt.Sprintf("%dm", delete)
   373  	}
   374  
   375  	if def != 0 {
   376  		ex["default"] = fmt.Sprintf("%dm", def)
   377  	}
   378  	return ex
   379  }