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  }