go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/providers/os/resources/mql_test.go (about)

     1  // Copyright (c) Mondoo, Inc.
     2  // SPDX-License-Identifier: BUSL-1.1
     3  
     4  package resources_test
     5  
     6  import (
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/stretchr/testify/assert"
    11  	"github.com/stretchr/testify/require"
    12  	"go.mondoo.com/cnquery/llx"
    13  	"go.mondoo.com/cnquery/providers-sdk/v1/testutils"
    14  )
    15  
    16  // Core Language constructs
    17  // ------------------------
    18  // These tests are more generic MQL and resource tests. We have migrated them
    19  // from their previous core package into the OS package, because it requires
    20  // more resources (like file). Long-term we'd like to move them to a standalone
    21  // (and dedicated) mock provider for testing. Other tests are found in the
    22  // core provider counterpart to this test file.
    23  
    24  func testChain(t *testing.T, codes ...string) {
    25  	tr := testutils.InitTester(testutils.LinuxMock())
    26  	for i := range codes {
    27  		code := codes[i]
    28  		t.Run(code, func(t *testing.T) {
    29  			tr.TestQuery(t, code)
    30  		})
    31  	}
    32  }
    33  
    34  func TestErroneousLlxChains(t *testing.T) {
    35  	testChain(t,
    36  		`file("/etc/crontab") {
    37  			permissions.group_readable == false
    38  			permissions.group_writeable == false
    39  			permissions.group_executable == false
    40  		}`,
    41  	)
    42  
    43  	testChain(t,
    44  		`file("/etc/profile").content.contains("umask 027") || file("/etc/bashrc").content.contains("umask 027")`,
    45  		`file("/etc/profile").content.contains("umask 027") || file("/etc/bashrc").content.contains("umask 027")`,
    46  	)
    47  
    48  	testChain(t,
    49  		`users.map(name) { _.contains("a") _.contains("b") }`,
    50  	)
    51  
    52  	testChain(t,
    53  		`user(name: 'i_definitely_dont_exist').authorizedkeys`,
    54  	)
    55  }
    56  
    57  func TestResource_InitWithResource(t *testing.T) {
    58  	x.TestSimple(t, []testutils.SimpleTest{
    59  		{
    60  			Code:        "file(asset.platform).exists",
    61  			Expectation: false,
    62  		},
    63  		{
    64  			Code:        "'linux'.contains(asset.family)",
    65  			Expectation: true,
    66  		},
    67  	})
    68  }
    69  
    70  func TestOS_Vars(t *testing.T) {
    71  	x.TestSimple(t, []testutils.SimpleTest{
    72  		{
    73  			Code:        "p = file('/dummy.json'); parse.json(file: p).params.length",
    74  			Expectation: int64(11),
    75  		},
    76  	})
    77  }
    78  
    79  func TestMap(t *testing.T) {
    80  	x := testutils.InitTester(testutils.LinuxMock())
    81  	x.TestSimple(t, []testutils.SimpleTest{
    82  		{
    83  			Code:        "{a: 123}",
    84  			Expectation: map[string]interface{}{"a": int64(123)},
    85  		},
    86  		{
    87  			Code:        "return {a: 123}",
    88  			Expectation: map[string]interface{}{"a": int64(123)},
    89  		},
    90  		{
    91  			Code:        "{a: 1, b: 2, c: 3}.where(key == 'c')",
    92  			Expectation: map[string]interface{}{"c": int64(3)},
    93  		},
    94  		{
    95  			Code:        "{a: 1, b: 2, c: 3}.where(value < 3)",
    96  			Expectation: map[string]interface{}{"a": int64(1), "b": int64(2)},
    97  		},
    98  		{
    99  			Code:        "parse.json('/dummy.json').params.length",
   100  			Expectation: int64(11),
   101  		},
   102  		{
   103  			Code:        "parse.json('/dummy.json').params.keys.length",
   104  			Expectation: int64(11),
   105  		},
   106  		{
   107  			Code:        "parse.json('/dummy.json').params.values.length",
   108  			Expectation: int64(11),
   109  		},
   110  		{
   111  			Code: "parse.json('/dummy.json').params { _['Protocol'] != 1 }",
   112  			Expectation: map[string]interface{}{
   113  				"__t": llx.BoolTrue,
   114  				"__s": llx.BoolTrue,
   115  				"CQ28lTwZsvVdJM4dCyeTdbQhExY8oiUIcMoPyPjXAJNgtjMLnHK6qgEVywRY1Hbw9QqInuL06EWIOaEMj2e9NA==": llx.BoolTrue,
   116  			},
   117  		},
   118  	})
   119  }
   120  
   121  func TestListResource(t *testing.T) {
   122  	x := testutils.InitTester(testutils.LinuxMock())
   123  
   124  	t.Run("list resource by default returns the list", func(t *testing.T) {
   125  		res := x.TestQuery(t, "users")
   126  		assert.NotEmpty(t, res)
   127  		assert.Len(t, res[0].Data.Value, 4)
   128  	})
   129  
   130  	x.TestSimple(t, []testutils.SimpleTest{
   131  		{
   132  			Code:        "users.where(name == 'root').length",
   133  			Expectation: int64(1),
   134  		},
   135  		{
   136  			Code:        "users.list.where(name == 'root').length",
   137  			Expectation: int64(1),
   138  		},
   139  		{
   140  			Code:        "users.where(name == 'rooot').list { uid }",
   141  			Expectation: []interface{}{},
   142  		},
   143  		{
   144  			Code:        "users.where(uid > 0).where(uid < 0).list",
   145  			Expectation: []interface{}{},
   146  		},
   147  		{
   148  			Code: `users.where(name == 'root').list {
   149  				uid == 0
   150  				gid == 0
   151  			}`,
   152  			Expectation: []interface{}{
   153  				map[string]interface{}{
   154  					"__t": llx.BoolTrue,
   155  					"__s": llx.BoolTrue,
   156  					"BamDDGp87sNG0hVjpmEAPEjF6fZmdA6j3nDinlgr/y5xK3KaLgulyscoeEEaEASm2RkRXifnWj3ZbF0OZBF6XA==": llx.BoolTrue,
   157  					"ytOUfV4UyOjY0C6HKzQ8GcA/hshrh2ahRySNG41RbFt3TNNf+6gBuHvs2hGTNDPUZR/oN8WH0QFIYYm/Vj3pGQ==": llx.BoolTrue,
   158  				},
   159  			},
   160  		},
   161  		{
   162  			Code:        "users.map(name)",
   163  			Expectation: []interface{}([]interface{}{"root", "bin", "chris", "christopher"}),
   164  		},
   165  		{
   166  			// outside variables cause the block to be standalone
   167  			Code:        "n=false; users.contains(n)",
   168  			ResultIndex: 1,
   169  			Expectation: false,
   170  		},
   171  		{
   172  			// variables do not override local fields in blocks
   173  			Code:        "name=false; users.contains(name)",
   174  			ResultIndex: 1,
   175  			Expectation: true,
   176  		},
   177  	})
   178  }
   179  
   180  func TestListResource_Assertions(t *testing.T) {
   181  	x := testutils.InitTester(testutils.LinuxMock())
   182  	x.TestSimple(t, []testutils.SimpleTest{
   183  		{
   184  			Code:        "users.contains(name == 'root')",
   185  			ResultIndex: 1,
   186  			Expectation: true,
   187  		},
   188  		{
   189  			Code:        "users.where(uid < 100).contains(name == 'root')",
   190  			ResultIndex: 1,
   191  			Expectation: true,
   192  		},
   193  		{
   194  			Code:        "users.all(uid >= 0)",
   195  			Expectation: true,
   196  		},
   197  		{
   198  			Code:        "users.where(uid < 100).all(uid >= 0)",
   199  			Expectation: true,
   200  		},
   201  		{
   202  			Code:        "users.any(uid < 100)",
   203  			Expectation: true,
   204  		},
   205  		{
   206  			Code:        "users.where(uid < 100).any(uid < 50)",
   207  			Expectation: true,
   208  		},
   209  		{
   210  			Code:        "users.one(uid == 0)",
   211  			Expectation: true,
   212  		},
   213  		{
   214  			Code:        "users.where(uid < 100).one(uid == 0)",
   215  			Expectation: true,
   216  		},
   217  		{
   218  			Code:        "users.none(uid == 99999)",
   219  			Expectation: true,
   220  		},
   221  		{
   222  			Code:        "users.where(uid < 100).none(uid == 1000)",
   223  			Expectation: true,
   224  		},
   225  	})
   226  }
   227  
   228  func TestResource_duplicateFields(t *testing.T) {
   229  	x := testutils.InitTester(testutils.LinuxMock())
   230  	x.TestSimple(t, []testutils.SimpleTest{
   231  		{
   232  			Code: "users.list.duplicates(gid) { gid }",
   233  			Expectation: []interface{}{
   234  				map[string]interface{}{
   235  					"__t": llx.BoolTrue,
   236  					"__s": llx.NilData,
   237  					"Cuv5ImO3PMlg/BnsKFcT/K88cResNOFnEZnbYwBT44aycwbRuvhhMqjq0E96i+POSgNSxO1QPi6U2VNNRuSPtQ==": &llx.RawData{
   238  						Type:  "\x05",
   239  						Value: int64(1000),
   240  						Error: nil,
   241  					},
   242  				},
   243  				map[string]interface{}{
   244  					"__t": llx.BoolTrue,
   245  					"__s": llx.NilData,
   246  					"Cuv5ImO3PMlg/BnsKFcT/K88cResNOFnEZnbYwBT44aycwbRuvhhMqjq0E96i+POSgNSxO1QPi6U2VNNRuSPtQ==": &llx.RawData{
   247  						Type:  "\x05",
   248  						Value: int64(1000),
   249  						Error: nil,
   250  					},
   251  				},
   252  			},
   253  		},
   254  	})
   255  }
   256  
   257  func TestDict_Methods_Contains(t *testing.T) {
   258  	p := "parse.json('/dummy.json')."
   259  
   260  	x := testutils.InitTester(testutils.LinuxMock())
   261  	x.TestSimple(t, []testutils.SimpleTest{
   262  		{
   263  			Code:        p + "params['hello'].contains('ll')",
   264  			ResultIndex: 1,
   265  			Expectation: true,
   266  		},
   267  		{
   268  			Code:        p + "params['hello'].contains('lloo')",
   269  			ResultIndex: 1,
   270  			Expectation: false,
   271  		},
   272  		{
   273  			Code:        p + "params['hello'].contains(['xx','he'])",
   274  			ResultIndex: 1,
   275  			Expectation: true,
   276  		},
   277  		{
   278  			Code:        p + "params['hello'].contains(['xx'])",
   279  			ResultIndex: 1,
   280  			Expectation: false,
   281  		},
   282  		{
   283  			Code:        p + "params['string-array'].contains('a')",
   284  			ResultIndex: 1,
   285  			Expectation: true,
   286  		},
   287  		{
   288  			Code:        p + "params['string-array'].containsOnly(['c', 'a', 'b'])",
   289  			ResultIndex: 1,
   290  			Expectation: true,
   291  		},
   292  		{
   293  			Code:        p + "params['string-array'].containsOnly(['a', 'b'])",
   294  			ResultIndex: 1,
   295  			Expectation: false,
   296  		},
   297  		// {
   298  		// 	p + "params['string-array'].containsOnly('a')",
   299  		// 	1, false,
   300  		// },
   301  		{
   302  			Code:        p + "params['string-array'].containsNone(['d','e'])",
   303  			ResultIndex: 1,
   304  			Expectation: true,
   305  		},
   306  		{
   307  			Code:        p + "params['string-array'].containsNone(['a', 'e'])",
   308  			ResultIndex: 1,
   309  			Expectation: false,
   310  		},
   311  		{
   312  			Code:        p + "params['string-array'].none('a')",
   313  			ResultIndex: 1,
   314  			Expectation: false,
   315  		},
   316  		{
   317  			Code:        p + "params['string-array'].contains(_ == 'a')",
   318  			ResultIndex: 1,
   319  			Expectation: true,
   320  		},
   321  		{
   322  			Code:        p + "params['string-array'].none(_ == /a/)",
   323  			ResultIndex: 1,
   324  			Expectation: false,
   325  		},
   326  		{
   327  			Code:        p + "params['string-array'].contains(value == 'a')",
   328  			ResultIndex: 1,
   329  			Expectation: true,
   330  		},
   331  		{
   332  			Code:        p + "params['string-array'].none(value == 'a')",
   333  			ResultIndex: 1,
   334  			Expectation: false,
   335  		},
   336  	})
   337  }
   338  
   339  func TestDict_Methods_Map(t *testing.T) {
   340  	p := "parse.json('/dummy.json')."
   341  
   342  	expectedTime, err := time.Parse(time.RFC3339, "2016-01-28T23:02:24Z")
   343  	if err != nil {
   344  		panic(err.Error())
   345  	}
   346  
   347  	x := testutils.InitTester(testutils.LinuxMock())
   348  	x.TestSimple(t, []testutils.SimpleTest{
   349  		{
   350  			Code:        p + "params['string-array'].where(_ == 'a')",
   351  			Expectation: []interface{}{"a"},
   352  		},
   353  		{
   354  			Code:        p + "params['string-array'].one(_ == 'a')",
   355  			ResultIndex: 1,
   356  			Expectation: true,
   357  		},
   358  		{
   359  			Code:        p + "params['string-array'].all(_ != 'z')",
   360  			ResultIndex: 1,
   361  			Expectation: true,
   362  		},
   363  		{
   364  			Code:        p + "params['string-array'].any(_ != 'a')",
   365  			ResultIndex: 1,
   366  			Expectation: true,
   367  		},
   368  		{
   369  			Code:        p + "params['does_not_exist'].any(_ != 'a')",
   370  			ResultIndex: 1,
   371  			Expectation: nil,
   372  		},
   373  		{
   374  			Code:        p + "params['f'].map(_['ff'])",
   375  			Expectation: []interface{}{float64(3)},
   376  		},
   377  		// {
   378  		// 	p + "params { _['1'] == _['1.0'] }",
   379  		// 	0, true,
   380  		// },
   381  		{
   382  			Code:        p + "params['1'] - 2",
   383  			Expectation: float64(-1),
   384  		},
   385  		{
   386  			Code:        p + "params['int-array']",
   387  			Expectation: []interface{}{float64(1), float64(2), float64(3)},
   388  		},
   389  		{
   390  			Code:        p + "params['hello'] + ' world'",
   391  			Expectation: "hello world",
   392  		},
   393  		{
   394  			Code:        p + "params['hello'].trim('ho')",
   395  			Expectation: "ell",
   396  		},
   397  		{
   398  			Code:        p + "params['dict'].length",
   399  			Expectation: int64(3),
   400  		},
   401  		{
   402  			Code:        p + "params['dict'].keys.length",
   403  			Expectation: int64(3),
   404  		},
   405  		{
   406  			Code:        p + "params['dict'].values.length",
   407  			Expectation: int64(3),
   408  		},
   409  		{
   410  			Code:        "parse.date(" + p + "params['date'])",
   411  			Expectation: &expectedTime,
   412  		},
   413  		{
   414  			Code:        p + "params.first",
   415  			Expectation: float64(1),
   416  		},
   417  		{
   418  			Code:        p + "params.last",
   419  			Expectation: true,
   420  		},
   421  		{
   422  			Code:        p + "params['aoa'].flat",
   423  			Expectation: []interface{}{float64(1), float64(2), float64(3)},
   424  		},
   425  	})
   426  
   427  	x.TestSimpleErrors(t, []testutils.SimpleTest{
   428  		{
   429  			Code:        p + "params['does not exist'].values",
   430  			Expectation: "Failed to get values of `null`",
   431  		},
   432  		{
   433  			Code:        p + "params['yo'] > 3",
   434  			ResultIndex: 1,
   435  			Expectation: "left side of operation is null",
   436  		},
   437  	})
   438  }
   439  
   440  func TestDict_Methods_Array(t *testing.T) {
   441  	p := "parse.json('/dummy.array.json')."
   442  
   443  	x := testutils.InitTester(testutils.LinuxMock())
   444  	x.TestSimple(t, []testutils.SimpleTest{
   445  		{
   446  			Code:        p + "params[0]",
   447  			Expectation: float64(1),
   448  		},
   449  		{
   450  			Code:        p + "params[1]",
   451  			Expectation: "hi",
   452  		},
   453  		{
   454  			Code:        p + "params[2]",
   455  			Expectation: map[string]interface{}{"ll": float64(0)},
   456  		},
   457  		{
   458  			Code:        p + "params.first",
   459  			Expectation: float64(1),
   460  		},
   461  		{
   462  			Code:        p + "params.last",
   463  			Expectation: "z",
   464  		},
   465  		{
   466  			Code:        p + "params.where(-1).first",
   467  			Expectation: nil,
   468  		},
   469  		{
   470  			Code:        p + "params.where(-1).last",
   471  			Expectation: nil,
   472  		},
   473  	})
   474  }
   475  
   476  func TestDict_Methods_OtherJson(t *testing.T) {
   477  	x := testutils.InitTester(testutils.LinuxMock())
   478  	x.TestSimple(t, []testutils.SimpleTest{
   479  		{
   480  			Code:        "parse.json('/dummy.number.json').params",
   481  			Expectation: float64(1.23),
   482  		},
   483  		{
   484  			Code:        "parse.json('/dummy.string.json').params",
   485  			Expectation: "hi",
   486  		},
   487  		{
   488  			Code:        "parse.json('/dummy.true.json').params",
   489  			Expectation: true,
   490  		},
   491  		{
   492  			Code:        "parse.json('/dummy.false.json').params",
   493  			Expectation: false,
   494  		},
   495  		{
   496  			Code:        "parse.json('/dummy.null.json').params",
   497  			Expectation: nil,
   498  		},
   499  	})
   500  }
   501  
   502  func TestArrayBlockError(t *testing.T) {
   503  	x := testutils.InitTester(testutils.LinuxMock())
   504  	res := x.TestQuery(t, "users.list { file(_.name + 'doesnotexist').content }")
   505  	assert.NotEmpty(t, res)
   506  	queryResult := res[len(res)-1]
   507  	require.NotNil(t, queryResult)
   508  	require.Error(t, queryResult.Data.Error)
   509  }
   510  
   511  func TestBrokenQueryExecutionGH674(t *testing.T) {
   512  	// See https://github.com/mondoohq/cnquery/issues/674
   513  	x := testutils.InitTester(testutils.LinuxMock())
   514  	bundle, err := x.Compile(`
   515  a = file("/tmp/ref1").content.trim
   516  file(a).path == "/tmp/ref2"
   517  file(a).content.trim == "asdf"
   518  	`)
   519  	require.NoError(t, err)
   520  
   521  	results := x.TestMqlc(t, bundle, nil)
   522  	require.Len(t, results, 5)
   523  }