From f311c7af88366cf4b90892928af7995f624f5f9a Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Tue, 21 Mar 2023 15:02:07 -0500 Subject: [PATCH] Build out all query operators --- .../utils/BackendQueryFilterUtils.java | 30 +++- .../utils/BackendQueryFilterUtilsTest.java | 92 ++++++++++ .../qqq/api/javalin/QJavalinApiHandler.java | 124 ++++++++++++-- .../api/javalin/QJavalinApiHandlerTest.java | 158 +++++++++++++++++- 4 files changed, 380 insertions(+), 24 deletions(-) create mode 100644 qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/utils/BackendQueryFilterUtilsTest.java diff --git a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/utils/BackendQueryFilterUtils.java b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/utils/BackendQueryFilterUtils.java index 87cab255..ea2ea9eb 100644 --- a/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/utils/BackendQueryFilterUtils.java +++ b/qqq-backend-core/src/main/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/utils/BackendQueryFilterUtils.java @@ -23,8 +23,8 @@ package com.kingsrook.qqq.backend.core.modules.backend.implementations.utils; import java.io.Serializable; +import java.math.BigDecimal; import java.time.LocalDate; -import java.util.ArrayList; import java.util.List; import java.util.Objects; import java.util.concurrent.atomic.AtomicBoolean; @@ -35,6 +35,7 @@ import com.kingsrook.qqq.backend.core.model.actions.tables.query.QueryInput; import com.kingsrook.qqq.backend.core.model.data.QRecord; import com.kingsrook.qqq.backend.core.utils.CollectionUtils; import com.kingsrook.qqq.backend.core.utils.ValueUtils; +import com.kingsrook.qqq.backend.core.utils.collections.MutableList; import org.apache.commons.lang.NotImplementedException; @@ -133,16 +134,17 @@ public class BackendQueryFilterUtils case BETWEEN -> { QFilterCriteria criteria0 = new QFilterCriteria().withValues(criterion.getValues()); - QFilterCriteria criteria1 = new QFilterCriteria().withValues(new ArrayList<>(criterion.getValues())); + QFilterCriteria criteria1 = new QFilterCriteria().withValues(new MutableList<>(criterion.getValues())); criteria1.getValues().remove(0); yield (testGreaterThan(criteria0, value) || testEquals(criteria0, value)) && (!testGreaterThan(criteria1, value) || testEquals(criteria1, value)); } case NOT_BETWEEN -> { QFilterCriteria criteria0 = new QFilterCriteria().withValues(criterion.getValues()); - QFilterCriteria criteria1 = new QFilterCriteria().withValues(criterion.getValues()); + QFilterCriteria criteria1 = new QFilterCriteria().withValues(new MutableList<>(criterion.getValues())); criteria1.getValues().remove(0); - yield !(testGreaterThan(criteria0, value) || testEquals(criteria0, value)) && (!testGreaterThan(criteria1, value) || testEquals(criteria1, value)); + boolean between = (testGreaterThan(criteria0, value) || testEquals(criteria0, value)) && (!testGreaterThan(criteria1, value) || testEquals(criteria1, value)); + yield !between; } }; return criterionMatches; @@ -275,6 +277,26 @@ public class BackendQueryFilterUtils return (valueDate.isAfter(criterionDate)); } + try + { + if(a instanceof Number numberA && b instanceof String stringB) + { + BigDecimal bdA = ValueUtils.getValueAsBigDecimal(numberA); + BigDecimal bdB = ValueUtils.getValueAsBigDecimal(stringB); + return (bdA.doubleValue() < bdB.doubleValue()); + } + else if(a instanceof String stringA && b instanceof Number numberB) + { + BigDecimal bdA = ValueUtils.getValueAsBigDecimal(stringA); + BigDecimal bdB = ValueUtils.getValueAsBigDecimal(numberB); + return (bdA.doubleValue() < bdB.doubleValue()); + } + } + catch(Exception e) + { + // ignore... + } + throw (new NotImplementedException("Greater/Less Than comparisons are not (yet?) implemented for the supplied types [" + b.getClass().getSimpleName() + "][" + a.getClass().getSimpleName() + "]")); } diff --git a/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/utils/BackendQueryFilterUtilsTest.java b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/utils/BackendQueryFilterUtilsTest.java new file mode 100644 index 00000000..765a9faf --- /dev/null +++ b/qqq-backend-core/src/test/java/com/kingsrook/qqq/backend/core/modules/backend/implementations/utils/BackendQueryFilterUtilsTest.java @@ -0,0 +1,92 @@ +/* + * QQQ - Low-code Application Framework for Engineers. + * Copyright (C) 2021-2023. Kingsrook, LLC + * 651 N Broad St Ste 205 # 6917 | Middletown DE 19709 | United States + * contact@kingsrook.com + * https://github.com/Kingsrook/ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero 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 Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package com.kingsrook.qqq.backend.core.modules.backend.implementations.utils; + + +import java.util.List; +import com.kingsrook.qqq.backend.core.model.actions.tables.query.QCriteriaOperator; +import com.kingsrook.qqq.backend.core.model.actions.tables.query.QFilterCriteria; +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + + +/******************************************************************************* + ** Unit test for BackendQueryFilterUtils + *******************************************************************************/ +class BackendQueryFilterUtilsTest +{ + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testDoesCriterionMatch() + { + ////////////////////////////////////////////// + // < and > w/ mix of numbers and strings... // + ////////////////////////////////////////////// + assertTrue(BackendQueryFilterUtils.doesCriteriaMatch(new QFilterCriteria("f", QCriteriaOperator.GREATER_THAN, 1), "f", "2")); + assertTrue(BackendQueryFilterUtils.doesCriteriaMatch(new QFilterCriteria("f", QCriteriaOperator.GREATER_THAN, "1"), "f", 2)); + assertFalse(BackendQueryFilterUtils.doesCriteriaMatch(new QFilterCriteria("f", QCriteriaOperator.GREATER_THAN, 1), "f", "1")); + assertFalse(BackendQueryFilterUtils.doesCriteriaMatch(new QFilterCriteria("f", QCriteriaOperator.GREATER_THAN, "1"), "f", 1)); + assertFalse(BackendQueryFilterUtils.doesCriteriaMatch(new QFilterCriteria("f", QCriteriaOperator.GREATER_THAN, 1), "f", "0")); + assertFalse(BackendQueryFilterUtils.doesCriteriaMatch(new QFilterCriteria("f", QCriteriaOperator.GREATER_THAN, "1"), "f", 0)); + + assertTrue(BackendQueryFilterUtils.doesCriteriaMatch(new QFilterCriteria("f", QCriteriaOperator.GREATER_THAN_OR_EQUALS, 1), "f", "2")); + assertTrue(BackendQueryFilterUtils.doesCriteriaMatch(new QFilterCriteria("f", QCriteriaOperator.GREATER_THAN_OR_EQUALS, "1"), "f", 2)); + assertTrue(BackendQueryFilterUtils.doesCriteriaMatch(new QFilterCriteria("f", QCriteriaOperator.GREATER_THAN_OR_EQUALS, 1), "f", "1")); + assertTrue(BackendQueryFilterUtils.doesCriteriaMatch(new QFilterCriteria("f", QCriteriaOperator.GREATER_THAN_OR_EQUALS, "1"), "f", 1)); + assertFalse(BackendQueryFilterUtils.doesCriteriaMatch(new QFilterCriteria("f", QCriteriaOperator.GREATER_THAN_OR_EQUALS, 1), "f", "0")); + assertFalse(BackendQueryFilterUtils.doesCriteriaMatch(new QFilterCriteria("f", QCriteriaOperator.GREATER_THAN_OR_EQUALS, "1"), "f", 0)); + + assertTrue(BackendQueryFilterUtils.doesCriteriaMatch(new QFilterCriteria("f", QCriteriaOperator.LESS_THAN, 2), "f", "1")); + assertTrue(BackendQueryFilterUtils.doesCriteriaMatch(new QFilterCriteria("f", QCriteriaOperator.LESS_THAN, "2"), "f", 1)); + assertFalse(BackendQueryFilterUtils.doesCriteriaMatch(new QFilterCriteria("f", QCriteriaOperator.LESS_THAN, 1), "f", "1")); + assertFalse(BackendQueryFilterUtils.doesCriteriaMatch(new QFilterCriteria("f", QCriteriaOperator.LESS_THAN, "1"), "f", 1)); + assertFalse(BackendQueryFilterUtils.doesCriteriaMatch(new QFilterCriteria("f", QCriteriaOperator.LESS_THAN, 0), "f", "1")); + assertFalse(BackendQueryFilterUtils.doesCriteriaMatch(new QFilterCriteria("f", QCriteriaOperator.LESS_THAN, "0"), "f", 1)); + + assertTrue(BackendQueryFilterUtils.doesCriteriaMatch(new QFilterCriteria("f", QCriteriaOperator.LESS_THAN_OR_EQUALS, 2), "f", "1")); + assertTrue(BackendQueryFilterUtils.doesCriteriaMatch(new QFilterCriteria("f", QCriteriaOperator.LESS_THAN_OR_EQUALS, "2"), "f", 1)); + assertTrue(BackendQueryFilterUtils.doesCriteriaMatch(new QFilterCriteria("f", QCriteriaOperator.LESS_THAN_OR_EQUALS, 1), "f", "1")); + assertTrue(BackendQueryFilterUtils.doesCriteriaMatch(new QFilterCriteria("f", QCriteriaOperator.LESS_THAN_OR_EQUALS, "1"), "f", 1)); + assertFalse(BackendQueryFilterUtils.doesCriteriaMatch(new QFilterCriteria("f", QCriteriaOperator.LESS_THAN_OR_EQUALS, 0), "f", "1")); + assertFalse(BackendQueryFilterUtils.doesCriteriaMatch(new QFilterCriteria("f", QCriteriaOperator.LESS_THAN_OR_EQUALS, "0"), "f", 1)); + + ///////////////////////////// + // between and not-between // + ///////////////////////////// + assertFalse(BackendQueryFilterUtils.doesCriteriaMatch(new QFilterCriteria("f", QCriteriaOperator.BETWEEN, List.of(1, 3)), "f", 0)); + assertTrue(BackendQueryFilterUtils.doesCriteriaMatch(new QFilterCriteria("f", QCriteriaOperator.BETWEEN, List.of(1, 3)), "f", 1)); + assertTrue(BackendQueryFilterUtils.doesCriteriaMatch(new QFilterCriteria("f", QCriteriaOperator.BETWEEN, List.of(1, 3)), "f", 2)); + assertTrue(BackendQueryFilterUtils.doesCriteriaMatch(new QFilterCriteria("f", QCriteriaOperator.BETWEEN, List.of(1, 3)), "f", 3)); + assertFalse(BackendQueryFilterUtils.doesCriteriaMatch(new QFilterCriteria("f", QCriteriaOperator.BETWEEN, List.of(1, 3)), "f", 4)); + + assertTrue(BackendQueryFilterUtils.doesCriteriaMatch(new QFilterCriteria("f", QCriteriaOperator.NOT_BETWEEN, List.of(1, 3)), "f", 0)); + assertFalse(BackendQueryFilterUtils.doesCriteriaMatch(new QFilterCriteria("f", QCriteriaOperator.NOT_BETWEEN, List.of(1, 3)), "f", 1)); + assertFalse(BackendQueryFilterUtils.doesCriteriaMatch(new QFilterCriteria("f", QCriteriaOperator.NOT_BETWEEN, List.of(1, 3)), "f", 2)); + assertFalse(BackendQueryFilterUtils.doesCriteriaMatch(new QFilterCriteria("f", QCriteriaOperator.NOT_BETWEEN, List.of(1, 3)), "f", 3)); + assertTrue(BackendQueryFilterUtils.doesCriteriaMatch(new QFilterCriteria("f", QCriteriaOperator.NOT_BETWEEN, List.of(1, 3)), "f", 4)); + } + +} \ No newline at end of file diff --git a/qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/javalin/QJavalinApiHandler.java b/qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/javalin/QJavalinApiHandler.java index e77aeba7..55c85e0d 100644 --- a/qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/javalin/QJavalinApiHandler.java +++ b/qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/javalin/QJavalinApiHandler.java @@ -24,6 +24,7 @@ package com.kingsrook.qqq.api.javalin; import java.io.Serializable; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; @@ -70,6 +71,7 @@ import com.kingsrook.qqq.backend.core.utils.ExceptionUtils; import com.kingsrook.qqq.backend.core.utils.JsonUtils; import com.kingsrook.qqq.backend.core.utils.StringUtils; import com.kingsrook.qqq.backend.core.utils.ValueUtils; +import com.kingsrook.qqq.backend.core.utils.collections.ListBuilder; import com.kingsrook.qqq.backend.javalin.QJavalinAccessLogger; import com.kingsrook.qqq.backend.javalin.QJavalinImplementation; import io.javalin.apibuilder.ApiBuilder; @@ -119,7 +121,7 @@ public class QJavalinApiHandler ApiBuilder.post("/", QJavalinApiHandler::doInsert); ApiBuilder.get("/query", QJavalinApiHandler::doQuery); - ApiBuilder.post("/query", QJavalinApiHandler::doQuery); + // ApiBuilder.post("/query", QJavalinApiHandler::doQuery); ApiBuilder.get("/{primaryKey}", QJavalinApiHandler::doGet); ApiBuilder.patch("/{primaryKey}", QJavalinApiHandler::doUpdate); @@ -318,7 +320,7 @@ public class QJavalinApiHandler } else if("or".equalsIgnoreCase(context.queryParam("booleanOperator"))) { - filter.setBooleanOperator(QQueryFilter.BooleanOperator.AND); + filter.setBooleanOperator(QQueryFilter.BooleanOperator.OR); } else if(StringUtils.hasContent(context.queryParam("booleanOperator"))) { @@ -401,9 +403,14 @@ public class QJavalinApiHandler { if(StringUtils.hasContent(value)) { - QCriteriaOperator operator = getCriteriaOperator(value); - List criteriaValues = getCriteriaValues(field, value); - filter.addCriteria(new QFilterCriteria(name, operator, criteriaValues)); + try + { + filter.addCriteria(parseQueryParamToCriteria(name, value)); + } + catch(Exception e) + { + badRequestMessages.add(e.getMessage()); + } } } } @@ -501,10 +508,42 @@ public class QJavalinApiHandler /******************************************************************************* ** *******************************************************************************/ - private static QCriteriaOperator getCriteriaOperator(String value) + private enum Operator { - // todo - all other operators - return (QCriteriaOperator.EQUALS); + /////////////////////////////////////////////////////////////////////////////////// + // order of these is important (e.g., because some are a sub-string of others!!) // + /////////////////////////////////////////////////////////////////////////////////// + EQ("=", QCriteriaOperator.EQUALS, QCriteriaOperator.NOT_EQUALS, true, 1), + LTE("<=", QCriteriaOperator.LESS_THAN_OR_EQUALS, QCriteriaOperator.GREATER_THAN, false, 1), + GTE(">=", QCriteriaOperator.GREATER_THAN_OR_EQUALS, QCriteriaOperator.LESS_THAN, false, 1), + LT("<", QCriteriaOperator.LESS_THAN, QCriteriaOperator.GREATER_THAN_OR_EQUALS, false, 1), + GT(">", QCriteriaOperator.GREATER_THAN, QCriteriaOperator.LESS_THAN_OR_EQUALS, false, 1), + EMPTY("EMPTY", QCriteriaOperator.IS_BLANK, QCriteriaOperator.IS_NOT_BLANK, true, 0), + BETWEEN("BETWEEN ", QCriteriaOperator.BETWEEN, QCriteriaOperator.NOT_BETWEEN, true, 2), + IN("IN ", QCriteriaOperator.IN, QCriteriaOperator.NOT_IN, true, 2), + // todo MATCHES + ; + + + private final String prefix; + private final QCriteriaOperator positiveOperator; + private final QCriteriaOperator negativeOperator; + private final boolean supportsNot; + private final int noOfValues; // 0 & 1 mean 0 & 1 ... 2 means 1 or more... + + + + /******************************************************************************* + ** + *******************************************************************************/ + Operator(String prefix, QCriteriaOperator positiveOperator, QCriteriaOperator negativeOperator, boolean supportsNot, int noOfValues) + { + this.prefix = prefix; + this.positiveOperator = positiveOperator; + this.negativeOperator = negativeOperator; + this.supportsNot = supportsNot; + this.noOfValues = noOfValues; + } } @@ -512,10 +551,73 @@ public class QJavalinApiHandler /******************************************************************************* ** *******************************************************************************/ - private static List getCriteriaValues(QFieldMetaData field, String value) + private static QFilterCriteria parseQueryParamToCriteria(String name, String value) throws QBadRequestException { - // todo - parse the thing, do stuff - return (List.of(value)); + /////////////////////////////////// + // process & discard a leading ! // + /////////////////////////////////// + boolean isNot = false; + if(value.startsWith("!") && value.length() > 1) + { + isNot = true; + value = value.substring(1); + } + + ////////////////////////// + // look for an operator // + ////////////////////////// + Operator selectedOperator = null; + for(Operator op : Operator.values()) + { + if(value.startsWith(op.prefix)) + { + selectedOperator = op; + if(!selectedOperator.supportsNot && isNot) + { + throw (new QBadRequestException("Unsupported operator: !" + selectedOperator.prefix)); + } + break; + } + } + + ///////////////////////////////////////////////////////////////////////////////////////////// + // if an operator was found, strip it away from the value for figuring out the values part // + ///////////////////////////////////////////////////////////////////////////////////////////// + if(selectedOperator != null) + { + value = value.substring(selectedOperator.prefix.length()); + } + else + { + //////////////////////////////////////////////////////////////// + // else - assume the default operator, and use the full value // + //////////////////////////////////////////////////////////////// + selectedOperator = Operator.EQ; + } + + //////////////////////////////////// + // figure out the criteria values // + // todo - quotes? // + //////////////////////////////////// + List criteriaValues; + if(selectedOperator.noOfValues == 1) + { + criteriaValues = ListBuilder.of(value); + } + else if(selectedOperator.noOfValues == 0) + { + if(StringUtils.hasContent(value)) + { + throw (new QBadRequestException("Unexpected value after operator " + selectedOperator.prefix + " for field " + name)); + } + criteriaValues = null; + } + else + { + criteriaValues = Arrays.asList(value.split(",")); + } + + return (new QFilterCriteria(name, isNot ? selectedOperator.negativeOperator : selectedOperator.positiveOperator, criteriaValues)); } diff --git a/qqq-middleware-api/src/test/java/com/kingsrook/qqq/api/javalin/QJavalinApiHandlerTest.java b/qqq-middleware-api/src/test/java/com/kingsrook/qqq/api/javalin/QJavalinApiHandlerTest.java index 919ba00f..dfc17e82 100644 --- a/qqq-middleware-api/src/test/java/com/kingsrook/qqq/api/javalin/QJavalinApiHandlerTest.java +++ b/qqq-middleware-api/src/test/java/com/kingsrook/qqq/api/javalin/QJavalinApiHandlerTest.java @@ -22,6 +22,7 @@ package com.kingsrook.qqq.api.javalin; +import java.util.ArrayList; import java.util.List; import com.kingsrook.qqq.api.BaseTest; import com.kingsrook.qqq.api.TestUtils; @@ -123,14 +124,14 @@ class QJavalinApiHandlerTest extends BaseTest @Test void testGet200() throws QException { - insertTestRecord(); + insertTestRecord(1, "Homer", "Simpson"); HttpResponse response = Unirest.get(BASE_URL + "/api/" + VERSION + "/person/1").asString(); assertEquals(200, response.getStatus()); JSONObject jsonObject = new JSONObject(response.getBody()); assertEquals(1, jsonObject.getInt("id")); - assertEquals("Darin", jsonObject.getString("firstName")); - assertEquals("Kelkhoff", jsonObject.getString("lastName")); + assertEquals("Homer", jsonObject.getString("firstName")); + assertEquals("Simpson", jsonObject.getString("lastName")); } @@ -138,11 +139,11 @@ class QJavalinApiHandlerTest extends BaseTest /******************************************************************************* ** *******************************************************************************/ - private static void insertTestRecord() throws QException + private static void insertTestRecord(Integer id, String firstName, String lastName) throws QException { InsertInput insertInput = new InsertInput(); insertInput.setTableName(TestUtils.TABLE_NAME_PERSON); - insertInput.setRecords(List.of(new QRecord().withValue("id", 1).withValue("firstName", "Darin").withValue("lastName", "Kelkhoff"))); + insertInput.setRecords(List.of(new QRecord().withValue("id", id).withValue("firstName", firstName).withValue("lastName", lastName))); new InsertAction().execute(insertInput); } @@ -210,7 +211,7 @@ class QJavalinApiHandlerTest extends BaseTest @Test void testQuery200SomethingFound() throws QException { - insertTestRecord(); + insertTestRecord(1, "Homer", "Simpson"); HttpResponse response = Unirest.get(BASE_URL + "/api/" + VERSION + "/person/query").asString(); assertEquals(200, response.getStatus()); @@ -222,8 +223,147 @@ class QJavalinApiHandlerTest extends BaseTest JSONArray jsonArray = jsonObject.getJSONArray("records"); jsonObject = jsonArray.getJSONObject(0); assertEquals(1, jsonObject.getInt("id")); - assertEquals("Darin", jsonObject.getString("firstName")); - assertEquals("Kelkhoff", jsonObject.getString("lastName")); + assertEquals("Homer", jsonObject.getString("firstName")); + assertEquals("Simpson", jsonObject.getString("lastName")); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testQuery200OrQuery() throws QException + { + insertSimpsons(); + assertPersonQueryFindsFirstNames(List.of("Homer", "Marge"), "booleanOperator=OR&firstName=Homer&firstName=Marge&orderBy=firstName"); + assertPersonQueryFindsFirstNames(List.of("Homer", "Marge", "Bart", "Lisa", "Maggie"), "booleanOperator=OR&firstName=!Homer&firstName=!Marge&orderBy=id"); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testQuery200EqualsAndNot() throws QException + { + insertSimpsons(); + assertPersonQueryFindsFirstNames(List.of("Homer"), "firstName=Homer"); // no operator implies = + assertPersonQueryFindsFirstNames(List.of("Homer"), "firstName==Homer"); // == is an explicit equals operator. + assertPersonQueryFindsFirstNames(List.of(), "firstName===Homer"); /// === is = "=Homer" + + assertPersonQueryFindsFirstNames(List.of("Marge", "Bart", "Lisa", "Maggie"), "firstName=!Homer&orderBy=id"); // ! alone implies not-equals + assertPersonQueryFindsFirstNames(List.of("Marge", "Bart", "Lisa", "Maggie"), "firstName=!=Homer&orderBy=id"); // != is explicit not-equals + assertPersonQueryFindsFirstNames(List.of("Homer", "Marge", "Bart", "Lisa", "Maggie"), "firstName=!==Homer&orderBy=id"); // !== is not-equals "=Homer" + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testQuery200In() throws QException + { + insertSimpsons(); + assertPersonQueryFindsFirstNames(List.of("Bart", "Lisa", "Maggie"), "firstName=IN Bart,Lisa,Maggie&orderBy=firstName"); + assertPersonQueryFindsFirstNames(List.of("Homer", "Marge"), "firstName=!IN Bart,Lisa,Maggie&orderBy=firstName"); + assertPersonQueryFindsFirstNames(List.of("Maggie"), "firstName=IN Maggie"); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testQuery200Between() throws QException + { + insertSimpsons(); + assertPersonQueryFindsFirstNames(List.of("Homer", "Marge", "Bart"), "id=BETWEEN 1,3&orderBy=id"); + assertPersonQueryFindsFirstNames(List.of("Homer", "Maggie"), "id=!BETWEEN 2,4&orderBy=id"); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testQuery200Empty() throws QException + { + insertSimpsons(); + assertPersonQueryFindsFirstNames(List.of("Homer", "Marge", "Bart", "Lisa", "Maggie"), "noOfShoes=EMPTY&orderBy=id"); + assertPersonQueryFindsFirstNames(List.of(), "noOfShoes=!EMPTY"); + assertPersonQueryFindsFirstNames(List.of("Homer", "Marge", "Bart", "Lisa", "Maggie"), "id=!EMPTY&orderBy=id"); + assertPersonQueryFindsFirstNames(List.of(), "id=EMPTY"); + + assertError("Unexpected value after operator EMPTY for field id", BASE_URL + "/api/" + VERSION + "/person/query?id=EMPTY 3"); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testQuery200LessThanGreaterThanEquals() throws QException + { + insertSimpsons(); + String GT = "%3E"; + assertPersonQueryFindsFirstNames(List.of("Lisa", "Maggie"), "id=" + GT + "3&orderBy=id"); + assertPersonQueryFindsFirstNames(List.of("Bart", "Lisa", "Maggie"), "id=" + GT + "=3&orderBy=id"); + + String LT = "%3C"; + assertPersonQueryFindsFirstNames(List.of("Homer", "Marge"), "id=" + LT + "3&orderBy=id"); + assertPersonQueryFindsFirstNames(List.of("Homer", "Marge", "Bart"), "id=" + LT + "=3&orderBy=id"); + + assertError("Unsupported operator: !>", BASE_URL + "/api/" + VERSION + "/person/query?id=!" + GT + "3"); + assertError("Unsupported operator: !>=", BASE_URL + "/api/" + VERSION + "/person/query?id=!" + GT + "=3"); + assertError("Unsupported operator: !<", BASE_URL + "/api/" + VERSION + "/person/query?id=!" + LT + "3"); + assertError("Unsupported operator: !<=", BASE_URL + "/api/" + VERSION + "/person/query?id=!" + LT + "=3"); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private static void insertSimpsons() throws QException + { + insertTestRecord(1, "Homer", "Simpson"); + insertTestRecord(2, "Marge", "Simpson"); + insertTestRecord(3, "Bart", "Simpson"); + insertTestRecord(4, "Lisa", "Simpson"); + insertTestRecord(5, "Maggie", "Simpson"); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private void assertPersonQueryFindsFirstNames(List expectedFirstNames, String queryString) + { + HttpResponse response = Unirest.get(BASE_URL + "/api/" + VERSION + "/person/query?" + queryString).asString(); + assertEquals(200, response.getStatus()); + JSONObject jsonObject = new JSONObject(response.getBody()); + assertEquals(expectedFirstNames.size(), jsonObject.getInt("count")); + + if(expectedFirstNames.isEmpty() && !jsonObject.has("records")) + { + return; + } + + JSONArray jsonArray = jsonObject.getJSONArray("records"); + List actualFirstNames = new ArrayList<>(); + for(int i = 0; i < jsonArray.length(); i++) + { + actualFirstNames.add(jsonArray.getJSONObject(i).getString("firstName")); + } + + assertEquals(expectedFirstNames, actualFirstNames); } @@ -234,7 +374,7 @@ class QJavalinApiHandlerTest extends BaseTest @Test void testQuery200ManyParams() { - HttpResponse response = Unirest.get(BASE_URL + "/api/" + VERSION + "/person/query?pageSize=49&pageNo=2&includeCount=true&booleanOperator=AND&firstName=Darin&orderBy=firstName desc").asString(); + HttpResponse response = Unirest.get(BASE_URL + "/api/" + VERSION + "/person/query?pageSize=49&pageNo=2&includeCount=true&booleanOperator=AND&firstName=Homer&orderBy=firstName desc").asString(); assertEquals(200, response.getStatus()); JSONObject jsonObject = new JSONObject(response.getBody()); assertEquals(0, jsonObject.getInt("count"));