changeset 186:69076d3867be

Add and expand integration tests for multiple equality statements and more unexpected query types Reviewed-by: jerboaa Review-thread: http://icedtea.classpath.org/pipermail/thermostat/2017-June/023826.html
author Christopher Koehler <chkoehle@redhat.com>
date Mon, 26 Jun 2017 08:55:20 -0400
parents 550361e046a6
children 8f0bcc7a9677
files common/core/src/main/java/com/redhat/thermostat/gateway/common/core/jaxrs/InvalidParameterValueException.java common/core/src/main/java/com/redhat/thermostat/gateway/common/core/model/OffsetParameter.java services/jvm-gc/src/main/java/com/redhat/thermostat/service/jvm/gc/JvmGcHttpHandler.java services/jvm-memory/src/main/java/com/redhat/thermostat/service/jvm/memory/JvmMemoryHttpHandler.java tests/integration-tests/src/test/java/com/redhat/thermostat/gateway/service/jvm/memory/JvmMemoryServiceIntegrationTest.java tests/test-utils/src/main/java/com/redhat/thermostat/gateway/tests/utils/HttpTestUtil.java
diffstat 6 files changed, 213 insertions(+), 48 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/core/src/main/java/com/redhat/thermostat/gateway/common/core/jaxrs/InvalidParameterValueException.java	Mon Jun 26 08:55:20 2017 -0400
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2012-2017 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat 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 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat 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 Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.gateway.common.core.jaxrs;
+
+import javax.ws.rs.BadRequestException;
+
+@SuppressWarnings("serial")
+public class InvalidParameterValueException extends BadRequestException {
+
+    public InvalidParameterValueException(String msg) {
+        super(msg);
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/common/core/src/main/java/com/redhat/thermostat/gateway/common/core/model/OffsetParameter.java	Mon Jun 26 08:55:20 2017 -0400
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2012-2017 Red Hat, Inc.
+ *
+ * This file is part of Thermostat.
+ *
+ * Thermostat 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 2, or (at your
+ * option) any later version.
+ *
+ * Thermostat 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 Thermostat; see the file COPYING.  If not see
+ * <http://www.gnu.org/licenses/>.
+ *
+ * Linking this code with other modules is making a combined work
+ * based on this code.  Thus, the terms and conditions of the GNU
+ * General Public License cover the whole combination.
+ *
+ * As a special exception, the copyright holders of this code give
+ * you permission to link this code with independent modules to
+ * produce an executable, regardless of the license terms of these
+ * independent modules, and to copy and distribute the resulting
+ * executable under terms of your choice, provided that you also
+ * meet, for each linked independent module, the terms and conditions
+ * of the license of that module.  An independent module is a module
+ * which is not derived from or based on this code.  If you modify
+ * this code, you may extend this exception to your version of the
+ * library, but you are not obligated to do so.  If you do not wish
+ * to do so, delete this exception statement from your version.
+ */
+
+package com.redhat.thermostat.gateway.common.core.model;
+
+import com.redhat.thermostat.gateway.common.core.jaxrs.InvalidParameterValueException;
+
+public class OffsetParameter {
+
+    private final Integer offset;
+
+    private OffsetParameter(Integer offset) {
+        this.offset = offset;
+    }
+
+    public Integer getValue() {
+        return offset;
+    }
+
+    public static OffsetParameter valueOf(String rawParam) {
+        if (rawParam == null) {
+            return null;
+        }
+        Integer intVal = Integer.valueOf(rawParam);
+        if (intVal < 0) {
+            // JAX RS throws 404 on illegal parameter types by default, make it throw
+            // 400 instead.
+            throw new InvalidParameterValueException("Offset value must not be negative");
+        }
+        return new OffsetParameter(intVal);
+    }
+}
--- a/services/jvm-gc/src/main/java/com/redhat/thermostat/service/jvm/gc/JvmGcHttpHandler.java	Wed Jun 21 11:00:17 2017 +0200
+++ b/services/jvm-gc/src/main/java/com/redhat/thermostat/service/jvm/gc/JvmGcHttpHandler.java	Mon Jun 26 08:55:20 2017 -0400
@@ -52,12 +52,12 @@
 
 import com.mongodb.DBObject;
 import com.redhat.thermostat.gateway.common.mongodb.ThermostatMongoStorage;
+import com.redhat.thermostat.gateway.common.mongodb.executor.MongoDataResultContainer;
 import com.redhat.thermostat.gateway.common.mongodb.executor.MongoExecutor;
+import com.redhat.thermostat.gateway.common.mongodb.response.MongoMetaDataGenerator;
 import com.redhat.thermostat.gateway.common.mongodb.response.MongoMetaDataResponseBuilder;
 import com.redhat.thermostat.gateway.common.mongodb.response.MongoResponseBuilder;
-import com.redhat.thermostat.gateway.common.mongodb.response.MongoMetaDataGenerator;
 import com.redhat.thermostat.gateway.common.mongodb.servlet.ServletContextConstants;
-import com.redhat.thermostat.gateway.common.mongodb.executor.MongoDataResultContainer;
 
 @Path("/")
 public class JvmGcHttpHandler {
--- a/services/jvm-memory/src/main/java/com/redhat/thermostat/service/jvm/memory/JvmMemoryHttpHandler.java	Wed Jun 21 11:00:17 2017 +0200
+++ b/services/jvm-memory/src/main/java/com/redhat/thermostat/service/jvm/memory/JvmMemoryHttpHandler.java	Mon Jun 26 08:55:20 2017 -0400
@@ -49,14 +49,16 @@
 import javax.ws.rs.QueryParam;
 import javax.ws.rs.core.Context;
 import javax.ws.rs.core.Response;
+
 import com.mongodb.DBObject;
+import com.redhat.thermostat.gateway.common.core.model.OffsetParameter;
 import com.redhat.thermostat.gateway.common.mongodb.ThermostatMongoStorage;
+import com.redhat.thermostat.gateway.common.mongodb.executor.MongoDataResultContainer;
 import com.redhat.thermostat.gateway.common.mongodb.executor.MongoExecutor;
+import com.redhat.thermostat.gateway.common.mongodb.response.MongoMetaDataGenerator;
 import com.redhat.thermostat.gateway.common.mongodb.response.MongoMetaDataResponseBuilder;
-import com.redhat.thermostat.gateway.common.mongodb.response.MongoMetaDataGenerator;
 import com.redhat.thermostat.gateway.common.mongodb.response.MongoResponseBuilder;
 import com.redhat.thermostat.gateway.common.mongodb.servlet.ServletContextConstants;
-import com.redhat.thermostat.gateway.common.mongodb.executor.MongoDataResultContainer;
 
 @Path("/")
 public class JvmMemoryHttpHandler {
@@ -67,7 +69,7 @@
     @Consumes({ "application/json" })
     @Produces({ "application/json", "text/html; charset=utf-8" })
     public Response getJvmMemory(@QueryParam("l") @DefaultValue("1") Integer limit,
-                                 @QueryParam("o") @DefaultValue("0") Integer offset,
+                                 @QueryParam("o") @DefaultValue("0") OffsetParameter offsetParam,
                                  @QueryParam("s") String sort,
                                  @QueryParam("q") String queries,
                                  @QueryParam("p") String projections,
@@ -75,6 +77,7 @@
                                  @Context ServletContext context,
                                  @Context HttpServletRequest requestInfo) {
         try {
+            Integer offset = offsetParam.getValue();
             ThermostatMongoStorage storage = (ThermostatMongoStorage) context.getAttribute(ServletContextConstants.MONGODB_CLIENT_ATTRIBUTE);
             MongoDataResultContainer execResult = mongoExecutor.execGetRequest(
                     storage.getDatabase().getCollection(collectionName), limit, offset, sort, queries, projections);
--- a/tests/integration-tests/src/test/java/com/redhat/thermostat/gateway/service/jvm/memory/JvmMemoryServiceIntegrationTest.java	Wed Jun 21 11:00:17 2017 +0200
+++ b/tests/integration-tests/src/test/java/com/redhat/thermostat/gateway/service/jvm/memory/JvmMemoryServiceIntegrationTest.java	Mon Jun 26 08:55:20 2017 -0400
@@ -54,32 +54,55 @@
     }
 
     @Test
-    public void testGet() throws InterruptedException, TimeoutException, ExecutionException {
-        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, resourceUrl, 200, HttpTestUtil.EMPTY_RESPONSE);
-    }
-
-    @Test
-    public void testGetWithQuery() throws InterruptedException, TimeoutException, ExecutionException {
-        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, resourceUrl + "?l=10", 200, HttpTestUtil.EMPTY_RESPONSE);
-    }
-
-    @Test
-    public void testGetWithMalformedQuery() throws InterruptedException, TimeoutException, ExecutionException {
+    public void testGetWithUnsupportedQuery() throws InterruptedException, TimeoutException, ExecutionException {
         HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, resourceUrl + "?xyz=5", 200, HttpTestUtil.EMPTY_RESPONSE);
     }
 
     @Test
-    public void testPostProperlyAddsData() throws InterruptedException, TimeoutException, ExecutionException {
-        String expectedResponse = "{\"response\":[{\"fakedata\":\"test\"}]}";
-        HttpTestUtil.testContentResponse(client, HttpMethod.POST, resourceUrl, "[{\"fakedata\":\"test\"}]", 200);
-        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, resourceUrl, 200, expectedResponse);
+    public void testDefaultLimitOne() throws InterruptedException, TimeoutException, ExecutionException {
+        String expected = "{\"response\":[{\"a\":\"b\"}]}";
+        HttpTestUtil.addRecords(client, resourceUrl, "[{\"a\":\"b\"},{\"a\":\"d\"}]");
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, resourceUrl + "?s=+a&l=", 200, expected);
+    }
+
+    @Test
+    public void testGetWithCommaQuery() throws InterruptedException, TimeoutException, ExecutionException {
+        String expectedFirst = "{\"response\":[{\"a\":\"b\",\"c\":\"d\",\"e\":\"f\"}]}";
+        String expectedAll = "{\"response\":[{\"a\":\"b\",\"c\":\"d\",\"e\":\"f\"},{\"x\":\"y\"},{\"z\":\"z\"}]}";
+        HttpTestUtil.addRecords(client, resourceUrl, "[{\"a\":\"b\",\"c\":\"d\",\"e\":\"f\"},{\"x\":\"y\"},{\"z\":\"z\"}]");
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, resourceUrl + "?l=5", 200, expectedAll);
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, resourceUrl + "?q=a==b", 200, expectedFirst);
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, resourceUrl + "?q=c==d", 200, expectedFirst);
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, resourceUrl + "?q=a==b,c==d", 200, expectedFirst);
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, resourceUrl + "?q=a==b,c==none", 200, HttpTestUtil.EMPTY_RESPONSE);
+    }
+
+    @Test
+    public void testGetWithAmpersandQuery() throws InterruptedException, TimeoutException, ExecutionException {
+        String expectedAll = "{\"response\":[{\"a\":\"b\"},{\"c\":\"d\"}]}";
+        String expectedAmpersand = "{\"response\":[{\"a\":\"b\"}]}";
+        HttpTestUtil.addRecords(client, resourceUrl, "[{\"a\":\"b\"},{\"c\":\"d\"}]");
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, resourceUrl + "?l=5", 200, expectedAll);
+
+        // Since q=a==b&c==d means "q= 'a==b'" and "c= '=d'", we should only
+        // get 'a: b' back.
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, resourceUrl + "?q=a==b&c==d", 200, expectedAmpersand);
+
+        // The following should find the one with multiple matches, not the
+        // single match. This means we will evaluate u==v only, and should get
+        // back the first one it finds since no limit query is specified.
+        String expectedSingleMatch = "{\"response\":[{\"u\":\"v\",\"x\":\"y\"}]}";
+        String expectedMultiMatch = "{\"response\":[{\"u\":\"v\",\"x\":\"y\"},{\"u\":\"v\"}]}";
+        HttpTestUtil.addRecords(client, resourceUrl, "[{\"x\":\"y\"},{\"u\":\"v\",\"x\":\"y\"},{\"u\":\"v\"}]");
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, resourceUrl + "?q=u==v&q=x==y", 200, expectedSingleMatch);
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, resourceUrl + "?q=u==v&q=x==y&l=5", 200, expectedMultiMatch);
     }
 
     @Test
     public void testMultiplePosts() throws InterruptedException, TimeoutException, ExecutionException {
         String expectedResponse = "{\"response\":[{\"fakedata\":\"test\"},{\"new\":\"data\"}]}";
-        HttpTestUtil.testContentResponse(client, HttpMethod.POST, resourceUrl, "[{\"fakedata\":\"test\"}]", 200);
-        HttpTestUtil.testContentResponse(client, HttpMethod.POST, resourceUrl, "[{\"new\":\"data\"}]", 200);
+        HttpTestUtil.addRecords(client, resourceUrl, "[{\"fakedata\":\"test\"}]");
+        HttpTestUtil.addRecords(client, resourceUrl, "[{\"new\":\"data\"}]");
         HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, resourceUrl + "?l=5", 200, expectedResponse);
     }
 
@@ -87,7 +110,7 @@
     public void testPostPutAddsData() throws InterruptedException, TimeoutException, ExecutionException {
         String expectedDataBeforePut = "{\"response\":[{\"a\":\"b\"}]}";
         String expectedDataAfterPut = "{\"response\":[{\"a\":\"b\",\"x\":\"y\"}]}";
-        HttpTestUtil.testContentResponse(client, HttpMethod.POST, resourceUrl, "[{\"a\":\"b\"}]", 200);
+        HttpTestUtil.addRecords(client, resourceUrl, "[{\"a\":\"b\"},{\"a\":\"c\"}]");
         HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, resourceUrl, 200, expectedDataBeforePut);
         HttpTestUtil.testContentResponse(client, HttpMethod.PUT, resourceUrl + "?q=a==b", "{\"set\":{\"x\":\"y\"}}", 200);
         HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, resourceUrl, 200, expectedDataAfterPut);
@@ -97,23 +120,30 @@
     public void testPostPutModifiesData() throws InterruptedException, TimeoutException, ExecutionException {
         String expectedDataBeforePut = "{\"response\":[{\"a\":\"b\"}]}";
         String expectedDataAfterPut = "{\"response\":[{\"a\":\"c\"}]}";
-        HttpTestUtil.testContentResponse(client, HttpMethod.POST, resourceUrl, "[{\"a\":\"b\"}]", 200);
+        String expectedAllDataAfterPut = "{\"response\":[{\"a\":\"c\"},{\"x\":\"y\"}]}";
+        HttpTestUtil.addRecords(client, resourceUrl, "[{\"a\":\"b\"},{\"x\":\"y\"}]");
         HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, resourceUrl, 200, expectedDataBeforePut);
         HttpTestUtil.testContentResponse(client, HttpMethod.PUT, resourceUrl + "?q=a==b", "{\"set\":{\"a\":\"c\"}}", 200);
         HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, resourceUrl, 200, expectedDataAfterPut);
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, resourceUrl + "?l=5", 200, expectedAllDataAfterPut);
     }
 
     @Test
     public void testDeleteProperlyDeletesData() throws InterruptedException, TimeoutException, ExecutionException {
-        HttpTestUtil.testContentResponse(client, HttpMethod.POST, resourceUrl, "[{\"fakedata\":\"test\"}]", 200);
+        HttpTestUtil.addRecords(client, resourceUrl, "[{\"fakedata\":\"test\"}]");
         HttpTestUtil.testContentlessResponse(client, HttpMethod.DELETE, resourceUrl + "?q=fakedata==test", 200);
         HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, resourceUrl, 200, HttpTestUtil.EMPTY_RESPONSE);
+
+        String expectedAfterDeletion = "{\"response\":[{\"c\":\"d\"}]}";
+        HttpTestUtil.addRecords(client, resourceUrl, "[{\"fakedata\":\"test\",\"a\":\"b\"},{\"c\":\"d\"}]");
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.DELETE, resourceUrl + "?q=a==b", 200);
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, resourceUrl, 200, expectedAfterDeletion);
     }
 
     @Test
     public void testMalformedDeleteRequestDoesNotMutateData() throws InterruptedException, TimeoutException, ExecutionException {
         String expectedDataResponse = "{\"response\":[{\"fakedata\":\"test\"}]}";
-        HttpTestUtil.testContentResponse(client, HttpMethod.POST, resourceUrl, "[{\"fakedata\":\"test\"}]", 200);
+        HttpTestUtil.addRecords(client, resourceUrl, "[{\"fakedata\":\"test\"}]");
         HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, resourceUrl, 200, expectedDataResponse);
         HttpTestUtil.testContentlessResponse(client, HttpMethod.DELETE, resourceUrl + "?q=nosuchkey==", 200);
         HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, resourceUrl, 200, expectedDataResponse);
@@ -121,7 +151,7 @@
 
     @Test
     public void testPutDataWithoutUrlQuery() throws InterruptedException, TimeoutException, ExecutionException {
-        HttpTestUtil.testContentResponse(client, HttpMethod.POST, resourceUrl, "[{\"fakedata\":\"test\"}]", 200);
+        HttpTestUtil.addRecords(client, resourceUrl, "[{\"fakedata\":\"test\"}]");
         HttpTestUtil.testContentResponse(client, HttpMethod.PUT, resourceUrl, "{\"set\":{\"fakedata\":\"test\"}}", 400);
     }
 
@@ -129,7 +159,7 @@
     public void testPostAndPutWithInvalidData() throws InterruptedException, TimeoutException, ExecutionException {
         String expectedDataResponse = "{\"response\":[{\"fakedata\":\"test\"}]}";
         String urlQuery = resourceUrl + "?q=nosuchkey==nosuchvalue";
-        HttpTestUtil.testContentResponse(client, HttpMethod.POST, urlQuery, "[{\"fakedata\":\"test\"}]", 200);
+        HttpTestUtil.addRecords(client, resourceUrl, "[{\"fakedata\":\"test\"}]");
         HttpTestUtil.testContentResponse(client, HttpMethod.PUT, urlQuery, "{\"set\":{\"fakedata\":\"somethingnew\"}}", 200);
         HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, resourceUrl + "?l=5", 200, expectedDataResponse);
     }
@@ -137,7 +167,7 @@
     @Test
     public void testPutWithIdenticalData() throws InterruptedException, TimeoutException, ExecutionException {
         String expectedDataResponse = "{\"response\":[{\"fakedata\":\"test\"}]}";
-        HttpTestUtil.testContentResponse(client, HttpMethod.POST, resourceUrl, "[{\"fakedata\":\"test\"}]", 200);
+        HttpTestUtil.addRecords(client, resourceUrl, "[{\"fakedata\":\"test\"}]");
         HttpTestUtil.testContentResponse(client, HttpMethod.PUT, resourceUrl, "{\"set\":{\"fakedata\":\"test\"}}", 400);
         HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, resourceUrl + "?l=5", 200, expectedDataResponse);
     }
@@ -145,7 +175,7 @@
     @Test
     public void testPutDifferentData() throws InterruptedException, TimeoutException, ExecutionException {
         String expectedDataResponse = "{\"response\":[{\"a\":\"b\",\"c\":\"d\"}]}";
-        HttpTestUtil.testContentResponse(client, HttpMethod.POST, resourceUrl, "[{\"a\":\"b\"}]", 200);
+        HttpTestUtil.addRecords(client, resourceUrl, "[{\"a\":\"b\"}]");
         HttpTestUtil.testContentResponse(client, HttpMethod.PUT, resourceUrl + "?q=a==b", "{\"set\":{\"c\":\"d\"}}", 200);
         HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, resourceUrl + "?l=5", 200, expectedDataResponse);
     }
@@ -153,9 +183,13 @@
     @Test
     public void testChangeDataWithPutMultipleTimes() throws InterruptedException, TimeoutException, ExecutionException {
         String expectedData = "{\"response\":[{\"a\":\"a2\",\"b\":\"b2\",\"c\":\"c2\"}]}";
-        HttpTestUtil.testContentResponse(client, HttpMethod.POST, resourceUrl, "[{\"a\":\"a2\"}]", 200);
+        HttpTestUtil.addRecords(client, resourceUrl, "[{\"a\":\"a2\"}]");
         HttpTestUtil.testContentResponse(client, HttpMethod.PUT, resourceUrl + "?q=a==a2", "{\"set\":{\"b\":\"b2\"}}", 200);
         HttpTestUtil.testContentResponse(client, HttpMethod.PUT, resourceUrl + "?q=a==a2", "{\"set\":{\"c\":\"c2\"}}", 200);
+
+        // It won't find the target from the query, so the addition should not occur to any object.
+        HttpTestUtil.testContentResponse(client, HttpMethod.PUT, resourceUrl + "?q=a==none", "{\"set\":{\"d\":\"d2\"}}", 200);
+
         HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, resourceUrl + "?l=5", 200, expectedData);
     }
 
@@ -166,35 +200,42 @@
 
     @Test
     public void testGetLimitWithQuery() throws InterruptedException, TimeoutException, ExecutionException {
-        String data = "[{\"a\":\"a2\"},{\"b\":\"b2\"}]";
         String expectedDataOne = "{\"response\":[{\"a\":\"a2\"}]}";
         String expectedDataAll = "{\"response\":[{\"a\":\"a2\"},{\"b\":\"b2\"}]}";
-        HttpTestUtil.testContentResponse(client, HttpMethod.POST, resourceUrl, data, 200);
+        HttpTestUtil.addRecords(client, resourceUrl, "[{\"a\":\"a2\"},{\"b\":\"b2\"}]");
         HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, resourceUrl, 200, expectedDataOne);
         HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, resourceUrl + "?l=2", 200, expectedDataAll);
     }
 
     @Test
     public void testQueryOffset() throws InterruptedException, TimeoutException, ExecutionException {
-        HttpTestUtil.testContentResponse(client, HttpMethod.POST, resourceUrl, "[{\"a\":\"1\"} , {\"b\":\"2\"}, {\"c\":\"3\"}]", 200);
+        String expectedOffsetRestOfData = "{\"response\":[{\"b\":\"2\"},{\"c\":\"3\"}]}";
+        HttpTestUtil.addRecords(client, resourceUrl, "[{\"a\":\"1\"},{\"b\":\"2\"},{\"c\":\"3\"}]");
         HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, resourceUrl + "?o=1", 200, "{\"response\":[{\"b\":\"2\"}]}");
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, resourceUrl + "?o=3", 200, HttpTestUtil.EMPTY_RESPONSE);
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, resourceUrl + "?l=5&o=1", 200, expectedOffsetRestOfData);
+    }
+
+    @Test
+    public void testNegativeOffsetQuery() throws InterruptedException, TimeoutException, ExecutionException {
+        HttpTestUtil.addRecords(client, resourceUrl, "[{\"a\":\"1\"}]");
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, resourceUrl + "?o=-1", 400);
+        HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, resourceUrl + "?o=-582", 400);
     }
 
     @Test
     public void testQueryOrdering() throws InterruptedException, TimeoutException, ExecutionException {
-        String content = "[{\"a\":1 },{\"a\":2 }]";
         String expectedGet = "{\"response\":[{\"a\":1},{\"a\":2}]}";
         String expectedGetReverse = "{\"response\":[{\"a\":2},{\"a\":1}]}";
-        HttpTestUtil.testContentResponse(client, HttpMethod.POST, resourceUrl, content, 200);
+        HttpTestUtil.addRecords(client, resourceUrl, "[{\"a\":1},{\"a\":2}]");
         HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, resourceUrl + "?l=10&s=+a", 200, expectedGet);
         HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, resourceUrl + "?l=10&s=-a", 200, expectedGetReverse);
     }
 
     @Test
     public void testQueryProjection() throws InterruptedException, TimeoutException, ExecutionException {
-        String content = "[{\"a\":\"1\",\"b\":\"2\",\"c\":\"3\"}]";
         String expectedGet = "{\"response\":[{\"b\":\"2\",\"c\":\"3\"}]}";
-        HttpTestUtil.testContentResponse(client, HttpMethod.POST, resourceUrl, content, 200);
+        HttpTestUtil.addRecords(client, resourceUrl, "[{\"a\":\"1\",\"b\":\"2\",\"c\":\"3\"}]");
         HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, resourceUrl + "?p=b,c", 200, expectedGet);
     }
 
@@ -210,7 +251,7 @@
                 "\"c\":\"test2\"}],\"metaData\":{\"payloadCount\":1,\"count\":3," +
                 "\"next\":\"http://127.0.0.1:30000/jvm-memory/0.0.2?o\\u003d1\\u0026l\\u003d1\\u0026q\\u003db\\u003d\\u003dtest1\\u0026m\\u003dtrue\"}}";
 
-        HttpTestUtil.testContentResponse(client, HttpMethod.POST, resourceUrl, data, 200);
+        HttpTestUtil.addRecords(client, resourceUrl, data);
         HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, resourceUrl + "?q=b==test1&m=true&l=1", 200, expectedResponse);
     }
 
@@ -227,7 +268,7 @@
                 "\"prev\":\"http://127.0.0.1:30000/jvm-memory/0.0.2?q\\u003db\\u003d\\u003dtest1\\u0026m\\u003dtrue\\u0026l\\u003d1\"," +
                 "\"next\":\"http://127.0.0.1:30000/jvm-memory/0.0.2?o\\u003d2\\u0026l\\u003d1\\u0026q\\u003db\\u003d\\u003dtest1\\u0026m\\u003dtrue\"}}";
 
-        HttpTestUtil.testContentResponse(client, HttpMethod.POST, resourceUrl, data, 200);
+        HttpTestUtil.addRecords(client, resourceUrl, data);
         HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, resourceUrl + "?q=b==test1&m=true&o=1", 200, expectedResponse);
     }
 
@@ -247,7 +288,7 @@
                 "\\u0026l\\u003d1\\u0026o\\u003d0\",\"next\":\"http://127.0.0.1:30000/jvm-memory/0.0.2?o\\u003d4" +
                 "\\u0026l\\u003d1\\u0026q\\u003db\\u003d\\u003dtest1\\u0026m\\u003dtrue\"}}";
 
-        HttpTestUtil.testContentResponse(client, HttpMethod.POST, resourceUrl, data, 200);
+        HttpTestUtil.addRecords(client, resourceUrl, data);
         HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, resourceUrl + "?q=b==test1&m=true&o=1&l=3", 200, expectedResponse);
     }
 
@@ -266,7 +307,7 @@
                 "\"prev\":\"http://127.0.0.1:30000/jvm-memory/0.0.2?q\\u003db\\u003d\\u003dtest1\\u0026m\\u003dtrue\\u0026l\\u003d2\\u0026o\\u003d1\"," +
                 "\"next\":\"http://127.0.0.1:30000/jvm-memory/0.0.2?o\\u003d5\\u0026l\\u003d1\\u0026q\\u003db\\u003d\\u003dtest1\\u0026m\\u003dtrue\"}}";
 
-        HttpTestUtil.testContentResponse(client, HttpMethod.POST, resourceUrl, data, 200);
+        HttpTestUtil.addRecords(client, resourceUrl, data);
         HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, resourceUrl + "?q=b==test1&m=true&o=3&l=2", 200, expectedResponse);
     }
 
@@ -284,7 +325,7 @@
                 "\"prev\":\"http://127.0.0.1:30000/jvm-memory/0.0.2?q\\u003db\\u003d\\u003dtest1\\u0026m\\u003dtrue\\u0026l\\u003d1\"," +
                 "\"next\":\"http://127.0.0.1:30000/jvm-memory/0.0.2?o\\u003d2\\u0026l\\u003d1\\u0026q\\u003db\\u003d\\u003dtest1\\u0026m\\u003dtrue\"}}";
 
-        HttpTestUtil.testContentResponse(client, HttpMethod.POST, resourceUrl, data, 200);
+        HttpTestUtil.addRecords(client, resourceUrl, data);
         HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, resourceUrl + "?q=b==test1&m=true&o=1&l=1", 200, expectedResponse);
     }
 
@@ -303,7 +344,7 @@
                 "\"prev\":\"http://127.0.0.1:30000/jvm-memory/0.0.2?q\\u003db\\u003d\\u003dtest1\\u0026m\\u003dtrue\\u0026l\\u003d1\\u0026o\\u003d0\"," +
                 "\"next\":\"http://127.0.0.1:30000/jvm-memory/0.0.2?o\\u003d3\\u0026l\\u003d2\\u0026q\\u003db\\u003d\\u003dtest1\\u0026m\\u003dtrue\"}}";
 
-        HttpTestUtil.testContentResponse(client, HttpMethod.POST, resourceUrl, data, 200);
+        HttpTestUtil.addRecords(client, resourceUrl, data);
         HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, resourceUrl + "?q=b==test1&m=true&o=1&l=2", 200, expectedResponse);
     }
 
@@ -322,7 +363,7 @@
                 "\"prev\":\"http://127.0.0.1:30000/jvm-memory/0.0.2?q\\u003db\\u003d\\u003dtest1\\u0026m\\u003dtrue\\u0026l\\u003d2\\u0026o\\u003d1\"," +
                 "\"next\":\"http://127.0.0.1:30000/jvm-memory/0.0.2?o\\u003d5\\u0026l\\u003d1\\u0026q\\u003db\\u003d\\u003dtest1\\u0026m\\u003dtrue\"}}";
 
-        HttpTestUtil.testContentResponse(client, HttpMethod.POST, resourceUrl, data, 200);
+        HttpTestUtil.addRecords(client, resourceUrl, data);
         HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, resourceUrl + "?q=b==test1&m=true&o=3&l=2", 200, expectedResponse);
     }
 
@@ -340,7 +381,7 @@
                 "\"prev\":\"http://127.0.0.1:30000/jvm-memory/0.0.2?q\\u003db\\u003d\\u003dtest1\\u0026m\\u003dtrue\\u0026l\\u003d2\\u0026o\\u003d0\"," +
                 "\"next\":\"http://127.0.0.1:30000/jvm-memory/0.0.2?o\\u003d4\\u0026l\\u003d2\\u0026q\\u003db\\u003d\\u003dtest1\\u0026m\\u003dtrue\"}}";
 
-        HttpTestUtil.testContentResponse(client, HttpMethod.POST, resourceUrl, data, 200);
+        HttpTestUtil.addRecords(client, resourceUrl, data);
         HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, resourceUrl + "?q=b==test1&m=true&o=2&l=2", 200, expectedResponse);
     }
 
@@ -359,7 +400,7 @@
                 "\"prev\":\"http://127.0.0.1:30000/jvm-memory/0.0.2?q\\u003db\\u003d\\u003dtest1\\u0026m\\u003dtrue\\u0026l\\u003d2\\u0026o\\u003d0\"," +
                 "\"next\":\"http://127.0.0.1:30000/jvm-memory/0.0.2?o\\u003d4\\u0026l\\u003d2\\u0026q\\u003db\\u003d\\u003dtest1\\u0026m\\u003dtrue\"}}";
 
-        HttpTestUtil.testContentResponse(client, HttpMethod.POST, resourceUrl, data, 200);
+        HttpTestUtil.addRecords(client, resourceUrl, data);
         HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, resourceUrl + "?q=b==test1&m=true&o=2&l=2", 200, expectedResponse);
     }
 
@@ -379,7 +420,7 @@
                 "\"prev\":\"http://127.0.0.1:30000/jvm-memory/0.0.2?q\\u003db\\u003d\\u003dtest1\\u0026m\\u003dtrue\\u0026l\\u003d2\\u0026o\\u003d0\"," +
                 "\"next\":\"http://127.0.0.1:30000/jvm-memory/0.0.2?o\\u003d5\\u0026l\\u003d1\\u0026q\\u003db\\u003d\\u003dtest1\\u0026m\\u003dtrue\"}}";
 
-        HttpTestUtil.testContentResponse(client, HttpMethod.POST, resourceUrl, data, 200);
+        HttpTestUtil.addRecords(client, resourceUrl, data);
         HttpTestUtil.testContentlessResponse(client, HttpMethod.GET, resourceUrl + "?q=b==test1&m=true&o=2&l=3", 200, expectedResponse);
     }
 }
--- a/tests/test-utils/src/main/java/com/redhat/thermostat/gateway/tests/utils/HttpTestUtil.java	Wed Jun 21 11:00:17 2017 +0200
+++ b/tests/test-utils/src/main/java/com/redhat/thermostat/gateway/tests/utils/HttpTestUtil.java	Mon Jun 26 08:55:20 2017 -0400
@@ -50,6 +50,15 @@
 
     public static final String EMPTY_RESPONSE = "{\"response\":[]}";
 
+    public static void addRecords(HttpClient client, String resourceUrl, String content) throws InterruptedException, ExecutionException, TimeoutException {
+        StringContentProvider stringContentProvider = new StringContentProvider(content, "UTF-8");
+        ContentResponse response = client.newRequest(resourceUrl)
+                                         .method(HttpMethod.POST)
+                                         .content(stringContentProvider, "application/json")
+                                         .send();
+        assertEquals(200, response.getStatus());
+    }
+
     public static void testContentlessResponse(HttpClient client,
                                                HttpMethod httpMethod,
                                                String url,