From fb8d16a1c1e5275504632bbcf561f426b0562cb4 Mon Sep 17 00:00:00 2001 From: Serge Huber Date: Wed, 1 Jul 2026 10:02:45 +0200 Subject: [PATCH 1/3] Add IT coverage for UNOMI-933/934/935 input validation at REST layer. Extend context.json validation tests and add REST create tests so bad payloads return 400 instead of 500. --- .../java/org/apache/unomi/itests/AllITs.java | 1 + .../unomi/itests/InputValidationIT.java | 36 +++++++ .../unomi/itests/RestCreateValidationIT.java | 102 ++++++++++++++++++ .../contextRequest_eventEmptySource.json | 9 ++ .../contextRequest_eventEmptyTarget.json | 9 ++ .../validation/rule_invalidCondition.json | 18 ++++ .../validation/segment_invalidCondition.json | 13 +++ 7 files changed, 188 insertions(+) create mode 100644 itests/src/test/java/org/apache/unomi/itests/RestCreateValidationIT.java create mode 100644 itests/src/test/resources/validation/contextRequest_eventEmptySource.json create mode 100644 itests/src/test/resources/validation/contextRequest_eventEmptyTarget.json create mode 100644 itests/src/test/resources/validation/rule_invalidCondition.json create mode 100644 itests/src/test/resources/validation/segment_invalidCondition.json diff --git a/itests/src/test/java/org/apache/unomi/itests/AllITs.java b/itests/src/test/java/org/apache/unomi/itests/AllITs.java index ace015dfb0..3ea7dd3400 100644 --- a/itests/src/test/java/org/apache/unomi/itests/AllITs.java +++ b/itests/src/test/java/org/apache/unomi/itests/AllITs.java @@ -51,6 +51,7 @@ CopyPropertiesActionIT.class, IncrementPropertyIT.class, InputValidationIT.class, + RestCreateValidationIT.class, ModifyConsentIT.class, PatchIT.class, ContextServletIT.class, diff --git a/itests/src/test/java/org/apache/unomi/itests/InputValidationIT.java b/itests/src/test/java/org/apache/unomi/itests/InputValidationIT.java index 316ca53540..efe3edcc3f 100644 --- a/itests/src/test/java/org/apache/unomi/itests/InputValidationIT.java +++ b/itests/src/test/java/org/apache/unomi/itests/InputValidationIT.java @@ -235,6 +235,42 @@ public void test_cookie_profileIdPattern() throws Exception { doGETRequestTest(CONTEXT_JS_URL, headers, null, 200, null); } + + /** + * UNOMI-933: bad event payloads on {@code /context.json} must return 400, not 500. + */ + @Test + public void test_contextRequest_garbageBody() throws Exception { + String url = CONTEXT_JSON_URL + "?sessionId=dummy-session-id"; + doPOSTRawBodyTest(url, "foo", 400, ERROR_MESSAGE_INVALID_DATA_RECEIVED); + } + + @Test + public void test_contextRequest_eventEmptySource() throws Exception { + doPOSTRequestTest(CONTEXT_JSON_URL, null, "/validation/contextRequest_eventEmptySource.json", 400, + ERROR_MESSAGE_INVALID_DATA_RECEIVED); + } + + @Test + public void test_contextRequest_eventEmptyTarget() throws Exception { + doPOSTRequestTest(CONTEXT_JSON_URL, null, "/validation/contextRequest_eventEmptyTarget.json", 400, + ERROR_MESSAGE_INVALID_DATA_RECEIVED); + } + + private void doPOSTRawBodyTest(String uri, String rawBody, int expectedHTTPStatusCode, String expectedErrorMessage) + throws Exception { + performPOSTRawBodyTest(getFullUrl(uri), rawBody, expectedHTTPStatusCode, expectedErrorMessage); + performPOSTRawBodyTest(getFullUrl("/cxs" + uri), rawBody, expectedHTTPStatusCode, expectedErrorMessage); + } + + private void performPOSTRawBodyTest(String url, String rawBody, int expectedHTTPStatusCode, String expectedErrorMessage) + throws IOException { + HttpPost request = new HttpPost(url); + request.setHeader("Content-Type", "application/json"); + request.setEntity(new StringEntity(rawBody, ContentType.APPLICATION_JSON)); + performRequest(request, null, expectedHTTPStatusCode, expectedErrorMessage); + } + private void doGETRequestTest(String uri, Map headers, String entityResourcePath, int expectedHTTPStatusCode, String expectedErrorMessage) throws Exception { // test old servlets performGETRequestTest(getFullUrl(uri), headers, entityResourcePath, expectedHTTPStatusCode, expectedErrorMessage); diff --git a/itests/src/test/java/org/apache/unomi/itests/RestCreateValidationIT.java b/itests/src/test/java/org/apache/unomi/itests/RestCreateValidationIT.java new file mode 100644 index 0000000000..63ffd588b9 --- /dev/null +++ b/itests/src/test/java/org/apache/unomi/itests/RestCreateValidationIT.java @@ -0,0 +1,102 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.unomi.itests; + +import com.fasterxml.jackson.databind.JsonNode; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.ContentType; +import org.apache.http.entity.StringEntity; +import org.apache.http.util.EntityUtils; +import org.apache.unomi.itests.tools.LogChecker; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.ops4j.pax.exam.junit.PaxExam; +import org.ops4j.pax.exam.spi.reactors.ExamReactorStrategy; +import org.ops4j.pax.exam.spi.reactors.PerSuite; + +import java.io.IOException; +import java.util.HashMap; + +import static org.junit.Assert.assertEquals; + +/** + * Integration tests for REST create endpoints returning HTTP 400 on invalid payloads + * (UNOMI-934 rules, UNOMI-935 segments). + */ +@RunWith(PaxExam.class) +@ExamReactorStrategy(PerSuite.class) +public class RestCreateValidationIT extends BaseIT { + + private static final String RULES_URL = "/cxs/rules"; + private static final String SEGMENTS_URL = "/cxs/segments"; + + @Override + protected LogChecker createLogChecker() { + return LogChecker.builder() + .addIgnoredSubstring("Response status code: 400") + .addIgnoredSubstring("Invalid rule condition") + .addIgnoredSubstring("BadSegmentConditionException") + .addIgnoredSubstring("IllegalArgumentExceptionMapper") + .addIgnoredSubstring("BadSegmentConditionExceptionMapper") + .addIgnoredSubstring("JsonMappingExceptionMapper") + .addIgnoredSubstring("RuntimeExceptionMapper") + .build(); + } + + @Test + public void testCreateRuleWithInvalidCondition_returnsBadRequest() throws Exception { + doPostJsonTest(RULES_URL, "/validation/rule_invalidCondition.json", 400); + } + + @Test + public void testCreateRuleWithMalformedJson_returnsBadRequest() throws Exception { + doPostRawBodyTest(RULES_URL, "foo", 400); + } + + @Test + public void testCreateSegmentWithInvalidCondition_returnsBadRequest() throws Exception { + doPostJsonTest(SEGMENTS_URL, "/validation/segment_invalidCondition.json", 400); + } + + @Test + public void testCreateSegmentWithMalformedJson_returnsBadRequest() throws Exception { + doPostRawBodyTest(SEGMENTS_URL, "foo", 400); + } + + private void doPostJsonTest(String uri, String entityResourcePath, int expectedStatusCode) throws Exception { + doPostRawBodyTest(uri, getValidatedBundleJSON(entityResourcePath, new HashMap<>()), expectedStatusCode); + } + + private void doPostRawBodyTest(String uri, String body, int expectedStatusCode) throws Exception { + performRestPostTest(getFullUrl(uri), body, expectedStatusCode); + } + + private void performRestPostTest(String url, String body, int expectedStatusCode) throws IOException { + HttpPost request = new HttpPost(url); + request.setHeader("Content-Type", "application/json"); + request.setEntity(new StringEntity(body, ContentType.APPLICATION_JSON)); + try (CloseableHttpResponse response = executeHttpRequest(request, AuthType.AUTO)) { + assertEquals("Unexpected HTTP status for POST " + url, expectedStatusCode, response.getStatusLine().getStatusCode()); + if (expectedStatusCode == 400) { + String responseBody = EntityUtils.toString(response.getEntity()); + JsonNode json = objectMapper.readTree(responseBody); + assertEquals("badRequest", json.get("errorMessage").asText()); + } + } + } +} diff --git a/itests/src/test/resources/validation/contextRequest_eventEmptySource.json b/itests/src/test/resources/validation/contextRequest_eventEmptySource.json new file mode 100644 index 0000000000..725762e642 --- /dev/null +++ b/itests/src/test/resources/validation/contextRequest_eventEmptySource.json @@ -0,0 +1,9 @@ +{ + "sessionId": "dummy-session-id", + "events": [ + { + "eventType": "view", + "source": {} + } + ] +} diff --git a/itests/src/test/resources/validation/contextRequest_eventEmptyTarget.json b/itests/src/test/resources/validation/contextRequest_eventEmptyTarget.json new file mode 100644 index 0000000000..1e1ff7541f --- /dev/null +++ b/itests/src/test/resources/validation/contextRequest_eventEmptyTarget.json @@ -0,0 +1,9 @@ +{ + "sessionId": "dummy-session-id", + "events": [ + { + "eventType": "view", + "target": {} + } + ] +} diff --git a/itests/src/test/resources/validation/rule_invalidCondition.json b/itests/src/test/resources/validation/rule_invalidCondition.json new file mode 100644 index 0000000000..e5bf459ba5 --- /dev/null +++ b/itests/src/test/resources/validation/rule_invalidCondition.json @@ -0,0 +1,18 @@ +{ + "metadata": { + "id": "it-validation-invalid-rule", + "name": "Invalid rule IT", + "scope": "systemscope", + "enabled": true + }, + "condition": {}, + "actions": [ + { + "type": "setPropertyAction", + "parameterValues": { + "setPropertyName": "properties.itValidation", + "setPropertyValue": "test" + } + } + ] +} diff --git a/itests/src/test/resources/validation/segment_invalidCondition.json b/itests/src/test/resources/validation/segment_invalidCondition.json new file mode 100644 index 0000000000..d184f0fe8f --- /dev/null +++ b/itests/src/test/resources/validation/segment_invalidCondition.json @@ -0,0 +1,13 @@ +{ + "metadata": { + "id": "it-validation-invalid-segment", + "name": "Invalid segment IT", + "scope": "systemscope" + }, + "condition": { + "type": "fakeConditionId", + "parameterValues": { + "param": "param value" + } + } +} From 4fd128fcec8d6ed3bcfaaef653a0510cadeef3bd Mon Sep 17 00:00:00 2001 From: Serge Huber Date: Wed, 1 Jul 2026 10:55:20 +0200 Subject: [PATCH 2/3] Fix UNOMI-933 context IT assertions for dual 400 response formats. Accept plain-text InvalidRequestException bodies or JSON badRequest from Jackson mappers on /context.json. --- .../unomi/itests/InputValidationIT.java | 64 +++++++++++++++---- 1 file changed, 53 insertions(+), 11 deletions(-) diff --git a/itests/src/test/java/org/apache/unomi/itests/InputValidationIT.java b/itests/src/test/java/org/apache/unomi/itests/InputValidationIT.java index efe3edcc3f..5e03b3095f 100644 --- a/itests/src/test/java/org/apache/unomi/itests/InputValidationIT.java +++ b/itests/src/test/java/org/apache/unomi/itests/InputValidationIT.java @@ -16,6 +16,7 @@ */ package org.apache.unomi.itests; +import com.fasterxml.jackson.databind.JsonNode; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; @@ -238,37 +239,78 @@ public void test_cookie_profileIdPattern() throws Exception { /** * UNOMI-933: bad event payloads on {@code /context.json} must return 400, not 500. + * Deserialization failures return JSON {@code badRequest}; schema validation returns plain text. */ @Test public void test_contextRequest_garbageBody() throws Exception { String url = CONTEXT_JSON_URL + "?sessionId=dummy-session-id"; - doPOSTRawBodyTest(url, "foo", 400, ERROR_MESSAGE_INVALID_DATA_RECEIVED); + doPOSTRawBodyTestBadRequest(url, "foo"); } @Test public void test_contextRequest_eventEmptySource() throws Exception { - doPOSTRequestTest(CONTEXT_JSON_URL, null, "/validation/contextRequest_eventEmptySource.json", 400, - ERROR_MESSAGE_INVALID_DATA_RECEIVED); + doPOSTRequestTestBadRequest(CONTEXT_JSON_URL, null, "/validation/contextRequest_eventEmptySource.json"); } @Test public void test_contextRequest_eventEmptyTarget() throws Exception { - doPOSTRequestTest(CONTEXT_JSON_URL, null, "/validation/contextRequest_eventEmptyTarget.json", 400, - ERROR_MESSAGE_INVALID_DATA_RECEIVED); + doPOSTRequestTestBadRequest(CONTEXT_JSON_URL, null, "/validation/contextRequest_eventEmptyTarget.json"); } - private void doPOSTRawBodyTest(String uri, String rawBody, int expectedHTTPStatusCode, String expectedErrorMessage) + private void doPOSTRawBodyTestBadRequest(String uri, String rawBody) throws Exception { + performPOSTRawBodyTestBadRequest(getFullUrl(uri), rawBody); + performPOSTRawBodyTestBadRequest(getFullUrl("/cxs" + uri), rawBody); + } + + private void doPOSTRequestTestBadRequest(String uri, Map headers, String entityResourcePath) throws Exception { - performPOSTRawBodyTest(getFullUrl(uri), rawBody, expectedHTTPStatusCode, expectedErrorMessage); - performPOSTRawBodyTest(getFullUrl("/cxs" + uri), rawBody, expectedHTTPStatusCode, expectedErrorMessage); + performPOSTRequestTestBadRequest(getFullUrl(uri), headers, entityResourcePath); + performPOSTRequestTestBadRequest(getFullUrl("/cxs" + uri), headers, entityResourcePath); } - private void performPOSTRawBodyTest(String url, String rawBody, int expectedHTTPStatusCode, String expectedErrorMessage) - throws IOException { + private void performPOSTRawBodyTestBadRequest(String url, String rawBody) throws IOException { HttpPost request = new HttpPost(url); request.setHeader("Content-Type", "application/json"); request.setEntity(new StringEntity(rawBody, ContentType.APPLICATION_JSON)); - performRequest(request, null, expectedHTTPStatusCode, expectedErrorMessage); + performRequestExpectBadRequest(request, null); + } + + private void performPOSTRequestTestBadRequest(String url, Map headers, String entityResourcePath) + throws IOException { + HttpPost request = new HttpPost(url); + if (headers == null) { + headers = new HashMap<>(); + } + headers.put("Content-Type", "application/json"); + if (entityResourcePath != null) { + request.setEntity(new StringEntity(getValidatedBundleJSON(entityResourcePath, new HashMap<>()), ContentType.create("application/json"))); + } + performRequestExpectBadRequest(request, headers); + } + + private void performRequestExpectBadRequest(HttpUriRequest request, Map headers) throws IOException { + CloseableHttpResponse response; + if (headers != null && !headers.isEmpty()) { + for (Map.Entry headerEntry : headers.entrySet()) { + request.addHeader(headerEntry.getKey(), headerEntry.getValue()); + } + } + try { + response = HttpClientThatWaitsForUnomi.doRequest(request, 400); + } catch (Exception e) { + fail("Something went wrong with the request to Unomi that is unexpected: " + e.getMessage()); + return; + } + assertEquals("Invalid response code", 400, response.getStatusLine().getStatusCode()); + assertInvalidClientDataResponse(EntityUtils.toString(response.getEntity())); + } + + private void assertInvalidClientDataResponse(String responseBody) throws IOException { + if (ERROR_MESSAGE_INVALID_DATA_RECEIVED.equals(responseBody)) { + return; + } + JsonNode json = objectMapper.readTree(responseBody); + assertEquals("badRequest", json.get("errorMessage").asText()); } private void doGETRequestTest(String uri, Map headers, String entityResourcePath, int expectedHTTPStatusCode, String expectedErrorMessage) throws Exception { From b32d762184c8b9d0712a372e52b0f323b7635dd7 Mon Sep 17 00:00:00 2001 From: Serge Huber Date: Wed, 1 Jul 2026 11:28:58 +0200 Subject: [PATCH 3/3] test: Harden UNOMI-933/934/935 IT coverage after code review - RestCreateValidationIT: add @After teardown, happy-path 204 tests, retry-aware HTTP client, null-safe body assertion, scoped LogChecker - InputValidationIT: extend garbageBody to cover /context.js, add GET variants for eventEmptySource/Target, null-safe assertInvalidClientDataResponse - Add rule_valid.json and segment_valid.json fixtures --- .../unomi/itests/InputValidationIT.java | 35 ++++++++-- .../unomi/itests/RestCreateValidationIT.java | 66 ++++++++++++++----- .../test/resources/validation/rule_valid.json | 21 ++++++ .../resources/validation/segment_valid.json | 12 ++++ 4 files changed, 114 insertions(+), 20 deletions(-) create mode 100644 itests/src/test/resources/validation/rule_valid.json create mode 100644 itests/src/test/resources/validation/segment_valid.json diff --git a/itests/src/test/java/org/apache/unomi/itests/InputValidationIT.java b/itests/src/test/java/org/apache/unomi/itests/InputValidationIT.java index 5e03b3095f..dd9ac69a7e 100644 --- a/itests/src/test/java/org/apache/unomi/itests/InputValidationIT.java +++ b/itests/src/test/java/org/apache/unomi/itests/InputValidationIT.java @@ -43,6 +43,7 @@ import java.util.Objects; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.fail; @RunWith(PaxExam.class) @@ -238,30 +239,52 @@ public void test_cookie_profileIdPattern() throws Exception { /** - * UNOMI-933: bad event payloads on {@code /context.json} must return 400, not 500. - * Deserialization failures return JSON {@code badRequest}; schema validation returns plain text. + * UNOMI-933: unparseable context request bodies on {@code /context.json} and {@code /context.js} + * must return 400, not 500. Deserialization failures return JSON {@code {"errorMessage":"badRequest"}}; + * schema-level validation returns plain text. */ @Test public void test_contextRequest_garbageBody() throws Exception { - String url = CONTEXT_JSON_URL + "?sessionId=dummy-session-id"; - doPOSTRawBodyTestBadRequest(url, "foo"); + // test JSON endpoint (and its /cxs/ mirror via doPOSTRawBodyTestBadRequest) + doPOSTRawBodyTestBadRequest(CONTEXT_JSON_URL + "?sessionId=dummy-session-id", "foo"); + // test JS endpoint (and its /cxs/ mirror) + doPOSTRawBodyTestBadRequest(CONTEXT_JS_URL + "?sessionId=dummy-session-id", "foo"); } @Test public void test_contextRequest_eventEmptySource() throws Exception { doPOSTRequestTestBadRequest(CONTEXT_JSON_URL, null, "/validation/contextRequest_eventEmptySource.json"); + doGETRequestTestBadRequest(CONTEXT_JSON_URL, "/validation/contextRequest_eventEmptySource.json"); } @Test public void test_contextRequest_eventEmptyTarget() throws Exception { doPOSTRequestTestBadRequest(CONTEXT_JSON_URL, null, "/validation/contextRequest_eventEmptyTarget.json"); + doGETRequestTestBadRequest(CONTEXT_JSON_URL, "/validation/contextRequest_eventEmptyTarget.json"); } private void doPOSTRawBodyTestBadRequest(String uri, String rawBody) throws Exception { + // test old servlets performPOSTRawBodyTestBadRequest(getFullUrl(uri), rawBody); + // test directly CXS endpoints performPOSTRawBodyTestBadRequest(getFullUrl("/cxs" + uri), rawBody); } + private void doGETRequestTestBadRequest(String uri, String entityResourcePath) throws Exception { + // test old servlets + performGETRequestTestBadRequest(getFullUrl(uri), entityResourcePath); + // test directly CXS endpoints + performGETRequestTestBadRequest(getFullUrl("/cxs" + uri), entityResourcePath); + } + + private void performGETRequestTestBadRequest(String url, String entityResourcePath) throws Exception { + if (entityResourcePath != null) { + String payload = getValidatedBundleJSON(entityResourcePath, new HashMap<>()); + url += (url.contains("?") ? "&" : "?") + "payload=" + URLEncoder.encode(payload, StandardCharsets.UTF_8.toString()); + } + performRequestExpectBadRequest(new HttpGet(url), null); + } + private void doPOSTRequestTestBadRequest(String uri, Map headers, String entityResourcePath) throws Exception { performPOSTRequestTestBadRequest(getFullUrl(uri), headers, entityResourcePath); @@ -310,7 +333,9 @@ private void assertInvalidClientDataResponse(String responseBody) throws IOExcep return; } JsonNode json = objectMapper.readTree(responseBody); - assertEquals("badRequest", json.get("errorMessage").asText()); + JsonNode errorNode = json.get("errorMessage"); + assertNotNull("Response JSON missing 'errorMessage' field. Body: " + responseBody, errorNode); + assertEquals("badRequest", errorNode.asText()); } private void doGETRequestTest(String uri, Map headers, String entityResourcePath, int expectedHTTPStatusCode, String expectedErrorMessage) throws Exception { diff --git a/itests/src/test/java/org/apache/unomi/itests/RestCreateValidationIT.java b/itests/src/test/java/org/apache/unomi/itests/RestCreateValidationIT.java index 63ffd588b9..cff021b3a3 100644 --- a/itests/src/test/java/org/apache/unomi/itests/RestCreateValidationIT.java +++ b/itests/src/test/java/org/apache/unomi/itests/RestCreateValidationIT.java @@ -22,7 +22,11 @@ import org.apache.http.entity.ContentType; import org.apache.http.entity.StringEntity; import org.apache.http.util.EntityUtils; +import org.apache.unomi.api.rules.Rule; +import org.apache.unomi.api.segments.Segment; import org.apache.unomi.itests.tools.LogChecker; +import org.apache.unomi.itests.tools.httpclient.HttpClientThatWaitsForUnomi; +import org.junit.After; import org.junit.Test; import org.junit.runner.RunWith; import org.ops4j.pax.exam.junit.PaxExam; @@ -33,10 +37,13 @@ import java.util.HashMap; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.fail; /** * Integration tests for REST create endpoints returning HTTP 400 on invalid payloads - * (UNOMI-934 rules, UNOMI-935 segments). + * (UNOMI-934 rules, UNOMI-935 segments). Verifies both the HTTP status code and the + * JSON response contract: {@code {"errorMessage":"badRequest"}}. */ @RunWith(PaxExam.class) @ExamReactorStrategy(PerSuite.class) @@ -54,13 +61,29 @@ protected LogChecker createLogChecker() { .addIgnoredSubstring("IllegalArgumentExceptionMapper") .addIgnoredSubstring("BadSegmentConditionExceptionMapper") .addIgnoredSubstring("JsonMappingExceptionMapper") - .addIgnoredSubstring("RuntimeExceptionMapper") + // RuntimeExceptionMapper logs "Bad request on ..." at WARN for client errors + .addIgnoredSubstring("Bad request on") .build(); } + @After + public void tearDown() throws InterruptedException { + removeItems(Rule.class, Segment.class); + } + + @Test + public void testCreateValidRule_returns204() throws Exception { + doPostRawBodyTest(RULES_URL, getValidatedBundleJSON("/validation/rule_valid.json", new HashMap<>()), 204); + } + + @Test + public void testCreateValidSegment_returns204() throws Exception { + doPostRawBodyTest(SEGMENTS_URL, getValidatedBundleJSON("/validation/segment_valid.json", new HashMap<>()), 204); + } + @Test public void testCreateRuleWithInvalidCondition_returnsBadRequest() throws Exception { - doPostJsonTest(RULES_URL, "/validation/rule_invalidCondition.json", 400); + doPostRawBodyTest(RULES_URL, getValidatedBundleJSON("/validation/rule_invalidCondition.json", new HashMap<>()), 400); } @Test @@ -70,7 +93,7 @@ public void testCreateRuleWithMalformedJson_returnsBadRequest() throws Exception @Test public void testCreateSegmentWithInvalidCondition_returnsBadRequest() throws Exception { - doPostJsonTest(SEGMENTS_URL, "/validation/segment_invalidCondition.json", 400); + doPostRawBodyTest(SEGMENTS_URL, getValidatedBundleJSON("/validation/segment_invalidCondition.json", new HashMap<>()), 400); } @Test @@ -78,10 +101,6 @@ public void testCreateSegmentWithMalformedJson_returnsBadRequest() throws Except doPostRawBodyTest(SEGMENTS_URL, "foo", 400); } - private void doPostJsonTest(String uri, String entityResourcePath, int expectedStatusCode) throws Exception { - doPostRawBodyTest(uri, getValidatedBundleJSON(entityResourcePath, new HashMap<>()), expectedStatusCode); - } - private void doPostRawBodyTest(String uri, String body, int expectedStatusCode) throws Exception { performRestPostTest(getFullUrl(uri), body, expectedStatusCode); } @@ -90,13 +109,30 @@ private void performRestPostTest(String url, String body, int expectedStatusCode HttpPost request = new HttpPost(url); request.setHeader("Content-Type", "application/json"); request.setEntity(new StringEntity(body, ContentType.APPLICATION_JSON)); - try (CloseableHttpResponse response = executeHttpRequest(request, AuthType.AUTO)) { - assertEquals("Unexpected HTTP status for POST " + url, expectedStatusCode, response.getStatusLine().getStatusCode()); - if (expectedStatusCode == 400) { - String responseBody = EntityUtils.toString(response.getEntity()); - JsonNode json = objectMapper.readTree(responseBody); - assertEquals("badRequest", json.get("errorMessage").asText()); - } + CloseableHttpResponse response; + try { + response = HttpClientThatWaitsForUnomi.doRequest(request, expectedStatusCode); + } catch (Exception e) { + fail("Request to " + url + " failed unexpectedly: " + e.getMessage()); + return; + } + assertEquals("Unexpected HTTP status for POST " + url, expectedStatusCode, response.getStatusLine().getStatusCode()); + if (expectedStatusCode == 400) { + assertBadRequestResponse(url, EntityUtils.toString(response.getEntity())); + } + } + + private void assertBadRequestResponse(String url, String responseBody) throws IOException { + assertNotNull("Expected a response body for 400 on POST " + url, responseBody); + JsonNode json; + try { + json = objectMapper.readTree(responseBody); + } catch (Exception e) { + fail("Expected JSON in 400 response for POST " + url + " but got: " + responseBody); + return; } + JsonNode errorNode = json.get("errorMessage"); + assertNotNull("Response JSON missing 'errorMessage' field for POST " + url + ". Body: " + responseBody, errorNode); + assertEquals("badRequest", errorNode.asText()); } } diff --git a/itests/src/test/resources/validation/rule_valid.json b/itests/src/test/resources/validation/rule_valid.json new file mode 100644 index 0000000000..02e5b12c8b --- /dev/null +++ b/itests/src/test/resources/validation/rule_valid.json @@ -0,0 +1,21 @@ +{ + "metadata": { + "id": "it-validation-valid-rule", + "name": "Valid rule IT", + "scope": "systemscope", + "enabled": false + }, + "condition": { + "type": "matchAllCondition", + "parameterValues": {} + }, + "actions": [ + { + "type": "setPropertyAction", + "parameterValues": { + "setPropertyName": "properties.itValidation", + "setPropertyValue": "test" + } + } + ] +} diff --git a/itests/src/test/resources/validation/segment_valid.json b/itests/src/test/resources/validation/segment_valid.json new file mode 100644 index 0000000000..a02f49f630 --- /dev/null +++ b/itests/src/test/resources/validation/segment_valid.json @@ -0,0 +1,12 @@ +{ + "metadata": { + "id": "it-validation-valid-segment", + "name": "Valid segment IT", + "scope": "systemscope", + "enabled": false + }, + "condition": { + "type": "matchAllCondition", + "parameterValues": {} + } +}