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 }