/* Copyright 2013 Red Hat, Inc. and/or its affiliates. This file is part of lightblue. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see <http://www.gnu.org/licenses/>. */ package com.redhat.lightblue.query; import com.fasterxml.jackson.databind.node.ObjectNode; import com.redhat.lightblue.util.Error; import com.redhat.lightblue.util.JsonUtils; import org.junit.Assert; import org.junit.Test; import org.skyscreamer.jsonassert.JSONAssert; import java.math.BigDecimal; import java.math.BigInteger; import java.util.List; public class QueryParseTest { final String valueQuery1 = "{\"field\":\"x.y.z\", \"op\":\"$eq\", \"rvalue\":\"string\"}"; final String valueQuery2 = "{\"field\":\"x.y.1\", \"op\":\"$gte\", \"rvalue\":1}"; final String valueQuery3 = "{\"field\":\"x.y.1\", \"op\":\"$neq\", \"rvalue\":12345678901234567890}"; final String valueQuery4 = "{\"field\":\"x.y.1\", \"op\":\"$eq\", \"rvalue\":true}"; final String valueQuery5 = "{\"field\":\"x.y.-1.x\", \"op\":\"$neq\", \"rvalue\":12345678901234567890123456789123456789.123456}"; final String fieldQuery1 = "{\"field\":\"x.-1.y\", \"op\":\"$eq\", \"rfield\":\"y.z.-1\"}"; final String fieldQuery2 = "{\"field\":\"x.1.y\", \"op\":\"$neq\", \"rfield\":\"y\"}"; final String naryQuery1 = "{\"field\":\"x.y\", \"op\":\"$in\", \"values\":[1,2,3,4,5]}"; final String naryQuery2 = "{\"field\":\"x.y.x\", \"op\":\"$nin\", \"values\":[\"x\",\"y\",\"z\"]}"; final String naryQuery3 = "{\"field\":\"x.y\", \"op\":\"$in\", \"rfield\":\"z\"}"; final String regexQuery1 = "{\"field\":\"x.y\", \"regex\":\"*pat*\"}"; final String regexQuery2 = "{\"field\":\"x.y\", \"regex\":\"*pat*\",\"caseInsensitive\":true}"; final String regexQuery3 = "{\"field\":\"x.y\", \"regex\":\"*pat*\",\"multiline\":true}"; final String regexQuery4 = "{\"field\":\"x.y\", \"regex\":\"*pat*\",\"extended\":true}"; final String regexQuery5 = "{\"field\":\"x.y\", \"regex\":\"*pat*\",\"dotall\":true}"; final String unaryQuery1 = "{ \"$not\": " + valueQuery1 + "}"; final String unaryQuery2 = "{ \"$not\": " + regexQuery1 + "}"; final String naryLogQueryAnd1 = "{ \"$and\" : [" + valueQuery1 + "," + fieldQuery1 + "," + naryQuery1 + "," + naryQuery3 + "," + unaryQuery1 + "]}"; final String naryLogQueryOr1 = "{ \"$or\" : [" + valueQuery1 + "," + fieldQuery1 + "," + naryQuery1 + "," + naryQuery3 + "," + unaryQuery1 + "]}"; final String arrContains1 = "{\"array\":\"x.y\", \"contains\":\"$any\", \"values\":[1,2,3,4,5]}"; final String arrContains2 = "{\"array\":\"x.y\", \"contains\":\"$any\", \"values\":[\"x\", \"y\"]}"; final String arrContains3 = "{\"array\":\"x.y\", \"contains\":\"$all\", \"values\":[\"x\", \"y\"]}"; final String arrContains4 = "{\"array\":\"x.y\", \"contains\":\"$none\", \"values\":[\"x\", \"y\"]}"; final String arrContains5 = "{\"array\":\"x.y\", \"contains\":\"$invalid\", \"values\":[\"x\", \"y\"]}"; final String arrMatch1 = "{\"array\":\"x.y\",\"elemMatch\":" + regexQuery1 + "}"; interface NestedTest { public void test(QueryExpression x); } @Test public void testValueQueries() throws Exception { testValueComparisonExpression(valueQuery1, "x.y.z", BinaryComparisonOperator._eq, "string"); testValueComparisonExpression(valueQuery2, "x.y.1", BinaryComparisonOperator._gte, new Integer(1)); testValueComparisonExpression(valueQuery3, "x.y.1", BinaryComparisonOperator._neq, new BigInteger("12345678901234567890")); testValueComparisonExpression(valueQuery4, "x.y.1", BinaryComparisonOperator._eq, Boolean.TRUE); testValueComparisonExpression(valueQuery5, "x.y.-1.x", BinaryComparisonOperator._neq, new BigDecimal("12345678901234567890123456789123456789.123456")); } @Test public void testFieldQueries() throws Exception { testFieldComparisonExpression(fieldQuery1, "x.-1.y", BinaryComparisonOperator._eq, "y.z.-1"); testFieldComparisonExpression(fieldQuery2, "x.1.y", BinaryComparisonOperator._neq, "y"); } @Test public void testNaryQueries() throws Exception { testNaryValueRelationalExpression(naryQuery1, "x.y", NaryRelationalOperator._in, 1, 2, 3, 4, 5); testNaryValueRelationalExpression(naryQuery2, "x.y.x", NaryRelationalOperator._not_in, "x", "y", "z"); testNaryFieldRelationalExpression(naryQuery3, "x.y", NaryRelationalOperator._in, "z"); } @Test public void testRegexQueries() throws Exception { testRegexQuery(regexQuery1, "x.y", "*pat*", false, false, false, false); testRegexQuery(regexQuery2, "x.y", "*pat*", true, false, false, false); testRegexQuery(regexQuery3, "x.y", "*pat*", false, true, false, false); testRegexQuery(regexQuery4, "x.y", "*pat*", false, false, true, false); testRegexQuery(regexQuery5, "x.y", "*pat*", false, false, false, true); } @Test public void testUnaries() throws Exception { testUnaryQuery(unaryQuery1, factoryU1NestedTest()); testUnaryQuery(unaryQuery2, new NestedTest() { @Override public void test(QueryExpression x) { asserts((RegexMatchExpression) x, "x.y", "*pat*", false, false, false, false); } }); } @Test public void testNaries() throws Exception { NestedTest[] tests = new NestedTest[]{ new NestedTest() { @Override public void test(QueryExpression x) { asserts((ValueComparisonExpression) x, "x.y.z", BinaryComparisonOperator._eq, "string"); } }, new NestedTest() { @Override public void test(QueryExpression x) { asserts((FieldComparisonExpression) x, "x.-1.y", BinaryComparisonOperator._eq, "y.z.-1"); } }, new NestedTest() { @Override public void test(QueryExpression x) { asserts((NaryValueRelationalExpression) x, "x.y", NaryRelationalOperator._in, 1, 2, 3, 4, 5); } }, new NestedTest() { @Override public void test(QueryExpression x) { asserts((NaryFieldRelationalExpression) x, "x.y", NaryRelationalOperator._in, "z"); } }, new NestedTest() { @Override public void test(QueryExpression x) { asserts((UnaryLogicalExpression) x, factoryU1NestedTest()); } }}; testNaryQuery(naryLogQueryAnd1, tests); testNaryQuery(naryLogQueryOr1, tests); } @Test public void testArrContains() throws Exception { testArrContains(arrContains1, "x.y", ContainsOperator._any, 1, 2, 3, 4, 5); testArrContains(arrContains2, "x.y", ContainsOperator._any, "x", "y"); testArrContains(arrContains3, "x.y", ContainsOperator._all, "x", "y"); testArrContains(arrContains4, "x.y", ContainsOperator._none, "x", "y"); try { testArrContains(arrContains5, "x.y", null); Assert.fail("invalid contains operator should fail"); } catch (Throwable t) { // valid } } @Test public void testArrMatch() throws Exception { testArrMatch(arrMatch1, "x.y", new NestedTest() { @Override public void test(QueryExpression x) { asserts((RegexMatchExpression) x, "x.y", "*pat*", false, false, false, false); } }); } /** * Check the behavior of RegexMatchExpression in case of bad formated input. * In this scenario, 'regex' is missing, expecting the system to raise a * Error Exception * * PS1:RegexMatchExpression (and other classes that extends QueryExpression) * hide their fromJson (shadowing), due it is static method and it can't * override (so this can lead to a problem when someone expects to rely in * the polymorphism using instance). But the shadowing methods use to change * the return type and the method's parameter type as well. PS2:The Error * exception is not reachable so far because the previous validations made * in the chain of fromJson calls , maybe removed in a future. * * @throws Exception */ @Test public void testRegexMatchExpressionFromJsonMethodExecptionField() throws Exception { String withoutFieldString = "{\"missing\":\"x.y\",\"regex\":\"*pat*\",\"dotall\":true}"; abstractTestRegexMatchExpressionFromJsonMethodExecption(withoutFieldString); } /** * Check the behavior of RegexMatchExpression in case of bad formated input. * In this scenario, 'regex' is missing, expecting the system to raise a * Error Exception * * PS1:RegexMatchExpression (and other classes that extends QueryExpression) * hide their fromJson (shadowing), due it is static method and it can't * override (so this can lead to a problem when someone expects to rely in * the polymorphism using instance). But the shadowing methods use to change * the return type and the method's parameter type as well. PS2:The Error * exception is not reachable so far because the previous validations made * in the chain of fromJson calls , maybe removed in a future. * * @throws Exception */ @Test public void testRegexMatchExpressionFromJsonMethodExecptionRegex() throws Exception { String withoutRegexString = "{\"field\":\"x.y\",\"missing\":\"*pat*\",\"dotall\":true}"; abstractTestRegexMatchExpressionFromJsonMethodExecption(withoutRegexString); } private void abstractTestRegexMatchExpressionFromJsonMethodExecption(String badInput) throws Exception { ObjectNode node = (ObjectNode) JsonUtils.json(badInput); /* I would use the ExpectedException but due the com.fasterxml.jackson.databind.ObjectNode implementation, the json returned from toString() uses some differnt enconding characters which would make very difficult to maintain the test, so I will just check the contents of the Error instance. Also the badInput need to be kind of trim() */ try { RegexMatchExpression query = RegexMatchExpression.fromJson(node); Assert.assertNull("Invocation must throw an execption", query); } catch (Error e) { Assert.assertEquals("query-api:InvalidRegexExpression", e.getErrorCode()); Assert.assertEquals(badInput, e.getMsg()); } } @Test public void testArrayContainsExpressionFromJsonMethodExecptionArray() throws Exception { String str = "{\"missing\":\"x.y\",\"contains\":\"$invalid\",\"values\":[\"x\",\"y\"]}"; abstractTestArrayContainsExpressionFromJsonMethodExecption(str); } @Test public void testArrayContainsExpressionFromJsonMethodExecptionContains() throws Exception { String str = "{\"array\":\"x.y\",\"missing\":\"$invalid\",\"values\":[\"x\",\"y\"]}"; abstractTestArrayContainsExpressionFromJsonMethodExecption(str); } @Test public void testArrayContainsExpressionFromJsonMethodExecptionValues() throws Exception { String str = "{\"array\":\"x.y\",\"contains\":\"$invalid\",\"missing\":[\"x\",\"y\"]}"; abstractTestArrayContainsExpressionFromJsonMethodExecption(str); } private void abstractTestArrayContainsExpressionFromJsonMethodExecption(String badInput) throws Exception { ObjectNode node = (ObjectNode) JsonUtils.json(badInput); try { ArrayContainsExpression query = ArrayContainsExpression.fromJson(node); Assert.assertNull("Invocation must throw an execption", query); } catch (Error e) { Assert.assertEquals("query-api:InvalidArrayComparisonExpression", e.getErrorCode()); Assert.assertEquals(badInput, e.getMsg()); } } private void testValueComparisonExpression(String q, String field, BinaryComparisonOperator op, Object value) throws Exception { QueryExpression query = QueryExpression.fromJson(JsonUtils.json(q)); Assert.assertTrue(query instanceof ValueComparisonExpression); asserts((ValueComparisonExpression) query, field, op, value); JSONAssert.assertEquals(q, QueryExpression.fromJson(JsonUtils.json(q)).toString(), false); } private static void asserts(ValueComparisonExpression x, String field, BinaryComparisonOperator op, Object value) { Assert.assertEquals(field, x.getField().toString()); Assert.assertEquals(op, x.getOp()); Assert.assertTrue(value.getClass().equals(x.getRvalue().getValue().getClass())); Assert.assertEquals(value.toString(), x.getRvalue().getValue().toString()); } private void testFieldComparisonExpression(String q, String field, BinaryComparisonOperator op, String rfield) throws Exception { QueryExpression query = QueryExpression.fromJson(JsonUtils.json(q)); Assert.assertTrue(query instanceof FieldComparisonExpression); asserts((FieldComparisonExpression) query, field, op, rfield); JSONAssert.assertEquals(q, QueryExpression.fromJson(JsonUtils.json(q)).toString(), false); } private void asserts(FieldComparisonExpression x, String field, BinaryComparisonOperator op, String rfield) { Assert.assertEquals(field, x.getField().toString()); Assert.assertEquals(op, x.getOp()); Assert.assertEquals(rfield, x.getRfield().toString()); } private void testNaryValueRelationalExpression(String q, String field, NaryRelationalOperator op, Object... value) throws Exception { QueryExpression query = QueryExpression.fromJson(JsonUtils.json(q)); Assert.assertTrue(query instanceof NaryValueRelationalExpression); asserts((NaryValueRelationalExpression) query, field, op, value); JSONAssert.assertEquals(q, QueryExpression.fromJson(JsonUtils.json(q)).toString(), false); } private void testNaryFieldRelationalExpression(String q, String field, NaryRelationalOperator op, String rfield) throws Exception { QueryExpression query = QueryExpression.fromJson(JsonUtils.json(q)); Assert.assertTrue(query instanceof NaryFieldRelationalExpression); asserts((NaryFieldRelationalExpression) query, field, op, rfield); JSONAssert.assertEquals(q, QueryExpression.fromJson(JsonUtils.json(q)).toString(), false); } private static void asserts(NaryValueRelationalExpression x, String field, NaryRelationalOperator op, Object... value) { Assert.assertEquals(field, x.getField().toString()); Assert.assertEquals(op, x.getOp()); Assert.assertEquals(value.length, x.getValues().size()); for (int i = 0; i < value.length; i++) { Assert.assertEquals(value[i].getClass(), x.getValues().get(i).getValue().getClass()); } } private static void asserts(NaryFieldRelationalExpression x, String field, NaryRelationalOperator op, String rfield) { Assert.assertEquals(field, x.getField().toString()); Assert.assertEquals(op, x.getOp()); Assert.assertEquals(rfield, x.getRfield().toString()); } private void testRegexQuery(String q, String field, String regex, boolean c, boolean m, boolean x, boolean d) throws Exception { QueryExpression query = QueryExpression.fromJson(JsonUtils.json(q)); Assert.assertTrue(query instanceof RegexMatchExpression); RegexMatchExpression mx = (RegexMatchExpression) query; asserts(mx, field, regex, c, m, x, d); JSONAssert.assertEquals(q, QueryExpression.fromJson(JsonUtils.json(q)).toString(), false); } private static void asserts(RegexMatchExpression x, String field, String regex, boolean c, boolean m, boolean ox, boolean d) { Assert.assertEquals(field, x.getField().toString()); Assert.assertEquals(regex, x.getRegex()); Assert.assertEquals(c, x.isCaseInsensitive()); Assert.assertEquals(m, x.isMultiline()); Assert.assertEquals(ox, x.isExtended()); Assert.assertEquals(d, x.isDotAll()); } private void testUnaryQuery(String q, NestedTest t) throws Exception { QueryExpression query = QueryExpression.fromJson(JsonUtils.json(q)); Assert.assertTrue(query instanceof UnaryLogicalExpression); JSONAssert.assertEquals(q, QueryExpression.fromJson(JsonUtils.json(q)).toString(), false); } private static void asserts(UnaryLogicalExpression x, NestedTest t) { t.test(x.getQuery()); } private void testNaryQuery(String q, NestedTest... t) throws Exception { QueryExpression query = QueryExpression.fromJson(JsonUtils.json(q)); Assert.assertTrue(query instanceof NaryLogicalExpression); NaryLogicalExpression x = (NaryLogicalExpression) query; List<QueryExpression> queries = x.getQueries(); Assert.assertEquals(t.length, queries.size()); for (int i = 0; i < t.length; i++) { t[i].test(queries.get(i)); } JSONAssert.assertEquals(q, QueryExpression.fromJson(JsonUtils.json(q)).toString(), false); } private void testArrContains(String q, String field, ContainsOperator op, Object... value) throws Exception { QueryExpression query = QueryExpression.fromJson(JsonUtils.json(q)); Assert.assertTrue(query instanceof ArrayContainsExpression); asserts((ArrayContainsExpression) query, field, op, value); JSONAssert.assertEquals(q, QueryExpression.fromJson(JsonUtils.json(q)).toString(), false); } private void asserts(ArrayContainsExpression x, String field, ContainsOperator op, Object... value) { Assert.assertEquals(field, x.getArray().toString()); Assert.assertEquals(op, x.getOp()); Assert.assertEquals(value.length, x.getValues().size()); for (int i = 0; i < value.length; i++) { Assert.assertEquals(value[i].getClass(), x.getValues().get(i).getValue().getClass()); Assert.assertEquals(value[i].toString(), x.getValues().get(i).getValue().toString()); } } private void testArrMatch(String q, String field, NestedTest t) throws Exception { QueryExpression query = QueryExpression.fromJson(JsonUtils.json(q)); Assert.assertTrue(query instanceof ArrayMatchExpression); asserts((ArrayMatchExpression) query, field, t); JSONAssert.assertEquals(q, QueryExpression.fromJson(JsonUtils.json(q)).toString(), false); } private void asserts(ArrayMatchExpression x, String field, NestedTest t) { Assert.assertEquals(field, x.getArray().toString()); t.test(x.getElemMatch()); } private NestedTest factoryU1NestedTest() { return new NestedTest() { public void test(QueryExpression x) { asserts((ValueComparisonExpression) x, "x.y.z", BinaryComparisonOperator._eq, "string"); } }; } }