go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/cli/printer/printer_test.go (about)

     1  // Copyright (c) Mondoo, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  package printer
     5  
     6  import (
     7  	"strings"
     8  	"testing"
     9  
    10  	"github.com/stretchr/testify/assert"
    11  	"github.com/stretchr/testify/require"
    12  	"go.mondoo.com/cnquery/llx"
    13  	"go.mondoo.com/cnquery/logger"
    14  	"go.mondoo.com/cnquery/providers-sdk/v1/testutils"
    15  	"go.mondoo.com/cnquery/utils/sortx"
    16  )
    17  
    18  var x = testutils.InitTester(testutils.LinuxMock())
    19  
    20  func init() {
    21  	logger.InitTestEnv()
    22  }
    23  
    24  func testQuery(t *testing.T, query string) (*llx.CodeBundle, map[string]*llx.RawResult) {
    25  	codeBundle, err := x.Compile(query)
    26  	require.NoError(t, err)
    27  
    28  	results, err := x.ExecuteCode(codeBundle, nil)
    29  	require.NoError(t, err)
    30  
    31  	return codeBundle, results
    32  }
    33  
    34  type simpleTest struct {
    35  	code     string
    36  	expected string
    37  	results  []string
    38  }
    39  
    40  func runSimpleTests(t *testing.T, tests []simpleTest) {
    41  	for i := range tests {
    42  		cur := tests[i]
    43  		t.Run(cur.code, func(t *testing.T) {
    44  			bundle, results := testQuery(t, cur.code)
    45  
    46  			s := DefaultPrinter.CodeBundle(bundle)
    47  			if cur.expected != "" {
    48  				assert.Equal(t, cur.expected, s)
    49  			}
    50  
    51  			length := len(results)
    52  
    53  			assert.Equal(t, length, len(cur.results), "make sure the right number of results are returned")
    54  
    55  			keys := sortx.Keys(results)
    56  			for idx, id := range keys {
    57  				result := results[id]
    58  				s = DefaultPrinter.Result(result, bundle)
    59  				assert.Equal(t, cur.results[idx], s)
    60  			}
    61  		})
    62  	}
    63  }
    64  
    65  type assessmentTest struct {
    66  	code   string
    67  	result string
    68  }
    69  
    70  func runAssessmentTests(t *testing.T, tests []assessmentTest) {
    71  	for i := range tests {
    72  		cur := tests[i]
    73  		t.Run(cur.code, func(t *testing.T) {
    74  			bundle, resultsMap := testQuery(t, cur.code)
    75  
    76  			raw := DefaultPrinter.Results(bundle, resultsMap)
    77  			assert.Equal(t, cur.result, raw)
    78  		})
    79  	}
    80  }
    81  
    82  func TestPrinter(t *testing.T) {
    83  	runSimpleTests(t, []simpleTest{
    84  		{
    85  			"if ( mondoo.version != null ) { mondoo.build }",
    86  			"", // ignore
    87  			[]string{
    88  				"mondoo.version: \"unstable\"",
    89  				"if: {\n" +
    90  					"  mondoo.build: \"development\"\n" +
    91  					"}",
    92  			},
    93  		},
    94  		{
    95  			"file('zzz') { content }",
    96  			"",
    97  			[]string{
    98  				"error: 1 error occurred:\n" +
    99  					"\t* file 'zzz' not found\n" +
   100  					"file: {\n  content: error: file 'zzz' not found\n}",
   101  			},
   102  		},
   103  		{
   104  			"[]",
   105  			"", // ignore
   106  			[]string{
   107  				"[]",
   108  			},
   109  		},
   110  		{
   111  			"{}",
   112  			"", // ignore
   113  			[]string{
   114  				"{}",
   115  			},
   116  		},
   117  		{
   118  			"['1-2'] { _.split('-') }",
   119  			"", // ignore
   120  			[]string{
   121  				"[\n" +
   122  					"  0: {\n" +
   123  					"    split: [\n" +
   124  					"      0: \"1\"\n" +
   125  					"      1: \"2\"\n" +
   126  					"    ]\n" +
   127  					"  }\n" +
   128  					"]",
   129  			},
   130  		},
   131  		{
   132  			"mondoo { version }",
   133  			"-> block 1\n   entrypoints: [<1,2>]\n   1: mondoo \n   2: {} bind: <1,1> type:block (=> <2,0>)\n-> block 2\n   entrypoints: [<2,2>]\n   1: mondoo id = context\n   2: version bind: <2,1> type:string\n",
   134  			[]string{
   135  				"mondoo: {\n  version: \"unstable\"\n}",
   136  			},
   137  		},
   138  		{
   139  			"mondoo { _.version }",
   140  			"-> block 1\n   entrypoints: [<1,2>]\n   1: mondoo \n   2: {} bind: <1,1> type:block (=> <2,0>)\n-> block 2\n   entrypoints: [<2,2>]\n   1: mondoo id = context\n   2: version bind: <2,1> type:string\n",
   141  			[]string{
   142  				"mondoo: {\n  version: \"unstable\"\n}",
   143  			},
   144  		},
   145  		{
   146  			"[1].where( _ > 0 )",
   147  			"-> block 1\n   entrypoints: [<1,2>]\n   1: [\n     0: 1\n   ]\n   2: where bind: <1,1> type:[]int (ref<1,1>, => <2,0>)\n-> block 2\n   entrypoints: [<2,2>]\n   1: _\n   2: >\005 bind: <2,1> type:bool (0)\n",
   148  			[]string{
   149  				"where: [\n  0: 1\n]",
   150  			},
   151  		},
   152  		{
   153  			"a = 3\n if(true) {\n a == 3 \n}",
   154  			"-> block 1\n   entrypoints: [<1,2>]\n   1: 3\n   2: if bind: <0,0> type:block (true, => <2,0>, [\n     0: ref<1,1>\n   ])\n-> block 2\n   entrypoints: [<2,2>]\n   1: ref<1,1>\n   2: ==\x05 bind: <2,1> type:bool (3)\n",
   155  			[]string{"if: {\n  a == 3: true\n}"},
   156  		},
   157  		{
   158  			"mondoo",
   159  			"", // ignore
   160  			[]string{
   161  				"mondoo: mondoo version=\"unstable\"",
   162  			},
   163  		},
   164  		{
   165  			"users",
   166  			"", // ignore
   167  			[]string{
   168  				"users.list: [\n" +
   169  					"  0: user name=\"root\" uid=0 gid=0\n" +
   170  					"  1: user name=\"bin\" uid=1 gid=1\n" +
   171  					"  2: user name=\"chris\" uid=1000 gid=1000\n" +
   172  					"  3: user name=\"christopher\" uid=1001 gid=1000\n" +
   173  					"]",
   174  			},
   175  		},
   176  	})
   177  }
   178  
   179  func TestPrinter_Assessment(t *testing.T) {
   180  	runAssessmentTests(t, []assessmentTest{
   181  		{
   182  			// mixed use: assertion and erroneous data field
   183  			"mondoo.build == 1; user(name: 'notthere').authorizedkeys.file",
   184  			strings.Join([]string{
   185  				"[failed] mondoo.build == 1; user(name: 'notthere').authorizedkeys.file",
   186  				"  [failed] mondoo.build == 1",
   187  				"    expected: == 1",
   188  				"    actual:   \"development\"",
   189  				"  [failed] user.authorizedkeys.file",
   190  				"    error: cannot find user with name 'notthere'",
   191  				"",
   192  			}, "\n"),
   193  		},
   194  		{
   195  			// mixed use: assertion and working data field
   196  			"mondoo.build == 1; mondoo.version",
   197  			strings.Join([]string{
   198  				"[failed] mondoo.build == 1; mondoo.version",
   199  				"  [failed] mondoo.build == 1",
   200  				"    expected: == 1",
   201  				"    actual:   \"development\"",
   202  				"  [ok] value: \"unstable\"",
   203  				"",
   204  			}, "\n"),
   205  		},
   206  		{
   207  			"[1,2,3].\n" +
   208  				"# @msg Found ${length} numbers\n" +
   209  				"none( _ > 1 )",
   210  			strings.Join([]string{
   211  				"[failed] Found 2 numbers",
   212  				"",
   213  			}, "\n"),
   214  		},
   215  		{
   216  			"# @msg Expected ${$expected.length} users but got ${length}\n" +
   217  				"users.none( uid == 0 )",
   218  			strings.Join([]string{
   219  				"[failed] Expected 4 users but got 1",
   220  				"",
   221  			}, "\n"),
   222  		},
   223  		{
   224  			"mondoo.build == 1",
   225  			strings.Join([]string{
   226  				"[failed] mondoo.build == 1",
   227  				"  expected: == 1",
   228  				"  actual:   \"development\"",
   229  				"",
   230  			}, "\n"),
   231  		},
   232  		{
   233  			"sshd.config { params['test'] }",
   234  			strings.Join([]string{
   235  				"sshd.config: {",
   236  				"  params[test]: null",
   237  				"}",
   238  			}, "\n"),
   239  		},
   240  		{
   241  			"mondoo.build == 1;mondoo.version =='unstable';",
   242  			strings.Join([]string{
   243  				"[failed] mondoo.build == 1;mondoo.version =='unstable';",
   244  				"  [failed] mondoo.build == 1",
   245  				"    expected: == 1",
   246  				"    actual:   \"development\"",
   247  				"  [ok] value: \"unstable\"",
   248  				"",
   249  			}, "\n"),
   250  		},
   251  		{
   252  			"if(true) {\n" +
   253  				"  # @msg Expected ${$expected.length} users but got ${length}\n" +
   254  				"  users.none( uid == 0 )\n" +
   255  				"}",
   256  			strings.Join([]string{
   257  				"if: {",
   258  				"  [failed] Expected 4 users but got 1",
   259  				"  users.where.list: [",
   260  				"    0: user name=\"root\" uid=0 gid=0",
   261  				"  ]",
   262  				"}",
   263  			}, "\n"),
   264  		},
   265  		{
   266  			"users.list.duplicates(gid).none()\n",
   267  			strings.Join([]string{
   268  				"[failed] [].none()",
   269  				"  actual:   [",
   270  				"    0: user {",
   271  				"      name: \"christopher\"",
   272  				"      gid: 1000",
   273  				"      uid: 1001",
   274  				"    }",
   275  				"    1: user {",
   276  				"      name: \"chris\"",
   277  				"      gid: 1000",
   278  				"      uid: 1000",
   279  				"    }",
   280  				"  ]",
   281  				"",
   282  			}, "\n"),
   283  		},
   284  		{
   285  			"users.all( uid < 1000 )\n",
   286  			strings.Join([]string{
   287  				"[failed] users.all()",
   288  				"  actual:   [",
   289  				"    0: user {",
   290  				"      uid: 1000",
   291  				"      gid: 1000",
   292  				"      name: \"chris\"",
   293  				"    }",
   294  				"    1: user {",
   295  				"      uid: 1001",
   296  				"      gid: 1000",
   297  				"      name: \"christopher\"",
   298  				"    }",
   299  				"  ]",
   300  				"",
   301  			}, "\n"),
   302  		},
   303  		{
   304  			"users.all( 1000 > uid )\n",
   305  			strings.Join([]string{
   306  				"[failed] users.all()",
   307  				"  actual:   [",
   308  				"    0: user {",
   309  				"      name: \"chris\"",
   310  				"      uid: 1000",
   311  				"      gid: 1000",
   312  				"    }",
   313  				"    1: user {",
   314  				"      name: \"christopher\"",
   315  				"      uid: 1001",
   316  				"      gid: 1000",
   317  				"    }",
   318  				"  ]",
   319  				"",
   320  			}, "\n"),
   321  		},
   322  		{
   323  			"users.all( uid == 0 && enabled == true )\n",
   324  			strings.Join([]string{
   325  				"[failed] users.all()",
   326  				"  actual:   [",
   327  				"    0: user {",
   328  				"      gid: 0",
   329  				"      name: \"root\"",
   330  				"      uid: 0",
   331  				"      enabled: false",
   332  				"    }",
   333  				"    1: user {",
   334  				"      gid: 1",
   335  				"      name: \"bin\"",
   336  				"      uid: 1",
   337  				"      enabled: false",
   338  				"    }",
   339  				"    2: user {",
   340  				"      gid: 1000",
   341  				"      name: \"chris\"",
   342  				"      uid: 1000",
   343  				"      enabled: false",
   344  				"    }",
   345  				"    3: user {",
   346  				"      gid: 1000",
   347  				"      name: \"christopher\"",
   348  				"      uid: 1001",
   349  				"      enabled: false",
   350  				"    }",
   351  				"  ]",
   352  				"",
   353  			}, "\n"),
   354  		},
   355  		{
   356  			"users.none( '/root' == home ); users.all( name != 'root' )\n",
   357  			strings.Join([]string{
   358  				"[failed] users.none( '/root' == home ); users.all( name != 'root' )",
   359  				"",
   360  				"  [failed] users.none()",
   361  				"    actual:   [",
   362  				"      0: user {",
   363  				"        gid: 0",
   364  				"        home: \"/root\"",
   365  				"        name: \"root\"",
   366  				"        uid: 0",
   367  				"      }",
   368  				"    ]",
   369  				"  [failed] users.all()",
   370  				"    actual:   [",
   371  				"      0: user {",
   372  				"        name: \"root\"",
   373  				"        uid: 0",
   374  				"        gid: 0",
   375  				"      }",
   376  				"    ]",
   377  				"",
   378  			}, "\n"),
   379  		},
   380  		// FIXME: these tests aren't working right in the current iteration.
   381  		// There is also something else a bit weird, namely it uses `groups`
   382  		// which is not a child field of the `user` resource. I'd love to restore
   383  		// these.
   384  		// 		{
   385  		// 			"users.all(groups.none(gid==0))\n",
   386  		// 			`[failed] users.all()
   387  		//   actual:   [
   388  		//     0: user {
   389  		//       uid: 0
   390  		//       groups.list: [
   391  		//         0: user group id = group/0/root
   392  		//         1: user group id = group/1/bin
   393  		//         2: user group id = group/1000/chris
   394  		//       ]
   395  		//       gid: 0
   396  		//       groups: groups
   397  		//       name: "root"
   398  		//     }
   399  		//     1: user {
   400  		//       uid: 1
   401  		//       groups.list: [
   402  		//         0: user group id = group/0/root
   403  		//         1: user group id = group/1/bin
   404  		//         2: user group id = group/1000/chris
   405  		//       ]
   406  		//       gid: 1
   407  		//       groups: groups
   408  		//       name: "bin"
   409  		//     }
   410  		//     2: user {
   411  		//       uid: 1000
   412  		//       groups.list: [
   413  		//         0: user group id = group/0/root
   414  		//         1: user group id = group/1/bin
   415  		//         2: user group id = group/1000/chris
   416  		//       ]
   417  		//       gid: 1000
   418  		//       groups: groups
   419  		//       name: "chris"
   420  		//     }
   421  		//     3: user {
   422  		//       uid: 1001
   423  		//       groups.list: [
   424  		//         0: user group id = group/0/root
   425  		//         1: user group id = group/1/bin
   426  		//         2: user group id = group/1000/chris
   427  		//       ]
   428  		//       gid: 1000
   429  		//       groups: groups
   430  		//       name: "christopher"
   431  		//     }
   432  		//   ]
   433  		// `,
   434  		// 		},
   435  		// {
   436  		// 	"users.all(groups.all(name == 'root'))\n",
   437  		// 	`[failed] users.all()
   438  		//   actual:   [
   439  		//     0: user {
   440  		//       uid: 0
   441  		//       groups.list: [
   442  		//         0: user group id = group/0/root
   443  		//         1: user group id = group/1/bin
   444  		//         2: user group id = group/1000/chris
   445  		//       ]
   446  		//       groups: groups
   447  		//       gid: 0
   448  		//       name: "root"
   449  		//     }
   450  		//     1: user {
   451  		//       uid: 1
   452  		//       groups.list: [
   453  		//         0: user group id = group/0/root
   454  		//         1: user group id = group/1/bin
   455  		//         2: user group id = group/1000/chris
   456  		//       ]
   457  		//       groups: groups
   458  		//       gid: 1
   459  		//       name: "bin"
   460  		//     }
   461  		//     2: user {
   462  		//       uid: 1000
   463  		//       groups.list: [
   464  		//         0: user group id = group/0/root
   465  		//         1: user group id = group/1/bin
   466  		//         2: user group id = group/1000/chris
   467  		//       ]
   468  		//       groups: groups
   469  		//       gid: 1000
   470  		//       name: "chris"
   471  		//     }
   472  		//     3: user {
   473  		//       uid: 1001
   474  		//       groups.list: [
   475  		//         0: user group id = group/0/root
   476  		//         1: user group id = group/1/bin
   477  		//         2: user group id = group/1000/chris
   478  		//       ]
   479  		//       groups: groups
   480  		//       gid: 1000
   481  		//       name: "christopher"
   482  		//     }
   483  		//   ]
   484  		// `,
   485  		// },
   486  		{
   487  			"users.all(sshkeys.length > 2)\n",
   488  			strings.Join([]string{
   489  				"[failed] users.all()",
   490  				"  actual:   [",
   491  				"    0: user {",
   492  				"      name: \"root\"",
   493  				"      gid: 0",
   494  				"      sshkeys: []",
   495  				"      sshkeys.length: 0",
   496  				"      uid: 0",
   497  				"    }",
   498  				"    1: user {",
   499  				"      name: \"bin\"",
   500  				"      gid: 1",
   501  				"      sshkeys: []",
   502  				"      sshkeys.length: 0",
   503  				"      uid: 1",
   504  				"    }",
   505  				"    2: user {",
   506  				"      name: \"chris\"",
   507  				"      gid: 1000",
   508  				"      sshkeys: []",
   509  				"      sshkeys.length: 0",
   510  				"      uid: 1000",
   511  				"    }",
   512  				"    3: user {",
   513  				"      name: \"christopher\"",
   514  				"      gid: 1000",
   515  				"      sshkeys: []",
   516  				"      sshkeys.length: 0",
   517  				"      uid: 1001",
   518  				"    }",
   519  				"  ]",
   520  				"",
   521  			}, "\n"),
   522  		},
   523  	})
   524  }
   525  
   526  func TestPrinter_Blocks(t *testing.T) {
   527  	runSimpleTests(t, []simpleTest{
   528  		{
   529  			"['a', 'b'] { x=_ \n x }",
   530  			"", // ignore
   531  			[]string{
   532  				strings.Join([]string{
   533  					"[",
   534  					"  0: {",
   535  					"    x: \"a\"",
   536  					"  }",
   537  					"  1: {",
   538  					"    x: \"b\"",
   539  					"  }",
   540  					"]",
   541  				}, "\n"),
   542  			},
   543  		},
   544  		{
   545  			"['a', 'b'] { x=_ \n x == 'a' }",
   546  			"", // ignore
   547  			[]string{
   548  				strings.Join([]string{
   549  					"[",
   550  					"  0: {",
   551  					"    x == \"a\": true",
   552  					"  }",
   553  					"  1: {",
   554  					"    x == \"a\": false",
   555  					"  }",
   556  					"]",
   557  				}, "\n"),
   558  			},
   559  		},
   560  	})
   561  }
   562  
   563  func TestPrinter_Buggy(t *testing.T) {
   564  	runSimpleTests(t, []simpleTest{
   565  		{
   566  			"mondoo",
   567  			"", // ignore
   568  			[]string{
   569  				"mondoo: mondoo version=\"unstable\"",
   570  			},
   571  		},
   572  	})
   573  }