123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972 |
- #include "test_base.h"
- #include <util/string/builder.h>
- #include <cmath>
- class TJsonPathCommonTest : public TJsonPathTestBase {
- public:
- TJsonPathCommonTest()
- : TJsonPathTestBase()
- {
- }
- UNIT_TEST_SUITE(TJsonPathCommonTest);
- UNIT_TEST(TestPrimary);
- UNIT_TEST(TestMemberAccess);
- UNIT_TEST(TestWildcardMemberAccess);
- UNIT_TEST(TestArrayAccess);
- UNIT_TEST(TestLastArrayIndex);
- UNIT_TEST(TestLastArrayIndexInvalid);
- UNIT_TEST(TestNonIntegerArrayIndex);
- UNIT_TEST(TestWildcardArrayAccess);
- UNIT_TEST(TestUnaryOperations);
- UNIT_TEST(TestUnaryOperationsErrors);
- UNIT_TEST(TestBinaryArithmeticOperations);
- UNIT_TEST(TestBinaryArithmeticOperationsErrors);
- UNIT_TEST(TestParseErrors);
- UNIT_TEST(TestVariables);
- UNIT_TEST(TestDivisionByZero);
- UNIT_TEST(TestInfinityResult);
- UNIT_TEST(TestLogicalOperations);
- UNIT_TEST(TestCompareOperations);
- UNIT_TEST(TestFilter);
- UNIT_TEST(TestFilterInvalid);
- UNIT_TEST(TestNumericMethods);
- UNIT_TEST(TestNumericMethodsErrors);
- UNIT_TEST(TestDoubleMethod);
- UNIT_TEST(TestDoubleMethodErrors);
- UNIT_TEST(TestTypeMethod);
- UNIT_TEST(TestSizeMethod);
- UNIT_TEST(TestKeyValueMethod);
- UNIT_TEST(TestKeyValueMethodErrors);
- UNIT_TEST(TestStartsWithPredicate);
- UNIT_TEST(TestStartsWithPredicateErrors);
- UNIT_TEST(TestExistsPredicate);
- UNIT_TEST(TestIsUnknownPredicate);
- UNIT_TEST(TestLikeRegexPredicate);
- UNIT_TEST_SUITE_END();
- void TestPrimary() {
- const TVector<TMultiOutputTestCase> testCases = {
- // Context object $ must return whole JSON when used alone
- {R"({"key": 123})", "$", {R"({"key":123})"}},
- {R"([1, 2, 3])", "$", {R"([1,2,3])"}},
- {"1.234", "$", {"1.234"}},
- {R"("some string")", "$", {R"("some string")"}},
- // Literal must not depend on input
- {R"({"key": 123})", "123", {"123"}},
- {R"([1, 2, 3])", "123", {"123"}},
- {"1.234", "123", {"123"}},
- {R"("some string")", "123", {"123"}},
- // Check various ways to define number literal
- {"1", "123.4", {"123.4"}},
- {"1", "0.567", {"0.567"}},
- {"1", "1234e-1", {"123.4"}},
- {"1", "567e-3", {"0.567"}},
- {"1", "123.4e-1", {"12.34"}},
- {"1", "123e3", {"123000"}},
- {"1", "123e+3", {"123000"}},
- {"1", "1.23e+1", {"12.3"}},
- {"1", "1.23e1", {"12.3"}},
- {"1", "12e0", {"12"}},
- {"1", "12.3e0", {"12.3"}},
- {"1", "0", {"0"}},
- {"1", "0.0", {"0"}},
- {"1", "0.0e0", {"0"}},
- // Check boolean and null literals
- {"1", "null", {"null"}},
- {"1", "false", {"false"}},
- {"1", "true", {"true"}},
- // Check string literals
- {"1", "\"string\"", {"\"string\""}},
- {"1", "\" space another space \"", {"\" space another space \""}},
- {"1", "\"привет\"", {"\"привет\""}},
- // NOTE: escaping is added by library/cpp/json
- {"1", "\"\r\n\t\"", {"\"\\r\\n\\t\""}},
- };
- for (const auto& testCase : testCases) {
- for (const auto mode : ALL_MODES) {
- RunTestCase(testCase.Json, mode + testCase.JsonPath, testCase.Result);
- }
- }
- }
- void TestMemberAccess() {
- const TVector<TMultiOutputTestCase> testCases = {
- {R"({"key": 123, "another_key": 456})", "$.key", {"123"}},
- {R"({"key": 123, "_another_28_key_$_": 456})", "$._another_28_key_$_", {"456"}},
- {R"({"key": 123, "another_key": 456})", " $.another_key ", {"456"}},
- {R"({"key": 123, "another_key": 456})", "$.key", {"123"}},
- {R"({"k\"ey": 123, "another_key": 456})", "$.\"k\\\"ey\"", {"123"}},
- {R"({"k\"ey": 123, "another_key": 456})", "$.'k\\\"ey'", {"123"}},
- {R"({"key": 123, "another_key": 456})", "$.'key'", {"123"}},
- {R"({"key": 123, "_another_28_key_$_": 456})", "$.'_another_28_key_$_'", {"456"}},
- {R"({"key": 123, "another_key": 456})", " $.'another_key' ", {"456"}},
- {R"({"key": 123, "another_key": 456})", "$.\"key\"", {"123"}},
- {R"({"key": 123, "_another_28_key_$_": 456})", "$.\"_another_28_key_$_\"", {"456"}},
- {R"({"key": 123, "another_key": 456})", " $.\"another_key\" ", {"456"}},
- {R"({"key": 123, "another key": 456})", "$.'another key'", {"456"}},
- {R"({"key": 123, "another key": 456})", "$.\"another key\"", {"456"}},
- {R"({"key": 123, "прием отбой": 456})", "$.'прием отбой'", {"456"}},
- {R"({"key": 123, "прием отбой": 456})", "$.\"прием отбой\"", {"456"}},
- {R"({"key": {"another": 456}})", "$.key.another", {"456"}},
- {R"({"key": {"another key": 456}})", "$.'key'.\"another key\"", {"456"}},
- };
- for (const auto& testCase : testCases) {
- for (const auto mode : ALL_MODES) {
- RunTestCase(testCase.Json, mode + testCase.JsonPath, testCase.Result);
- }
- }
- }
- void TestWildcardMemberAccess() {
- const TVector<TMultiOutputTestCase> testCases = {
- {R"({
- "first": 12,
- "second": 72
- })", "$.*", {"12", "72"}},
- {R"({
- "friends": {
- "Nik": {"age": 18},
- "Kate": {"age": 72}
- }
- })", "$.friends.*.age", {"72", "18"}},
- {R"({
- "friends": {
- "Nik": {"age": 18},
- "Kate": {"age": 72}
- }
- })", "$.*.*.*", {"72", "18"}},
- {R"({})", "$.*.key", {}},
- };
- for (const auto& testCase : testCases) {
- for (const auto mode : ALL_MODES) {
- RunTestCase(testCase.Json, mode + testCase.JsonPath, testCase.Result);
- }
- }
- }
- void TestArrayAccess() {
- const TVector<TMultiOutputTestCase> testCases = {
- {R"([1, 2, 3])", "$[0]", {"1"}},
- {R"([1, 2, 3, 4, 5, 6])", "$[0 to 2]", {"1", "2", "3"}},
- {R"([1, 2, 3, 4, 5, 6])", "$[5, 0 to 2, 0, 0, 3 to 5, 2]", {"6", "1", "2", "3", "1", "1", "4", "5", "6", "3"}},
- {R"({
- "friends": [
- {"name": "Nik", "age": 18},
- {"name": "Kate", "age": 72},
- {"name": "Foma", "age": 50},
- {"name": "Jora", "age": 60}
- ]
- })", "$.friends[1 to 3, 0].age", {"72", "50", "60", "18"}},
- {R"({
- "range": {
- "from": 1,
- "to": 2
- },
- "friends": [
- {"name": "Nik", "age": 18},
- {"name": "Kate", "age": 72},
- {"name": "Foma", "age": 50},
- {"name": "Jora", "age": 60}
- ]
- })", "$.friends[$.range.from to $.range.to].age", {"72", "50"}},
- {R"({
- "range": {
- "from": [1, 3, 4],
- "to": {"key1": 1, "key2": 2, "key3": 3}
- },
- "friends": [
- {"name": "Nik", "age": 18},
- {"name": "Kate", "age": 72},
- {"name": "Foma", "age": 50},
- {"name": "Jora", "age": 60}
- ]
- })", "$.friends[$.range.from[1] to $.range.to.key3].age", {"60"}},
- };
- for (const auto& testCase : testCases) {
- for (const auto mode : ALL_MODES) {
- RunTestCase(testCase.Json, mode + testCase.JsonPath, testCase.Result);
- }
- }
- }
- void TestLastArrayIndex() {
- const TVector<TMultiOutputTestCase> testCases = {
- {R"([1, 2, 3])", "$[last]", {"3"}},
- {R"([1, 2, 3])", "$[1 to last]", {"2", "3"}},
- {R"([1, 2, 3])", "$[last to last]", {"3"}},
- {R"([1, 2, 3, 5, 6])", "$[1, last, last, 0, 2 to last, 3]", {"2", "6", "6", "1", "3", "5", "6", "5"}},
- {R"([
- [1, 2, 3, 4],
- [5, 6, 7, 8]
- ])", "$[*][last]", {"4", "8"}},
- {R"({
- "ranges": [
- {"from": 1, "to": 3},
- {"from": 0, "to": 1}
- ],
- "friends": [
- {"name": "Nik", "age": 18},
- {"name": "Kate", "age": 72},
- {"name": "Foma", "age": 50},
- {"name": "Jora", "age": 60}
- ]
- })", "$.friends[last, $.ranges[last].from to $.ranges[last].to, 2 to last].age", {"60", "18", "72", "50", "60"}},
- {R"({
- "ranges": [
- {"from": 1.23, "to": 3.75},
- {"from": 0.58, "to": 1.00001}
- ],
- "friends": [
- {"name": "Nik", "age": 18},
- {"name": "Kate", "age": 72},
- {"name": "Foma", "age": 50},
- {"name": "Jora", "age": 60}
- ]
- })", "$.friends[last, $.ranges[last].from to $.ranges[last].to, 2 to last].age", {"60", "18", "72", "50", "60"}},
- };
- for (const auto& testCase : testCases) {
- for (const auto mode : ALL_MODES) {
- RunTestCase(testCase.Json, mode + testCase.JsonPath, testCase.Result);
- }
- }
- }
- void TestLastArrayIndexInvalid() {
- const TVector<TRuntimeErrorTestCase> testCases = {
- {R"({})", "last", C(TIssuesIds::JSONPATH_LAST_OUTSIDE_OF_ARRAY_SUBSCRIPT)},
- };
- for (const auto& testCase : testCases) {
- for (const auto mode : ALL_MODES) {
- RunRuntimeErrorTestCase(testCase.Json, mode + testCase.JsonPath, testCase.Error);
- }
- }
- }
- void TestNonIntegerArrayIndex() {
- const TVector<TRuntimeErrorTestCase> testCases = {
- {R"({
- "range": {
- "from": [1, 3, 4],
- "to": {"key1": 1, "key2": 2, "key3": 3}
- },
- "friends": [1, 2, 3]
- })", "$.friends[$.range.from[*] to $.range.to.*]", C(TIssuesIds::JSONPATH_INVALID_ARRAY_INDEX)},
- };
- for (const auto& testCase : testCases) {
- for (const auto mode : ALL_MODES) {
- RunRuntimeErrorTestCase(testCase.Json, mode + testCase.JsonPath, testCase.Error);
- }
- }
- }
- void TestWildcardArrayAccess() {
- const TVector<TMultiOutputTestCase> testCases = {
- {R"([1, 2, 3])", "$[*]", {"1", "2", "3"}},
- {R"([[1], [2], [3, 4, 5]])", "$[*][*]", {"1", "2", "3", "4", "5"}},
- {R"({
- "friends": [
- {"name": "Nik", "age": 18},
- {"name": "Kate", "age": 72},
- {"name": "Foma", "age": 50},
- {"name": "Jora", "age": 60}
- ]
- })", "$.friends[*].age", {"18", "72", "50", "60"}},
- };
- for (const auto& testCase : testCases) {
- for (const auto mode : ALL_MODES) {
- RunTestCase(testCase.Json, mode + testCase.JsonPath, testCase.Result);
- }
- }
- }
- void TestUnaryOperations() {
- const TVector<TMultiOutputTestCase> testCases = {
- {R"([])", "-3", {"-3"}},
- {R"([])", "+3", {"3"}},
- {R"(-1)", "-$", {"1"}},
- {R"(-1)", "+$", {"-1"}},
- {R"({
- "range": {
- "from": -1,
- "to": -2
- },
- "array": [1, 2, 3, 4]
- })", "$.array[-$.range.from to -$.range.to]", {"2", "3"}},
- {R"({
- "range": {
- "from": 1,
- "to": -2
- },
- "array": [1, 2, 3, 4]
- })", "$.array[+$.range.from to -$.range.to]", {"2", "3"}},
- {R"({
- "range": {
- "from": -1,
- "to": 2
- },
- "array": [1, 2, 3, 4]
- })", "$.array[-$.range.from to +$.range.to]", {"2", "3"}},
- {R"({
- "range": {
- "from": 1,
- "to": 2
- },
- "array": [1, 2, 3, 4]
- })", "$.array[+$.range.from to +$.range.to]", {"2", "3"}},
- {R"([1, 2, 3])", "-$[*]", {"-1", "-2", "-3"}},
- {"30000000000000000000000000", "-$", {"-3e+25"}},
- };
- for (const auto& testCase : testCases) {
- for (const auto mode : ALL_MODES) {
- RunTestCase(testCase.Json, mode + testCase.JsonPath, testCase.Result);
- }
- }
- }
- void TestUnaryOperationsErrors() {
- const TVector<TRuntimeErrorTestCase> testCases = {
- {R"({})", "-$", C(TIssuesIds::JSONPATH_INVALID_UNARY_OPERATION_ARGUMENT_TYPE)},
- {R"([1, 2, [], 4])", "-$[*]", C(TIssuesIds::JSONPATH_INVALID_UNARY_OPERATION_ARGUMENT_TYPE)},
- {R"([1, 2, {}, 4])", "-$[*]", C(TIssuesIds::JSONPATH_INVALID_UNARY_OPERATION_ARGUMENT_TYPE)},
- };
- for (const auto& testCase : testCases) {
- for (const auto mode : ALL_MODES) {
- RunRuntimeErrorTestCase(testCase.Json, mode + testCase.JsonPath, testCase.Error);
- }
- }
- }
- void TestBinaryArithmeticOperations() {
- const TVector<TMultiOutputTestCase> testCases = {
- {"[]", "1 + 2", {"3"}},
- {"[]", "1 - 2", {"-1"}},
- {"[]", "10 * 5", {"50"}},
- {"[]", "10 / 5", {"2"}},
- {"[]", "13 % 5", {"3"}},
- {"[]", "20 * 2 + 5", {"45"}},
- {"[]", "20 / 2 + 5", {"15"}},
- {"[]", "20 % 2 + 5", {"5"}},
- {"[]", "20 * (2 + 5)", {"140"}},
- {"[]", "20 / (2 + 3)", {"4"}},
- {"[]", "20 % (2 + 5)", {"6"}},
- {"[]", "5 / 2", {"2.5"}},
- {"[5.24 , 2.62]", "$[0] / $[1]", {"2"}},
- {"[5.24, 2.62]", "$[0] % $[1]", {"0"}},
- {"[3.753, 2.35]", "$[0] % $[1]", {"1.403"}},
- {"[]", "- 1 + 1", {"0"}},
- {"[]", "+ 1 + 1", {"2"}},
- {"[1, 2, 3, 4]", "$[last, last-1, last-2, last-3]", {"4", "3", "2", "1"}},
- };
- for (const auto& testCase : testCases) {
- for (const auto mode : ALL_MODES) {
- RunTestCase(testCase.Json, mode + testCase.JsonPath, testCase.Result);
- }
- }
- }
- void TestBinaryArithmeticOperationsErrors() {
- const TVector<TRuntimeErrorTestCase> testCases = {
- {"[1, 2, 3]", "$[*] + 1", C(TIssuesIds::JSONPATH_INVALID_BINARY_OPERATION_ARGUMENT)},
- {"[1, 2, 3]", "1 + $[*]", C(TIssuesIds::JSONPATH_INVALID_BINARY_OPERATION_ARGUMENT)},
- {"[1, 2, 3]", "$[*] + $[*]", C(TIssuesIds::JSONPATH_INVALID_BINARY_OPERATION_ARGUMENT)},
- {"[1, 2, 3]", "$ + 1", C(TIssuesIds::JSONPATH_INVALID_BINARY_OPERATION_ARGUMENT_TYPE)},
- {"[1, 2, 3]", "1 + $", C(TIssuesIds::JSONPATH_INVALID_BINARY_OPERATION_ARGUMENT_TYPE)},
- {"[1, 2, 3]", "$ + $", C(TIssuesIds::JSONPATH_INVALID_BINARY_OPERATION_ARGUMENT_TYPE)},
- };
- for (const auto& testCase : testCases) {
- for (const auto mode : ALL_MODES) {
- RunRuntimeErrorTestCase(testCase.Json, mode + testCase.JsonPath, testCase.Error);
- }
- }
- }
- void TestParseErrors() {
- const TVector<TString> testCases = {
- "strict",
- "strict smth.key",
- "strict $.",
- "strict $.$key",
- "strict $.28key",
- "strict $.ke^y",
- "strict $.привет",
- "strict $._пока_28_ключ_$_",
- " strict $.пока ",
- "lax",
- "lax smth.key",
- "lax $.",
- "lax $.$key",
- "lax $.28key",
- "lax $.ke^y",
- "lax $.привет",
- "lax $._пока_28_ключ_$_",
- " lax $.пока ",
- "12.",
- "12..3",
- "12.3e",
- "12.3e++1",
- "12.3e--1",
- "1e100000000000000000000000000000000",
- "true || false",
- "1 && (true == true)",
- "!true",
- "$[*] ? (@.active) . id",
- "!(1 > 2).type()",
- "(null) is unknown",
- "(12 * 12) is unknown",
- R"($ like_regex "[[[")",
- R"($ like_regex "[0-9]+" flag "x")",
- "$.first fjrfrfq fqijrhfqiwrjhfqrf qrfqr",
- };
- for (const auto& testCase : testCases) {
- RunParseErrorTestCase(testCase);
- }
- }
- void TestVariables() {
- TVector<TVariablesTestCase> testCases = {
- {"123", {{"var", "456"}}, "$ + $var", {"579"}},
- {"123", {{"var", "456"}}, "$var", {"456"}},
- {"123", {{"var", R"({"key": [1, 2, 3, 4, 5]})"}}, "$var.key[2 to last]", {"3", "4", "5"}},
- {"123", {{"to", "1"}, {"strict", "2"}}, "$to + $strict", {"3"}},
- };
- for (const auto& testCase : testCases) {
- for (const auto mode : ALL_MODES) {
- RunVariablesTestCase(testCase.Json, testCase.Variables, mode + testCase.JsonPath, testCase.Result);
- }
- }
- }
- void TestDivisionByZero() {
- const TVector<TRuntimeErrorTestCase> testCases = {
- {"0", "1 / $", C(TIssuesIds::JSONPATH_DIVISION_BY_ZERO)},
- {"0.00000000000000000001", "1 / $", C(TIssuesIds::JSONPATH_DIVISION_BY_ZERO)},
- };
- for (const auto& testCase : testCases) {
- for (const auto mode : ALL_MODES) {
- RunRuntimeErrorTestCase(testCase.Json, mode + testCase.JsonPath, testCase.Error);
- }
- }
- }
- void TestInfinityResult() {
- const double step = 1000000000;
- double current = step;
- TStringBuilder literal;
- TStringBuilder query;
- literal << '"' << step;
- query << step;
- while (!std::isinf(current)) {
- query << " * " << step;
- literal << "000000000";
- current *= step;
- }
- literal << '"';
- const TVector<TRuntimeErrorTestCase> testCases = {
- {"0", TString(query), C(TIssuesIds::JSONPATH_BINARY_OPERATION_RESULT_INFINITY)},
- {TString(literal), "$.double()", C(TIssuesIds::JSONPATH_INFINITE_NUMBER_STRING)},
- };
- for (const auto& testCase : testCases) {
- for (const auto mode : ALL_MODES) {
- RunRuntimeErrorTestCase(testCase.Json, mode + testCase.JsonPath, testCase.Error);
- }
- }
- }
- void TestLogicalOperations() {
- const TVector<TMultiOutputTestCase> testCases = {
- // JsonPath does not allow to use boolean literals in boolean operators.
- // Here we use their replacements:
- // 1. "(1 < true)" for "null"
- // 2. "(true == true)" for "true"
- // 3. "(true != true)" for "false"
- {"1", "(1 < true) || (1 < true)", {"null"}},
- {"1", "(1 < true) || (true != true)", {"null"}},
- {"1", "(1 < true) || (true == true)", {"true"}},
- {"1", "(true != true) || (1 < true)", {"null"}},
- {"1", "(true != true) || (true != true)", {"false"}},
- {"1", "(true != true) || (true == true)", {"true"}},
- {"1", "(true == true) || (1 < true)", {"true"}},
- {"1", "(true == true) || (true != true)", {"true"}},
- {"1", "(true == true) || (true == true)", {"true"}},
- {"1", "(1 < true) && (1 < true)", {"null"}},
- {"1", "(1 < true) && (true != true)", {"false"}},
- {"1", "(1 < true) && (true == true)", {"null"}},
- {"1", "(true != true) && (1 < true)", {"false"}},
- {"1", "(true != true) && (true != true)", {"false"}},
- {"1", "(true != true) && (true == true)", {"false"}},
- {"1", "(true == true) && (1 < true)", {"null"}},
- {"1", "(true == true) && (true != true)", {"false"}},
- {"1", "(true == true) && (true == true)", {"true"}},
- {"1", "(true != true) && (true != true) || (true == true)", {"true"}},
- {"1", "(true != true) && ((true != true) || (true == true))", {"false"}},
- {"1", "(true != true) || (true != true) || (true == true)", {"true"}},
- {"1", "(true == true) && (true == true) && (true == true) && (true != true)", {"false"}},
- {"1", "!(1 < true)", {"null"}},
- {"1", "!(true != true)", {"true"}},
- {"1", "!(true == true)", {"false"}},
- };
- for (const auto& testCase : testCases) {
- for (const auto mode : ALL_MODES) {
- RunTestCase(testCase.Json, mode + testCase.JsonPath, testCase.Result);
- }
- }
- }
- void TestCompareOperations() {
- const TVector<TString> operations = {"==", "<", "<=", ">", ">=", "!=", "<>"};
- // All compare operations between null and non-null operands are false
- for (const auto& op : operations) {
- RunTestCase("1", TStringBuilder() << "null " << op << " 1", {"false"});
- RunTestCase("1", TStringBuilder() << "1 " << op << " null", {"false"});
- }
- // If one of the operands is not scalar, comparison results to null
- for (const auto& op : operations) {
- RunTestCase("[[]]", TStringBuilder() << "$ " << op << " 1", {"null"});
- RunTestCase("[[]]", TStringBuilder() << "1 " << op << " $", {"null"});
- RunTestCase("[[]]", TStringBuilder() << "$ " << op << " $", {"null"});
- RunTestCase("{}", TStringBuilder() << "$ " << op << " 1", {"null"});
- RunTestCase("{}", TStringBuilder() << "1 " << op << " $", {"null"});
- RunTestCase("{}", TStringBuilder() << "$ " << op << " $", {"null"});
- }
- // If both operands are null, only == is true
- for (const auto& op : operations) {
- const TString result = op == "==" ? "true" : "false";
- RunTestCase("1", TStringBuilder() << "null " << op << " null", {result});
- }
- const TVector<TMultiOutputTestCase> testCases = {
- // Check comparison of numbers
- {"1", "1.23 < 4.56", {"true"}},
- {"1", "1.23 > 4.56", {"false"}},
- {"1", "1.23 <= 4.56", {"true"}},
- {"1", "1.23 >= 4.56", {"false"}},
- {"1", "1.23 == 1.23", {"true"}},
- {"1", "1.23 != 1.23", {"false"}},
- {"1", "1.23 <> 4.56", {"true"}},
- {"1", "1.00000000000000000001 == 1.00000000000000000002", {"true"}},
- // Check numbers of different kinds (int64 vs double)
- {"1", "1 < 2.33", {"true"}},
- {"1", "1 > 4.56", {"false"}},
- {"1", "1 <= 4.56", {"true"}},
- {"1", "1 >= 4.56", {"false"}},
- {"1", "1 == 1.23", {"false"}},
- {"1", "1 != 1.23", {"true"}},
- {"1", "1 <> 4.56", {"true"}},
- // Check comparison of strings
- {"1", R"("abc" < "def")", {"true"}},
- {"1", R"("abc" > "def")", {"false"}},
- {"1", R"("abc" <= "def")", {"true"}},
- {"1", R"("abc" >= "def")", {"false"}},
- {"1", R"("abc" == "abc")", {"true"}},
- {"1", R"("abc" != "abc")", {"false"}},
- {"1", R"("abc" <> "def")", {"true"}},
- // Check comparison of UTF8 strings
- // First string is U+00e9 (LATIN SMALL LETTER E WITH ACUTE), "é"
- // Second string is U+0065 (LATIN SMALL LETTER E) U+0301 (COMBINING ACUTE ACCENT), "é"
- {"1", R"("é" < "é")", {"false"}},
- {"1", R"("é" > "é")", {"true"}},
- {"1", R"("привет" == "привет")", {"true"}},
- // Check cross-product comparison
- {R"({
- "left": [1],
- "right": [4, 5, 6]
- })", "$.left[*] < $.right[*]", {"true"}},
- {R"({
- "left": [4, 5, 6],
- "right": [1]
- })", "$.left[*] < $.right[*]", {"false"}},
- {R"({
- "left": [1, 2, 3],
- "right": [4, 5, 6]
- })", "$.left[*] < $.right[*]", {"true"}},
- {R"({
- "left": [10, 30, 40],
- "right": [1, 2, 15]
- })", "$.left[*] < $.right[*]", {"true"}},
- {R"({
- "left": [10, 30, 40],
- "right": [1, 2, 3]
- })", "$.left[*] < $.right[*]", {"false"}},
- // Check incomparable types
- {"1", "1 < true", {"null"}},
- {"1", R"(true <> "def")", {"null"}},
- // Check error in arguments
- {R"({
- "array": [1, 2, 3, 4, 5],
- "invalid_index": {
- "key": 1
- }
- })", "$.array[$.invalid_index] < 3", {"null"}},
- {R"({
- "array": [1, 2, 3, 4, 5],
- "invalid_index": {
- "key": 1
- }
- })", "5 >= $.array[$.invalid_index]", {"null"}},
- };
- for (const auto& testCase : testCases) {
- for (const auto mode : ALL_MODES) {
- RunTestCase(testCase.Json, mode + testCase.JsonPath, testCase.Result);
- }
- }
- }
- void TestFilter() {
- const TVector<TMultiOutputTestCase> testCases = {
- {"[1, 2, 3]", "$[*] ? (@ > 2)", {"3"}},
- {R"([
- {"age": 18},
- {"age": 25},
- {"age": 50},
- {"age": 5}
- ])", "$[*] ? (@.age >= 18)", {R"({"age":18})", R"({"age":25})", R"({"age":50})"}},
- {R"([
- {"age": 18},
- {"age": 25},
- {"age": 50},
- {"age": 5}
- ])", "$[*] ? (@.age >= 18) ? (@.age <= 30)", {R"({"age":18})", R"({"age":25})"}},
- {R"([
- {"age": 18},
- {"age": 25},
- {"age": 50},
- {"age": 5}
- ])", "$[*] ? (@.age >= 18) ? (@.age <= 30) . age", {"18", "25"}},
- {R"([
- {"age": 18},
- {"age": 25},
- {"age": 50},
- {"age": 5}
- ])", "$[*] ? (@.age >= 18 && @.age <= 30) . age", {"18", "25"}},
- {R"([
- {"age": 18},
- {"age": 25},
- {"age": 50},
- {"age": 5}
- ])", "$[*] ? (@.age >= 18 || @.age <= 30) . age", {"18", "25", "50", "5"}},
- {R"([
- {
- "id": 1,
- "is_valid": false,
- "days_till_doom": 11,
- "age_estimation": 4
- },
- {
- "id": 2,
- "is_valid": true,
- "days_till_doom": 5,
- "age_estimation": 3
- },
- {
- "id": 3,
- "is_valid": true,
- "days_till_doom": 20,
- "age_estimation": 10
- },
- {
- "id": 4,
- "is_valid": true,
- "days_till_doom": 30,
- "age_estimation": 2
- }
- ])", "$[*] ? (@.is_valid == true && @.days_till_doom > 10 && 2 * @.age_estimation <= 12).id", {"4"}},
- };
- for (const auto& testCase : testCases) {
- for (const auto mode : ALL_MODES) {
- RunTestCase(testCase.Json, mode + testCase.JsonPath, testCase.Result);
- }
- }
- }
- void TestFilterInvalid() {
- const TVector<TRuntimeErrorTestCase> testCases = {
- {R"({})", "@", C(TIssuesIds::JSONPATH_FILTER_OBJECT_OUTSIDE_OF_FILTER)},
- };
- for (const auto& testCase : testCases) {
- for (const auto mode : ALL_MODES) {
- RunRuntimeErrorTestCase(testCase.Json, mode + testCase.JsonPath, testCase.Error);
- }
- }
- }
- void TestNumericMethods() {
- const TVector<TMultiOutputTestCase> testCases = {
- {"[-1.23, 4.56, 3, 0]", "$[*].abs()", {"1.23", "4.56", "3", "0"}},
- {"[-1.23, 4.56, 3, 0]", "$[*].floor()", {"-2", "4", "3", "0"}},
- {"[-1.23, 4.56, 3, 0]", "$[*].ceiling()", {"-1", "5", "3", "0"}},
- {"-123.45", "$.ceiling().abs().floor()", {"123"}},
- };
- for (const auto& testCase : testCases) {
- for (const auto mode : ALL_MODES) {
- RunTestCase(testCase.Json, mode + testCase.JsonPath, testCase.Result);
- }
- }
- }
- void TestNumericMethodsErrors() {
- const TVector<TRuntimeErrorTestCase> testCases = {
- {R"(["1", true, null])", "$[*].abs()", C(TIssuesIds::JSONPATH_INVALID_NUMERIC_METHOD_ARGUMENT)},
- {R"(["1", true, null])", "$[*].floor()", C(TIssuesIds::JSONPATH_INVALID_NUMERIC_METHOD_ARGUMENT)},
- {R"(["1", true, null])", "$[*].ceiling()", C(TIssuesIds::JSONPATH_INVALID_NUMERIC_METHOD_ARGUMENT)},
- };
- for (const auto& testCase : testCases) {
- for (const auto mode : ALL_MODES) {
- RunRuntimeErrorTestCase(testCase.Json, mode + testCase.JsonPath, testCase.Error);
- }
- }
- }
- void TestDoubleMethod() {
- const TVector<TMultiOutputTestCase> testCases = {
- {R"([
- "123", "123.4", "0.567", "1234e-1", "567e-3", "123.4e-1",
- "123e3", "123e+3", "1.23e+1", "1.23e1",
- "12e0", "12.3e0", "0", "0.0", "0.0e0"
- ])", "$[*].double()", {
- "123", "123.4", "0.567", "123.4", "0.567", "12.34",
- "123000", "123000", "12.3", "12.3",
- "12", "12.3", "0", "0", "0",
- }},
- {R"("-123.45e1")", "$.double().abs().floor()", {"1234"}},
- };
- for (const auto& testCase : testCases) {
- for (const auto mode : ALL_MODES) {
- RunTestCase(testCase.Json, mode + testCase.JsonPath, testCase.Result);
- }
- }
- }
- void TestDoubleMethodErrors() {
- const TVector<TRuntimeErrorTestCase> testCases = {
- {R"(["1", true, null])", "$[*].double()", C(TIssuesIds::JSONPATH_INVALID_DOUBLE_METHOD_ARGUMENT)},
- {R"("hi stranger")", "$.double()", C(TIssuesIds::JSONPATH_INVALID_NUMBER_STRING)},
- };
- for (const auto& testCase : testCases) {
- for (const auto mode : ALL_MODES) {
- RunRuntimeErrorTestCase(testCase.Json, mode + testCase.JsonPath, testCase.Error);
- }
- }
- }
- void TestTypeMethod() {
- const TVector<TMultiOutputTestCase> testCases = {
- {"null", "$.type()", {"\"null\""}},
- {"true", "$.type()", {"\"boolean\""}},
- {"false", "$.type()", {"\"boolean\""}},
- {"1", "$.type()", {"\"number\""}},
- {"-1", "$.type()", {"\"number\""}},
- {"4.56", "$.type()", {"\"number\""}},
- {"-4.56", "$.type()", {"\"number\""}},
- {"\"some string\"", "$.type()", {"\"string\""}},
- {"[]", "$.type()", {"\"array\""}},
- {"[1, 2, 3, 4]", "$.type()", {"\"array\""}},
- {"{}", "$.type()", {"\"object\""}},
- {"{\"key\": 123}", "$.type()", {"\"object\""}},
- };
- for (const auto& testCase : testCases) {
- for (const auto mode : ALL_MODES) {
- RunTestCase(testCase.Json, mode + testCase.JsonPath, testCase.Result);
- }
- }
- }
- void TestSizeMethod() {
- const TVector<TMultiOutputTestCase> testCases = {
- {"null", "$.size()", {"1"}},
- {"true", "$.size()", {"1"}},
- {"false", "$.size()", {"1"}},
- {"1", "$.size()", {"1"}},
- {"-1", "$.size()", {"1"}},
- {"4.56", "$.size()", {"1"}},
- {"-4.56", "$.size()", {"1"}},
- {"\"some string\"", "$.size()", {"1"}},
- {"[]", "$.size()", {"0"}},
- {"[1, 2, 3, 4]", "$.size()", {"4"}},
- {"{}", "$.size()", {"1"}},
- {"{\"key\": 123}", "$.size()", {"1"}},
- };
- for (const auto& testCase : testCases) {
- for (const auto mode : ALL_MODES) {
- RunTestCase(testCase.Json, mode + testCase.JsonPath, testCase.Result);
- }
- }
- }
- void TestKeyValueMethod() {
- const TVector<TMultiOutputTestCase> testCases = {
- {R"({
- "one": 1,
- "two": 2,
- "three": 3
- })", "$.keyvalue()", {
- R"({"name":"one","value":1})",
- R"({"name":"three","value":3})",
- R"({"name":"two","value":2})",
- }},
- {R"({
- "one": "string",
- "two": [1, 2, 3, 4],
- "three": [4, 5]
- })", R"($.keyvalue() ? (@.value.type() == "array" && @.value.size() > 2).name)", {"\"two\""}},
- };
- for (const auto& testCase : testCases) {
- for (const auto mode : ALL_MODES) {
- RunTestCase(testCase.Json, mode + testCase.JsonPath, testCase.Result);
- }
- }
- }
- void TestKeyValueMethodErrors() {
- const TVector<TRuntimeErrorTestCase> testCases = {
- {"\"string\"", "$.keyvalue()", C(TIssuesIds::JSONPATH_INVALID_KEYVALUE_METHOD_ARGUMENT)},
- {"[1, 2, 3, 4]", "$.keyvalue()", C(TIssuesIds::JSONPATH_INVALID_KEYVALUE_METHOD_ARGUMENT)},
- };
- for (const auto& testCase : testCases) {
- for (const auto mode : ALL_MODES) {
- RunRuntimeErrorTestCase(testCase.Json, mode + testCase.JsonPath, testCase.Error);
- }
- }
- }
- void TestStartsWithPredicate() {
- const TVector<TMultiOutputTestCase> testCases = {
- {"1", R"("some string" starts with "some")", {"true"}},
- {"1", R"("some string" starts with "string")", {"false"}},
- {R"(["some string", "string"])", R"($[*] ? (@ starts with "string"))", {"\"string\""}},
- };
- for (const auto& testCase : testCases) {
- for (const auto mode : ALL_MODES) {
- RunTestCase(testCase.Json, mode + testCase.JsonPath, testCase.Result);
- }
- }
- }
- void TestStartsWithPredicateErrors() {
- const TVector<TRuntimeErrorTestCase> testCases = {
- {R"(["first", "second"])", R"($[*] starts with "first")", C(TIssuesIds::JSONPATH_INVALID_STARTS_WITH_ARGUMENT)},
- {"1", R"(1 starts with "string")", C(TIssuesIds::JSONPATH_INVALID_STARTS_WITH_ARGUMENT)},
- };
- for (const auto& testCase : testCases) {
- for (const auto mode : ALL_MODES) {
- RunRuntimeErrorTestCase(testCase.Json, mode + testCase.JsonPath, testCase.Error);
- }
- }
- }
- void TestExistsPredicate() {
- const TVector<TMultiOutputTestCase> testCases = {
- {R"({
- "key": 123
- })", "exists ($.key)", {"true"}},
- {"\"string\"", "exists ($ * 2)", {"null"}},
- {R"(["some string", 2])", "$[*] ? (exists (@ * 2))", {"2"}},
- };
- for (const auto& testCase : testCases) {
- for (const auto mode : ALL_MODES) {
- RunTestCase(testCase.Json, mode + testCase.JsonPath, testCase.Result);
- }
- }
- }
- void TestIsUnknownPredicate() {
- const TVector<TMultiOutputTestCase> testCases = {
- {"1", "(1 < true) is unknown", {"true"}},
- {"1", "(true == true) is unknown", {"false"}},
- {"1", "(true == false) is unknown", {"false"}},
- {R"(["some string", -20])", "$[*] ? ((1 < @) is unknown)", {"\"some string\""}},
- };
- for (const auto& testCase : testCases) {
- for (const auto mode : ALL_MODES) {
- RunTestCase(testCase.Json, mode + testCase.JsonPath, testCase.Result);
- }
- }
- }
- void TestLikeRegexPredicate() {
- const TVector<TMultiOutputTestCase> testCases = {
- {R"(["string", "123", "456"])", R"($[*] like_regex "[0-9]+")", {"true"}},
- {R"(["string", "another string"])", R"($[*] like_regex "[0-9]+")", {"false"}},
- // Case insensitive flag
- {R"("AbCd")", R"($ like_regex "abcd")", {"false"}},
- {R"("AbCd")", R"($ like_regex "abcd" flag "i")", {"true"}},
- {R"(["string", "123", "456"])", R"($[*] ? (@ like_regex "[0-9]+"))", {"\"123\"", "\"456\""}},
- };
- for (const auto& testCase : testCases) {
- for (const auto mode : ALL_MODES) {
- RunTestCase(testCase.Json, mode + testCase.JsonPath, testCase.Result);
- }
- }
- }
- };
- UNIT_TEST_SUITE_REGISTRATION(TJsonPathCommonTest);
|