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 }