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