github.com/adevinta/lava@v0.7.2/internal/engine/jobs_test.go (about) 1 // Copyright 2023 Adevinta 2 3 package engine 4 5 import ( 6 "fmt" 7 "testing" 8 9 "github.com/adevinta/vulcan-agent/jobrunner" 10 checkcatalog "github.com/adevinta/vulcan-check-catalog/pkg/model" 11 types "github.com/adevinta/vulcan-types" 12 "github.com/google/go-cmp/cmp" 13 "github.com/google/go-cmp/cmp/cmpopts" 14 15 "github.com/adevinta/lava/internal/assettypes" 16 "github.com/adevinta/lava/internal/checktypes" 17 "github.com/adevinta/lava/internal/config" 18 ) 19 20 func TestGenerateChecks(t *testing.T) { 21 tests := []struct { 22 name string 23 catalog checktypes.Catalog 24 targets []config.Target 25 want []check 26 }{ 27 { 28 name: "one checktype and one target", 29 catalog: checktypes.Catalog{ 30 "checktype1": { 31 Name: "checktype1", 32 Description: "checktype1 description", 33 Image: "namespace/repository:tag", 34 Assets: []string{ 35 "DomainName", 36 }, 37 }, 38 }, 39 targets: []config.Target{ 40 { 41 Identifier: "example.com", 42 AssetType: types.DomainName, 43 }, 44 }, 45 want: []check{ 46 { 47 checktype: checkcatalog.Checktype{ 48 Name: "checktype1", 49 Description: "checktype1 description", 50 Image: "namespace/repository:tag", 51 Assets: []string{ 52 "DomainName", 53 }, 54 }, 55 target: config.Target{ 56 Identifier: "example.com", 57 AssetType: types.DomainName, 58 }, 59 options: map[string]any{}, 60 }, 61 }, 62 }, 63 { 64 name: "target overrides checktype options", 65 catalog: checktypes.Catalog{ 66 "checktype1": { 67 Name: "checktype1", 68 Description: "checktype1 description", 69 Image: "namespace/repository:tag", 70 Assets: []string{ 71 "DomainName", 72 }, 73 Options: map[string]interface{}{ 74 "option1": "checktype value 1", 75 "option2": "checktype value 2", 76 "option3": "checktype value 3", 77 }, 78 }, 79 }, 80 targets: []config.Target{ 81 { 82 Identifier: "example.com", 83 AssetType: types.DomainName, 84 Options: map[string]interface{}{ 85 "option2": "target value 2", 86 }, 87 }, 88 }, 89 want: []check{ 90 { 91 checktype: checkcatalog.Checktype{ 92 Name: "checktype1", 93 Description: "checktype1 description", 94 Image: "namespace/repository:tag", 95 Assets: []string{ 96 "DomainName", 97 }, 98 Options: map[string]interface{}{ 99 "option1": "checktype value 1", 100 "option2": "checktype value 2", 101 "option3": "checktype value 3", 102 }, 103 }, 104 target: config.Target{ 105 Identifier: "example.com", 106 AssetType: types.DomainName, 107 Options: map[string]interface{}{ 108 "option2": "target value 2", 109 }, 110 }, 111 options: map[string]interface{}{ 112 "option1": "checktype value 1", 113 "option2": "target value 2", 114 "option3": "checktype value 3", 115 }, 116 }, 117 }, 118 }, 119 { 120 name: "two checktypes and one target", 121 catalog: checktypes.Catalog{ 122 "checktype1": { 123 Name: "checktype1", 124 Description: "checktype1 description", 125 Image: "namespace/repository:tag", 126 Assets: []string{ 127 "DomainName", 128 }, 129 }, 130 "checktype2": { 131 Name: "checktype2", 132 Description: "checktype2 description", 133 Image: "namespace2/repository2:tag", 134 Assets: []string{ 135 "DomainName", 136 }, 137 }, 138 }, 139 targets: []config.Target{ 140 { 141 Identifier: "example.com", 142 AssetType: types.DomainName, 143 }, 144 }, 145 want: []check{ 146 { 147 checktype: checkcatalog.Checktype{ 148 Name: "checktype1", 149 Description: "checktype1 description", 150 Image: "namespace/repository:tag", 151 Assets: []string{ 152 "DomainName", 153 }, 154 }, 155 target: config.Target{ 156 Identifier: "example.com", 157 AssetType: types.DomainName, 158 }, 159 options: map[string]any{}, 160 }, 161 { 162 checktype: checkcatalog.Checktype{ 163 Name: "checktype2", 164 Description: "checktype2 description", 165 Image: "namespace2/repository2:tag", 166 Assets: []string{ 167 "DomainName", 168 }, 169 }, 170 target: config.Target{ 171 Identifier: "example.com", 172 AssetType: types.DomainName, 173 }, 174 options: map[string]any{}, 175 }, 176 }, 177 }, 178 { 179 name: "incompatible target", 180 catalog: checktypes.Catalog{ 181 "checktype1": { 182 Name: "checktype1", 183 Description: "checktype1 description", 184 Image: "namespace/repository:tag", 185 Assets: []string{ 186 "DomainName", 187 }, 188 }, 189 }, 190 targets: []config.Target{ 191 { 192 Identifier: "example.com", 193 AssetType: types.GitRepository, 194 }, 195 }, 196 want: nil, 197 }, 198 { 199 name: "invalid target asset type", 200 catalog: checktypes.Catalog{ 201 "checktype1": { 202 Name: "checktype1", 203 Description: "checktype1 description", 204 Image: "namespace/repository:tag", 205 Assets: []string{ 206 "Hostname", 207 }, 208 }, 209 }, 210 targets: []config.Target{ 211 { 212 Identifier: "example.com", 213 AssetType: "InvalidAssetType", 214 }, 215 }, 216 want: nil, 217 }, 218 { 219 name: "no checktypes", 220 catalog: nil, 221 targets: []config.Target{ 222 { 223 Identifier: "example.com", 224 AssetType: types.GitRepository, 225 }, 226 }, 227 want: nil, 228 }, 229 { 230 name: "no targets", 231 catalog: checktypes.Catalog{ 232 "checktype1": { 233 Name: "checktype1", 234 Description: "checktype1 description", 235 Image: "namespace/repository:tag", 236 Assets: []string{ 237 "DomainName", 238 }, 239 }, 240 }, 241 targets: nil, 242 want: nil, 243 }, 244 { 245 name: "target without asset type", 246 catalog: checktypes.Catalog{ 247 "checktype1": { 248 Name: "checktype1", 249 Description: "checktype1 description", 250 Image: "namespace/repository:tag", 251 Assets: []string{ 252 "DomainName", 253 }, 254 }, 255 }, 256 targets: []config.Target{ 257 { 258 Identifier: "example.com", 259 }, 260 }, 261 want: nil, 262 }, 263 { 264 name: "one checktype with two asset types and one target", 265 catalog: checktypes.Catalog{ 266 "checktype1": { 267 Name: "checktype1", 268 Description: "checktype1 description", 269 Image: "namespace/repository:tag", 270 Assets: []string{ 271 "Hostname", 272 "WebAddress", 273 }, 274 }, 275 }, 276 targets: []config.Target{ 277 { 278 Identifier: "www.example.com", 279 AssetType: types.Hostname, 280 }, 281 }, 282 want: []check{ 283 { 284 checktype: checkcatalog.Checktype{ 285 Name: "checktype1", 286 Description: "checktype1 description", 287 Image: "namespace/repository:tag", 288 Assets: []string{ 289 "Hostname", 290 "WebAddress", 291 }, 292 }, 293 target: config.Target{ 294 Identifier: "www.example.com", 295 AssetType: types.Hostname, 296 }, 297 options: map[string]any{}, 298 }, 299 }, 300 }, 301 { 302 name: "one checktype with two asset types and one target identifier with two asset types", 303 catalog: checktypes.Catalog{ 304 "checktype1": { 305 Name: "checktype1", 306 Description: "checktype1 description", 307 Image: "namespace/repository:tag", 308 Assets: []string{ 309 "Hostname", 310 "DomainName", 311 }, 312 }, 313 }, 314 targets: []config.Target{ 315 { 316 Identifier: "example.com", 317 AssetType: types.DomainName, 318 }, 319 { 320 Identifier: "example.com", 321 AssetType: types.Hostname, 322 }, 323 }, 324 want: []check{ 325 { 326 checktype: checkcatalog.Checktype{ 327 Name: "checktype1", 328 Description: "checktype1 description", 329 Image: "namespace/repository:tag", 330 Assets: []string{ 331 "Hostname", 332 "DomainName", 333 }, 334 }, 335 target: config.Target{ 336 Identifier: "example.com", 337 AssetType: types.Hostname, 338 }, 339 options: map[string]any{}, 340 }, 341 { 342 checktype: checkcatalog.Checktype{ 343 Name: "checktype1", 344 Description: "checktype1 description", 345 Image: "namespace/repository:tag", 346 Assets: []string{ 347 "Hostname", 348 "DomainName", 349 }, 350 }, 351 target: config.Target{ 352 Identifier: "example.com", 353 AssetType: types.DomainName, 354 }, 355 options: map[string]any{}, 356 }, 357 }, 358 }, 359 { 360 name: "one target identifier with two asset types", 361 catalog: checktypes.Catalog{ 362 "checktype1": { 363 Name: "checktype1", 364 Description: "checktype1 description", 365 Image: "namespace/repository:tag", 366 Assets: []string{ 367 "Hostname", 368 }, 369 }, 370 }, 371 targets: []config.Target{ 372 { 373 Identifier: "https://www.example.com", 374 AssetType: types.Hostname, 375 }, 376 { 377 Identifier: "https://www.example.com", 378 AssetType: types.WebAddress, 379 }, 380 }, 381 want: []check{ 382 { 383 checktype: checkcatalog.Checktype{ 384 Name: "checktype1", 385 Description: "checktype1 description", 386 Image: "namespace/repository:tag", 387 Assets: []string{ 388 "Hostname", 389 }, 390 }, 391 target: config.Target{ 392 Identifier: "https://www.example.com", 393 AssetType: types.Hostname, 394 }, 395 options: map[string]any{}, 396 }, 397 }, 398 }, 399 { 400 name: "duplicated targets", 401 catalog: checktypes.Catalog{ 402 "checktype1": { 403 Name: "checktype1", 404 Description: "checktype1 description", 405 Image: "namespace/repository:tag", 406 Assets: []string{ 407 "DomainName", 408 }, 409 }, 410 }, 411 targets: []config.Target{ 412 { 413 Identifier: "example.com", 414 AssetType: types.DomainName, 415 }, 416 { 417 Identifier: "example.com", 418 AssetType: types.DomainName, 419 }, 420 }, 421 want: []check{ 422 { 423 checktype: checkcatalog.Checktype{ 424 Name: "checktype1", 425 Description: "checktype1 description", 426 Image: "namespace/repository:tag", 427 Assets: []string{ 428 "DomainName", 429 }, 430 }, 431 target: config.Target{ 432 Identifier: "example.com", 433 AssetType: types.DomainName, 434 }, 435 options: map[string]any{}, 436 }, 437 }, 438 }, 439 { 440 name: "lava asset type", 441 catalog: checktypes.Catalog{ 442 "checktype1": { 443 Name: "checktype1", 444 Description: "checktype1 description", 445 Image: "namespace/repository:tag", 446 Assets: []string{ 447 "GitRepository", 448 }, 449 }, 450 }, 451 targets: []config.Target{ 452 { 453 Identifier: ".", 454 AssetType: assettypes.Path, 455 }, 456 }, 457 want: []check{ 458 { 459 checktype: checkcatalog.Checktype{ 460 Name: "checktype1", 461 Description: "checktype1 description", 462 Image: "namespace/repository:tag", 463 Assets: []string{ 464 "GitRepository", 465 }, 466 }, 467 target: config.Target{ 468 Identifier: ".", 469 AssetType: assettypes.Path, 470 }, 471 options: map[string]any{}, 472 }, 473 }, 474 }, 475 } 476 477 for _, tt := range tests { 478 t.Run(tt.name, func(t *testing.T) { 479 got := generateChecks(tt.catalog, tt.targets) 480 diffOpts := []cmp.Option{ 481 cmp.AllowUnexported(check{}), 482 cmpopts.SortSlices(checkLess), 483 cmpopts.IgnoreFields(check{}, "id"), 484 } 485 if diff := cmp.Diff(tt.want, got, diffOpts...); diff != "" { 486 t.Errorf("checks mismatch (-want +got):\n%v", diff) 487 } 488 }) 489 } 490 } 491 492 func TestGenerateJobs(t *testing.T) { 493 tests := []struct { 494 name string 495 catalog checktypes.Catalog 496 targets []config.Target 497 want []jobrunner.Job 498 wantNilErr bool 499 }{ 500 { 501 name: "one checktype and one target", 502 catalog: checktypes.Catalog{ 503 "checktype1": { 504 Name: "checktype1", 505 Description: "checktype1 description", 506 Image: "namespace/repository:tag", 507 Assets: []string{ 508 "DomainName", 509 }, 510 }, 511 }, 512 targets: []config.Target{ 513 { 514 Identifier: "example.com", 515 AssetType: types.DomainName, 516 }, 517 }, 518 want: []jobrunner.Job{ 519 { 520 Image: "namespace/repository:tag", 521 Target: "example.com", 522 AssetType: "DomainName", 523 Options: "{}", 524 }, 525 }, 526 wantNilErr: true, 527 }, 528 { 529 name: "two checktypes and one target", 530 catalog: checktypes.Catalog{ 531 "checktype1": { 532 Name: "checktype1", 533 Description: "checktype1 description", 534 Image: "namespace/repository:tag", 535 Assets: []string{ 536 "DomainName", 537 }, 538 }, 539 "checktype2": { 540 Name: "checktype2", 541 Description: "checktype2 description", 542 Image: "namespace2/repository2:tag", 543 Assets: []string{ 544 "DomainName", 545 }, 546 }, 547 }, 548 targets: []config.Target{ 549 { 550 Identifier: "example.com", 551 AssetType: types.DomainName, 552 }, 553 }, 554 want: []jobrunner.Job{ 555 { 556 Image: "namespace/repository:tag", 557 Target: "example.com", 558 AssetType: "DomainName", 559 Options: "{}", 560 }, 561 { 562 Image: "namespace2/repository2:tag", 563 Target: "example.com", 564 AssetType: "DomainName", 565 Options: "{}", 566 }, 567 }, 568 wantNilErr: true, 569 }, 570 { 571 name: "one checktype and one target with valid required vars", 572 catalog: checktypes.Catalog{ 573 "checktype1": { 574 Name: "checktype1", 575 Description: "checktype1 description", 576 Image: "namespace/repository:tag", 577 Assets: []string{ 578 "DomainName", 579 }, 580 RequiredVars: []any{ 581 "REQUIRED_VAR_1", 582 "REQUIRED_VAR_2", 583 }, 584 }, 585 }, 586 targets: []config.Target{ 587 { 588 Identifier: "example.com", 589 AssetType: types.DomainName, 590 }, 591 }, 592 want: []jobrunner.Job{ 593 { 594 Image: "namespace/repository:tag", 595 Target: "example.com", 596 AssetType: "DomainName", 597 Options: "{}", 598 RequiredVars: []string{ 599 "REQUIRED_VAR_1", 600 "REQUIRED_VAR_2", 601 }, 602 }, 603 }, 604 wantNilErr: true, 605 }, 606 { 607 name: "one checktype and one target with invalid required vars", 608 catalog: checktypes.Catalog{ 609 "checktype1": { 610 Name: "checktype1", 611 Description: "checktype1 description", 612 Image: "namespace/repository:tag", 613 Assets: []string{ 614 "DomainName", 615 }, 616 RequiredVars: []int{ 617 1, 618 2, 619 }, 620 }, 621 }, 622 targets: []config.Target{ 623 { 624 Identifier: "example.com", 625 AssetType: types.DomainName, 626 }, 627 }, 628 want: nil, 629 wantNilErr: false, 630 }, 631 } 632 633 for _, tt := range tests { 634 t.Run(tt.name, func(t *testing.T) { 635 got, err := generateJobs(tt.catalog, tt.targets) 636 if (err == nil) != tt.wantNilErr { 637 t.Fatalf("unexpected error value: %v", err) 638 } 639 diffOpts := []cmp.Option{ 640 cmpopts.SortSlices(jobLess), 641 cmpopts.IgnoreFields(jobrunner.Job{}, "CheckID"), 642 } 643 if diff := cmp.Diff(tt.want, got, diffOpts...); diff != "" { 644 t.Errorf("checks mismatch (-want +got):\n%v", diff) 645 } 646 }) 647 } 648 } 649 650 func checkLess(a, b check) bool { 651 h := func(c check) string { 652 c.id = "" 653 return fmt.Sprintf("%#v", c) 654 } 655 return h(a) < h(b) 656 } 657 658 func jobLess(a, b jobrunner.Job) bool { 659 h := func(j jobrunner.Job) string { 660 j.CheckID = "" 661 return fmt.Sprintf("%#v", j) 662 } 663 return h(a) < h(b) 664 }