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