github.com/hashicorp/terraform-plugin-sdk@v1.17.2/helper/schema/resource_timeout_test.go (about)

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