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 }