From fc7036bd2f7c3bfde13205f7320172667226d294 Mon Sep 17 00:00:00 2001 From: cnathe Date: Mon, 28 Sep 2026 14:14:05 -0500 Subject: [PATCH 1/3] Test selectRows count-only requests --- src/org/labkey/test/tests/ClientAPITest.java | 63 ++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/src/org/labkey/test/tests/ClientAPITest.java b/src/org/labkey/test/tests/ClientAPITest.java index 128d39c435..f7c53844c5 100644 --- a/src/org/labkey/test/tests/ClientAPITest.java +++ b/src/org/labkey/test/tests/ClientAPITest.java @@ -27,6 +27,7 @@ import org.junit.Test; import org.junit.experimental.categories.Category; import org.labkey.remoteapi.CommandException; +import org.labkey.remoteapi.CommandResponse; import org.labkey.remoteapi.Connection; import org.labkey.remoteapi.SimpleGetCommand; import org.labkey.remoteapi.SimplePostCommand; @@ -83,6 +84,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThrows; import static org.junit.Assert.assertTrue; import static org.labkey.test.WebTestHelper.getHttpResponse; @@ -1527,6 +1529,67 @@ protected HttpGet getHttpRequest(Connection connection, String folderPath) throw } } + private record CountCase(Map filters, int expectedCount) {} + + // GH Issue 1607 + @Test + public void testSelectRowsCountOnly() throws Exception + { + Connection cn = createDefaultConnection(); + List countCases = List.of( + new CountCase(Map.of(), 7), + new CountCase(Map.of("query.Age~gt", 30), 5), + new CountCase(Map.of("query.FirstName~eq", "Nobody"), 0), + new CountCase(Map.of("query.Age~eq", "notANumber"), 0)); + + for (double apiVersion : List.of(8.3, 9.1, 17.1)) + { + for (CountCase countCase : countCases) + { + String description = "apiVersion " + apiVersion + ", filters " + countCase.filters(); + + log("Verify count-only request: " + description); + CommandResponse countOnly = selectPeople(cn, apiVersion, countCase.filters(), Map.of("query.maxRows", 0, "includeTotalCount", true)); + assertEquals("Wrong count-only rowCount: " + description, countCase.expectedCount(), ((Number) countOnly.getProperty("rowCount")).intValue()); + List rows = countOnly.getProperty("rows"); + assertTrue("Count-only request should return no rows: " + description, rows.isEmpty()); + + CommandResponse paged = selectPeople(cn, apiVersion, countCase.filters(), Map.of("query.maxRows", 1)); + assertEquals("Count-only rowCount should match paged rowCount: " + description, ((Number) paged.getProperty("rowCount")).intValue(), ((Number) countOnly.getProperty("rowCount")).intValue()); + + log("Verify maxRows=0 without an explicit includeTotalCount=true stays metadata only: " + description); + CommandResponse metaDataOnly = selectPeople(cn, apiVersion, countCase.filters(), Map.of("query.maxRows", 0)); + assertNull("Metadata-only request should not return rowCount: " + description, metaDataOnly.getProperty("rowCount")); + assertNotNull("Metadata-only request should return metaData: " + description, metaDataOnly.getProperty("metaData")); + metaDataOnly = selectPeople(cn, apiVersion, countCase.filters(), Map.of("query.maxRows", 0, "includeTotalCount", false)); + assertNull("includeTotalCount=false request should not return rowCount: " + description, metaDataOnly.getProperty("rowCount")); + } + } + + log("Verify count-only request with a JSON body"); + SimplePostCommand post = new SimplePostCommand("query", "selectRows.api"); + post.setRequiredVersion(17.1); + post.setJsonObject(new JSONObject(Map.of("schemaName", "lists", "query.queryName", LIST_NAME, + "query.Age~gt", 30, "query.maxRows", 0, "includeTotalCount", true))); + CommandResponse postResponse = post.execute(cn, API_FOLDER_PATH); + assertEquals("Wrong count-only rowCount for JSON body", 5, ((Number) postResponse.getProperty("rowCount")).intValue()); + List postRows = postResponse.getProperty("rows"); + assertTrue("Count-only JSON body request should return no rows", postRows.isEmpty()); + } + + private CommandResponse selectPeople(Connection cn, double apiVersion, Map filters, Map params) throws IOException, CommandException + { + Map allParams = new HashMap<>(filters); + allParams.putAll(params); + allParams.put("schemaName", "lists"); + allParams.put("query.queryName", LIST_NAME); + + SimpleGetCommand command = new SimpleGetCommand("query", "selectRows.api"); + command.setRequiredVersion(apiVersion); + command.setParameters(allParams); + return command.execute(cn, API_FOLDER_PATH); + } + @Test public void suggestedColumnsInQueryDetailsTest() throws Exception { From 3d1da681ff29c72cf509b3bbb2e5f3edcc81f0da Mon Sep 17 00:00:00 2001 From: cnathe Date: Mon, 28 Sep 2026 14:50:15 -0500 Subject: [PATCH 2/3] Claude feedback: add count-only request test case --- src/org/labkey/test/tests/ClientAPITest.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/org/labkey/test/tests/ClientAPITest.java b/src/org/labkey/test/tests/ClientAPITest.java index f7c53844c5..612e664400 100644 --- a/src/org/labkey/test/tests/ClientAPITest.java +++ b/src/org/labkey/test/tests/ClientAPITest.java @@ -1557,6 +1557,11 @@ public void testSelectRowsCountOnly() throws Exception CommandResponse paged = selectPeople(cn, apiVersion, countCase.filters(), Map.of("query.maxRows", 1)); assertEquals("Count-only rowCount should match paged rowCount: " + description, ((Number) paged.getProperty("rowCount")).intValue(), ((Number) countOnly.getProperty("rowCount")).intValue()); + log("Verify count-only request without metadata and with an offset: " + description); + CommandResponse noMetaData = selectPeople(cn, apiVersion, countCase.filters(), Map.of("query.maxRows", 0, "includeTotalCount", true, "includeMetadata", false, "query.offset", 100)); + assertEquals("Wrong count-only rowCount without metadata: " + description, countCase.expectedCount(), ((Number) noMetaData.getProperty("rowCount")).intValue()); + assertNull("Count-only request with includeMetadata=false should not return metaData: " + description, noMetaData.getProperty("metaData")); + log("Verify maxRows=0 without an explicit includeTotalCount=true stays metadata only: " + description); CommandResponse metaDataOnly = selectPeople(cn, apiVersion, countCase.filters(), Map.of("query.maxRows", 0)); assertNull("Metadata-only request should not return rowCount: " + description, metaDataOnly.getProperty("rowCount")); From a9c433b6cf5afec609ac048ad8b8985e1360ae06 Mon Sep 17 00:00:00 2001 From: cnathe Date: Mon, 28 Sep 2026 17:07:54 -0500 Subject: [PATCH 3/3] CR feedback --- src/org/labkey/test/tests/ClientAPITest.java | 30 +++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/org/labkey/test/tests/ClientAPITest.java b/src/org/labkey/test/tests/ClientAPITest.java index 612e664400..f80c6f3893 100644 --- a/src/org/labkey/test/tests/ClientAPITest.java +++ b/src/org/labkey/test/tests/ClientAPITest.java @@ -1531,6 +1531,11 @@ protected HttpGet getHttpRequest(Connection connection, String folderPath) throw private record CountCase(Map filters, int expectedCount) {} + private static final String MAX_ROWS_PARAM = "query.maxRows"; + private static final String OFFSET_PARAM = "query.offset"; + private static final String INCLUDE_TOTAL_COUNT_PARAM = "includeTotalCount"; + private static final String INCLUDE_METADATA_PARAM = "includeMetadata"; + // GH Issue 1607 @Test public void testSelectRowsCountOnly() throws Exception @@ -1549,24 +1554,24 @@ public void testSelectRowsCountOnly() throws Exception String description = "apiVersion " + apiVersion + ", filters " + countCase.filters(); log("Verify count-only request: " + description); - CommandResponse countOnly = selectPeople(cn, apiVersion, countCase.filters(), Map.of("query.maxRows", 0, "includeTotalCount", true)); - assertEquals("Wrong count-only rowCount: " + description, countCase.expectedCount(), ((Number) countOnly.getProperty("rowCount")).intValue()); + CommandResponse countOnly = selectPeople(cn, apiVersion, countCase.filters(), Map.of(MAX_ROWS_PARAM, 0, INCLUDE_TOTAL_COUNT_PARAM, true)); + assertEquals("Wrong count-only rowCount: " + description, countCase.expectedCount(), getRowCount(countOnly)); List rows = countOnly.getProperty("rows"); assertTrue("Count-only request should return no rows: " + description, rows.isEmpty()); - CommandResponse paged = selectPeople(cn, apiVersion, countCase.filters(), Map.of("query.maxRows", 1)); - assertEquals("Count-only rowCount should match paged rowCount: " + description, ((Number) paged.getProperty("rowCount")).intValue(), ((Number) countOnly.getProperty("rowCount")).intValue()); + CommandResponse paged = selectPeople(cn, apiVersion, countCase.filters(), Map.of(MAX_ROWS_PARAM, 1)); + assertEquals("Count-only rowCount should match paged rowCount: " + description, getRowCount(paged), getRowCount(countOnly)); log("Verify count-only request without metadata and with an offset: " + description); - CommandResponse noMetaData = selectPeople(cn, apiVersion, countCase.filters(), Map.of("query.maxRows", 0, "includeTotalCount", true, "includeMetadata", false, "query.offset", 100)); - assertEquals("Wrong count-only rowCount without metadata: " + description, countCase.expectedCount(), ((Number) noMetaData.getProperty("rowCount")).intValue()); + CommandResponse noMetaData = selectPeople(cn, apiVersion, countCase.filters(), Map.of(MAX_ROWS_PARAM, 0, INCLUDE_TOTAL_COUNT_PARAM, true, INCLUDE_METADATA_PARAM, false, OFFSET_PARAM, 100)); + assertEquals("Wrong count-only rowCount without metadata: " + description, countCase.expectedCount(), getRowCount(noMetaData)); assertNull("Count-only request with includeMetadata=false should not return metaData: " + description, noMetaData.getProperty("metaData")); log("Verify maxRows=0 without an explicit includeTotalCount=true stays metadata only: " + description); - CommandResponse metaDataOnly = selectPeople(cn, apiVersion, countCase.filters(), Map.of("query.maxRows", 0)); + CommandResponse metaDataOnly = selectPeople(cn, apiVersion, countCase.filters(), Map.of(MAX_ROWS_PARAM, 0)); assertNull("Metadata-only request should not return rowCount: " + description, metaDataOnly.getProperty("rowCount")); assertNotNull("Metadata-only request should return metaData: " + description, metaDataOnly.getProperty("metaData")); - metaDataOnly = selectPeople(cn, apiVersion, countCase.filters(), Map.of("query.maxRows", 0, "includeTotalCount", false)); + metaDataOnly = selectPeople(cn, apiVersion, countCase.filters(), Map.of(MAX_ROWS_PARAM, 0, INCLUDE_TOTAL_COUNT_PARAM, false)); assertNull("includeTotalCount=false request should not return rowCount: " + description, metaDataOnly.getProperty("rowCount")); } } @@ -1575,9 +1580,9 @@ public void testSelectRowsCountOnly() throws Exception SimplePostCommand post = new SimplePostCommand("query", "selectRows.api"); post.setRequiredVersion(17.1); post.setJsonObject(new JSONObject(Map.of("schemaName", "lists", "query.queryName", LIST_NAME, - "query.Age~gt", 30, "query.maxRows", 0, "includeTotalCount", true))); + "query.Age~gt", 30, MAX_ROWS_PARAM, 0, INCLUDE_TOTAL_COUNT_PARAM, true))); CommandResponse postResponse = post.execute(cn, API_FOLDER_PATH); - assertEquals("Wrong count-only rowCount for JSON body", 5, ((Number) postResponse.getProperty("rowCount")).intValue()); + assertEquals("Wrong count-only rowCount for JSON body", 5, getRowCount(postResponse)); List postRows = postResponse.getProperty("rows"); assertTrue("Count-only JSON body request should return no rows", postRows.isEmpty()); } @@ -1595,6 +1600,11 @@ private CommandResponse selectPeople(Connection cn, double apiVersion, Map