go.mondoo.com/cnquery@v0.0.0-20231005093811-59568235f6ea/providers/core/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 "strconv" 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/providers-sdk/v1/testutils" 14 ) 15 16 // Core Language constructs 17 // ------------------------ 18 // These tests are more generic MQL and resource tests. They have no dependency 19 // on any other resources and test important MQL constructs. 20 21 func TestCore_Props(t *testing.T) { 22 tests := []struct { 23 code string 24 props map[string]*llx.Primitive 25 resultIndex int 26 expectation interface{} 27 err error 28 }{ 29 { 30 `props.name`, 31 map[string]*llx.Primitive{"name": llx.StringPrimitive("bob")}, 32 0, "bob", nil, 33 }, 34 { 35 `props.name == 'bob'`, 36 map[string]*llx.Primitive{"name": llx.StringPrimitive("bob")}, 37 1, true, nil, 38 }, 39 } 40 41 x := testutils.InitTester(testutils.LinuxMock()) 42 43 for i := range tests { 44 cur := tests[i] 45 t.Run(cur.code, func(t *testing.T) { 46 res := x.TestQueryP(t, cur.code, cur.props) 47 require.NotEmpty(t, res) 48 49 if len(res) <= cur.resultIndex { 50 t.Error("insufficient results, looking for result idx " + strconv.Itoa(cur.resultIndex)) 51 return 52 } 53 54 assert.NotNil(t, res[cur.resultIndex].Result().Error) 55 assert.Equal(t, cur.expectation, res[cur.resultIndex].Data.Value) 56 }) 57 } 58 } 59 60 func TestCore_If(t *testing.T) { 61 x.TestSimple(t, []testutils.SimpleTest{ 62 { 63 Code: "if ( mondoo.version == null ) { 123 }", 64 ResultIndex: 1, 65 Expectation: nil, 66 }, 67 { 68 Code: "if (true) { return 123 } return 456", 69 Expectation: int64(123), 70 }, 71 { 72 Code: "if (true) { return [1] } return [2,3]", 73 Expectation: []interface{}{int64(1)}, 74 }, 75 { 76 Code: "if (false) { return 123 } return 456", 77 Expectation: int64(456), 78 }, 79 { 80 Code: "if (false) { return 123 } if (true) { return 456 } return 789", 81 Expectation: int64(456), 82 }, 83 { 84 Code: "if (false) { return 123 } if (false) { return 456 } return 789", 85 Expectation: int64(789), 86 }, 87 { 88 // This test comes out from an issue we had where return was not 89 // generating a single entrypoint, causing the first reported 90 // value to be used as the return value. 91 Code: ` 92 if (true) { 93 a = asset.platform != '' 94 b = false 95 return a || b 96 } 97 `, Expectation: true, 98 }, 99 { 100 Code: "if ( mondoo.version != null ) { 123 }", 101 ResultIndex: 1, 102 Expectation: map[string]interface{}{ 103 "__t": llx.BoolData(true), 104 "__s": llx.NilData, 105 "NmGComMxT/GJkwpf/IcA+qceUmwZCEzHKGt+8GEh+f8Y0579FxuDO+4FJf0/q2vWRE4dN2STPMZ+3xG3Mdm1fA==": llx.IntData(123), 106 }, 107 }, 108 { 109 Code: "if ( mondoo.version != null ) { 123 } else { 456 }", 110 ResultIndex: 1, 111 Expectation: map[string]interface{}{ 112 "__t": llx.BoolData(true), 113 "__s": llx.NilData, 114 "NmGComMxT/GJkwpf/IcA+qceUmwZCEzHKGt+8GEh+f8Y0579FxuDO+4FJf0/q2vWRE4dN2STPMZ+3xG3Mdm1fA==": llx.IntData(123), 115 }, 116 }, 117 { 118 Code: "if ( mondoo.version == null ) { 123 } else { 456 }", 119 ResultIndex: 1, 120 Expectation: map[string]interface{}{ 121 "__t": llx.BoolData(true), 122 "__s": llx.NilData, 123 "3ZDJLpfu1OBftQi3eANcQSCltQum8mPyR9+fI7XAY9ZUMRpyERirCqag9CFMforO/u0zJolHNyg+2gE9hSTyGQ==": llx.IntData(456), 124 }, 125 }, 126 { 127 Code: "if (false) { 123 } else if (true) { 456 } else { 789 }", 128 Expectation: map[string]interface{}{ 129 "__t": llx.BoolData(true), 130 "__s": llx.NilData, 131 "3ZDJLpfu1OBftQi3eANcQSCltQum8mPyR9+fI7XAY9ZUMRpyERirCqag9CFMforO/u0zJolHNyg+2gE9hSTyGQ==": llx.IntData(456), 132 }, 133 }, 134 { 135 Code: "if (false) { 123 } else if (false) { 456 } else { 789 }", 136 Expectation: map[string]interface{}{ 137 "__t": llx.BoolData(true), 138 "__s": llx.NilData, 139 "Oy5SF8NbUtxaBwvZPpsnd0K21CY+fvC44FSd2QpgvIL689658Na52udy7qF2+hHjczk35TAstDtFZq7JIHNCmg==": llx.IntData(789), 140 }, 141 }, 142 }) 143 144 x.TestSimpleErrors(t, []testutils.SimpleTest{ 145 // if-conditions need to be called with a bloc 146 { 147 Code: "if(asset.family.contains('arch'))", 148 ResultIndex: 1, Expectation: "Called if with 1 arguments, expected at least 3", 149 }, 150 }) 151 } 152 153 func TestCore_Switch(t *testing.T) { 154 x.TestSimple(t, []testutils.SimpleTest{ 155 { 156 Code: "switch { case 3 > 2: 123; default: 321 }", 157 Expectation: int64(123), 158 }, 159 { 160 Code: "switch { case 1 > 2: 123; default: 321 }", 161 Expectation: int64(321), 162 }, 163 { 164 Code: "switch { case 3 > 2: return 123; default: return 321 }", 165 Expectation: int64(123), 166 }, 167 { 168 Code: "switch { case 1 > 2: return 123; default: return 321 }", 169 Expectation: int64(321), 170 }, 171 { 172 Code: "switch ( 3 ) { case _ > 2: return 123; default: return 321 }", 173 Expectation: int64(123), 174 }, 175 { 176 Code: "switch ( 1 ) { case _ > 2: true; default: false }", 177 Expectation: false, 178 }, 179 }) 180 } 181 182 func TestCore_Vars(t *testing.T) { 183 x.TestSimple(t, []testutils.SimpleTest{ 184 { 185 Code: "a = [1,2,3]; return a", 186 Expectation: []interface{}{int64(1), int64(2), int64(3)}, 187 }, 188 { 189 Code: "a = 1; b = [a]; return b", 190 Expectation: []interface{}{int64(1)}, 191 }, 192 { 193 Code: "a = 1; b = a + 2; return b", 194 Expectation: int64(3), 195 }, 196 { 197 Code: "a = 1; b = [a + 2]; return b", 198 Expectation: []interface{}{int64(3)}, 199 }, 200 }) 201 } 202 203 // Base types and operations 204 // ------------------------- 205 206 func TestBooleans(t *testing.T) { 207 x.TestSimple(t, []testutils.SimpleTest{ 208 { 209 Code: "true || false || false", 210 ResultIndex: 1, 211 Expectation: true, 212 }, 213 { 214 Code: "false || true || false", 215 ResultIndex: 1, 216 Expectation: true, 217 }, 218 { 219 Code: "false || false || true", 220 ResultIndex: 1, 221 Expectation: true, 222 }, 223 }) 224 } 225 226 func TestOperations_Equality(t *testing.T) { 227 vals := []string{ 228 "null", 229 "true", "false", 230 "0", "1", 231 "1.0", "1.5", 232 "'1'", "'1.0'", "'a'", 233 "/1/", "/a/", "/nope/", 234 "[1]", "[null]", 235 } 236 237 extraEquality := map[string]map[string]struct{}{ 238 "1": { 239 "1.0": struct{}{}, 240 "'1'": struct{}{}, 241 "/1/": struct{}{}, 242 "[1]": struct{}{}, 243 "[1.0]": struct{}{}, 244 }, 245 "1.0": { 246 "[1]": struct{}{}, 247 }, 248 "'a'": { 249 "/a/": struct{}{}, 250 }, 251 "'1'": { 252 "1.0": struct{}{}, 253 "[1]": struct{}{}, 254 }, 255 "/1/": { 256 "1.0": struct{}{}, 257 "'1'": struct{}{}, 258 "'1.0'": struct{}{}, 259 "[1]": struct{}{}, 260 "1.5": struct{}{}, 261 }, 262 } 263 264 simpleTests := []testutils.SimpleTest{} 265 266 for i := 0; i < len(vals); i++ { 267 for j := i; j < len(vals); j++ { 268 a := vals[i] 269 b := vals[j] 270 res := a == b 271 272 if sub, ok := extraEquality[a]; ok { 273 if _, ok := sub[b]; ok { 274 res = true 275 } 276 } 277 if sub, ok := extraEquality[b]; ok { 278 if _, ok := sub[a]; ok { 279 res = true 280 } 281 } 282 283 simpleTests = append(simpleTests, []testutils.SimpleTest{ 284 {Code: a + " == " + b, Expectation: res}, 285 {Code: a + " != " + b, Expectation: !res}, 286 {Code: "a = " + a + " a == " + b, ResultIndex: 1, Expectation: res}, 287 {Code: "a = " + a + " a != " + b, ResultIndex: 1, Expectation: !res}, 288 {Code: "b = " + b + "; " + a + " == b", ResultIndex: 1, Expectation: res}, 289 {Code: "b = " + b + "; " + a + " != b", ResultIndex: 1, Expectation: !res}, 290 {Code: "a = " + a + "; b = " + b + "; a == b", ResultIndex: 2, Expectation: res}, 291 {Code: "a = " + a + "; b = " + b + "; a != b", ResultIndex: 2, Expectation: !res}, 292 }...) 293 } 294 } 295 296 x.TestSimple(t, simpleTests) 297 } 298 299 func TestEmpty(t *testing.T) { 300 empty := []string{ 301 "null", 302 "''", 303 "[]", 304 "{}", 305 } 306 nonEmpty := []string{ 307 "true", "false", 308 "0", "1.0", 309 "'a'", 310 "/a/", 311 "[null]", "[1]", 312 "{a: 1}", 313 } 314 315 tests := []testutils.SimpleTest{} 316 for i := range empty { 317 tests = append(tests, testutils.SimpleTest{ 318 Code: empty[i] + " == empty", 319 ResultIndex: 1, 320 Expectation: true, 321 }) 322 } 323 324 for i := range nonEmpty { 325 tests = append(tests, testutils.SimpleTest{ 326 Code: nonEmpty[i] + " == empty", 327 ResultIndex: 1, 328 Expectation: false, 329 }) 330 } 331 332 x.TestSimple(t, tests) 333 } 334 335 func TestNumber_Methods(t *testing.T) { 336 x.TestSimple(t, []testutils.SimpleTest{ 337 { 338 Code: "1 + 2", Expectation: int64(3), 339 }, 340 { 341 Code: "1 - 2", Expectation: int64(-1), 342 }, 343 { 344 Code: "1 * 2", Expectation: int64(2), 345 }, 346 { 347 Code: "4 / 2", Expectation: int64(2), 348 }, 349 { 350 Code: "1.0 + 2.0", Expectation: float64(3), 351 }, 352 { 353 Code: "1 - 2.0", Expectation: float64(-1), 354 }, 355 { 356 Code: "1.0 * 2", Expectation: float64(2), 357 }, 358 { 359 Code: "4.0 / 2.0", Expectation: float64(2), 360 }, 361 { 362 Code: "1 < Infinity", Expectation: true, 363 }, 364 { 365 Code: "1 == NaN", Expectation: false, 366 }, 367 }) 368 } 369 370 func TestString_Methods(t *testing.T) { 371 x := testutils.InitTester(testutils.LinuxMock()) 372 x.TestSimple(t, []testutils.SimpleTest{ 373 { 374 Code: "'hello'.contains('ll')", 375 Expectation: true, 376 }, 377 { 378 Code: "'hello'.contains('lloo')", 379 Expectation: false, 380 }, 381 { 382 Code: "'hello'.contains(['lo', 'la'])", 383 Expectation: true, 384 }, 385 { 386 Code: "'hello'.contains(['lu', 'la'])", 387 Expectation: false, 388 }, 389 { 390 Code: "'hello'.contains(23)", 391 Expectation: false, 392 }, 393 { 394 Code: "'hello123'.contains(23)", 395 Expectation: true, 396 }, 397 { 398 Code: "'hello123'.contains([5,6,7])", 399 Expectation: false, 400 }, 401 { 402 Code: "'hello123'.contains([5,1,7])", 403 Expectation: true, 404 }, 405 { 406 Code: "'hello'.contains(/l+/)", 407 Expectation: true, 408 }, 409 { 410 Code: "'hello'.contains(/l$/)", 411 Expectation: false, 412 }, 413 { 414 Code: "'hello'.contains([/^l/, /l$/])", 415 Expectation: false, 416 }, 417 { 418 Code: "'hello'.contains([/z/, /ll/])", 419 Expectation: true, 420 }, 421 { 422 Code: "'oh-hello-world!'.camelcase", 423 Expectation: "ohHelloWorld!", 424 }, 425 { 426 Code: "'HeLlO'.downcase", 427 Expectation: "hello", 428 }, 429 { 430 Code: "'hello'.length", 431 Expectation: int64(5), 432 }, 433 { 434 Code: "'hello world'.split(' ')", 435 Expectation: []interface{}{"hello", "world"}, 436 }, 437 { 438 Code: "'he\nll\no'.lines", 439 Expectation: []interface{}{"he", "ll", "o"}, 440 }, 441 { 442 Code: "' \n\t yo \t \n '.trim", 443 Expectation: "yo", 444 }, 445 { 446 Code: "' \tyo \n '.trim(' \n')", 447 Expectation: "\tyo", 448 }, 449 { 450 Code: "'hello ' + 'world'", 451 Expectation: "hello world", 452 }, 453 }) 454 } 455 456 func TestScore_Methods(t *testing.T) { 457 x := testutils.InitTester(testutils.LinuxMock()) 458 x.TestSimple(t, []testutils.SimpleTest{ 459 { 460 Code: "score(100)", 461 Expectation: []byte{0x00, byte(100)}, 462 }, 463 { 464 Code: "score(\"CVSS:3.1/AV:P/AC:H/PR:L/UI:N/S:U/C:H/I:L/A:H\")", 465 Expectation: []byte{0x01, 0x03, 0x01, 0x04, 0x00, 0x00, 0x00, 0x01, 0x00}, 466 }, 467 }) 468 } 469 470 func TestTypeof_Methods(t *testing.T) { 471 x := testutils.InitTester(testutils.LinuxMock()) 472 x.TestSimple(t, []testutils.SimpleTest{ 473 { 474 Code: "typeof(null)", 475 Expectation: "null", 476 }, 477 { 478 Code: "typeof(123)", 479 Expectation: "int", 480 }, 481 { 482 Code: "typeof([1,2,3])", 483 Expectation: "[]int", 484 }, 485 { 486 Code: "a = 123; typeof(a)", 487 Expectation: "int", 488 }, 489 }) 490 } 491 492 func TestArray_Access(t *testing.T) { 493 x := testutils.InitTester(testutils.LinuxMock()) 494 x.TestSimpleErrors(t, []testutils.SimpleTest{ 495 { 496 Code: "[0,1,2][100000]", 497 Expectation: "array index out of bound (trying to access element 100000, max: 2)", 498 }, 499 }) 500 501 x.TestSimple(t, []testutils.SimpleTest{ 502 { 503 Code: "[1,2,3][-1]", 504 Expectation: int64(3), 505 }, 506 { 507 Code: "[1,2,3][-3]", 508 Expectation: int64(1), 509 }, 510 { 511 Code: "[1,2,3].first", 512 Expectation: int64(1), 513 }, 514 { 515 Code: "[1,2,3].last", 516 Expectation: int64(3), 517 }, 518 { 519 Code: "[].first", 520 Expectation: nil, 521 }, 522 { 523 Code: "[].last", 524 Expectation: nil, 525 }, 526 }) 527 } 528 529 func TestArray(t *testing.T) { 530 x := testutils.InitTester(testutils.LinuxMock()) 531 x.TestSimple(t, []testutils.SimpleTest{ 532 { 533 Code: "[1,2,3]", 534 Expectation: []interface{}{int64(1), int64(2), int64(3)}, 535 }, 536 { 537 Code: "return [1,2,3]", 538 Expectation: []interface{}{int64(1), int64(2), int64(3)}, 539 }, 540 { 541 Code: "[1,2,3] { _ == 2 }", 542 Expectation: []interface{}{ 543 map[string]interface{}{"__t": llx.BoolFalse, "__s": llx.BoolFalse, "OPhfwvbw0iVuMErS9tKL5qNj1lqTg3PEE1LITWEwW7a70nH8z8eZLi4x/aZqZQlyrQK13GAlUMY1w8g131EPog==": llx.BoolFalse}, 544 map[string]interface{}{"__t": llx.BoolTrue, "__s": llx.BoolTrue, "OPhfwvbw0iVuMErS9tKL5qNj1lqTg3PEE1LITWEwW7a70nH8z8eZLi4x/aZqZQlyrQK13GAlUMY1w8g131EPog==": llx.BoolTrue}, 545 map[string]interface{}{"__t": llx.BoolFalse, "__s": llx.BoolFalse, "OPhfwvbw0iVuMErS9tKL5qNj1lqTg3PEE1LITWEwW7a70nH8z8eZLi4x/aZqZQlyrQK13GAlUMY1w8g131EPog==": llx.BoolFalse}, 546 }, 547 }, 548 { 549 Code: "[1,2,3] { a = _ }", 550 Expectation: []interface{}{ 551 map[string]interface{}{"__t": llx.BoolTrue, "__s": llx.NilData}, 552 map[string]interface{}{"__t": llx.BoolTrue, "__s": llx.NilData}, 553 map[string]interface{}{"__t": llx.BoolTrue, "__s": llx.NilData}, 554 }, 555 }, 556 { 557 Code: "[1,2,3].where()", 558 Expectation: []interface{}{int64(1), int64(2), int64(3)}, 559 }, 560 { 561 Code: "[true, true, false].where(true)", 562 Expectation: []interface{}{true, true}, 563 }, 564 { 565 Code: "[false, true, false].where(false)", 566 Expectation: []interface{}{false, false}, 567 }, 568 { 569 Code: "[1,2,3].where(2)", 570 Expectation: []interface{}{int64(2)}, 571 }, 572 { 573 Code: "[1,2,3].where(_ > 2)", 574 Expectation: []interface{}{int64(3)}, 575 }, 576 { 577 Code: "[1,2,3].where(_ >= 2)", 578 Expectation: []interface{}{int64(2), int64(3)}, 579 }, 580 { 581 Code: "['yo','ho','ho'].where( /y.$/ )", 582 Expectation: []interface{}{"yo"}, 583 }, 584 { 585 Code: "x = ['a','b']; y = 'c'; x.contains(y)", 586 ResultIndex: 1, 587 Expectation: false, 588 }, 589 { 590 Code: "[1,2,3].contains(_ >= 2)", 591 ResultIndex: 1, 592 Expectation: true, 593 }, 594 { 595 Code: "[1,2,3].all(_ < 9)", 596 ResultIndex: 1, 597 Expectation: true, 598 }, 599 { 600 Code: "[1,2,3].any(_ > 1)", 601 ResultIndex: 1, 602 Expectation: true, 603 }, 604 { 605 Code: "[1,2,3].one(_ == 2)", 606 ResultIndex: 1, 607 Expectation: true, 608 }, 609 { 610 Code: "[1,2,3].none(_ == 4)", 611 ResultIndex: 1, 612 Expectation: true, 613 }, 614 { 615 Code: "[[0,1],[1,2]].map(_[1])", 616 Expectation: []interface{}{int64(1), int64(2)}, 617 }, 618 { 619 Code: "[[0],[[1, 2]], 3].flat", 620 Expectation: []interface{}{int64(0), int64(1), int64(2), int64(3)}, 621 }, 622 { 623 Code: "[0].where(_ > 0).where(_ > 0)", 624 Expectation: []interface{}{}, 625 }, 626 { 627 Code: "[1,2,2,2,3].unique()", 628 Expectation: []interface{}{int64(1), int64(2), int64(3)}, 629 }, 630 { 631 Code: "[1,1,2,2,2,3].duplicates()", 632 Expectation: []interface{}{int64(1), int64(2)}, 633 }, 634 { 635 Code: "[2,1,2,2].containsOnly([2])", 636 Expectation: []interface{}{int64(1)}, 637 }, 638 { 639 Code: "[2,1,2,1].containsOnly([1,2])", 640 ResultIndex: 0, Expectation: []interface{}(nil), 641 }, 642 { 643 Code: "a = [1]; [2,1,2,1].containsOnly(a)", 644 Expectation: []interface{}{int64(2), int64(2)}, 645 }, 646 { 647 Code: "[2,1,2,2].containsNone([1])", 648 Expectation: []interface{}{int64(1)}, 649 }, 650 { 651 Code: "[2,1,2,1].containsNone([3,4])", 652 ResultIndex: 0, Expectation: []interface{}(nil), 653 }, 654 { 655 Code: "a = [1]; [2,1,2,1].containsNone(a)", 656 Expectation: []interface{}{int64(1), int64(1)}, 657 }, 658 { 659 Code: "['a','b'] != /c/", 660 ResultIndex: 0, Expectation: true, 661 }, 662 { 663 Code: "[1,2] + [3]", 664 Expectation: []interface{}{int64(1), int64(2), int64(3)}, 665 }, 666 { 667 Code: "[3,1,3,4,2] - [3,4,5]", 668 Expectation: []interface{}{int64(1), int64(2)}, 669 }, 670 }) 671 } 672 673 func TestResource_Default(t *testing.T) { 674 x := testutils.InitTester(testutils.LinuxMock()) 675 res := x.TestQuery(t, "mondoo") 676 require.NotEmpty(t, res) 677 vals := res[0].Data.Value.(map[string]interface{}) 678 require.NotNil(t, vals) 679 require.Equal(t, llx.StringData("unstable"), vals["J4anmJ+mXJX380Qslh563U7Bs5d6fiD2ghVxV9knAU0iy/P+IVNZsDhBbCmbpJch3Tm0NliAMiaY47lmw887Jw=="]) 680 } 681 682 func TestBrokenQueryExecution(t *testing.T) { 683 x := testutils.InitTester(testutils.LinuxMock()) 684 bundle, err := x.Compile("'asdf'.contains('asdf') == true") 685 require.NoError(t, err) 686 bundle.CodeV2.Blocks[0].Chunks[1].Id = "fakecontains" 687 688 results := x.TestMqlc(t, bundle, nil) 689 require.Len(t, results, 3) 690 require.Error(t, results[0].Data.Error) 691 require.Error(t, results[1].Data.Error) 692 require.Error(t, results[2].Data.Error) 693 }