github.com/iaas-resource-provision/iaas-rpc@v1.0.7-0.20211021023331-ed21f798c408/internal/lang/funcs/defaults_test.go (about) 1 package funcs 2 3 import ( 4 "fmt" 5 "testing" 6 7 "github.com/zclconf/go-cty/cty" 8 ) 9 10 func TestDefaults(t *testing.T) { 11 tests := []struct { 12 Input, Defaults cty.Value 13 Want cty.Value 14 WantErr string 15 }{ 16 { 17 Input: cty.ObjectVal(map[string]cty.Value{ 18 "a": cty.NullVal(cty.String), 19 }), 20 Defaults: cty.ObjectVal(map[string]cty.Value{ 21 "a": cty.StringVal("hello"), 22 }), 23 Want: cty.ObjectVal(map[string]cty.Value{ 24 "a": cty.StringVal("hello"), 25 }), 26 }, 27 { 28 Input: cty.ObjectVal(map[string]cty.Value{ 29 "a": cty.StringVal("hey"), 30 }), 31 Defaults: cty.ObjectVal(map[string]cty.Value{ 32 "a": cty.StringVal("hello"), 33 }), 34 Want: cty.ObjectVal(map[string]cty.Value{ 35 "a": cty.StringVal("hey"), 36 }), 37 }, 38 { 39 Input: cty.ObjectVal(map[string]cty.Value{ 40 "a": cty.NullVal(cty.String), 41 }), 42 Defaults: cty.ObjectVal(map[string]cty.Value{ 43 "a": cty.NullVal(cty.String), 44 }), 45 Want: cty.ObjectVal(map[string]cty.Value{ 46 "a": cty.NullVal(cty.String), 47 }), 48 }, 49 { 50 Input: cty.ObjectVal(map[string]cty.Value{ 51 "a": cty.NullVal(cty.String), 52 }), 53 Defaults: cty.ObjectVal(map[string]cty.Value{}), 54 Want: cty.ObjectVal(map[string]cty.Value{ 55 "a": cty.NullVal(cty.String), 56 }), 57 }, 58 { 59 Input: cty.ObjectVal(map[string]cty.Value{}), 60 Defaults: cty.ObjectVal(map[string]cty.Value{ 61 "a": cty.NullVal(cty.String), 62 }), 63 WantErr: `.a: target type does not expect an attribute named "a"`, 64 }, 65 66 { 67 Input: cty.ObjectVal(map[string]cty.Value{ 68 "a": cty.ListVal([]cty.Value{ 69 cty.NullVal(cty.String), 70 }), 71 }), 72 Defaults: cty.ObjectVal(map[string]cty.Value{ 73 "a": cty.StringVal("hello"), 74 }), 75 Want: cty.ObjectVal(map[string]cty.Value{ 76 "a": cty.ListVal([]cty.Value{ 77 cty.StringVal("hello"), 78 }), 79 }), 80 }, 81 { 82 Input: cty.ObjectVal(map[string]cty.Value{ 83 "a": cty.ListVal([]cty.Value{ 84 cty.NullVal(cty.String), 85 cty.StringVal("hey"), 86 cty.NullVal(cty.String), 87 }), 88 }), 89 Defaults: cty.ObjectVal(map[string]cty.Value{ 90 "a": cty.StringVal("hello"), 91 }), 92 Want: cty.ObjectVal(map[string]cty.Value{ 93 "a": cty.ListVal([]cty.Value{ 94 cty.StringVal("hello"), 95 cty.StringVal("hey"), 96 cty.StringVal("hello"), 97 }), 98 }), 99 }, 100 { 101 // Using defaults with single set elements is a pretty 102 // odd thing to do, but this behavior is just here because 103 // it generalizes from how we handle collections. It's 104 // tested only to ensure it doesn't change accidentally 105 // in future. 106 Input: cty.ObjectVal(map[string]cty.Value{ 107 "a": cty.SetVal([]cty.Value{ 108 cty.NullVal(cty.String), 109 cty.StringVal("hey"), 110 }), 111 }), 112 Defaults: cty.ObjectVal(map[string]cty.Value{ 113 "a": cty.StringVal("hello"), 114 }), 115 Want: cty.ObjectVal(map[string]cty.Value{ 116 "a": cty.SetVal([]cty.Value{ 117 cty.StringVal("hey"), 118 cty.StringVal("hello"), 119 }), 120 }), 121 }, 122 { 123 Input: cty.ObjectVal(map[string]cty.Value{ 124 "a": cty.MapVal(map[string]cty.Value{ 125 "x": cty.NullVal(cty.String), 126 "y": cty.StringVal("hey"), 127 "z": cty.NullVal(cty.String), 128 }), 129 }), 130 Defaults: cty.ObjectVal(map[string]cty.Value{ 131 "a": cty.StringVal("hello"), 132 }), 133 Want: cty.ObjectVal(map[string]cty.Value{ 134 "a": cty.MapVal(map[string]cty.Value{ 135 "x": cty.StringVal("hello"), 136 "y": cty.StringVal("hey"), 137 "z": cty.StringVal("hello"), 138 }), 139 }), 140 }, 141 { 142 Input: cty.ObjectVal(map[string]cty.Value{ 143 "a": cty.ListVal([]cty.Value{ 144 cty.ObjectVal(map[string]cty.Value{ 145 "b": cty.StringVal("hey"), 146 }), 147 cty.ObjectVal(map[string]cty.Value{ 148 "b": cty.NullVal(cty.String), 149 }), 150 cty.ObjectVal(map[string]cty.Value{ 151 "b": cty.StringVal("hey"), 152 }), 153 }), 154 }), 155 Defaults: cty.ObjectVal(map[string]cty.Value{ 156 "a": cty.ObjectVal(map[string]cty.Value{ 157 "b": cty.StringVal("hello"), 158 }), 159 }), 160 Want: cty.ObjectVal(map[string]cty.Value{ 161 "a": cty.ListVal([]cty.Value{ 162 cty.ObjectVal(map[string]cty.Value{ 163 "b": cty.StringVal("hey"), 164 }), 165 cty.ObjectVal(map[string]cty.Value{ 166 "b": cty.StringVal("hello"), 167 }), 168 cty.ObjectVal(map[string]cty.Value{ 169 "b": cty.StringVal("hey"), 170 }), 171 }), 172 }), 173 }, 174 { 175 Input: cty.ListVal([]cty.Value{ 176 cty.ObjectVal(map[string]cty.Value{ 177 "b": cty.StringVal("hey"), 178 }), 179 cty.ObjectVal(map[string]cty.Value{ 180 "b": cty.NullVal(cty.String), 181 }), 182 cty.ObjectVal(map[string]cty.Value{ 183 "b": cty.StringVal("hey"), 184 }), 185 }), 186 Defaults: cty.ObjectVal(map[string]cty.Value{ 187 "b": cty.StringVal("hello"), 188 }), 189 Want: cty.ListVal([]cty.Value{ 190 cty.ObjectVal(map[string]cty.Value{ 191 "b": cty.StringVal("hey"), 192 }), 193 cty.ObjectVal(map[string]cty.Value{ 194 "b": cty.StringVal("hello"), 195 }), 196 cty.ObjectVal(map[string]cty.Value{ 197 "b": cty.StringVal("hey"), 198 }), 199 }), 200 }, 201 { 202 Input: cty.ObjectVal(map[string]cty.Value{ 203 "a": cty.SetVal([]cty.Value{ 204 cty.ObjectVal(map[string]cty.Value{ 205 "b": cty.StringVal("boop"), 206 }), 207 cty.ObjectVal(map[string]cty.Value{ 208 "b": cty.NullVal(cty.String), 209 }), 210 cty.ObjectVal(map[string]cty.Value{ 211 "b": cty.StringVal("hey"), 212 }), 213 }), 214 }), 215 Defaults: cty.ObjectVal(map[string]cty.Value{ 216 "a": cty.ObjectVal(map[string]cty.Value{ 217 "b": cty.StringVal("hello"), 218 }), 219 }), 220 Want: cty.ObjectVal(map[string]cty.Value{ 221 "a": cty.SetVal([]cty.Value{ 222 cty.ObjectVal(map[string]cty.Value{ 223 "b": cty.StringVal("boop"), 224 }), 225 cty.ObjectVal(map[string]cty.Value{ 226 "b": cty.StringVal("hello"), 227 }), 228 cty.ObjectVal(map[string]cty.Value{ 229 "b": cty.StringVal("hey"), 230 }), 231 }), 232 }), 233 }, 234 { 235 Input: cty.ObjectVal(map[string]cty.Value{ 236 "a": cty.SetVal([]cty.Value{ 237 cty.ObjectVal(map[string]cty.Value{ 238 "b": cty.StringVal("hello"), 239 }), 240 cty.ObjectVal(map[string]cty.Value{ 241 "b": cty.NullVal(cty.String), 242 }), 243 }), 244 }), 245 Defaults: cty.ObjectVal(map[string]cty.Value{ 246 "a": cty.ObjectVal(map[string]cty.Value{ 247 "b": cty.StringVal("hello"), 248 }), 249 }), 250 Want: cty.ObjectVal(map[string]cty.Value{ 251 "a": cty.SetVal([]cty.Value{ 252 // After applying defaults, the one with a null value 253 // coalesced with the one with a non-null value, 254 // and so there's only one left. 255 cty.ObjectVal(map[string]cty.Value{ 256 "b": cty.StringVal("hello"), 257 }), 258 }), 259 }), 260 }, 261 { 262 Input: cty.ObjectVal(map[string]cty.Value{ 263 "a": cty.MapVal(map[string]cty.Value{ 264 "boop": cty.ObjectVal(map[string]cty.Value{ 265 "b": cty.StringVal("hey"), 266 }), 267 "beep": cty.ObjectVal(map[string]cty.Value{ 268 "b": cty.NullVal(cty.String), 269 }), 270 }), 271 }), 272 Defaults: cty.ObjectVal(map[string]cty.Value{ 273 "a": cty.ObjectVal(map[string]cty.Value{ 274 "b": cty.StringVal("hello"), 275 }), 276 }), 277 Want: cty.ObjectVal(map[string]cty.Value{ 278 "a": cty.MapVal(map[string]cty.Value{ 279 "boop": cty.ObjectVal(map[string]cty.Value{ 280 "b": cty.StringVal("hey"), 281 }), 282 "beep": cty.ObjectVal(map[string]cty.Value{ 283 "b": cty.StringVal("hello"), 284 }), 285 }), 286 }), 287 }, 288 { 289 Input: cty.ObjectVal(map[string]cty.Value{ 290 "a": cty.ListVal([]cty.Value{ 291 cty.ObjectVal(map[string]cty.Value{ 292 "b": cty.StringVal("hey"), 293 }), 294 cty.ObjectVal(map[string]cty.Value{ 295 "b": cty.NullVal(cty.String), 296 }), 297 cty.ObjectVal(map[string]cty.Value{ 298 "b": cty.StringVal("hey"), 299 }), 300 }), 301 }), 302 Defaults: cty.ObjectVal(map[string]cty.Value{ 303 "a": cty.StringVal("hello"), 304 }), 305 WantErr: `.a: the default value for a collection of an object type must itself be an object type, not string`, 306 }, 307 { 308 Input: cty.ObjectVal(map[string]cty.Value{ 309 "a": cty.ListVal([]cty.Value{ 310 cty.NullVal(cty.String), 311 cty.StringVal("hey"), 312 cty.NullVal(cty.String), 313 }), 314 }), 315 Defaults: cty.ObjectVal(map[string]cty.Value{ 316 // The default value for a list must be a single value 317 // of the list's element type which provides defaults 318 // for each element separately, so the default for a 319 // list of string should be just a single string, not 320 // a list of string. 321 "a": cty.ListVal([]cty.Value{ 322 cty.StringVal("hello"), 323 }), 324 }), 325 WantErr: `.a: invalid default value for string: string required`, 326 }, 327 { 328 Input: cty.ObjectVal(map[string]cty.Value{ 329 "a": cty.TupleVal([]cty.Value{ 330 cty.NullVal(cty.String), 331 cty.StringVal("hey"), 332 cty.NullVal(cty.String), 333 }), 334 }), 335 Defaults: cty.ObjectVal(map[string]cty.Value{ 336 "a": cty.StringVal("hello"), 337 }), 338 WantErr: `.a: the default value for a tuple type must itself be a tuple type, not string`, 339 }, 340 { 341 Input: cty.ObjectVal(map[string]cty.Value{ 342 "a": cty.TupleVal([]cty.Value{ 343 cty.NullVal(cty.String), 344 cty.StringVal("hey"), 345 cty.NullVal(cty.String), 346 }), 347 }), 348 Defaults: cty.ObjectVal(map[string]cty.Value{ 349 "a": cty.TupleVal([]cty.Value{ 350 cty.StringVal("hello 0"), 351 cty.StringVal("hello 1"), 352 cty.StringVal("hello 2"), 353 }), 354 }), 355 Want: cty.ObjectVal(map[string]cty.Value{ 356 "a": cty.TupleVal([]cty.Value{ 357 cty.StringVal("hello 0"), 358 cty.StringVal("hey"), 359 cty.StringVal("hello 2"), 360 }), 361 }), 362 }, 363 { 364 // There's no reason to use this function for plain primitive 365 // types, because the "default" argument in a variable definition 366 // already has the equivalent behavior. This function is only 367 // to deal with the situation of a complex-typed variable where 368 // only parts of the data structure are optional. 369 Input: cty.NullVal(cty.String), 370 Defaults: cty.StringVal("hello"), 371 WantErr: `only object types and collections of object types can have defaults applied`, 372 }, 373 // When applying default values to structural types, null objects or 374 // tuples in the input should be passed through. 375 { 376 Input: cty.ObjectVal(map[string]cty.Value{ 377 "a": cty.NullVal(cty.Object(map[string]cty.Type{ 378 "x": cty.String, 379 "y": cty.String, 380 })), 381 "b": cty.NullVal(cty.Tuple([]cty.Type{cty.String, cty.String})), 382 }), 383 Defaults: cty.ObjectVal(map[string]cty.Value{ 384 "a": cty.ObjectVal(map[string]cty.Value{ 385 "x": cty.StringVal("hello"), 386 "y": cty.StringVal("there"), 387 }), 388 "b": cty.TupleVal([]cty.Value{ 389 cty.StringVal("how are"), 390 cty.StringVal("you?"), 391 }), 392 }), 393 Want: cty.ObjectVal(map[string]cty.Value{ 394 "a": cty.NullVal(cty.Object(map[string]cty.Type{ 395 "x": cty.String, 396 "y": cty.String, 397 })), 398 "b": cty.NullVal(cty.Tuple([]cty.Type{cty.String, cty.String})), 399 }), 400 }, 401 // When applying default values to structural types, we permit null 402 // values in the defaults, and just pass through the input value. 403 { 404 Input: cty.ObjectVal(map[string]cty.Value{ 405 "a": cty.ListVal([]cty.Value{ 406 cty.ObjectVal(map[string]cty.Value{ 407 "p": cty.StringVal("xyz"), 408 "q": cty.StringVal("xyz"), 409 }), 410 }), 411 "b": cty.SetVal([]cty.Value{ 412 cty.TupleVal([]cty.Value{ 413 cty.NumberIntVal(0), 414 cty.NumberIntVal(2), 415 }), 416 cty.TupleVal([]cty.Value{ 417 cty.NumberIntVal(1), 418 cty.NumberIntVal(3), 419 }), 420 }), 421 "c": cty.NullVal(cty.String), 422 }), 423 Defaults: cty.ObjectVal(map[string]cty.Value{ 424 "c": cty.StringVal("tada"), 425 }), 426 Want: cty.ObjectVal(map[string]cty.Value{ 427 "a": cty.ListVal([]cty.Value{ 428 cty.ObjectVal(map[string]cty.Value{ 429 "p": cty.StringVal("xyz"), 430 "q": cty.StringVal("xyz"), 431 }), 432 }), 433 "b": cty.SetVal([]cty.Value{ 434 cty.TupleVal([]cty.Value{ 435 cty.NumberIntVal(0), 436 cty.NumberIntVal(2), 437 }), 438 cty.TupleVal([]cty.Value{ 439 cty.NumberIntVal(1), 440 cty.NumberIntVal(3), 441 }), 442 }), 443 "c": cty.StringVal("tada"), 444 }), 445 }, 446 // When applying default values to collection types, null collections in the 447 // input should result in empty collections in the output. 448 { 449 Input: cty.ObjectVal(map[string]cty.Value{ 450 "a": cty.NullVal(cty.List(cty.String)), 451 "b": cty.NullVal(cty.Map(cty.String)), 452 "c": cty.NullVal(cty.Set(cty.String)), 453 }), 454 Defaults: cty.ObjectVal(map[string]cty.Value{ 455 "a": cty.StringVal("hello"), 456 "b": cty.StringVal("hi"), 457 "c": cty.StringVal("greetings"), 458 }), 459 Want: cty.ObjectVal(map[string]cty.Value{ 460 "a": cty.ListValEmpty(cty.String), 461 "b": cty.MapValEmpty(cty.String), 462 "c": cty.SetValEmpty(cty.String), 463 }), 464 }, 465 // When specifying fallbacks, we allow mismatched primitive attribute 466 // types so long as a safe conversion is possible. This means that we 467 // can accept number or boolean values for string attributes. 468 { 469 Input: cty.ObjectVal(map[string]cty.Value{ 470 "a": cty.NullVal(cty.String), 471 "b": cty.NullVal(cty.String), 472 "c": cty.NullVal(cty.String), 473 }), 474 Defaults: cty.ObjectVal(map[string]cty.Value{ 475 "a": cty.NumberIntVal(5), 476 "b": cty.True, 477 "c": cty.StringVal("greetings"), 478 }), 479 Want: cty.ObjectVal(map[string]cty.Value{ 480 "a": cty.StringVal("5"), 481 "b": cty.StringVal("true"), 482 "c": cty.StringVal("greetings"), 483 }), 484 }, 485 // Fallbacks with mismatched primitive attribute types which do not 486 // have safe conversions must not pass the suitable fallback check, 487 // even if unsafe conversion would be possible. 488 { 489 Input: cty.ObjectVal(map[string]cty.Value{ 490 "a": cty.NullVal(cty.Bool), 491 }), 492 Defaults: cty.ObjectVal(map[string]cty.Value{ 493 "a": cty.StringVal("5"), 494 }), 495 WantErr: ".a: invalid default value for bool: bool required", 496 }, 497 } 498 499 for _, test := range tests { 500 t.Run(fmt.Sprintf("defaults(%#v, %#v)", test.Input, test.Defaults), func(t *testing.T) { 501 got, gotErr := Defaults(test.Input, test.Defaults) 502 503 if test.WantErr != "" { 504 if gotErr == nil { 505 t.Fatalf("unexpected success\nwant error: %s", test.WantErr) 506 } 507 if got, want := gotErr.Error(), test.WantErr; got != want { 508 t.Fatalf("wrong error\ngot: %s\nwant: %s", got, want) 509 } 510 return 511 } else if gotErr != nil { 512 t.Fatalf("unexpected error\ngot: %s", gotErr.Error()) 513 } 514 515 if !test.Want.RawEquals(got) { 516 t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, test.Want) 517 } 518 }) 519 } 520 }