From ae01c874a55b72b28673267472c43e257765ef83 Mon Sep 17 00:00:00 2001 From: almeidast Date: Mon, 30 Sep 2024 20:04:12 +0100 Subject: [PATCH 01/14] create a method to search routes and listeners by parameter "q" --- .../swisspush/gateleen/hook/HookHandler.java | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/gateleen-hook/src/main/java/org/swisspush/gateleen/hook/HookHandler.java b/gateleen-hook/src/main/java/org/swisspush/gateleen/hook/HookHandler.java index 66b1a59e..509d8038 100755 --- a/gateleen-hook/src/main/java/org/swisspush/gateleen/hook/HookHandler.java +++ b/gateleen-hook/src/main/java/org/swisspush/gateleen/hook/HookHandler.java @@ -569,6 +569,14 @@ public boolean handle(final RoutingContext ctx) { } } + HttpServerResponse response = ctx.response(); + String queryParam = request.getParam("q"); + + if ((queryParam != null) && !queryParam.isEmpty()) { + this.handleHookSearch(queryParam,response); + return true; + } + /* * 2) Check if we have to queue a request for listeners */ @@ -592,6 +600,39 @@ public boolean handle(final RoutingContext ctx) { } } + /** + * Handles hook search requests based on the 'destination' property. + * Searches in both routes and listeners. + * + * @param queryParam the RoutingContext of the request + */ + public void handleHookSearch(String queryParam,HttpServerResponse response) { + JsonObject result = new JsonObject(); + JsonArray matchingRoutes = new JsonArray(); + JsonArray matchingListeners = new JsonArray(); + + // Search routes by destination + routeRepository.getRoutes().forEach((routeKey, route) -> { + if (route.getHook().getDestination().contains(queryParam)) { + matchingRoutes.add(routeKey); + } + }); + + // Search listeners by destination + listenerRepository.getListeners().forEach(listener -> { + if (listener.getHook().getDestination().contains(queryParam)) { + matchingListeners.add(listener.getListenerId()); + } + }); + + // Build and send the response + result.put("routes", matchingRoutes); + result.put("listeners", matchingListeners); + + response.putHeader("content-type", "application/json").end(result.encode()); + } + + /** * Create a listing of routes in the given parent. This happens * only if we have a GET request, the routes are listable and From 9e39c832b80c4aad44dc309e7f298ff1b04516b7 Mon Sep 17 00:00:00 2001 From: almeidast Date: Thu, 3 Oct 2024 08:52:33 +0100 Subject: [PATCH 02/14] Fix null pointer in HookHandler Add the tests to cover new implementations lto cover HookHandlerSearch --- .../swisspush/gateleen/hook/HookHandler.java | 10 +- .../gateleen/hook/HookHandlerTest.java | 141 ++++++++++++++++++ 2 files changed, 148 insertions(+), 3 deletions(-) diff --git a/gateleen-hook/src/main/java/org/swisspush/gateleen/hook/HookHandler.java b/gateleen-hook/src/main/java/org/swisspush/gateleen/hook/HookHandler.java index 509d8038..3c67e360 100755 --- a/gateleen-hook/src/main/java/org/swisspush/gateleen/hook/HookHandler.java +++ b/gateleen-hook/src/main/java/org/swisspush/gateleen/hook/HookHandler.java @@ -570,10 +570,14 @@ public boolean handle(final RoutingContext ctx) { } HttpServerResponse response = ctx.response(); - String queryParam = request.getParam("q"); + String queryParam = null; - if ((queryParam != null) && !queryParam.isEmpty()) { - this.handleHookSearch(queryParam,response); + if (request.params() != null) { + queryParam = request.getParam("q"); + } + + if (queryParam != null && !queryParam.isEmpty()) { + this.handleHookSearch(queryParam, response); return true; } diff --git a/gateleen-hook/src/test/java/org/swisspush/gateleen/hook/HookHandlerTest.java b/gateleen-hook/src/test/java/org/swisspush/gateleen/hook/HookHandlerTest.java index 0dbef201..17ac1f21 100644 --- a/gateleen-hook/src/test/java/org/swisspush/gateleen/hook/HookHandlerTest.java +++ b/gateleen-hook/src/test/java/org/swisspush/gateleen/hook/HookHandlerTest.java @@ -15,6 +15,7 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mockito; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -31,11 +32,13 @@ import org.swisspush.gateleen.queue.queuing.RequestQueue; import org.swisspush.gateleen.routing.Router; +import java.lang.reflect.Field; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Set; import java.util.concurrent.CountDownLatch; +import java.util.Map; import static io.vertx.core.http.HttpMethod.PUT; import static org.junit.Assert.assertEquals; @@ -641,6 +644,144 @@ public void hookRegistration_RouteWithRouteMultiplier(TestContext testContext) t vertx.eventBus().request("gateleen.hook-route-remove", "pathToRouterResource"); } + @Test + public void testHookHandleSearchWithMatchingRoutesAndListeners(TestContext context) throws Exception { + // Mock the response + HttpServerResponse response = Mockito.mock(HttpServerResponse.class); + Mockito.when(response.putHeader(anyString(), anyString())).thenReturn(response); + + // Mock the request and set the query parameter + HttpServerRequest request = Mockito.mock(HttpServerRequest.class); + Mockito.when(request.response()).thenReturn(response); + Mockito.when(request.getParam("q")).thenReturn("destination"); + + // Mock the route and listener + Route mockRoute = Mockito.mock(Route.class); + HttpHook mockHook = new HttpHook("destination/matching"); + Mockito.when(mockRoute.getHook()).thenReturn(mockHook); + + Listener mockListener = new Listener("listener1", "monitoredUrl", "destination/matching", mockHook); + + // Mock repositories + ListenerRepository listenerRepository = Mockito.mock(ListenerRepository.class); + RouteRepository routeRepository = Mockito.mock(RouteRepository.class); + + // Configure mocked behavior for repositories + Mockito.when(routeRepository.getRoutes()).thenReturn(Map.of("route1", mockRoute)); + Mockito.when(listenerRepository.getListeners()).thenReturn(Collections.singletonList(mockListener)); + + // Create HookHandler instance + HookHandler hookHandler = new HookHandler(vertx, httpClient, storage, loggingResourceManager, + logAppenderRepository, monitoringHandler, "userProfilePath", HOOK_ROOT_URI, requestQueue, + false + ); + + // Use reflection to set private fields + setPrivateField(hookHandler, "listenerRepository", listenerRepository); + setPrivateField(hookHandler, "routeRepository", routeRepository); + + // Call the method under test + hookHandler.handleHookSearch("destination", response); + + // Capture the output and verify the response was sent correctly + ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); + Mockito.verify(response).end(captor.capture()); + + // Check the result + String capturedResult = captor.getValue(); + JsonObject result = new JsonObject(capturedResult); + JsonArray routes = result.getJsonArray("routes"); + JsonArray listeners = result.getJsonArray("listeners"); + + // Assert the expected results + context.assertTrue(routes.contains("route1")); + context.assertTrue(listeners.contains("listener1")); + + // Verify the content-type header was set correctly + Mockito.verify(response).putHeader("content-type", "application/json"); + } + + + private void setPrivateField(Object target, String fieldName, Object value) throws Exception { + Field field = target.getClass().getDeclaredField(fieldName); + field.setAccessible(true); + field.set(target, value); + } + + @Test + public void testHookHandleSearchWithNoMatchingRoutesOrListeners(TestContext context) throws Exception { + // Mock the response + HttpServerResponse response = Mockito.mock(HttpServerResponse.class); + Mockito.when(response.putHeader(anyString(), anyString())).thenReturn(response); + + // Mock the request and set the query parameter + HttpServerRequest request = Mockito.mock(HttpServerRequest.class); + Mockito.when(request.response()).thenReturn(response); + Mockito.when(request.getParam("q")).thenReturn("destination"); + + // Mock the route and listener + Route mockRoute = Mockito.mock(Route.class); + HttpHook mockHook = new HttpHook("destination/matching"); + Mockito.when(mockRoute.getHook()).thenReturn(mockHook); + + Listener mockListener = new Listener("listener1", "monitoredUrl", "destination/matching", mockHook); + + // Mock repositories + ListenerRepository listenerRepository = Mockito.mock(ListenerRepository.class); + RouteRepository routeRepository = Mockito.mock(RouteRepository.class); + + // Configure mocked behavior for repositories with no matching routes or listeners + Mockito.when(routeRepository.getRoutes()).thenReturn(Map.of()); + Mockito.when(listenerRepository.getListeners()).thenReturn(Collections.emptyList()); + + // Create HookHandler instance + HookHandler hookHandler = new HookHandler(vertx, httpClient, storage, loggingResourceManager, + logAppenderRepository, monitoringHandler, "userProfilePath", HOOK_ROOT_URI, requestQueue, + false + ); + + // Use reflection to set private fields + setPrivateField(hookHandler, "listenerRepository", listenerRepository); + setPrivateField(hookHandler, "routeRepository", routeRepository); + + // Call the method under test + hookHandler.handleHookSearch("destination", response); + + // Capture the output and verify the response was sent correctly + ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); + Mockito.verify(response).end(captor.capture()); + + // Check the result + String capturedResult = captor.getValue(); + JsonObject result = new JsonObject(capturedResult); + JsonArray routes = result.getJsonArray("routes"); + JsonArray listeners = result.getJsonArray("listeners"); + + // Assert that there are no matching routes or listeners + context.assertTrue(routes.isEmpty()); + context.assertTrue(listeners.isEmpty()); + + // Verify the content-type header was set correctly + Mockito.verify(response).putHeader("content-type", "application/json"); + } + + @Test + public void testHookHandleSearchWithInvalidQueryParam(TestContext context) { + // Mocking the HttpServerResponse and request objects + HttpServerResponse response = Mockito.mock(HttpServerResponse.class); + Mockito.when(response.putHeader(anyString(), anyString())).thenReturn(response); + + HttpServerRequest request = Mockito.mock(HttpServerRequest.class); + Mockito.when(request.response()).thenReturn(response); + Mockito.when(request.getParam("q")).thenReturn(null); + + // Call hookHandleSearch + hookHandler.handleHookSearch(null, response); + + // Verify that nothing is returned + Mockito.verify(response, Mockito.never()).end(any(Buffer.class)); + } + /////////////////////////////////////////////////////////////////////////////// // Helpers From d9f1c769bd9559f8d46a29be0a4f9141dbccf4e8 Mon Sep 17 00:00:00 2001 From: almeidast Date: Wed, 9 Oct 2024 21:44:09 +0100 Subject: [PATCH 03/14] Fix tests and implement more validations. Add new tests for HookHandler Create testes with storage --- .../swisspush/gateleen/hook/HookHandler.java | 91 +++--- .../gateleen/hook/HookHandlerTest.java | 268 +++++++++--------- 2 files changed, 184 insertions(+), 175 deletions(-) diff --git a/gateleen-hook/src/main/java/org/swisspush/gateleen/hook/HookHandler.java b/gateleen-hook/src/main/java/org/swisspush/gateleen/hook/HookHandler.java index 3c67e360..d54a3431 100755 --- a/gateleen-hook/src/main/java/org/swisspush/gateleen/hook/HookHandler.java +++ b/gateleen-hook/src/main/java/org/swisspush/gateleen/hook/HookHandler.java @@ -70,6 +70,7 @@ import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; +import java.util.function.Function; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -123,6 +124,13 @@ public class HookHandler implements LoggableResource { public static final String LISTABLE = "listable"; public static final String COLLECTION = "collection"; + private static final String CONTENT_TYPE_JSON = "application/json"; + private static final String LISTENERS_KEY = "listeners"; + private static final String ROUTES_KEY = "routes"; + private static final String DESTINATION_KEY = "destination"; + private static final String CONTENT_TYPE_HEADER = "content-type"; + + private final Comparator collectionContentComparator; private static final Logger log = LoggerFactory.getLogger(HookHandler.class); @@ -569,16 +577,24 @@ public boolean handle(final RoutingContext ctx) { } } - HttpServerResponse response = ctx.response(); - String queryParam = null; - - if (request.params() != null) { - queryParam = request.getParam("q"); - } + // 1. Check if the request method is GET + if (request.method() == HttpMethod.GET) { + String uri = request.uri(); + String queryParam = request.getParam("q"); - if (queryParam != null && !queryParam.isEmpty()) { - this.handleHookSearch(queryParam, response); - return true; + // 2. Check if the URI is for listeners or routes and has a query parameter + if (queryParam != null && !queryParam.isEmpty()) { + if (uri.contains(HOOK_LISTENER_STORAGE_PATH)) { + handleListenerSearch(queryParam, request.response()); + return true; + } else if (uri.contains(HOOK_ROUTE_STORAGE_PATH)) { + handleRouteSearch(queryParam, request.response()); + return true; + } + } + else { + return false; + } } /* @@ -604,38 +620,45 @@ public boolean handle(final RoutingContext ctx) { } } - /** - * Handles hook search requests based on the 'destination' property. - * Searches in both routes and listeners. - * - * @param queryParam the RoutingContext of the request - */ - public void handleHookSearch(String queryParam,HttpServerResponse response) { - JsonObject result = new JsonObject(); - JsonArray matchingRoutes = new JsonArray(); - JsonArray matchingListeners = new JsonArray(); + private void handleListenerSearch(String queryParam, HttpServerResponse response) { + handleSearch( + listenerRepository.getListeners().stream().collect(Collectors.toMap(Listener::getListenerId, listener -> listener)), + listener -> listener.getHook().getDestination(), + queryParam, + LISTENERS_KEY, + response + ); + } - // Search routes by destination - routeRepository.getRoutes().forEach((routeKey, route) -> { - if (route.getHook().getDestination().contains(queryParam)) { - matchingRoutes.add(routeKey); - } - }); + private void handleRouteSearch(String queryParam, HttpServerResponse response) { + handleSearch( + routeRepository.getRoutes(), + route -> route.getHook().getDestination(), + queryParam, + ROUTES_KEY, + response + ); + } + + private void handleSearch(Map repository, Function getDestination, String queryParam, String resultKey, HttpServerResponse response) { + JsonArray matchingResults = new JsonArray(); - // Search listeners by destination - listenerRepository.getListeners().forEach(listener -> { - if (listener.getHook().getDestination().contains(queryParam)) { - matchingListeners.add(listener.getListenerId()); + repository.forEach((key, value) -> { + String destination = getDestination.apply(value); + if (destination != null && destination.contains(queryParam)) { + matchingResults.add(key); } }); - // Build and send the response - result.put("routes", matchingRoutes); - result.put("listeners", matchingListeners); + JsonObject result = new JsonObject(); + result.put(resultKey, matchingResults); - response.putHeader("content-type", "application/json").end(result.encode()); - } + // Set headers safely before writing the response + response.putHeader(CONTENT_TYPE_HEADER, CONTENT_TYPE_JSON); + response.write(result.encode()); + response.end(); + } /** * Create a listing of routes in the given parent. This happens diff --git a/gateleen-hook/src/test/java/org/swisspush/gateleen/hook/HookHandlerTest.java b/gateleen-hook/src/test/java/org/swisspush/gateleen/hook/HookHandlerTest.java index 17ac1f21..a346457f 100644 --- a/gateleen-hook/src/test/java/org/swisspush/gateleen/hook/HookHandlerTest.java +++ b/gateleen-hook/src/test/java/org/swisspush/gateleen/hook/HookHandlerTest.java @@ -8,6 +8,7 @@ import io.vertx.core.json.JsonArray; import io.vertx.core.json.JsonObject; import io.vertx.core.net.HostAndPort; +import io.vertx.ext.unit.Async; import io.vertx.ext.unit.TestContext; import io.vertx.ext.unit.junit.VertxUnitRunner; import io.vertx.ext.web.RoutingContext; @@ -19,10 +20,7 @@ import org.mockito.Mockito; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.swisspush.gateleen.core.http.DummyHttpServerRequest; -import org.swisspush.gateleen.core.http.DummyHttpServerResponse; -import org.swisspush.gateleen.core.http.FastFailHttpServerRequest; -import org.swisspush.gateleen.core.http.FastFailHttpServerResponse; +import org.swisspush.gateleen.core.http.*; import org.swisspush.gateleen.core.storage.MockResourceStorage; import org.swisspush.gateleen.hook.reducedpropagation.ReducedPropagationManager; import org.swisspush.gateleen.logging.LogAppenderRepository; @@ -41,8 +39,9 @@ import java.util.Map; import static io.vertx.core.http.HttpMethod.PUT; -import static org.junit.Assert.assertEquals; +import static org.junit.Assert.*; import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; import static org.swisspush.gateleen.core.util.HttpRequestHeader.*; /** @@ -72,15 +71,15 @@ public class HookHandlerTest { @Before public void setUp() { vertx = Vertx.vertx(); - routingContext = Mockito.mock(RoutingContext.class); - httpClient = Mockito.mock(HttpClient.class); - Mockito.when(httpClient.request(any(HttpMethod.class), anyString())).thenReturn(Mockito.mock(Future.class)); + routingContext = mock(RoutingContext.class); + httpClient = mock(HttpClient.class); + when(httpClient.request(any(HttpMethod.class), anyString())).thenReturn(mock(Future.class)); storage = new MockResourceStorage(); - loggingResourceManager = Mockito.mock(LoggingResourceManager.class); - logAppenderRepository = Mockito.mock(LogAppenderRepository.class); - monitoringHandler = Mockito.mock(MonitoringHandler.class); - requestQueue = Mockito.mock(RequestQueue.class); - reducedPropagationManager = Mockito.mock(ReducedPropagationManager.class); + loggingResourceManager = mock(LoggingResourceManager.class); + logAppenderRepository = mock(LogAppenderRepository.class); + monitoringHandler = mock(MonitoringHandler.class); + requestQueue = mock(RequestQueue.class); + reducedPropagationManager = mock(ReducedPropagationManager.class); hookHandler = new HookHandler(vertx, httpClient, storage, loggingResourceManager, logAppenderRepository, monitoringHandler, @@ -136,7 +135,7 @@ public void testListenerEnqueueWithDefaultQueueingStrategy(TestContext context) PUTRequest putRequest = new PUTRequest(uri, originalPayload); putRequest.addHeader(CONTENT_LENGTH.getName(), "99"); - Mockito.when(routingContext.request()).thenReturn(putRequest); + when(routingContext.request()).thenReturn(putRequest); hookHandler.handle(routingContext); @@ -163,7 +162,7 @@ public void testListenerEnqueueWithDefaultQueueingStrategyBecauseOfInvalidConfig PUTRequest putRequest = new PUTRequest(uri, originalPayload); putRequest.addHeader(CONTENT_LENGTH.getName(), "99"); - Mockito.when(routingContext.request()).thenReturn(putRequest); + when(routingContext.request()).thenReturn(putRequest); hookHandler.handle(routingContext); // verify that enqueue has been called WITH the payload @@ -188,7 +187,7 @@ public void testListenerEnqueueWithDiscardPayloadQueueingStrategy(TestContext co String originalPayload = "{\"key\":123}"; PUTRequest putRequest = new PUTRequest(uri, originalPayload); putRequest.addHeader(CONTENT_LENGTH.getName(), "99"); - Mockito.when(routingContext.request()).thenReturn(putRequest); + when(routingContext.request()).thenReturn(putRequest); hookHandler.handle(routingContext); // verify that enqueue has been called WITHOUT the payload but with 'Content-Length : 0' header @@ -200,7 +199,7 @@ public void testListenerEnqueueWithDiscardPayloadQueueingStrategy(TestContext co }), anyString(), any(Handler.class)); PUTRequest putRequestWithoutContentLengthHeader = new PUTRequest(uri, originalPayload); - Mockito.when(routingContext.request()).thenReturn(putRequestWithoutContentLengthHeader); + when(routingContext.request()).thenReturn(putRequestWithoutContentLengthHeader); hookHandler.handle(routingContext); // verify that enqueue has been called WITHOUT the payload and WITHOUT 'Content-Length' header @@ -229,7 +228,7 @@ public void testListenerEnqueueWithReducedPropagationQueueingStrategyButNoManage String originalPayload = "{\"key\":123}"; PUTRequest putRequest = new PUTRequest(uri, originalPayload); putRequest.addHeader(CONTENT_LENGTH.getName(), "99"); - Mockito.when(routingContext.request()).thenReturn(putRequest); + when(routingContext.request()).thenReturn(putRequest); hookHandler.handle(routingContext); // verify that no enqueue (or lockedEnqueue) has been called because no ReducedPropagationManager was configured @@ -255,7 +254,7 @@ public void testListenerEnqueueWithReducedPropagationQueueingStrategy(TestContex PUTRequest putRequest = new PUTRequest(uri, originalPayload); putRequest.addHeader(CONTENT_LENGTH.getName(), "99"); - Mockito.when(routingContext.request()).thenReturn(putRequest); + when(routingContext.request()).thenReturn(putRequest); hookHandler.handle(routingContext); String targetUri = "/playground/server/push/v1/devices/" + deviceId + "/playground/server/tests/hooktest/abc123"; @@ -277,7 +276,7 @@ public void testListenerEnqueueWithInvalidReducedPropagationQueueingStrategy(Tes PUTRequest putRequest = new PUTRequest(uri, originalPayload); putRequest.addHeader(CONTENT_LENGTH.getName(), "99"); - Mockito.when(routingContext.request()).thenReturn(putRequest); + when(routingContext.request()).thenReturn(putRequest); hookHandler.handle(routingContext); // verify that enqueue has been called WITH the payload @@ -304,7 +303,7 @@ public void testListenerEnqueueWithMatchingRequestsHeaderFilter(TestContext cont putRequest.addHeader(CONTENT_LENGTH.getName(), "99"); putRequest.addHeader("x-foo", "A"); - Mockito.when(routingContext.request()).thenReturn(putRequest); + when(routingContext.request()).thenReturn(putRequest); hookHandler.handle(routingContext); // verify that enqueue has been called WITH the payload @@ -329,7 +328,7 @@ public void testListenerNoEnqueueWithoutMatchingRequestsHeaderFilter(TestContext String originalPayload = "{\"key\":123}"; PUTRequest putRequest = new PUTRequest(uri, originalPayload); putRequest.addHeader(CONTENT_LENGTH.getName(), "99"); - Mockito.when(routingContext.request()).thenReturn(putRequest); + when(routingContext.request()).thenReturn(putRequest); hookHandler.handle(routingContext); // verify that no enqueue has been called since the header did not match @@ -372,7 +371,7 @@ public void hookRegistration_usesDefaultExpiryIfExpireAfterHeaderIsNegativeNumbe } // Trigger work - Mockito.when(routingContext.request()).thenReturn(request); + when(routingContext.request()).thenReturn(request); hookHandler.handle(routingContext); // Assert request was ok @@ -408,7 +407,7 @@ public void hookRegistration_RouteWithTimeout(TestContext testContext) { } // Trigger work - Mockito.when(routingContext.request()).thenReturn(request); + when(routingContext.request()).thenReturn(request); hookHandler.handle(routingContext); // Assert request was ok @@ -443,7 +442,7 @@ public void hookRegistration_usesDefaultExpiryWhenHeaderContainsCorruptValue(Tes } // Trigger work - Mockito.when(routingContext.request()).thenReturn(request); + when(routingContext.request()).thenReturn(request); hookHandler.handle(routingContext); // Assert request was ok @@ -474,7 +473,7 @@ public void hookRegistration_usesDefaultExpiryIfHeaderIsMissing(TestContext test } // Trigger work - Mockito.when(routingContext.request()).thenReturn(request); + when(routingContext.request()).thenReturn(request); hookHandler.handle(routingContext); // Assert request was ok @@ -505,7 +504,7 @@ public void hookRegistration_usesMinusOneIfExpireAfterIsSetToMinusOne(TestContex } // Trigger work - Mockito.when(routingContext.request()).thenReturn(request); + when(routingContext.request()).thenReturn(request); hookHandler.handle(routingContext); // Assert request was ok @@ -539,7 +538,7 @@ public void listenerRegistration_acceptOnlyWhitelistedHttpMethods(TestContext te } // Trigger - Mockito.when(routingContext.request()).thenReturn(request); + when(routingContext.request()).thenReturn(request); hookHandler.handle(routingContext); { // Assert request got accepted. @@ -575,7 +574,7 @@ public void listenerRegistration_rejectNotWhitelistedHttpMethods(TestContext tes } // Trigger - Mockito.when(routingContext.request()).thenReturn(request); + when(routingContext.request()).thenReturn(request); hookHandler.handle(routingContext); { // Assert request got rejected. @@ -645,141 +644,128 @@ public void hookRegistration_RouteWithRouteMultiplier(TestContext testContext) t } @Test - public void testHookHandleSearchWithMatchingRoutesAndListeners(TestContext context) throws Exception { - // Mock the response - HttpServerResponse response = Mockito.mock(HttpServerResponse.class); - Mockito.when(response.putHeader(anyString(), anyString())).thenReturn(response); - - // Mock the request and set the query parameter - HttpServerRequest request = Mockito.mock(HttpServerRequest.class); - Mockito.when(request.response()).thenReturn(response); - Mockito.when(request.getParam("q")).thenReturn("destination"); - - // Mock the route and listener - Route mockRoute = Mockito.mock(Route.class); - HttpHook mockHook = new HttpHook("destination/matching"); - Mockito.when(mockRoute.getHook()).thenReturn(mockHook); - - Listener mockListener = new Listener("listener1", "monitoredUrl", "destination/matching", mockHook); - - // Mock repositories - ListenerRepository listenerRepository = Mockito.mock(ListenerRepository.class); - RouteRepository routeRepository = Mockito.mock(RouteRepository.class); - - // Configure mocked behavior for repositories - Mockito.when(routeRepository.getRoutes()).thenReturn(Map.of("route1", mockRoute)); - Mockito.when(listenerRepository.getListeners()).thenReturn(Collections.singletonList(mockListener)); - - // Create HookHandler instance - HookHandler hookHandler = new HookHandler(vertx, httpClient, storage, loggingResourceManager, - logAppenderRepository, monitoringHandler, "userProfilePath", HOOK_ROOT_URI, requestQueue, - false - ); - - // Use reflection to set private fields - setPrivateField(hookHandler, "listenerRepository", listenerRepository); - setPrivateField(hookHandler, "routeRepository", routeRepository); - - // Call the method under test - hookHandler.handleHookSearch("destination", response); - - // Capture the output and verify the response was sent correctly - ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); - Mockito.verify(response).end(captor.capture()); + public void testHandleListenerSearch_Success() { + // Arrange + String queryParam = "validQueryParam"; + String uri = "registrations/listeners/?q=" + queryParam; + + HttpServerRequest request = mock(HttpServerRequest.class); + HttpServerResponse response = mock(HttpServerResponse.class); + + // Mock request and response behavior + when(request.uri()).thenReturn(uri); + when(request.method()).thenReturn(HttpMethod.GET); + when(request.getParam("q")).thenReturn(queryParam); + when(request.response()).thenReturn(response); + + // Mock RoutingContext + RoutingContext routingContext = mock(RoutingContext.class); + when(routingContext.request()).thenReturn(request); + + // Act + boolean result = hookHandler.handle(routingContext); // Calls the public `handle` method + + // Assert + assertTrue(result); // Ensure the handler returns true for a valid listener search + verify(response, times(1)).end(); // Ensure `response.end()` was called + } + @Test + public void testHandleListenerSearch_MissingQueryParam() { + String uri = "registrations/listeners/?q="; - // Check the result - String capturedResult = captor.getValue(); - JsonObject result = new JsonObject(capturedResult); - JsonArray routes = result.getJsonArray("routes"); - JsonArray listeners = result.getJsonArray("listeners"); + HttpServerRequest request = mock(HttpServerRequest.class); + HttpServerResponse response = mock(HttpServerResponse.class); - // Assert the expected results - context.assertTrue(routes.contains("route1")); - context.assertTrue(listeners.contains("listener1")); + when(request.uri()).thenReturn(uri); + when(request.method()).thenReturn(HttpMethod.GET); + when(request.getParam("q")).thenReturn(""); + when(request.response()).thenReturn(response); - // Verify the content-type header was set correctly - Mockito.verify(response).putHeader("content-type", "application/json"); - } + RoutingContext routingContext = mock(RoutingContext.class); + when(routingContext.request()).thenReturn(request); + boolean result = hookHandler.handle(routingContext); - private void setPrivateField(Object target, String fieldName, Object value) throws Exception { - Field field = target.getClass().getDeclaredField(fieldName); - field.setAccessible(true); - field.set(target, value); + assertFalse(result); + verify(response, never()).end(); } @Test - public void testHookHandleSearchWithNoMatchingRoutesOrListeners(TestContext context) throws Exception { - // Mock the response + public void testHandleListenerWithStorageAndSearchSuccess(TestContext context) { + vertx = Vertx.vertx(); + storage = new MockResourceStorage(); + LoggingResourceManager loggingResourceManager = Mockito.mock(LoggingResourceManager.class); + LogAppenderRepository logAppenderRepository = Mockito.mock(LogAppenderRepository.class); + MonitoringHandler monitoringHandler = Mockito.mock(MonitoringHandler.class); + RequestQueue requestQueue = Mockito.mock(RequestQueue.class); + + hookHandler = new HookHandler(vertx, Mockito.mock(HttpClient.class), storage, loggingResourceManager, logAppenderRepository, monitoringHandler, + "userProfilePath", "hookRootURI/", requestQueue, false, null); + // Prepopulate storage with a listener resource + storage.putMockData("hookRootURI/registrations/listeners/listener1", "{ \"hook\": { \"destination\": \"/test/endpoint\" } }"); + + // Mock RoutingContext and its behavior + RoutingContext routingContext = Mockito.mock(RoutingContext.class); + HttpServerRequest request = Mockito.mock(HttpServerRequest.class); HttpServerResponse response = Mockito.mock(HttpServerResponse.class); - Mockito.when(response.putHeader(anyString(), anyString())).thenReturn(response); - // Mock the request and set the query parameter - HttpServerRequest request = Mockito.mock(HttpServerRequest.class); + // Simulate a GET request with a query parameter + Mockito.when(request.method()).thenReturn(HttpMethod.GET); + Mockito.when(request.uri()).thenReturn("hookRootURI/registrations/listeners/?q=test"); + Mockito.when(request.getParam("q")).thenReturn("test"); Mockito.when(request.response()).thenReturn(response); - Mockito.when(request.getParam("q")).thenReturn("destination"); - - // Mock the route and listener - Route mockRoute = Mockito.mock(Route.class); - HttpHook mockHook = new HttpHook("destination/matching"); - Mockito.when(mockRoute.getHook()).thenReturn(mockHook); - - Listener mockListener = new Listener("listener1", "monitoredUrl", "destination/matching", mockHook); - - // Mock repositories - ListenerRepository listenerRepository = Mockito.mock(ListenerRepository.class); - RouteRepository routeRepository = Mockito.mock(RouteRepository.class); - - // Configure mocked behavior for repositories with no matching routes or listeners - Mockito.when(routeRepository.getRoutes()).thenReturn(Map.of()); - Mockito.when(listenerRepository.getListeners()).thenReturn(Collections.emptyList()); - - // Create HookHandler instance - HookHandler hookHandler = new HookHandler(vertx, httpClient, storage, loggingResourceManager, - logAppenderRepository, monitoringHandler, "userProfilePath", HOOK_ROOT_URI, requestQueue, - false - ); - // Use reflection to set private fields - setPrivateField(hookHandler, "listenerRepository", listenerRepository); - setPrivateField(hookHandler, "routeRepository", routeRepository); - - // Call the method under test - hookHandler.handleHookSearch("destination", response); + Mockito.when(routingContext.request()).thenReturn(request); - // Capture the output and verify the response was sent correctly - ArgumentCaptor captor = ArgumentCaptor.forClass(String.class); - Mockito.verify(response).end(captor.capture()); + // Async handler to check the result + Async async = context.async(); - // Check the result - String capturedResult = captor.getValue(); - JsonObject result = new JsonObject(capturedResult); - JsonArray routes = result.getJsonArray("routes"); - JsonArray listeners = result.getJsonArray("listeners"); + // Act: Call the hookHandler.handle method + boolean handled = hookHandler.handle(routingContext); - // Assert that there are no matching routes or listeners - context.assertTrue(routes.isEmpty()); - context.assertTrue(listeners.isEmpty()); + // Assert that it was handled + context.assertTrue(handled); - // Verify the content-type header was set correctly - Mockito.verify(response).putHeader("content-type", "application/json"); + // Verify that the response ended correctly (simulating a successful response) + Mockito.verify(response, Mockito.times(1)).end(); + async.complete(); } @Test - public void testHookHandleSearchWithInvalidQueryParam(TestContext context) { - // Mocking the HttpServerResponse and request objects + public void testHandleListenerWithStorageAndSearchFailure(TestContext context) { + vertx = Vertx.vertx(); + storage = new MockResourceStorage(); + LoggingResourceManager loggingResourceManager = Mockito.mock(LoggingResourceManager.class); + LogAppenderRepository logAppenderRepository = Mockito.mock(LogAppenderRepository.class); + MonitoringHandler monitoringHandler = Mockito.mock(MonitoringHandler.class); + RequestQueue requestQueue = Mockito.mock(RequestQueue.class); + + hookHandler = new HookHandler(vertx, Mockito.mock(HttpClient.class), storage, loggingResourceManager, logAppenderRepository, monitoringHandler, + "userProfilePath", "hookRootURI/", requestQueue, false, null); + RoutingContext routingContext = Mockito.mock(RoutingContext.class); + HttpServerRequest request = Mockito.mock(HttpServerRequest.class); HttpServerResponse response = Mockito.mock(HttpServerResponse.class); - Mockito.when(response.putHeader(anyString(), anyString())).thenReturn(response); - HttpServerRequest request = Mockito.mock(HttpServerRequest.class); + // Simulate a GET request without a query parameter + Mockito.when(request.method()).thenReturn(HttpMethod.GET); + Mockito.when(request.uri()).thenReturn("hookRootURI/registrations/listeners/"); + Mockito.when(request.getParam("q")).thenReturn(null); // No query parameter Mockito.when(request.response()).thenReturn(response); - Mockito.when(request.getParam("q")).thenReturn(null); - // Call hookHandleSearch - hookHandler.handleHookSearch(null, response); + Mockito.when(routingContext.request()).thenReturn(request); + + // Async handler to check the result + Async async = context.async(); + + // Act: Call the hookHandler.handle method + boolean handled = hookHandler.handle(routingContext); + + // Assert that it was NOT handled (as the query param is missing) + context.assertFalse(handled); - // Verify that nothing is returned - Mockito.verify(response, Mockito.never()).end(any(Buffer.class)); + // Verify that the response was NOT ended (because it shouldn't be processed) + Mockito.verify(response, Mockito.never()).end(); + async.complete(); } @@ -805,7 +791,7 @@ public HttpServerResponse response() { }; putRequest.addHeader(CONTENT_LENGTH.getName(), "99"); - Mockito.when(routingContext.request()).thenReturn(putRequest); + when(routingContext.request()).thenReturn(putRequest); hookHandler.handle(routingContext); latch.await(); From 4a0b791f28025860dd0433edf1926c018cb4c4c1 Mon Sep 17 00:00:00 2001 From: almeidast Date: Thu, 10 Oct 2024 15:36:18 +0100 Subject: [PATCH 04/14] Add integration tests for handleListenerSearch - Verifies successful listener search when multiple listeners are present in storage. - Ensures correct retrieval of a single listener from storage. - Tests failure case when searching for a non-existent listener among multiple listeners. - Ensures proper handling of search when no listeners are registered in storage. --- .../gateleen/hook/HookHandlerTest.java | 201 ++++++++++++++++++ 1 file changed, 201 insertions(+) diff --git a/gateleen-hook/src/test/java/org/swisspush/gateleen/hook/HookHandlerTest.java b/gateleen-hook/src/test/java/org/swisspush/gateleen/hook/HookHandlerTest.java index a346457f..474d7774 100644 --- a/gateleen-hook/src/test/java/org/swisspush/gateleen/hook/HookHandlerTest.java +++ b/gateleen-hook/src/test/java/org/swisspush/gateleen/hook/HookHandlerTest.java @@ -768,6 +768,207 @@ public void testHandleListenerWithStorageAndSearchFailure(TestContext context) { async.complete(); } + @Test + public void testSearchMultipleListeners_Success(TestContext context) { + Vertx vertx = Vertx.vertx(); + MockResourceStorage storage = new MockResourceStorage(); + HttpClient httpClient = vertx.createHttpClient(); + + // Create mock implementation for RequestQueue + RequestQueue requestQueue = Mockito.mock(RequestQueue.class); + + // Initialize HookHandler + String HOOK_LISTENER_STORAGE_PATH = "/_hooks/listeners/"; + HookHandler hookHandler = new HookHandler(vertx, httpClient, storage, loggingResourceManager, + logAppenderRepository, monitoringHandler, + "userProfilePath", "/hookRootUri", + requestQueue, false, reducedPropagationManager); + + // Add multiple listeners to the storage + JsonObject listener1 = new JsonObject() + .put("requesturl", "/playground/server/tests/hooktest/_hooks/listeners/http/push/x99") + .put("expirationTime", "2025-01-03T14:15:53.277") + .put("hook", new JsonObject().put("destination", "/playground/server/push/v1/devices/x99")); + JsonObject listener2 = new JsonObject() + .put("requesturl", "/playground/server/tests/hooktest/_hooks/listeners/http/push/x100") + .put("expirationTime", "2025-01-03T14:15:53.277") + .put("hook", new JsonObject().put("destination", "/playground/server/push/v1/devices/x100")); + JsonObject listener3 = new JsonObject() + .put("requesturl", "/playground/server/tests/hooktest/_hooks/listeners/http/push/x101") + .put("expirationTime", "2025-01-03T14:15:53.277") + .put("hook", new JsonObject().put("destination", "/playground/server/push/v1/devices/x101")); + + storage.putMockData(HOOK_LISTENER_STORAGE_PATH + "x99", listener1.encode()); + storage.putMockData(HOOK_LISTENER_STORAGE_PATH + "x100", listener2.encode()); + storage.putMockData(HOOK_LISTENER_STORAGE_PATH + "x101", listener3.encode()); + + // Configure HttpServer for integration test + io.vertx.ext.web.Router router = io.vertx.ext.web.Router.router(vertx); + router.route().handler(hookHandler::handle); + + HttpServer server = vertx.createHttpServer(); + server.requestHandler(router).listen(8080, ar -> { + if (ar.succeeded()) { + // Make a real HTTP request to the HookHandler + httpClient.request(HttpMethod.GET, 8080, "localhost", "/_hooks/listeners?q=x99") + .compose(HttpClientRequest::send) + .compose(response -> { + context.assertEquals(200, response.statusCode()); + return response.body(); + }) + .onSuccess(body -> { + JsonObject jsonResponse = new JsonObject(body.toString()); + context.assertTrue(jsonResponse.getJsonArray("listeners").contains("x99")); + context.assertFalse(jsonResponse.getJsonArray("listeners").contains("x100")); + context.async().complete(); + }) + .onFailure(context::fail); + } else { + context.fail(ar.cause()); + } + }); + } + @Test + public void testSearchSingleListener_Success(TestContext context) { + Vertx vertx = Vertx.vertx(); + MockResourceStorage storage = new MockResourceStorage(); + HttpClient httpClient = vertx.createHttpClient(); + + // Create mock implementation for RequestQueue + RequestQueue requestQueue = Mockito.mock(RequestQueue.class); + + // Initialize HookHandler + HookHandler hookHandler = new HookHandler(vertx, httpClient, storage, loggingResourceManager, + logAppenderRepository, monitoringHandler, + "userProfilePath", "/hookRootUri/", + requestQueue, false, reducedPropagationManager); + + // Insert a single listener to the storage + JsonObject listener1 = new JsonObject() + .put("requesturl", "/playground/server/tests/hooktest/_hooks/listeners/http/push/listener1") + .put("expirationTime", "2025-01-03T14:15:53.277") + .put("hook", new JsonObject().put("destination", "/playground/server/push/v1/devices/listener1")); + storage.putMockData("/_hooks/listeners/listener1", listener1.encode()); + + // Configure HttpServer for integration test + io.vertx.ext.web.Router router = io.vertx.ext.web.Router.router(vertx); + router.route().handler(hookHandler::handle); + + HttpServer server = vertx.createHttpServer(); + server.requestHandler(router).listen(8080, ar -> { + if (ar.succeeded()) { + // Make a real HTTP request to the HookHandler + httpClient.request(HttpMethod.GET, 8080, "localhost", "/_hooks/listeners?q=listener1") + .compose(HttpClientRequest::send) + .compose(response -> { + context.assertEquals(200, response.statusCode()); + return response.body(); + }) + .onSuccess(body -> { + JsonObject jsonResponse = new JsonObject(body.toString()); + context.assertTrue(jsonResponse.getJsonArray("listeners").contains("listener1")); + context.async().complete(); + }) + .onFailure(context::fail); + } else { + context.fail(ar.cause()); + } + }); + } + @Test + public void testSearchListenerNotFound_MultipleListeners_Failure(TestContext context) { + Vertx vertx = Vertx.vertx(); + MockResourceStorage storage = new MockResourceStorage(); + HttpClient httpClient = vertx.createHttpClient(); + + // Create mock implementation for RequestQueue + RequestQueue requestQueue = Mockito.mock(RequestQueue.class); + + // Initialize HookHandler + String HOOK_LISTENER_STORAGE_PATH = "/_hooks/listeners/"; + HookHandler hookHandler = new HookHandler(vertx, httpClient, storage, loggingResourceManager, + logAppenderRepository, monitoringHandler, + "userProfilePath", "/hookRootUri", + requestQueue, false, reducedPropagationManager); + + // Add multiple listeners to the storage + JsonObject listener2 = new JsonObject() + .put("requesturl", "/playground/server/tests/hooktest/_hooks/listeners/http/push/x100") + .put("expirationTime", "2025-01-03T14:15:53.277") + .put("hook", new JsonObject().put("destination", "/playground/server/push/v1/devices/x100")); + JsonObject listener3 = new JsonObject() + .put("requesturl", "/playground/server/tests/hooktest/_hooks/listeners/http/push/x101") + .put("expirationTime", "2025-01-03T14:15:53.277") + .put("hook", new JsonObject().put("destination", "/playground/server/push/v1/devices/x101")); + + storage.putMockData(HOOK_LISTENER_STORAGE_PATH + "x100", listener2.encode()); + storage.putMockData(HOOK_LISTENER_STORAGE_PATH + "x101", listener3.encode()); + + // Configure HttpServer for integration test + io.vertx.ext.web.Router router = io.vertx.ext.web.Router.router(vertx); + router.route().handler(hookHandler::handle); + + HttpServer server = vertx.createHttpServer(); + server.requestHandler(router).listen(8080, ar -> { + if (ar.succeeded()) { + // Make a real HTTP request to the HookHandler + httpClient.request(HttpMethod.GET, 8080, "localhost", "/_hooks/listeners?q=x99") + .compose(HttpClientRequest::send) + .compose(response -> { + context.assertEquals(200, response.statusCode()); + return response.body(); + }) + .onSuccess(body -> { + JsonObject jsonResponse = new JsonObject(body.toString()); + context.assertFalse(jsonResponse.getJsonArray("listeners").contains("x99")); + context.async().complete(); + }) + .onFailure(context::fail); + } else { + context.fail(ar.cause()); + } + }); + } + @Test + public void testSearchListenerNotFound_NoListeners_Failure(TestContext context) { + Vertx vertx = Vertx.vertx(); + MockResourceStorage storage = new MockResourceStorage(); + HttpClient httpClient = vertx.createHttpClient(); + + // Create mock implementation for RequestQueue + RequestQueue requestQueue = Mockito.mock(RequestQueue.class); + + // Initialize HookHandler + HookHandler hookHandler = new HookHandler(vertx, httpClient, storage, loggingResourceManager, + logAppenderRepository, monitoringHandler, + "userProfilePath", "/hookRootUri/", + requestQueue, false, reducedPropagationManager); + + // Configure HttpServer for integration test + io.vertx.ext.web.Router router = io.vertx.ext.web.Router.router(vertx); + router.route().handler(hookHandler::handle); + + HttpServer server = vertx.createHttpServer(); + server.requestHandler(router).listen(8080, ar -> { + if (ar.succeeded()) { + // Make a real HTTP request to the HookHandler + httpClient.request(HttpMethod.GET, 8080, "localhost", "/_hooks/listeners?q=invalid") + .compose(HttpClientRequest::send) + .compose(response -> { + context.assertEquals(200, response.statusCode()); + return response.body(); + }) + .onSuccess(body -> { + JsonObject jsonResponse = new JsonObject(body.toString()); + context.assertFalse(jsonResponse.getJsonArray("listeners").contains("invalid")); + context.async().complete(); + }) + .onFailure(context::fail); + } else { + context.fail(ar.cause()); + } + }); + } /////////////////////////////////////////////////////////////////////////////// // Helpers From 922708c2b877b661ebe70df60b9267e7d4e6462b Mon Sep 17 00:00:00 2001 From: almeidast Date: Fri, 11 Oct 2024 12:12:14 +0100 Subject: [PATCH 05/14] Add an integration test at ListenerTest --- .../swisspush/gateleen/hook/ListenerTest.java | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/gateleen-test/src/test/java/org/swisspush/gateleen/hook/ListenerTest.java b/gateleen-test/src/test/java/org/swisspush/gateleen/hook/ListenerTest.java index 4282fe13..c51fc33b 100755 --- a/gateleen-test/src/test/java/org/swisspush/gateleen/hook/ListenerTest.java +++ b/gateleen-test/src/test/java/org/swisspush/gateleen/hook/ListenerTest.java @@ -942,4 +942,35 @@ private void checkGETStatusCodeWithAwait(final String request, final Integer sta private void checkGETBodyWithAwait(final String requestUrl, final String body) { await().atMost(TEN_SECONDS).until(() -> when().get(requestUrl).then().extract().body().asString(), equalTo(body)); } + /** + * Test for hookHandleSearch with listener storage path and valid query param.
+ * eg. register / unregister: http://localhost:7012/gateleen/server/listenertest/_hooks/listeners/listener/1
+ * requestUrl: http://localhost:7012/gateleen/server/listenertest/listener/test?q=testQuery + */ + @Test + public void testHookHandleSearch_ListenerPathWithValidQueryParam(TestContext context) { + Async async = context.async(); + delete(); + initRoutingRules(); + + String queryParam = "testQuery"; + String listenerPath = "/_hooks/listeners"; + String requestUrl = requestUrlBase + listenerPath + "?q=" + queryParam; + + // Register a listener + TestUtils.registerListener(requestUrlBase + listenerPath, targetUrlBase, new String[]{"GET", "POST"}, null); + + // Send GET request + given().queryParam("q", queryParam) + .when().get(requestUrl) + .then().assertThat().statusCode(200); + + // Validate the response + checkGETStatusCodeWithAwait(requestUrl, 200); + + TestUtils.unregisterListener(requestUrlBase + listenerPath); + + async.complete(); + } + } From 2f712c04257c27618b5fcc08311fd7b1b1bb1e55 Mon Sep 17 00:00:00 2001 From: almeidast Date: Fri, 11 Oct 2024 12:47:42 +0100 Subject: [PATCH 06/14] Fix return method --- .../main/java/org/swisspush/gateleen/hook/HookHandler.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/gateleen-hook/src/main/java/org/swisspush/gateleen/hook/HookHandler.java b/gateleen-hook/src/main/java/org/swisspush/gateleen/hook/HookHandler.java index d54a3431..1957e2b9 100755 --- a/gateleen-hook/src/main/java/org/swisspush/gateleen/hook/HookHandler.java +++ b/gateleen-hook/src/main/java/org/swisspush/gateleen/hook/HookHandler.java @@ -581,7 +581,6 @@ public boolean handle(final RoutingContext ctx) { if (request.method() == HttpMethod.GET) { String uri = request.uri(); String queryParam = request.getParam("q"); - // 2. Check if the URI is for listeners or routes and has a query parameter if (queryParam != null && !queryParam.isEmpty()) { if (uri.contains(HOOK_LISTENER_STORAGE_PATH)) { @@ -592,9 +591,6 @@ public boolean handle(final RoutingContext ctx) { return true; } } - else { - return false; - } } /* From a0161a25aa5d45dc634ed511cc9656b0fa1c67d7 Mon Sep 17 00:00:00 2001 From: almeidast Date: Fri, 11 Oct 2024 15:25:44 +0100 Subject: [PATCH 07/14] Added test for hookHandleSearch to verify behavior when no matching listener is found for a given query parameter. --- .../swisspush/gateleen/hook/ListenerTest.java | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/gateleen-test/src/test/java/org/swisspush/gateleen/hook/ListenerTest.java b/gateleen-test/src/test/java/org/swisspush/gateleen/hook/ListenerTest.java index c51fc33b..06dc038d 100755 --- a/gateleen-test/src/test/java/org/swisspush/gateleen/hook/ListenerTest.java +++ b/gateleen-test/src/test/java/org/swisspush/gateleen/hook/ListenerTest.java @@ -973,4 +973,37 @@ public void testHookHandleSearch_ListenerPathWithValidQueryParam(TestContext con async.complete(); } + /** + * Test for hookHandleSearch with listener storage path and valid query param but no match found.
+ * eg. register / unregister: http://localhost:7012/gateleen/server/listenertest/_hooks/listeners/listener/1
+ * requestUrl: http://localhost:7012/gateleen/server/listenertest/listener/test?q=nonMatchingQuery + */ + @Test + public void testHookHandleSearch_ListenerPathWithNonMatchingQueryParam(TestContext context) { + Async async = context.async(); + delete(); + initRoutingRules(); + + String nonMatchingQueryParam = "nonMatchingQuery"; + String listenerPath = "/_hooks/listeners"; + String requestUrl = requestUrlBase + listenerPath + "?q=" + nonMatchingQueryParam; + + // Register a listener with a different query param + String differentQueryParam = "differentQuery"; + TestUtils.registerListener(requestUrlBase + listenerPath + "?q=" + differentQueryParam, targetUrlBase, new String[]{"GET", "POST"}, null); + + // Send GET request with non-matching query param + given().queryParam("q", nonMatchingQueryParam) + .when().get(requestUrl) + .then().assertThat().statusCode(404); // Expecting 404 as no listener matches the query + + // Validate that the response is not found + checkGETStatusCodeWithAwait(requestUrl, 404); + + // Unregister the listener + TestUtils.unregisterListener(requestUrlBase + listenerPath); + + async.complete(); + } + } From 322508fd88d102abe3eb9eb19c09cfa4a1fe9be6 Mon Sep 17 00:00:00 2001 From: almeidast Date: Fri, 11 Oct 2024 16:59:46 +0100 Subject: [PATCH 08/14] Add tests for hookHandleSearch with no matching listeners and no listeners registered. - Test for handling searches with no matching listeners, returning an empty list. - Test for handling searches when no listeners are registered, ensuring an empty list is returned. - Fix hookHandler Search --- .../swisspush/gateleen/hook/HookHandler.java | 14 ++++--- .../swisspush/gateleen/hook/ListenerTest.java | 38 +++++++++++++++++-- 2 files changed, 44 insertions(+), 8 deletions(-) diff --git a/gateleen-hook/src/main/java/org/swisspush/gateleen/hook/HookHandler.java b/gateleen-hook/src/main/java/org/swisspush/gateleen/hook/HookHandler.java index 1957e2b9..49e30042 100755 --- a/gateleen-hook/src/main/java/org/swisspush/gateleen/hook/HookHandler.java +++ b/gateleen-hook/src/main/java/org/swisspush/gateleen/hook/HookHandler.java @@ -583,10 +583,10 @@ public boolean handle(final RoutingContext ctx) { String queryParam = request.getParam("q"); // 2. Check if the URI is for listeners or routes and has a query parameter if (queryParam != null && !queryParam.isEmpty()) { - if (uri.contains(HOOK_LISTENER_STORAGE_PATH)) { + if (uri.contains(LISTENERS_KEY)) { handleListenerSearch(queryParam, request.response()); return true; - } else if (uri.contains(HOOK_ROUTE_STORAGE_PATH)) { + } else if (uri.contains(ROUTES_KEY)) { handleRouteSearch(queryParam, request.response()); return true; } @@ -649,11 +649,15 @@ private void handleSearch(Map repository, Function get JsonObject result = new JsonObject(); result.put(resultKey, matchingResults); - // Set headers safely before writing the response + String encodedResult = result.encode(); // Convert the result to a string + + // Set Content-Length header before sending the response response.putHeader(CONTENT_TYPE_HEADER, CONTENT_TYPE_JSON); - response.write(result.encode()); - response.end(); + response.putHeader("Content-Length", String.valueOf(encodedResult.length())); // Set content length + // Write and end the response + response.write(encodedResult); + response.end(); } /** diff --git a/gateleen-test/src/test/java/org/swisspush/gateleen/hook/ListenerTest.java b/gateleen-test/src/test/java/org/swisspush/gateleen/hook/ListenerTest.java index 06dc038d..7cdba97f 100755 --- a/gateleen-test/src/test/java/org/swisspush/gateleen/hook/ListenerTest.java +++ b/gateleen-test/src/test/java/org/swisspush/gateleen/hook/ListenerTest.java @@ -995,10 +995,12 @@ public void testHookHandleSearch_ListenerPathWithNonMatchingQueryParam(TestConte // Send GET request with non-matching query param given().queryParam("q", nonMatchingQueryParam) .when().get(requestUrl) - .then().assertThat().statusCode(404); // Expecting 404 as no listener matches the query + .then().assertThat() + .statusCode(200) // Expecting 200 as the request is valid but no match found + .body("listeners", org.hamcrest.Matchers.empty()); // Expecting an empty list of listeners - // Validate that the response is not found - checkGETStatusCodeWithAwait(requestUrl, 404); + // Validate that the response is 200 and the result is an empty array + checkGETStatusCodeWithAwait(requestUrl, 200); // Unregister the listener TestUtils.unregisterListener(requestUrlBase + listenerPath); @@ -1006,4 +1008,34 @@ public void testHookHandleSearch_ListenerPathWithNonMatchingQueryParam(TestConte async.complete(); } + /** + * Test for hookHandleSearch with listener storage path and valid query param but no listeners registered.
+ * eg. register / unregister: http://localhost:7012/gateleen/server/listenertest/_hooks/listeners/listener/1
+ * requestUrl: http://localhost:7012/gateleen/server/listenertest/listener/test?q=someQuery + */ + @Test + public void testHookHandleSearch_NoListenersRegistered(TestContext context) { + Async async = context.async(); + delete(); + initRoutingRules(); + + String queryParam = "someQuery"; + String listenerPath = "/_hooks/listeners"; + String requestUrl = requestUrlBase + listenerPath + "?q=" + queryParam; + + // No listeners registered + + // Send GET request with a query param + given().queryParam("q", queryParam) + .when().get(requestUrl) + .then().assertThat() + .statusCode(200) // Expecting 200 as the request is valid but no listeners are registered + .body("listeners", org.hamcrest.Matchers.empty()); // Expecting an empty list of listeners + + // Validate that the response is 200 and the result is an empty array + checkGETStatusCodeWithAwait(requestUrl, 200); + + async.complete(); + } + } From 55ab41bb39425b8c34707edc1bf4327eb75f71c2 Mon Sep 17 00:00:00 2001 From: almeidast Date: Fri, 11 Oct 2024 18:53:50 +0100 Subject: [PATCH 09/14] Implemented a search mechanism that iterates over the provided repository. It checks if the destination of each item contains the query string (queryParam), adding matching items to the result. --- .../swisspush/gateleen/hook/HookHandler.java | 27 ++++++++++++++----- 1 file changed, 21 insertions(+), 6 deletions(-) diff --git a/gateleen-hook/src/main/java/org/swisspush/gateleen/hook/HookHandler.java b/gateleen-hook/src/main/java/org/swisspush/gateleen/hook/HookHandler.java index 49e30042..40812349 100755 --- a/gateleen-hook/src/main/java/org/swisspush/gateleen/hook/HookHandler.java +++ b/gateleen-hook/src/main/java/org/swisspush/gateleen/hook/HookHandler.java @@ -636,12 +636,29 @@ private void handleRouteSearch(String queryParam, HttpServerResponse response) { ); } + /** + * Search the repository for items matching the query parameter. + * Returns a JSON response with the matched results. + * If any essential parameter (repository, response, getDestination) is null, + * a 400 Bad Request is returned. + * + * @param repository The items to search. + * @param getDestination Function to extract destinations. + * @param queryParam The query string to match. + * @param resultKey The key for the result in the response. + * @param response The HTTP response to return the results. Must not be null. + */ private void handleSearch(Map repository, Function getDestination, String queryParam, String resultKey, HttpServerResponse response) { + if (repository == null || getDestination == null) { + response.setStatusCode(400).end(); // Bad request for missing parameters + return; + } + JsonArray matchingResults = new JsonArray(); repository.forEach((key, value) -> { String destination = getDestination.apply(value); - if (destination != null && destination.contains(queryParam)) { + if (destination != null && destination.contains(queryParam != null ? queryParam : "")) { matchingResults.add(key); } }); @@ -649,17 +666,15 @@ private void handleSearch(Map repository, Function get JsonObject result = new JsonObject(); result.put(resultKey, matchingResults); - String encodedResult = result.encode(); // Convert the result to a string + String encodedResult = result.encode(); - // Set Content-Length header before sending the response response.putHeader(CONTENT_TYPE_HEADER, CONTENT_TYPE_JSON); - response.putHeader("Content-Length", String.valueOf(encodedResult.length())); // Set content length - - // Write and end the response + response.putHeader("Content-Length", String.valueOf(encodedResult.length())); response.write(encodedResult); response.end(); } + /** * Create a listing of routes in the given parent. This happens * only if we have a GET request, the routes are listable and From 8feb3865add8bd00618166971a8f6aa5cfc73724 Mon Sep 17 00:00:00 2001 From: almeidast Date: Wed, 16 Oct 2024 12:05:10 +0100 Subject: [PATCH 10/14] Fix error in mock tests --- .../gateleen/hook/HookHandlerTest.java | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/gateleen-hook/src/test/java/org/swisspush/gateleen/hook/HookHandlerTest.java b/gateleen-hook/src/test/java/org/swisspush/gateleen/hook/HookHandlerTest.java index 474d7774..c5e073cc 100644 --- a/gateleen-hook/src/test/java/org/swisspush/gateleen/hook/HookHandlerTest.java +++ b/gateleen-hook/src/test/java/org/swisspush/gateleen/hook/HookHandlerTest.java @@ -680,6 +680,7 @@ public void testHandleListenerSearch_MissingQueryParam() { when(request.method()).thenReturn(HttpMethod.GET); when(request.getParam("q")).thenReturn(""); when(request.response()).thenReturn(response); + when(request.headers()).thenReturn(MultiMap.caseInsensitiveMultiMap()); RoutingContext routingContext = mock(RoutingContext.class); when(routingContext.request()).thenReturn(request); @@ -746,28 +747,28 @@ public void testHandleListenerWithStorageAndSearchFailure(TestContext context) { HttpServerRequest request = Mockito.mock(HttpServerRequest.class); HttpServerResponse response = Mockito.mock(HttpServerResponse.class); - // Simulate a GET request without a query parameter + // Mock necessary methods Mockito.when(request.method()).thenReturn(HttpMethod.GET); Mockito.when(request.uri()).thenReturn("hookRootURI/registrations/listeners/"); - Mockito.when(request.getParam("q")).thenReturn(null); // No query parameter + Mockito.when(request.getParam("q")).thenReturn(null); // No query param Mockito.when(request.response()).thenReturn(response); + Mockito.when(request.headers()).thenReturn(MultiMap.caseInsensitiveMultiMap()); - Mockito.when(routingContext.request()).thenReturn(request); - // Async handler to check the result - Async async = context.async(); + HttpClient selfClient = Mockito.mock(HttpClient.class); + Mockito.when(selfClient.request(Mockito.any(), Mockito.anyString())).thenReturn(Future.failedFuture(new Exception("Mocked failure"))); - // Act: Call the hookHandler.handle method + Mockito.when(routingContext.request()).thenReturn(request); + + // Act boolean handled = hookHandler.handle(routingContext); - // Assert that it was NOT handled (as the query param is missing) + // Assert context.assertFalse(handled); - - // Verify that the response was NOT ended (because it shouldn't be processed) Mockito.verify(response, Mockito.never()).end(); - async.complete(); } + @Test public void testSearchMultipleListeners_Success(TestContext context) { Vertx vertx = Vertx.vertx(); From 4004646251bac88a50c53e1f4c0a3ec66da1c78d Mon Sep 17 00:00:00 2001 From: almeidast Date: Wed, 16 Oct 2024 14:01:22 +0100 Subject: [PATCH 11/14] - add tests for Route - improve listeners tests --- .../swisspush/gateleen/hook/ListenerTest.java | 1 + .../gateleen/hook/RouteListingTest.java | 87 +++++++++++++++++++ 2 files changed, 88 insertions(+) diff --git a/gateleen-test/src/test/java/org/swisspush/gateleen/hook/ListenerTest.java b/gateleen-test/src/test/java/org/swisspush/gateleen/hook/ListenerTest.java index 7cdba97f..b22a283d 100755 --- a/gateleen-test/src/test/java/org/swisspush/gateleen/hook/ListenerTest.java +++ b/gateleen-test/src/test/java/org/swisspush/gateleen/hook/ListenerTest.java @@ -942,6 +942,7 @@ private void checkGETStatusCodeWithAwait(final String request, final Integer sta private void checkGETBodyWithAwait(final String requestUrl, final String body) { await().atMost(TEN_SECONDS).until(() -> when().get(requestUrl).then().extract().body().asString(), equalTo(body)); } + /** * Test for hookHandleSearch with listener storage path and valid query param.
* eg. register / unregister: http://localhost:7012/gateleen/server/listenertest/_hooks/listeners/listener/1
diff --git a/gateleen-test/src/test/java/org/swisspush/gateleen/hook/RouteListingTest.java b/gateleen-test/src/test/java/org/swisspush/gateleen/hook/RouteListingTest.java index 4a76ac64..ed7ae6bf 100644 --- a/gateleen-test/src/test/java/org/swisspush/gateleen/hook/RouteListingTest.java +++ b/gateleen-test/src/test/java/org/swisspush/gateleen/hook/RouteListingTest.java @@ -15,6 +15,7 @@ import org.swisspush.gateleen.TestUtils; import static io.restassured.RestAssured.*; +import static org.swisspush.gateleen.TestUtils.checkGETStatusCodeWithAwait; /** * Test class for the hook route feature. @@ -223,5 +224,91 @@ private void assertResponse(final Response response, final String[] expectedArra Assert.assertEquals(expectedArray.length, array.size()); Assert.assertThat(array, Matchers.contains(expectedArray)); } + /** + * Test for route listing with a valid query parameter. + */ + @Test + public void testRouteListing_ValidQueryParam(TestContext context) { + Async async = context.async(); + delete(); + initSettings(); + + String queryParam = "testQuery"; + String routePath = "/routes"; + String requestUrl = requestUrlBase + routePath + "?q=" + queryParam; + + // Register a route + TestUtils.registerRoute(requestUrlBase + routePath, targetUrlBase, new String[]{"GET", "POST"}, null, true, true); + + // Send GET request with a valid query param + given().queryParam("q", queryParam) + .when().get(requestUrl) + .then().assertThat().statusCode(200); + + // Validate response + checkGETStatusCodeWithAwait(requestUrl, 200); + + TestUtils.unregisterRoute(requestUrlBase + routePath); + + async.complete(); + } + + /** + * Test for route listing with a non-matching query parameter. + */ + @Test + public void testRouteListing_NonMatchingQueryParam(TestContext context) { + Async async = context.async(); + delete(); + initSettings(); + + String nonMatchingQueryParam = "nonMatchingQuery"; + String routePath = "/routes"; + String requestUrl = requestUrlBase + routePath + "?q=" + nonMatchingQueryParam; + + // Register a route with a different query param + String differentQueryParam = "differentQuery"; + TestUtils.registerRoute(requestUrlBase + routePath + "?q=" + differentQueryParam, targetUrlBase, new String[]{"GET", "POST"}, null, true, true); + + // Send GET request with non-matching query param + given().queryParam("q", nonMatchingQueryParam) + .when().get(requestUrl) + .then().assertThat().statusCode(200) + .body("routes", Matchers.empty()); + + // Validate response + checkGETStatusCodeWithAwait(requestUrl, 200); + + TestUtils.unregisterRoute(requestUrlBase + routePath); + + async.complete(); + } + + /** + * Test for route listing when no routes are registered. + */ + @Test + public void testRouteListing_NoRoutesRegistered(TestContext context) { + Async async = context.async(); + delete(); + initSettings(); + + String queryParam = "someQuery"; + String routePath = "/routes"; + String requestUrl = requestUrlBase + routePath + "?q=" + queryParam; + + // No routes registered + + // Send GET request with a query param + given().queryParam("q", queryParam) + .when().get(requestUrl) + .then().assertThat().statusCode(200) + .body("routes", Matchers.empty()); + + // Validate response + checkGETStatusCodeWithAwait(requestUrl, 200); + + async.complete(); + } } From 64e69057992f09c6de5c0ab2a6b16e6c103561a6 Mon Sep 17 00:00:00 2001 From: steniobhz Date: Thu, 17 Oct 2024 13:53:16 +0100 Subject: [PATCH 12/14] Update README_hook.md --- gateleen-hook/README_hook.md | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/gateleen-hook/README_hook.md b/gateleen-hook/README_hook.md index 556c2a84..5a1cc9e5 100644 --- a/gateleen-hook/README_hook.md +++ b/gateleen-hook/README_hook.md @@ -240,10 +240,43 @@ hookHandler.enableResourceLogging(true); ``` +## Query-Based Listener and Route Search +Gateleen allows searching for listeners and routes using the query parameter `q`. This simplifies filtering the registered hooks based on query parameters. +### Listener Search with `q` +Search for listeners based on a query parameter like this: +``` +GET http://myserver:7012/gateleen/server/listenertest/_hooks/listeners/listener/1?q=testQuery +``` + +The response will contain the matching listeners. If no match is found, an empty list is returned: + +**Example response with matches:** +```json +{ + "listeners": [ + { + "destination": "/path/to/destination", + "methods": ["GET", "POST"] + } + ] +} +``` +**Example response with no matches:** +```json +{ + "listeners": [] +} +``` +### Route Search with `q` +Similarly, you can search for routes using a query parameter: +``` +GET http://myserver:7012/gateleen/server/listenertest/_hooks/routes?q=testRoute +``` +The response contains the matching routes, or an empty list if no match is found. From 6aeec5cb744bc73e976090146417001539267b97 Mon Sep 17 00:00:00 2001 From: almeidast Date: Thu, 17 Oct 2024 18:10:07 +0100 Subject: [PATCH 13/14] add validation to return 400 instead 404 search parameter != "q" or null --- .../swisspush/gateleen/hook/HookHandler.java | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/gateleen-hook/src/main/java/org/swisspush/gateleen/hook/HookHandler.java b/gateleen-hook/src/main/java/org/swisspush/gateleen/hook/HookHandler.java index 40812349..4fd633a4 100755 --- a/gateleen-hook/src/main/java/org/swisspush/gateleen/hook/HookHandler.java +++ b/gateleen-hook/src/main/java/org/swisspush/gateleen/hook/HookHandler.java @@ -90,7 +90,6 @@ public class HookHandler implements LoggableResource { public static final String HOOKS_LISTENERS_URI_PART = "/_hooks/listeners/"; public static final String LISTENER_QUEUE_PREFIX = "listener-hook"; private static final String X_QUEUE = "x-queue"; - private static final String X_EXPIRE_AFTER = "X-Expire-After"; private static final String LISTENER_HOOK_TARGET_PATH = "listeners/"; public static final String HOOKS_ROUTE_URI_PART = "/_hooks/route"; @@ -127,7 +126,6 @@ public class HookHandler implements LoggableResource { private static final String CONTENT_TYPE_JSON = "application/json"; private static final String LISTENERS_KEY = "listeners"; private static final String ROUTES_KEY = "routes"; - private static final String DESTINATION_KEY = "destination"; private static final String CONTENT_TYPE_HEADER = "content-type"; @@ -549,13 +547,12 @@ public void registerListenerRegistrationHandler(Handler readyHandler) { public boolean handle(final RoutingContext ctx) { HttpServerRequest request = ctx.request(); boolean consumed = false; - + var requestUri = request.uri(); /* * 1) Un- / Register Listener / Routes */ var requestMethod = request.method(); if (requestMethod == PUT) { - var requestUri = request.uri(); if (requestUri.contains(HOOKS_LISTENERS_URI_PART)) { handleListenerRegistration(request); return true; @@ -566,7 +563,6 @@ public boolean handle(final RoutingContext ctx) { } } if (requestMethod == DELETE) { - var requestUri = request.uri(); if (requestUri.contains(HOOKS_LISTENERS_URI_PART)) { handleListenerUnregistration(request); return true; @@ -579,17 +575,22 @@ public boolean handle(final RoutingContext ctx) { // 1. Check if the request method is GET if (request.method() == HttpMethod.GET) { - String uri = request.uri(); String queryParam = request.getParam("q"); - // 2. Check if the URI is for listeners or routes and has a query parameter - if (queryParam != null && !queryParam.isEmpty()) { - if (uri.contains(LISTENERS_KEY)) { + // If the 'q' parameter exists, proceed with search handling + if (queryParam != null) { + // Check if the URI corresponds to listeners or routes + if (requestUri.contains(LISTENERS_KEY)) { handleListenerSearch(queryParam, request.response()); return true; - } else if (uri.contains(ROUTES_KEY)) { + } else if (requestUri.contains(ROUTES_KEY)) { handleRouteSearch(queryParam, request.response()); return true; } + }else{ + if (!request.params().isEmpty()) { + request.response().setStatusCode(400).end("Bad Request: Only the 'q' parameter is allowed"); + return true; + } } } @@ -649,8 +650,9 @@ private void handleRouteSearch(String queryParam, HttpServerResponse response) { * @param response The HTTP response to return the results. Must not be null. */ private void handleSearch(Map repository, Function getDestination, String queryParam, String resultKey, HttpServerResponse response) { - if (repository == null || getDestination == null) { - response.setStatusCode(400).end(); // Bad request for missing parameters + + if (repository == null || getDestination == null || resultKey == null || queryParam.isEmpty()) { + response.setStatusCode(400).end("Bad Request: One or more required parameters are missing or null"); return; } @@ -658,7 +660,7 @@ private void handleSearch(Map repository, Function get repository.forEach((key, value) -> { String destination = getDestination.apply(value); - if (destination != null && destination.contains(queryParam != null ? queryParam : "")) { + if (destination != null && destination.contains(queryParam)) { matchingResults.add(key); } }); From 4f7f4ff2df18691fd15fc31d8303eb8d59698b2a Mon Sep 17 00:00:00 2001 From: almeidast Date: Thu, 17 Oct 2024 18:11:17 +0100 Subject: [PATCH 14/14] add testes to check if parameter is valid --- .../swisspush/gateleen/hook/ListenerTest.java | 66 +++++++++++++++++-- 1 file changed, 60 insertions(+), 6 deletions(-) diff --git a/gateleen-test/src/test/java/org/swisspush/gateleen/hook/ListenerTest.java b/gateleen-test/src/test/java/org/swisspush/gateleen/hook/ListenerTest.java index b22a283d..9f1edd05 100755 --- a/gateleen-test/src/test/java/org/swisspush/gateleen/hook/ListenerTest.java +++ b/gateleen-test/src/test/java/org/swisspush/gateleen/hook/ListenerTest.java @@ -945,8 +945,7 @@ private void checkGETBodyWithAwait(final String requestUrl, final String body) { /** * Test for hookHandleSearch with listener storage path and valid query param.
- * eg. register / unregister: http://localhost:7012/gateleen/server/listenertest/_hooks/listeners/listener/1
- * requestUrl: http://localhost:7012/gateleen/server/listenertest/listener/test?q=testQuery + * requestUrl: http://localhost:7012/playground/server/hooks/v1/registrations/listeners/?q=testQuery */ @Test public void testHookHandleSearch_ListenerPathWithValidQueryParam(TestContext context) { @@ -976,8 +975,7 @@ public void testHookHandleSearch_ListenerPathWithValidQueryParam(TestContext con /** * Test for hookHandleSearch with listener storage path and valid query param but no match found.
- * eg. register / unregister: http://localhost:7012/gateleen/server/listenertest/_hooks/listeners/listener/1
- * requestUrl: http://localhost:7012/gateleen/server/listenertest/listener/test?q=nonMatchingQuery + * requestUrl: http://localhost:7012/playground/server/hooks/v1/registrations/listeners/?q=nonMatchingQuery */ @Test public void testHookHandleSearch_ListenerPathWithNonMatchingQueryParam(TestContext context) { @@ -1011,8 +1009,7 @@ public void testHookHandleSearch_ListenerPathWithNonMatchingQueryParam(TestConte /** * Test for hookHandleSearch with listener storage path and valid query param but no listeners registered.
- * eg. register / unregister: http://localhost:7012/gateleen/server/listenertest/_hooks/listeners/listener/1
- * requestUrl: http://localhost:7012/gateleen/server/listenertest/listener/test?q=someQuery + * requestUrl: http://localhost:7012/playground/server/hooks/v1/registrations/listeners/?q=someQuery */ @Test public void testHookHandleSearch_NoListenersRegistered(TestContext context) { @@ -1039,4 +1036,61 @@ public void testHookHandleSearch_NoListenersRegistered(TestContext context) { async.complete(); } + + @Test + public void testHookHandleSearch_ListenerPathInvalidParam(TestContext context) { + Async async = context.async(); + delete(); + initRoutingRules(); + + String queryParam = "testQuery"; + String listenerPath = "/_hooks/listeners"; + String requestUrl = requestUrlBase + listenerPath + "?www=" + queryParam; + + // Register a listener + TestUtils.registerListener(requestUrlBase + listenerPath, targetUrlBase, new String[]{"GET", "POST"}, null); + + // Send GET request + given().queryParam("www", queryParam) + .when().get(requestUrl) + .then().assertThat().statusCode(400); + + // Validate the response + checkGETStatusCodeWithAwait(requestUrl, 400); + + TestUtils.unregisterListener(requestUrlBase + listenerPath); + + async.complete(); + } + + /** + * Test for hookHandleSearch with listener storage path and no query parameter.
+ * requestUrl: http://localhost:7012/playground/server/hooks/v1/registrations/listeners/?q= + */ + @Test + public void testHookHandleSearch_NoQueryParameter(TestContext context) { + Async async = context.async(); + delete(); + initRoutingRules(); + + String queryParam = ""; + String listenerPath = "/_hooks/listeners"; + String requestUrl = requestUrlBase + listenerPath + "?q=" + queryParam; + + // Register a listener + TestUtils.registerListener(requestUrlBase + listenerPath, targetUrlBase, new String[]{"GET", "POST"}, null); + + // Send GET request + given().queryParam("q", queryParam) + .when().get(requestUrl) + .then().assertThat().statusCode(400); + + // Validate the response + checkGETStatusCodeWithAwait(requestUrl, 400); + + TestUtils.unregisterListener(requestUrlBase + listenerPath); + + async.complete(); + } + }