github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/helper/pluginutils/hclspecutils/dec_test.go (about) 1 package hclspecutils 2 3 import ( 4 "testing" 5 6 "github.com/hashicorp/hcl/v2/hcldec" 7 "github.com/hashicorp/nomad/ci" 8 "github.com/hashicorp/nomad/plugins/shared/hclspec" 9 "github.com/stretchr/testify/require" 10 "github.com/zclconf/go-cty/cty" 11 ) 12 13 type testConversions struct { 14 Name string 15 Input *hclspec.Spec 16 Expected hcldec.Spec 17 ExpectedError string 18 } 19 20 func testSpecConversions(t *testing.T, cases []testConversions) { 21 t.Helper() 22 23 for _, c := range cases { 24 t.Run(c.Name, func(t *testing.T) { 25 act, diag := Convert(c.Input) 26 if diag.HasErrors() { 27 if c.ExpectedError == "" { 28 t.Fatalf("Convert %q failed: %v", c.Name, diag.Error()) 29 } 30 31 require.Contains(t, diag.Error(), c.ExpectedError) 32 } else if c.ExpectedError != "" { 33 t.Fatalf("Expected error %q", c.ExpectedError) 34 } 35 36 require.EqualValues(t, c.Expected, act) 37 }) 38 } 39 } 40 41 func TestDec_Convert_Object(t *testing.T) { 42 ci.Parallel(t) 43 44 tests := []testConversions{ 45 { 46 Name: "Object w/ only attributes", 47 Input: &hclspec.Spec{ 48 Block: &hclspec.Spec_Object{ 49 Object: &hclspec.Object{ 50 Attributes: map[string]*hclspec.Spec{ 51 "foo": { 52 Block: &hclspec.Spec_Attr{ 53 Attr: &hclspec.Attr{ 54 Type: "string", 55 Required: false, 56 }, 57 }, 58 }, 59 "bar": { 60 Block: &hclspec.Spec_Attr{ 61 Attr: &hclspec.Attr{ 62 Type: "number", 63 Required: true, 64 }, 65 }, 66 }, 67 "baz": { 68 Block: &hclspec.Spec_Attr{ 69 Attr: &hclspec.Attr{ 70 Type: "bool", 71 }, 72 }, 73 }, 74 }, 75 }, 76 }, 77 }, 78 Expected: hcldec.ObjectSpec(map[string]hcldec.Spec{ 79 "foo": &hcldec.AttrSpec{ 80 Name: "foo", 81 Type: cty.String, 82 Required: false, 83 }, 84 "bar": &hcldec.AttrSpec{ 85 Name: "bar", 86 Type: cty.Number, 87 Required: true, 88 }, 89 "baz": &hcldec.AttrSpec{ 90 Name: "baz", 91 Type: cty.Bool, 92 Required: false, 93 }, 94 }), 95 }, 96 } 97 98 testSpecConversions(t, tests) 99 } 100 101 func TestDec_Convert_Array(t *testing.T) { 102 ci.Parallel(t) 103 104 tests := []testConversions{ 105 { 106 Name: "array basic", 107 Input: &hclspec.Spec{ 108 Block: &hclspec.Spec_Array{ 109 Array: &hclspec.Array{ 110 Values: []*hclspec.Spec{ 111 { 112 Block: &hclspec.Spec_Attr{ 113 Attr: &hclspec.Attr{ 114 Name: "foo", 115 Required: true, 116 Type: "string", 117 }, 118 }, 119 }, 120 { 121 Block: &hclspec.Spec_Attr{ 122 Attr: &hclspec.Attr{ 123 Name: "bar", 124 Required: true, 125 Type: "string", 126 }, 127 }, 128 }, 129 }, 130 }, 131 }, 132 }, 133 Expected: hcldec.TupleSpec{ 134 &hcldec.AttrSpec{ 135 Name: "foo", 136 Type: cty.String, 137 Required: true, 138 }, 139 &hcldec.AttrSpec{ 140 Name: "bar", 141 Type: cty.String, 142 Required: true, 143 }, 144 }, 145 }, 146 } 147 148 testSpecConversions(t, tests) 149 } 150 151 func TestDec_Convert_Attr(t *testing.T) { 152 ci.Parallel(t) 153 154 tests := []testConversions{ 155 { 156 Name: "attr basic type", 157 Input: &hclspec.Spec{ 158 Block: &hclspec.Spec_Attr{ 159 Attr: &hclspec.Attr{ 160 Name: "foo", 161 Required: true, 162 Type: "string", 163 }, 164 }, 165 }, 166 Expected: &hcldec.AttrSpec{ 167 Name: "foo", 168 Type: cty.String, 169 Required: true, 170 }, 171 }, 172 { 173 Name: "attr object type", 174 Input: &hclspec.Spec{ 175 Block: &hclspec.Spec_Attr{ 176 Attr: &hclspec.Attr{ 177 Name: "foo", 178 Required: true, 179 Type: "object({name1 = string, name2 = bool})", 180 }, 181 }, 182 }, 183 Expected: &hcldec.AttrSpec{ 184 Name: "foo", 185 Type: cty.Object(map[string]cty.Type{ 186 "name1": cty.String, 187 "name2": cty.Bool, 188 }), 189 Required: true, 190 }, 191 }, 192 { 193 Name: "attr no name", 194 Input: &hclspec.Spec{ 195 Block: &hclspec.Spec_Attr{ 196 Attr: &hclspec.Attr{ 197 Required: true, 198 Type: "string", 199 }, 200 }, 201 }, 202 ExpectedError: "Missing name in attribute spec", 203 }, 204 } 205 206 testSpecConversions(t, tests) 207 } 208 209 func TestDec_Convert_Block(t *testing.T) { 210 ci.Parallel(t) 211 212 tests := []testConversions{ 213 { 214 Name: "block with attr", 215 Input: &hclspec.Spec{ 216 Block: &hclspec.Spec_BlockValue{ 217 BlockValue: &hclspec.Block{ 218 Name: "test", 219 Required: true, 220 Nested: &hclspec.Spec{ 221 Block: &hclspec.Spec_Attr{ 222 Attr: &hclspec.Attr{ 223 Name: "foo", 224 Type: "string", 225 }, 226 }, 227 }, 228 }, 229 }, 230 }, 231 Expected: &hcldec.BlockSpec{ 232 TypeName: "test", 233 Required: true, 234 Nested: &hcldec.AttrSpec{ 235 Name: "foo", 236 Type: cty.String, 237 Required: false, 238 }, 239 }, 240 }, 241 { 242 Name: "block with nested block", 243 Input: &hclspec.Spec{ 244 Block: &hclspec.Spec_BlockValue{ 245 BlockValue: &hclspec.Block{ 246 Name: "test", 247 Required: true, 248 Nested: &hclspec.Spec{ 249 Block: &hclspec.Spec_BlockValue{ 250 BlockValue: &hclspec.Block{ 251 Name: "test", 252 Required: true, 253 Nested: &hclspec.Spec{ 254 Block: &hclspec.Spec_Attr{ 255 Attr: &hclspec.Attr{ 256 Name: "foo", 257 Type: "string", 258 }, 259 }, 260 }, 261 }, 262 }, 263 }, 264 }, 265 }, 266 }, 267 Expected: &hcldec.BlockSpec{ 268 TypeName: "test", 269 Required: true, 270 Nested: &hcldec.BlockSpec{ 271 TypeName: "test", 272 Required: true, 273 Nested: &hcldec.AttrSpec{ 274 Name: "foo", 275 Type: cty.String, 276 Required: false, 277 }, 278 }, 279 }, 280 }, 281 } 282 283 testSpecConversions(t, tests) 284 } 285 286 func TestDec_Convert_BlockAttrs(t *testing.T) { 287 ci.Parallel(t) 288 289 tests := []testConversions{ 290 { 291 Name: "block attr", 292 Input: &hclspec.Spec{ 293 Block: &hclspec.Spec_BlockAttrs{ 294 BlockAttrs: &hclspec.BlockAttrs{ 295 Name: "test", 296 Type: "string", 297 Required: true, 298 }, 299 }, 300 }, 301 Expected: &hcldec.BlockAttrsSpec{ 302 TypeName: "test", 303 ElementType: cty.String, 304 Required: true, 305 }, 306 }, 307 { 308 Name: "block list no name", 309 Input: &hclspec.Spec{ 310 Block: &hclspec.Spec_BlockAttrs{ 311 BlockAttrs: &hclspec.BlockAttrs{ 312 Type: "string", 313 Required: true, 314 }, 315 }, 316 }, 317 ExpectedError: "Missing name in block_attrs spec", 318 }, 319 } 320 321 testSpecConversions(t, tests) 322 } 323 324 func TestDec_Convert_BlockList(t *testing.T) { 325 ci.Parallel(t) 326 327 tests := []testConversions{ 328 { 329 Name: "block list with attr", 330 Input: &hclspec.Spec{ 331 Block: &hclspec.Spec_BlockList{ 332 BlockList: &hclspec.BlockList{ 333 Name: "test", 334 MinItems: 1, 335 MaxItems: 3, 336 Nested: &hclspec.Spec{ 337 Block: &hclspec.Spec_Attr{ 338 Attr: &hclspec.Attr{ 339 Name: "foo", 340 Type: "string", 341 }, 342 }, 343 }, 344 }, 345 }, 346 }, 347 Expected: &hcldec.BlockListSpec{ 348 TypeName: "test", 349 MinItems: 1, 350 MaxItems: 3, 351 Nested: &hcldec.AttrSpec{ 352 Name: "foo", 353 Type: cty.String, 354 Required: false, 355 }, 356 }, 357 }, 358 { 359 Name: "block list no name", 360 Input: &hclspec.Spec{ 361 Block: &hclspec.Spec_BlockList{ 362 BlockList: &hclspec.BlockList{ 363 MinItems: 1, 364 MaxItems: 3, 365 Nested: &hclspec.Spec{ 366 Block: &hclspec.Spec_Attr{ 367 Attr: &hclspec.Attr{ 368 Name: "foo", 369 Type: "string", 370 }, 371 }, 372 }, 373 }, 374 }, 375 }, 376 ExpectedError: "Missing name in block_list spec", 377 }, 378 } 379 380 testSpecConversions(t, tests) 381 } 382 383 func TestDec_Convert_BlockSet(t *testing.T) { 384 ci.Parallel(t) 385 386 tests := []testConversions{ 387 { 388 Name: "block set with attr", 389 Input: &hclspec.Spec{ 390 Block: &hclspec.Spec_BlockSet{ 391 BlockSet: &hclspec.BlockSet{ 392 Name: "test", 393 MinItems: 1, 394 MaxItems: 3, 395 Nested: &hclspec.Spec{ 396 Block: &hclspec.Spec_Attr{ 397 Attr: &hclspec.Attr{ 398 Name: "foo", 399 Type: "string", 400 }, 401 }, 402 }, 403 }, 404 }, 405 }, 406 Expected: &hcldec.BlockSetSpec{ 407 TypeName: "test", 408 MinItems: 1, 409 MaxItems: 3, 410 Nested: &hcldec.AttrSpec{ 411 Name: "foo", 412 Type: cty.String, 413 Required: false, 414 }, 415 }, 416 }, 417 { 418 Name: "block set missing name", 419 Input: &hclspec.Spec{ 420 Block: &hclspec.Spec_BlockSet{ 421 BlockSet: &hclspec.BlockSet{ 422 MinItems: 1, 423 MaxItems: 3, 424 Nested: &hclspec.Spec{ 425 Block: &hclspec.Spec_Attr{ 426 Attr: &hclspec.Attr{ 427 Name: "foo", 428 Type: "string", 429 }, 430 }, 431 }, 432 }, 433 }, 434 }, 435 ExpectedError: "Missing name in block_set spec", 436 }, 437 } 438 439 testSpecConversions(t, tests) 440 } 441 442 func TestDec_Convert_BlockMap(t *testing.T) { 443 ci.Parallel(t) 444 445 tests := []testConversions{ 446 { 447 Name: "block map with attr", 448 Input: &hclspec.Spec{ 449 Block: &hclspec.Spec_BlockMap{ 450 BlockMap: &hclspec.BlockMap{ 451 Name: "test", 452 Labels: []string{"key1", "key2"}, 453 Nested: &hclspec.Spec{ 454 Block: &hclspec.Spec_Attr{ 455 Attr: &hclspec.Attr{ 456 Name: "foo", 457 Type: "string", 458 }, 459 }, 460 }, 461 }, 462 }, 463 }, 464 Expected: &hcldec.BlockMapSpec{ 465 TypeName: "test", 466 LabelNames: []string{"key1", "key2"}, 467 Nested: &hcldec.AttrSpec{ 468 Name: "foo", 469 Type: cty.String, 470 Required: false, 471 }, 472 }, 473 }, 474 { 475 Name: "block map missing name", 476 Input: &hclspec.Spec{ 477 Block: &hclspec.Spec_BlockMap{ 478 BlockMap: &hclspec.BlockMap{ 479 Labels: []string{"key1", "key2"}, 480 Nested: &hclspec.Spec{ 481 Block: &hclspec.Spec_Attr{ 482 Attr: &hclspec.Attr{ 483 Name: "foo", 484 Type: "string", 485 }, 486 }, 487 }, 488 }, 489 }, 490 }, 491 ExpectedError: "Missing name in block_map spec", 492 }, 493 { 494 Name: "block map missing labels", 495 Input: &hclspec.Spec{ 496 Block: &hclspec.Spec_BlockMap{ 497 BlockMap: &hclspec.BlockMap{ 498 Name: "foo", 499 Nested: &hclspec.Spec{ 500 Block: &hclspec.Spec_Attr{ 501 Attr: &hclspec.Attr{ 502 Name: "foo", 503 Type: "string", 504 }, 505 }, 506 }, 507 }, 508 }, 509 }, 510 ExpectedError: "Invalid block label name list", 511 }, 512 } 513 514 testSpecConversions(t, tests) 515 } 516 517 func TestDec_Convert_Default(t *testing.T) { 518 ci.Parallel(t) 519 520 tests := []testConversions{ 521 { 522 Name: "default attr", 523 Input: &hclspec.Spec{ 524 Block: &hclspec.Spec_Default{ 525 Default: &hclspec.Default{ 526 Primary: &hclspec.Spec{ 527 Block: &hclspec.Spec_Attr{ 528 Attr: &hclspec.Attr{ 529 Name: "foo", 530 Type: "string", 531 Required: true, 532 }, 533 }, 534 }, 535 Default: &hclspec.Spec{ 536 Block: &hclspec.Spec_Literal{ 537 Literal: &hclspec.Literal{ 538 Value: "\"hi\"", 539 }, 540 }, 541 }, 542 }, 543 }, 544 }, 545 Expected: &hcldec.DefaultSpec{ 546 Primary: &hcldec.AttrSpec{ 547 Name: "foo", 548 Type: cty.String, 549 Required: true, 550 }, 551 Default: &hcldec.LiteralSpec{ 552 Value: cty.StringVal("hi"), 553 }, 554 }, 555 }, 556 } 557 558 testSpecConversions(t, tests) 559 } 560 561 func TestDec_Convert_Literal(t *testing.T) { 562 ci.Parallel(t) 563 564 tests := []testConversions{ 565 { 566 Name: "bool: true", 567 Input: &hclspec.Spec{ 568 Block: &hclspec.Spec_Literal{ 569 Literal: &hclspec.Literal{ 570 Value: "true", 571 }, 572 }, 573 }, 574 Expected: &hcldec.LiteralSpec{ 575 Value: cty.BoolVal(true), 576 }, 577 }, 578 { 579 Name: "bool: false", 580 Input: &hclspec.Spec{ 581 Block: &hclspec.Spec_Literal{ 582 Literal: &hclspec.Literal{ 583 Value: "false", 584 }, 585 }, 586 }, 587 Expected: &hcldec.LiteralSpec{ 588 Value: cty.BoolVal(false), 589 }, 590 }, 591 { 592 Name: "string", 593 Input: &hclspec.Spec{ 594 Block: &hclspec.Spec_Literal{ 595 Literal: &hclspec.Literal{ 596 Value: "\"hi\"", 597 }, 598 }, 599 }, 600 Expected: &hcldec.LiteralSpec{ 601 Value: cty.StringVal("hi"), 602 }, 603 }, 604 { 605 Name: "string w/ func", 606 Input: &hclspec.Spec{ 607 Block: &hclspec.Spec_Literal{ 608 Literal: &hclspec.Literal{ 609 Value: "reverse(\"hi\")", 610 }, 611 }, 612 }, 613 Expected: &hcldec.LiteralSpec{ 614 Value: cty.StringVal("ih"), 615 }, 616 }, 617 { 618 Name: "list string", 619 Input: &hclspec.Spec{ 620 Block: &hclspec.Spec_Literal{ 621 Literal: &hclspec.Literal{ 622 Value: "[\"hi\", \"bye\"]", 623 }, 624 }, 625 }, 626 Expected: &hcldec.LiteralSpec{ 627 Value: cty.TupleVal([]cty.Value{cty.StringVal("hi"), cty.StringVal("bye")}), 628 }, 629 }, 630 } 631 632 testSpecConversions(t, tests) 633 }