From c6fa22524c0d34d6d4d482f3ed7090a0bb6b2cca Mon Sep 17 00:00:00 2001 From: Darin Kelkhoff Date: Tue, 21 Mar 2023 11:50:54 -0500 Subject: [PATCH] Coverage on query --- .../qqq/api/javalin/QJavalinApiHandler.java | 62 +++++---- .../kingsrook/qqq/api/model/APIVersion.java | 33 +++++ .../api/javalin/QJavalinApiHandlerTest.java | 130 +++++++++++++++--- 3 files changed, 186 insertions(+), 39 deletions(-) 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 cb369bf0..e77aeba7 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 @@ -72,7 +72,6 @@ import com.kingsrook.qqq.backend.core.utils.StringUtils; import com.kingsrook.qqq.backend.core.utils.ValueUtils; import com.kingsrook.qqq.backend.javalin.QJavalinAccessLogger; import com.kingsrook.qqq.backend.javalin.QJavalinImplementation; -import com.kingsrook.qqq.backend.javalin.QJavalinUtils; import io.javalin.apibuilder.ApiBuilder; import io.javalin.apibuilder.EndpointGroup; import io.javalin.http.ContentType; @@ -198,16 +197,7 @@ public class QJavalinApiHandler // todo - make sure table is supported in this version QTableMetaData table = qInstance.getTable(tableName); - - if(table == null) - { - throw (new QNotFoundException("Could not find any resources at path " + context.path())); - } - - if(!getApiVersionRange(table).includes(new APIVersion(version))) - { - throw (new QNotFoundException("This version of this API does not contain the resource path " + context.path())); - } + validateTableAndVersion(context, version, table); GetInput getInput = new GetInput(); @@ -270,16 +260,7 @@ public class QJavalinApiHandler // todo - make sure table is supported in this version QTableMetaData table = qInstance.getTable(tableName); - - if(table == null) - { - throw (new QNotFoundException("Could not find any resources at path " + context.path())); - } - - if(!getApiVersionRange(table).includes(new APIVersion(version))) - { - throw (new QNotFoundException("This version of this API does not contain the resource path " + context.path())); - } + validateTableAndVersion(context, version, table); QueryInput queryInput = new QueryInput(); setupSession(context, queryInput); @@ -308,7 +289,18 @@ public class QJavalinApiHandler badRequestMessages.add("pageSize must be between 1 and 1000."); } - Integer pageNo = Objects.requireNonNullElse(QJavalinUtils.integerQueryParam(context, "pageNo"), 1); + Integer pageNo = 1; + if(StringUtils.hasContent(context.queryParam("pageNo"))) + { + try + { + pageNo = ValueUtils.getValueAsInteger(context.queryParam("pageNo")); + } + catch(Exception e) + { + badRequestMessages.add("Could not parse pageNo as an integer"); + } + } if(pageNo < 1) { badRequestMessages.add("pageNo must be greater than 0."); @@ -432,7 +424,7 @@ public class QJavalinApiHandler } else { - throw (new QBadRequestException("Requested failed with " + badRequestMessages.size() + " reasons: " + StringUtils.join(" \n", badRequestMessages))); + throw (new QBadRequestException("Request failed with " + badRequestMessages.size() + " reasons: " + StringUtils.join(" \n", badRequestMessages))); } } @@ -482,6 +474,30 @@ public class QJavalinApiHandler + /******************************************************************************* + ** + *******************************************************************************/ + private static void validateTableAndVersion(Context context, String version, QTableMetaData table) throws QNotFoundException + { + if(table == null) + { + throw (new QNotFoundException("Could not find any resources at path " + context.path())); + } + + APIVersion requestApiVersion = new APIVersion(version); + if(!ApiMiddlewareType.getApiInstanceMetaData(qInstance).getSupportedVersions().contains(requestApiVersion)) + { + throw (new QNotFoundException("This version of this API does not contain the resource path " + context.path())); + } + + if(!getApiVersionRange(table).includes(requestApiVersion)) + { + throw (new QNotFoundException("This version of this API does not contain the resource path " + context.path())); + } + } + + + /******************************************************************************* ** *******************************************************************************/ diff --git a/qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/model/APIVersion.java b/qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/model/APIVersion.java index a7802c0a..0f1c435a 100644 --- a/qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/model/APIVersion.java +++ b/qqq-middleware-api/src/main/java/com/kingsrook/qqq/api/model/APIVersion.java @@ -104,4 +104,37 @@ public class APIVersion implements Comparable { return (version); } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Override + public boolean equals(Object o) + { + if(this == o) + { + return true; + } + + if(o == null || getClass() != o.getClass()) + { + return false; + } + + APIVersion that = (APIVersion) o; + return Objects.equals(version, that.version); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Override + public int hashCode() + { + return Objects.hash(version); + } } 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 115d1bfa..919ba00f 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 @@ -29,12 +29,12 @@ import com.kingsrook.qqq.backend.core.actions.tables.InsertAction; import com.kingsrook.qqq.backend.core.exceptions.QException; import com.kingsrook.qqq.backend.core.exceptions.QInstanceValidationException; import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertInput; -import com.kingsrook.qqq.backend.core.model.actions.tables.insert.InsertOutput; import com.kingsrook.qqq.backend.core.model.data.QRecord; import com.kingsrook.qqq.backend.core.model.metadata.QInstance; import com.kingsrook.qqq.backend.javalin.QJavalinImplementation; import kong.unirest.HttpResponse; import kong.unirest.Unirest; +import org.json.JSONArray; import org.json.JSONObject; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; @@ -123,10 +123,7 @@ class QJavalinApiHandlerTest extends BaseTest @Test void testGet200() 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"))); - InsertOutput insertOutput = new InsertAction().execute(insertInput); + insertTestRecord(); HttpResponse response = Unirest.get(BASE_URL + "/api/" + VERSION + "/person/1").asString(); assertEquals(200, response.getStatus()); @@ -141,16 +138,12 @@ class QJavalinApiHandlerTest extends BaseTest /******************************************************************************* ** *******************************************************************************/ - @Test - void testQuery400() + private static void insertTestRecord() throws QException { - HttpResponse response = Unirest.get(BASE_URL + "/api/" + VERSION + "/person/query?asdf=Darin&orderBy=asdf asdf").asString(); - assertEquals(400, response.getStatus()); - JSONObject jsonObject = new JSONObject(response.getBody()); - String error = jsonObject.getString("error"); - assertThat(error).contains("orderBy direction for field asdf must be either ASC or DESC"); - assertThat(error).contains("Unrecognized orderBy field name: asdf"); - assertThat(error).contains("Unrecognized filter criteria field: asdf"); + 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"))); + new InsertAction().execute(insertInput); } @@ -159,9 +152,49 @@ class QJavalinApiHandlerTest extends BaseTest ** *******************************************************************************/ @Test - void testQuery() + void testQuery404() { - HttpResponse response = Unirest.get(BASE_URL + "/api/" + VERSION + "/person/query?firstName=Darin&orderBy=firstName desc").asString(); + assertError(404, BASE_URL + "/api/" + VERSION + "/notATable/query?"); + assertError(404, BASE_URL + "/api/notAVersion/person/query?"); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testQuery400() + { + String base = BASE_URL + "/api/" + VERSION + "/person/query?"; + + assertError("Could not parse pageNo as an integer", base + "pageNo=foo"); + assertError("pageNo must be greater than 0", base + "pageNo=0"); + + assertError("Could not parse pageSize as an integer", base + "pageSize=foo"); + assertError("pageSize must be between 1 and 1000.", base + "pageSize=0"); + assertError("pageSize must be between 1 and 1000.", base + "pageSize=1001"); + + assertError("booleanOperator must be either AND or OR", base + "booleanOperator=not"); + assertError("includeCount must be either true or false", base + "includeCount=maybe"); + + assertError("orderBy direction for field firstName must be either ASC or DESC", base + "orderBy=firstName foo"); + assertError("Unrecognized format for orderBy clause: firstName asc foo", base + "orderBy=firstName asc foo"); + assertError("Unrecognized orderBy field name: foo", base + "orderBy=foo"); + assertError("Unrecognized filter criteria field: foo", base + "foo=bar"); + + assertError("Request failed with 2 reasons", base + "foo=bar&bar=baz"); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testQuery200NoParams() + { + HttpResponse response = Unirest.get(BASE_URL + "/api/" + VERSION + "/person/query").asString(); assertEquals(200, response.getStatus()); JSONObject jsonObject = new JSONObject(response.getBody()); assertEquals(0, jsonObject.getInt("count")); @@ -169,4 +202,69 @@ class QJavalinApiHandlerTest extends BaseTest assertEquals(50, jsonObject.getInt("pageSize")); } + + + /******************************************************************************* + ** + *******************************************************************************/ + @Test + void testQuery200SomethingFound() throws QException + { + insertTestRecord(); + + HttpResponse response = Unirest.get(BASE_URL + "/api/" + VERSION + "/person/query").asString(); + assertEquals(200, response.getStatus()); + JSONObject jsonObject = new JSONObject(response.getBody()); + assertEquals(1, jsonObject.getInt("count")); + assertEquals(1, jsonObject.getInt("pageNo")); + assertEquals(50, jsonObject.getInt("pageSize")); + + JSONArray jsonArray = jsonObject.getJSONArray("records"); + jsonObject = jsonArray.getJSONObject(0); + assertEquals(1, jsonObject.getInt("id")); + assertEquals("Darin", jsonObject.getString("firstName")); + assertEquals("Kelkhoff", jsonObject.getString("lastName")); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + @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(); + assertEquals(200, response.getStatus()); + JSONObject jsonObject = new JSONObject(response.getBody()); + assertEquals(0, jsonObject.getInt("count")); + assertEquals(2, jsonObject.getInt("pageNo")); + assertEquals(49, jsonObject.getInt("pageSize")); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private void assertError(Integer statusCode, String url) + { + HttpResponse response = Unirest.get(url).asString(); + assertEquals(statusCode, response.getStatus()); + } + + + + /******************************************************************************* + ** + *******************************************************************************/ + private void assertError(String expectedErrorMessage, String url) + { + HttpResponse response = Unirest.get(url).asString(); + assertEquals(400, response.getStatus()); + JSONObject jsonObject = new JSONObject(response.getBody()); + String error = jsonObject.getString("error"); + assertThat(error).contains(expectedErrorMessage); + } + } \ No newline at end of file