From 6574b452c2fba85d075c474dd3fc212278b3e5ff Mon Sep 17 00:00:00 2001 From: runner Date: Mon, 30 Sep 2024 05:06:57 +0000 Subject: [PATCH 01/47] updating poms for 2.1.12 branch with snapshot versions From 693bfca3170030d6fde0a1955a55d208f5e14bee Mon Sep 17 00:00:00 2001 From: runner Date: Mon, 30 Sep 2024 05:06:59 +0000 Subject: [PATCH 02/47] updating poms for 2.1.13-SNAPSHOT development --- gateleen-cache/pom.xml | 2 +- gateleen-core/pom.xml | 2 +- gateleen-delegate/pom.xml | 2 +- gateleen-delta/pom.xml | 2 +- gateleen-expansion/pom.xml | 2 +- gateleen-hook-js/pom.xml | 2 +- gateleen-hook/pom.xml | 2 +- gateleen-kafka/pom.xml | 2 +- gateleen-logging/pom.xml | 2 +- gateleen-merge/pom.xml | 2 +- gateleen-monitoring/pom.xml | 2 +- gateleen-packing/pom.xml | 2 +- gateleen-player/pom.xml | 2 +- gateleen-playground/pom.xml | 2 +- gateleen-qos/pom.xml | 2 +- gateleen-queue/pom.xml | 2 +- gateleen-routing/pom.xml | 2 +- gateleen-runconfig/pom.xml | 2 +- gateleen-scheduler/pom.xml | 2 +- gateleen-security/pom.xml | 2 +- gateleen-test/pom.xml | 2 +- gateleen-testhelper/pom.xml | 2 +- gateleen-user/pom.xml | 2 +- gateleen-validation/pom.xml | 2 +- pom.xml | 2 +- 25 files changed, 25 insertions(+), 25 deletions(-) diff --git a/gateleen-cache/pom.xml b/gateleen-cache/pom.xml index 566c6bc2..f729e0c4 100644 --- a/gateleen-cache/pom.xml +++ b/gateleen-cache/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12-SNAPSHOT + 2.1.13-SNAPSHOT gateleen-cache diff --git a/gateleen-core/pom.xml b/gateleen-core/pom.xml index a4542cdd..a1374ed4 100644 --- a/gateleen-core/pom.xml +++ b/gateleen-core/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12-SNAPSHOT + 2.1.13-SNAPSHOT gateleen-core diff --git a/gateleen-delegate/pom.xml b/gateleen-delegate/pom.xml index 6441dec3..1e528418 100644 --- a/gateleen-delegate/pom.xml +++ b/gateleen-delegate/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12-SNAPSHOT + 2.1.13-SNAPSHOT gateleen-delegate diff --git a/gateleen-delta/pom.xml b/gateleen-delta/pom.xml index ee032654..fd1ef753 100644 --- a/gateleen-delta/pom.xml +++ b/gateleen-delta/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12-SNAPSHOT + 2.1.13-SNAPSHOT gateleen-delta diff --git a/gateleen-expansion/pom.xml b/gateleen-expansion/pom.xml index 20d4c7f8..80528935 100644 --- a/gateleen-expansion/pom.xml +++ b/gateleen-expansion/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12-SNAPSHOT + 2.1.13-SNAPSHOT gateleen-expansion diff --git a/gateleen-hook-js/pom.xml b/gateleen-hook-js/pom.xml index 580d6631..d09e3486 100644 --- a/gateleen-hook-js/pom.xml +++ b/gateleen-hook-js/pom.xml @@ -4,7 +4,7 @@ org.swisspush.gateleen gateleen - 2.1.12-SNAPSHOT + 2.1.13-SNAPSHOT gateleen-hook-js jar diff --git a/gateleen-hook/pom.xml b/gateleen-hook/pom.xml index 16d1fe26..3e75cb6b 100644 --- a/gateleen-hook/pom.xml +++ b/gateleen-hook/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12-SNAPSHOT + 2.1.13-SNAPSHOT gateleen-hook diff --git a/gateleen-kafka/pom.xml b/gateleen-kafka/pom.xml index 72562504..dd6bf50e 100644 --- a/gateleen-kafka/pom.xml +++ b/gateleen-kafka/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12-SNAPSHOT + 2.1.13-SNAPSHOT gateleen-kafka diff --git a/gateleen-logging/pom.xml b/gateleen-logging/pom.xml index 40ebe5a6..74b30ea8 100644 --- a/gateleen-logging/pom.xml +++ b/gateleen-logging/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12-SNAPSHOT + 2.1.13-SNAPSHOT gateleen-logging diff --git a/gateleen-merge/pom.xml b/gateleen-merge/pom.xml index 5c53639d..6aae5e51 100644 --- a/gateleen-merge/pom.xml +++ b/gateleen-merge/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12-SNAPSHOT + 2.1.13-SNAPSHOT gateleen-merge diff --git a/gateleen-monitoring/pom.xml b/gateleen-monitoring/pom.xml index 748f87a0..a525247e 100644 --- a/gateleen-monitoring/pom.xml +++ b/gateleen-monitoring/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12-SNAPSHOT + 2.1.13-SNAPSHOT gateleen-monitoring diff --git a/gateleen-packing/pom.xml b/gateleen-packing/pom.xml index 8dcd4067..e896dea6 100644 --- a/gateleen-packing/pom.xml +++ b/gateleen-packing/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12-SNAPSHOT + 2.1.13-SNAPSHOT gateleen-packing diff --git a/gateleen-player/pom.xml b/gateleen-player/pom.xml index b7388fba..ac3dc8e7 100644 --- a/gateleen-player/pom.xml +++ b/gateleen-player/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12-SNAPSHOT + 2.1.13-SNAPSHOT gateleen-player diff --git a/gateleen-playground/pom.xml b/gateleen-playground/pom.xml index b021cb2e..f47fd876 100644 --- a/gateleen-playground/pom.xml +++ b/gateleen-playground/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12-SNAPSHOT + 2.1.13-SNAPSHOT gateleen-playground diff --git a/gateleen-qos/pom.xml b/gateleen-qos/pom.xml index b7ee05fd..8ca94b3e 100644 --- a/gateleen-qos/pom.xml +++ b/gateleen-qos/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12-SNAPSHOT + 2.1.13-SNAPSHOT gateleen-qos diff --git a/gateleen-queue/pom.xml b/gateleen-queue/pom.xml index 05311590..99204819 100644 --- a/gateleen-queue/pom.xml +++ b/gateleen-queue/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12-SNAPSHOT + 2.1.13-SNAPSHOT gateleen-queue diff --git a/gateleen-routing/pom.xml b/gateleen-routing/pom.xml index 78ac1902..26f4bfce 100644 --- a/gateleen-routing/pom.xml +++ b/gateleen-routing/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12-SNAPSHOT + 2.1.13-SNAPSHOT gateleen-routing diff --git a/gateleen-runconfig/pom.xml b/gateleen-runconfig/pom.xml index 61fa79bf..3b74e46d 100644 --- a/gateleen-runconfig/pom.xml +++ b/gateleen-runconfig/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12-SNAPSHOT + 2.1.13-SNAPSHOT gateleen-runconfig diff --git a/gateleen-scheduler/pom.xml b/gateleen-scheduler/pom.xml index 50cdad37..dae147c1 100644 --- a/gateleen-scheduler/pom.xml +++ b/gateleen-scheduler/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12-SNAPSHOT + 2.1.13-SNAPSHOT gateleen-scheduler diff --git a/gateleen-security/pom.xml b/gateleen-security/pom.xml index 7e4f6aaa..1e53af8b 100644 --- a/gateleen-security/pom.xml +++ b/gateleen-security/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12-SNAPSHOT + 2.1.13-SNAPSHOT gateleen-security diff --git a/gateleen-test/pom.xml b/gateleen-test/pom.xml index 34a345e9..fabf66f4 100644 --- a/gateleen-test/pom.xml +++ b/gateleen-test/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12-SNAPSHOT + 2.1.13-SNAPSHOT gateleen-test jar diff --git a/gateleen-testhelper/pom.xml b/gateleen-testhelper/pom.xml index 3e89f551..6cc33c30 100644 --- a/gateleen-testhelper/pom.xml +++ b/gateleen-testhelper/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12-SNAPSHOT + 2.1.13-SNAPSHOT gateleen-testhelper diff --git a/gateleen-user/pom.xml b/gateleen-user/pom.xml index 1e3c3c4c..a1975813 100644 --- a/gateleen-user/pom.xml +++ b/gateleen-user/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12-SNAPSHOT + 2.1.13-SNAPSHOT gateleen-user diff --git a/gateleen-validation/pom.xml b/gateleen-validation/pom.xml index 81e4c52e..d0e624cc 100644 --- a/gateleen-validation/pom.xml +++ b/gateleen-validation/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12-SNAPSHOT + 2.1.13-SNAPSHOT gateleen-validation diff --git a/pom.xml b/pom.xml index 928b15bd..b67508a7 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12-SNAPSHOT + 2.1.13-SNAPSHOT pom gateleen Middleware library based on Vert.x to build advanced JSON/REST communication servers From 636a7dd7b6e097cd409291fea272ef44cca6555b Mon Sep 17 00:00:00 2001 From: runner Date: Mon, 30 Sep 2024 05:07:01 +0000 Subject: [PATCH 03/47] updating poms for branch'release-2.1.12' with non-snapshot versions --- gateleen-cache/pom.xml | 2 +- gateleen-core/pom.xml | 2 +- gateleen-delegate/pom.xml | 2 +- gateleen-delta/pom.xml | 2 +- gateleen-expansion/pom.xml | 2 +- gateleen-hook-js/pom.xml | 2 +- gateleen-hook/pom.xml | 2 +- gateleen-kafka/pom.xml | 2 +- gateleen-logging/pom.xml | 2 +- gateleen-merge/pom.xml | 2 +- gateleen-monitoring/pom.xml | 2 +- gateleen-packing/pom.xml | 2 +- gateleen-player/pom.xml | 2 +- gateleen-playground/pom.xml | 2 +- gateleen-qos/pom.xml | 2 +- gateleen-queue/pom.xml | 2 +- gateleen-routing/pom.xml | 2 +- gateleen-runconfig/pom.xml | 2 +- gateleen-scheduler/pom.xml | 2 +- gateleen-security/pom.xml | 2 +- gateleen-test/pom.xml | 2 +- gateleen-testhelper/pom.xml | 2 +- gateleen-user/pom.xml | 2 +- gateleen-validation/pom.xml | 2 +- pom.xml | 2 +- 25 files changed, 25 insertions(+), 25 deletions(-) diff --git a/gateleen-cache/pom.xml b/gateleen-cache/pom.xml index 566c6bc2..307fb6fc 100644 --- a/gateleen-cache/pom.xml +++ b/gateleen-cache/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12-SNAPSHOT + 2.1.12 gateleen-cache diff --git a/gateleen-core/pom.xml b/gateleen-core/pom.xml index a4542cdd..5e71204d 100644 --- a/gateleen-core/pom.xml +++ b/gateleen-core/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12-SNAPSHOT + 2.1.12 gateleen-core diff --git a/gateleen-delegate/pom.xml b/gateleen-delegate/pom.xml index 6441dec3..f7c0fc53 100644 --- a/gateleen-delegate/pom.xml +++ b/gateleen-delegate/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12-SNAPSHOT + 2.1.12 gateleen-delegate diff --git a/gateleen-delta/pom.xml b/gateleen-delta/pom.xml index ee032654..9e8678a7 100644 --- a/gateleen-delta/pom.xml +++ b/gateleen-delta/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12-SNAPSHOT + 2.1.12 gateleen-delta diff --git a/gateleen-expansion/pom.xml b/gateleen-expansion/pom.xml index 20d4c7f8..bd593490 100644 --- a/gateleen-expansion/pom.xml +++ b/gateleen-expansion/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12-SNAPSHOT + 2.1.12 gateleen-expansion diff --git a/gateleen-hook-js/pom.xml b/gateleen-hook-js/pom.xml index 580d6631..d17c0d24 100644 --- a/gateleen-hook-js/pom.xml +++ b/gateleen-hook-js/pom.xml @@ -4,7 +4,7 @@ org.swisspush.gateleen gateleen - 2.1.12-SNAPSHOT + 2.1.12 gateleen-hook-js jar diff --git a/gateleen-hook/pom.xml b/gateleen-hook/pom.xml index 16d1fe26..b49ba6c1 100644 --- a/gateleen-hook/pom.xml +++ b/gateleen-hook/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12-SNAPSHOT + 2.1.12 gateleen-hook diff --git a/gateleen-kafka/pom.xml b/gateleen-kafka/pom.xml index 72562504..52d43e7e 100644 --- a/gateleen-kafka/pom.xml +++ b/gateleen-kafka/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12-SNAPSHOT + 2.1.12 gateleen-kafka diff --git a/gateleen-logging/pom.xml b/gateleen-logging/pom.xml index 40ebe5a6..1048fd6c 100644 --- a/gateleen-logging/pom.xml +++ b/gateleen-logging/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12-SNAPSHOT + 2.1.12 gateleen-logging diff --git a/gateleen-merge/pom.xml b/gateleen-merge/pom.xml index 5c53639d..48344398 100644 --- a/gateleen-merge/pom.xml +++ b/gateleen-merge/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12-SNAPSHOT + 2.1.12 gateleen-merge diff --git a/gateleen-monitoring/pom.xml b/gateleen-monitoring/pom.xml index 748f87a0..f97d1712 100644 --- a/gateleen-monitoring/pom.xml +++ b/gateleen-monitoring/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12-SNAPSHOT + 2.1.12 gateleen-monitoring diff --git a/gateleen-packing/pom.xml b/gateleen-packing/pom.xml index 8dcd4067..e5c0f850 100644 --- a/gateleen-packing/pom.xml +++ b/gateleen-packing/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12-SNAPSHOT + 2.1.12 gateleen-packing diff --git a/gateleen-player/pom.xml b/gateleen-player/pom.xml index b7388fba..91c187cd 100644 --- a/gateleen-player/pom.xml +++ b/gateleen-player/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12-SNAPSHOT + 2.1.12 gateleen-player diff --git a/gateleen-playground/pom.xml b/gateleen-playground/pom.xml index b021cb2e..7da25ad5 100644 --- a/gateleen-playground/pom.xml +++ b/gateleen-playground/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12-SNAPSHOT + 2.1.12 gateleen-playground diff --git a/gateleen-qos/pom.xml b/gateleen-qos/pom.xml index b7ee05fd..8bf407dc 100644 --- a/gateleen-qos/pom.xml +++ b/gateleen-qos/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12-SNAPSHOT + 2.1.12 gateleen-qos diff --git a/gateleen-queue/pom.xml b/gateleen-queue/pom.xml index 05311590..aa135f53 100644 --- a/gateleen-queue/pom.xml +++ b/gateleen-queue/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12-SNAPSHOT + 2.1.12 gateleen-queue diff --git a/gateleen-routing/pom.xml b/gateleen-routing/pom.xml index 78ac1902..fb0fd2a6 100644 --- a/gateleen-routing/pom.xml +++ b/gateleen-routing/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12-SNAPSHOT + 2.1.12 gateleen-routing diff --git a/gateleen-runconfig/pom.xml b/gateleen-runconfig/pom.xml index 61fa79bf..fdd2dc92 100644 --- a/gateleen-runconfig/pom.xml +++ b/gateleen-runconfig/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12-SNAPSHOT + 2.1.12 gateleen-runconfig diff --git a/gateleen-scheduler/pom.xml b/gateleen-scheduler/pom.xml index 50cdad37..26cec985 100644 --- a/gateleen-scheduler/pom.xml +++ b/gateleen-scheduler/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12-SNAPSHOT + 2.1.12 gateleen-scheduler diff --git a/gateleen-security/pom.xml b/gateleen-security/pom.xml index 7e4f6aaa..0c536898 100644 --- a/gateleen-security/pom.xml +++ b/gateleen-security/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12-SNAPSHOT + 2.1.12 gateleen-security diff --git a/gateleen-test/pom.xml b/gateleen-test/pom.xml index 34a345e9..e9f44e27 100644 --- a/gateleen-test/pom.xml +++ b/gateleen-test/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12-SNAPSHOT + 2.1.12 gateleen-test jar diff --git a/gateleen-testhelper/pom.xml b/gateleen-testhelper/pom.xml index 3e89f551..b134d8c0 100644 --- a/gateleen-testhelper/pom.xml +++ b/gateleen-testhelper/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12-SNAPSHOT + 2.1.12 gateleen-testhelper diff --git a/gateleen-user/pom.xml b/gateleen-user/pom.xml index 1e3c3c4c..14db8014 100644 --- a/gateleen-user/pom.xml +++ b/gateleen-user/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12-SNAPSHOT + 2.1.12 gateleen-user diff --git a/gateleen-validation/pom.xml b/gateleen-validation/pom.xml index 81e4c52e..fc0720be 100644 --- a/gateleen-validation/pom.xml +++ b/gateleen-validation/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12-SNAPSHOT + 2.1.12 gateleen-validation diff --git a/pom.xml b/pom.xml index 928b15bd..c5ed5704 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12-SNAPSHOT + 2.1.12 pom gateleen Middleware library based on Vert.x to build advanced JSON/REST communication servers From 450462a7465a8c8ee44200141bf8a5afa2790ca0 Mon Sep 17 00:00:00 2001 From: runner Date: Mon, 30 Sep 2024 05:12:38 +0000 Subject: [PATCH 04/47] updating develop poms to master versions to avoid merge conflicts --- gateleen-cache/pom.xml | 2 +- gateleen-core/pom.xml | 2 +- gateleen-delegate/pom.xml | 2 +- gateleen-delta/pom.xml | 2 +- gateleen-expansion/pom.xml | 2 +- gateleen-hook-js/pom.xml | 2 +- gateleen-hook/pom.xml | 2 +- gateleen-kafka/pom.xml | 2 +- gateleen-logging/pom.xml | 2 +- gateleen-merge/pom.xml | 2 +- gateleen-monitoring/pom.xml | 2 +- gateleen-packing/pom.xml | 2 +- gateleen-player/pom.xml | 2 +- gateleen-playground/pom.xml | 2 +- gateleen-qos/pom.xml | 2 +- gateleen-queue/pom.xml | 2 +- gateleen-routing/pom.xml | 2 +- gateleen-runconfig/pom.xml | 2 +- gateleen-scheduler/pom.xml | 2 +- gateleen-security/pom.xml | 2 +- gateleen-test/pom.xml | 2 +- gateleen-testhelper/pom.xml | 2 +- gateleen-user/pom.xml | 2 +- gateleen-validation/pom.xml | 2 +- pom.xml | 2 +- 25 files changed, 25 insertions(+), 25 deletions(-) diff --git a/gateleen-cache/pom.xml b/gateleen-cache/pom.xml index f729e0c4..307fb6fc 100644 --- a/gateleen-cache/pom.xml +++ b/gateleen-cache/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.13-SNAPSHOT + 2.1.12 gateleen-cache diff --git a/gateleen-core/pom.xml b/gateleen-core/pom.xml index a1374ed4..5e71204d 100644 --- a/gateleen-core/pom.xml +++ b/gateleen-core/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.13-SNAPSHOT + 2.1.12 gateleen-core diff --git a/gateleen-delegate/pom.xml b/gateleen-delegate/pom.xml index 1e528418..f7c0fc53 100644 --- a/gateleen-delegate/pom.xml +++ b/gateleen-delegate/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.13-SNAPSHOT + 2.1.12 gateleen-delegate diff --git a/gateleen-delta/pom.xml b/gateleen-delta/pom.xml index fd1ef753..9e8678a7 100644 --- a/gateleen-delta/pom.xml +++ b/gateleen-delta/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.13-SNAPSHOT + 2.1.12 gateleen-delta diff --git a/gateleen-expansion/pom.xml b/gateleen-expansion/pom.xml index 80528935..bd593490 100644 --- a/gateleen-expansion/pom.xml +++ b/gateleen-expansion/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.13-SNAPSHOT + 2.1.12 gateleen-expansion diff --git a/gateleen-hook-js/pom.xml b/gateleen-hook-js/pom.xml index d09e3486..d17c0d24 100644 --- a/gateleen-hook-js/pom.xml +++ b/gateleen-hook-js/pom.xml @@ -4,7 +4,7 @@ org.swisspush.gateleen gateleen - 2.1.13-SNAPSHOT + 2.1.12 gateleen-hook-js jar diff --git a/gateleen-hook/pom.xml b/gateleen-hook/pom.xml index 3e75cb6b..b49ba6c1 100644 --- a/gateleen-hook/pom.xml +++ b/gateleen-hook/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.13-SNAPSHOT + 2.1.12 gateleen-hook diff --git a/gateleen-kafka/pom.xml b/gateleen-kafka/pom.xml index dd6bf50e..52d43e7e 100644 --- a/gateleen-kafka/pom.xml +++ b/gateleen-kafka/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.13-SNAPSHOT + 2.1.12 gateleen-kafka diff --git a/gateleen-logging/pom.xml b/gateleen-logging/pom.xml index 74b30ea8..1048fd6c 100644 --- a/gateleen-logging/pom.xml +++ b/gateleen-logging/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.13-SNAPSHOT + 2.1.12 gateleen-logging diff --git a/gateleen-merge/pom.xml b/gateleen-merge/pom.xml index 6aae5e51..48344398 100644 --- a/gateleen-merge/pom.xml +++ b/gateleen-merge/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.13-SNAPSHOT + 2.1.12 gateleen-merge diff --git a/gateleen-monitoring/pom.xml b/gateleen-monitoring/pom.xml index a525247e..f97d1712 100644 --- a/gateleen-monitoring/pom.xml +++ b/gateleen-monitoring/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.13-SNAPSHOT + 2.1.12 gateleen-monitoring diff --git a/gateleen-packing/pom.xml b/gateleen-packing/pom.xml index e896dea6..e5c0f850 100644 --- a/gateleen-packing/pom.xml +++ b/gateleen-packing/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.13-SNAPSHOT + 2.1.12 gateleen-packing diff --git a/gateleen-player/pom.xml b/gateleen-player/pom.xml index ac3dc8e7..91c187cd 100644 --- a/gateleen-player/pom.xml +++ b/gateleen-player/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.13-SNAPSHOT + 2.1.12 gateleen-player diff --git a/gateleen-playground/pom.xml b/gateleen-playground/pom.xml index f47fd876..7da25ad5 100644 --- a/gateleen-playground/pom.xml +++ b/gateleen-playground/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.13-SNAPSHOT + 2.1.12 gateleen-playground diff --git a/gateleen-qos/pom.xml b/gateleen-qos/pom.xml index 8ca94b3e..8bf407dc 100644 --- a/gateleen-qos/pom.xml +++ b/gateleen-qos/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.13-SNAPSHOT + 2.1.12 gateleen-qos diff --git a/gateleen-queue/pom.xml b/gateleen-queue/pom.xml index 99204819..aa135f53 100644 --- a/gateleen-queue/pom.xml +++ b/gateleen-queue/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.13-SNAPSHOT + 2.1.12 gateleen-queue diff --git a/gateleen-routing/pom.xml b/gateleen-routing/pom.xml index 26f4bfce..fb0fd2a6 100644 --- a/gateleen-routing/pom.xml +++ b/gateleen-routing/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.13-SNAPSHOT + 2.1.12 gateleen-routing diff --git a/gateleen-runconfig/pom.xml b/gateleen-runconfig/pom.xml index 3b74e46d..fdd2dc92 100644 --- a/gateleen-runconfig/pom.xml +++ b/gateleen-runconfig/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.13-SNAPSHOT + 2.1.12 gateleen-runconfig diff --git a/gateleen-scheduler/pom.xml b/gateleen-scheduler/pom.xml index dae147c1..26cec985 100644 --- a/gateleen-scheduler/pom.xml +++ b/gateleen-scheduler/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.13-SNAPSHOT + 2.1.12 gateleen-scheduler diff --git a/gateleen-security/pom.xml b/gateleen-security/pom.xml index 1e53af8b..0c536898 100644 --- a/gateleen-security/pom.xml +++ b/gateleen-security/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.13-SNAPSHOT + 2.1.12 gateleen-security diff --git a/gateleen-test/pom.xml b/gateleen-test/pom.xml index fabf66f4..e9f44e27 100644 --- a/gateleen-test/pom.xml +++ b/gateleen-test/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.13-SNAPSHOT + 2.1.12 gateleen-test jar diff --git a/gateleen-testhelper/pom.xml b/gateleen-testhelper/pom.xml index 6cc33c30..b134d8c0 100644 --- a/gateleen-testhelper/pom.xml +++ b/gateleen-testhelper/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.13-SNAPSHOT + 2.1.12 gateleen-testhelper diff --git a/gateleen-user/pom.xml b/gateleen-user/pom.xml index a1975813..14db8014 100644 --- a/gateleen-user/pom.xml +++ b/gateleen-user/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.13-SNAPSHOT + 2.1.12 gateleen-user diff --git a/gateleen-validation/pom.xml b/gateleen-validation/pom.xml index d0e624cc..fc0720be 100644 --- a/gateleen-validation/pom.xml +++ b/gateleen-validation/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.13-SNAPSHOT + 2.1.12 gateleen-validation diff --git a/pom.xml b/pom.xml index b67508a7..c5ed5704 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.13-SNAPSHOT + 2.1.12 pom gateleen Middleware library based on Vert.x to build advanced JSON/REST communication servers From bf6015bb55784927216552cce3c90edc9bb335a4 Mon Sep 17 00:00:00 2001 From: runner Date: Mon, 30 Sep 2024 05:12:40 +0000 Subject: [PATCH 05/47] Updating develop poms back to pre merge state --- gateleen-cache/pom.xml | 2 +- gateleen-core/pom.xml | 2 +- gateleen-delegate/pom.xml | 2 +- gateleen-delta/pom.xml | 2 +- gateleen-expansion/pom.xml | 2 +- gateleen-hook-js/pom.xml | 2 +- gateleen-hook/pom.xml | 2 +- gateleen-kafka/pom.xml | 2 +- gateleen-logging/pom.xml | 2 +- gateleen-merge/pom.xml | 2 +- gateleen-monitoring/pom.xml | 2 +- gateleen-packing/pom.xml | 2 +- gateleen-player/pom.xml | 2 +- gateleen-playground/pom.xml | 2 +- gateleen-qos/pom.xml | 2 +- gateleen-queue/pom.xml | 2 +- gateleen-routing/pom.xml | 2 +- gateleen-runconfig/pom.xml | 2 +- gateleen-scheduler/pom.xml | 2 +- gateleen-security/pom.xml | 2 +- gateleen-test/pom.xml | 2 +- gateleen-testhelper/pom.xml | 2 +- gateleen-user/pom.xml | 2 +- gateleen-validation/pom.xml | 2 +- pom.xml | 2 +- 25 files changed, 25 insertions(+), 25 deletions(-) diff --git a/gateleen-cache/pom.xml b/gateleen-cache/pom.xml index 307fb6fc..f729e0c4 100644 --- a/gateleen-cache/pom.xml +++ b/gateleen-cache/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12 + 2.1.13-SNAPSHOT gateleen-cache diff --git a/gateleen-core/pom.xml b/gateleen-core/pom.xml index 5e71204d..a1374ed4 100644 --- a/gateleen-core/pom.xml +++ b/gateleen-core/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12 + 2.1.13-SNAPSHOT gateleen-core diff --git a/gateleen-delegate/pom.xml b/gateleen-delegate/pom.xml index f7c0fc53..1e528418 100644 --- a/gateleen-delegate/pom.xml +++ b/gateleen-delegate/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12 + 2.1.13-SNAPSHOT gateleen-delegate diff --git a/gateleen-delta/pom.xml b/gateleen-delta/pom.xml index 9e8678a7..fd1ef753 100644 --- a/gateleen-delta/pom.xml +++ b/gateleen-delta/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12 + 2.1.13-SNAPSHOT gateleen-delta diff --git a/gateleen-expansion/pom.xml b/gateleen-expansion/pom.xml index bd593490..80528935 100644 --- a/gateleen-expansion/pom.xml +++ b/gateleen-expansion/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12 + 2.1.13-SNAPSHOT gateleen-expansion diff --git a/gateleen-hook-js/pom.xml b/gateleen-hook-js/pom.xml index d17c0d24..d09e3486 100644 --- a/gateleen-hook-js/pom.xml +++ b/gateleen-hook-js/pom.xml @@ -4,7 +4,7 @@ org.swisspush.gateleen gateleen - 2.1.12 + 2.1.13-SNAPSHOT gateleen-hook-js jar diff --git a/gateleen-hook/pom.xml b/gateleen-hook/pom.xml index b49ba6c1..3e75cb6b 100644 --- a/gateleen-hook/pom.xml +++ b/gateleen-hook/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12 + 2.1.13-SNAPSHOT gateleen-hook diff --git a/gateleen-kafka/pom.xml b/gateleen-kafka/pom.xml index 52d43e7e..dd6bf50e 100644 --- a/gateleen-kafka/pom.xml +++ b/gateleen-kafka/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12 + 2.1.13-SNAPSHOT gateleen-kafka diff --git a/gateleen-logging/pom.xml b/gateleen-logging/pom.xml index 1048fd6c..74b30ea8 100644 --- a/gateleen-logging/pom.xml +++ b/gateleen-logging/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12 + 2.1.13-SNAPSHOT gateleen-logging diff --git a/gateleen-merge/pom.xml b/gateleen-merge/pom.xml index 48344398..6aae5e51 100644 --- a/gateleen-merge/pom.xml +++ b/gateleen-merge/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12 + 2.1.13-SNAPSHOT gateleen-merge diff --git a/gateleen-monitoring/pom.xml b/gateleen-monitoring/pom.xml index f97d1712..a525247e 100644 --- a/gateleen-monitoring/pom.xml +++ b/gateleen-monitoring/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12 + 2.1.13-SNAPSHOT gateleen-monitoring diff --git a/gateleen-packing/pom.xml b/gateleen-packing/pom.xml index e5c0f850..e896dea6 100644 --- a/gateleen-packing/pom.xml +++ b/gateleen-packing/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12 + 2.1.13-SNAPSHOT gateleen-packing diff --git a/gateleen-player/pom.xml b/gateleen-player/pom.xml index 91c187cd..ac3dc8e7 100644 --- a/gateleen-player/pom.xml +++ b/gateleen-player/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12 + 2.1.13-SNAPSHOT gateleen-player diff --git a/gateleen-playground/pom.xml b/gateleen-playground/pom.xml index 7da25ad5..f47fd876 100644 --- a/gateleen-playground/pom.xml +++ b/gateleen-playground/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12 + 2.1.13-SNAPSHOT gateleen-playground diff --git a/gateleen-qos/pom.xml b/gateleen-qos/pom.xml index 8bf407dc..8ca94b3e 100644 --- a/gateleen-qos/pom.xml +++ b/gateleen-qos/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12 + 2.1.13-SNAPSHOT gateleen-qos diff --git a/gateleen-queue/pom.xml b/gateleen-queue/pom.xml index aa135f53..99204819 100644 --- a/gateleen-queue/pom.xml +++ b/gateleen-queue/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12 + 2.1.13-SNAPSHOT gateleen-queue diff --git a/gateleen-routing/pom.xml b/gateleen-routing/pom.xml index fb0fd2a6..26f4bfce 100644 --- a/gateleen-routing/pom.xml +++ b/gateleen-routing/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12 + 2.1.13-SNAPSHOT gateleen-routing diff --git a/gateleen-runconfig/pom.xml b/gateleen-runconfig/pom.xml index fdd2dc92..3b74e46d 100644 --- a/gateleen-runconfig/pom.xml +++ b/gateleen-runconfig/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12 + 2.1.13-SNAPSHOT gateleen-runconfig diff --git a/gateleen-scheduler/pom.xml b/gateleen-scheduler/pom.xml index 26cec985..dae147c1 100644 --- a/gateleen-scheduler/pom.xml +++ b/gateleen-scheduler/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12 + 2.1.13-SNAPSHOT gateleen-scheduler diff --git a/gateleen-security/pom.xml b/gateleen-security/pom.xml index 0c536898..1e53af8b 100644 --- a/gateleen-security/pom.xml +++ b/gateleen-security/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12 + 2.1.13-SNAPSHOT gateleen-security diff --git a/gateleen-test/pom.xml b/gateleen-test/pom.xml index e9f44e27..fabf66f4 100644 --- a/gateleen-test/pom.xml +++ b/gateleen-test/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12 + 2.1.13-SNAPSHOT gateleen-test jar diff --git a/gateleen-testhelper/pom.xml b/gateleen-testhelper/pom.xml index b134d8c0..6cc33c30 100644 --- a/gateleen-testhelper/pom.xml +++ b/gateleen-testhelper/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12 + 2.1.13-SNAPSHOT gateleen-testhelper diff --git a/gateleen-user/pom.xml b/gateleen-user/pom.xml index 14db8014..a1975813 100644 --- a/gateleen-user/pom.xml +++ b/gateleen-user/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12 + 2.1.13-SNAPSHOT gateleen-user diff --git a/gateleen-validation/pom.xml b/gateleen-validation/pom.xml index fc0720be..d0e624cc 100644 --- a/gateleen-validation/pom.xml +++ b/gateleen-validation/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12 + 2.1.13-SNAPSHOT gateleen-validation diff --git a/pom.xml b/pom.xml index c5ed5704..b67508a7 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ org.swisspush.gateleen gateleen - 2.1.12 + 2.1.13-SNAPSHOT pom gateleen Middleware library based on Vert.x to build advanced JSON/REST communication servers From ae01c874a55b72b28673267472c43e257765ef83 Mon Sep 17 00:00:00 2001 From: almeidast Date: Mon, 30 Sep 2024 20:04:12 +0100 Subject: [PATCH 06/47] 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 07/47] 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 08/47] 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 09/47] 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 10/47] 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 11/47] 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 12/47] 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 13/47] 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 14/47] 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 15/47] 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 16/47] - 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 17/47] 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 18/47] 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 19/47] 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(); + } + } From 777b906d4381aeeb3383dd46b4e0a8a0c16b5f27 Mon Sep 17 00:00:00 2001 From: almeidast Date: Mon, 21 Oct 2024 19:26:15 +0100 Subject: [PATCH 20/47] Fix RouteListingTest and added more validations. --- .../gateleen/hook/RouteListingTest.java | 75 +++++++++++-------- 1 file changed, 43 insertions(+), 32 deletions(-) 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 ed7ae6bf..06f27d52 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 @@ -224,31 +224,35 @@ 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(); + delete(); // Remove any pre-existing data + initSettings(); // Initialize routing rules - String queryParam = "testQuery"; + String queryParam = "routeTests"; String routePath = "/routes"; - String requestUrl = requestUrlBase + routePath + "?q=" + queryParam; + String requestUrl = requestUrlBase + routePath; - // Register a route - TestUtils.registerRoute(requestUrlBase + routePath, targetUrlBase, new String[]{"GET", "POST"}, null, true, true); + addRoute(queryParam, true, true); - // Send GET request with a valid query param - given().queryParam("q", queryParam) - .when().get(requestUrl) - .then().assertThat().statusCode(200); + // Verify that the route was correctly registered + Response response = given() + .queryParam("q", queryParam) + .when().get(requestUrl + "?q=" + queryParam) + .then().assertThat().statusCode(200) + .extract().response(); - // Validate response - checkGETStatusCodeWithAwait(requestUrl, 200); + // Assert that the response contains the expected query param + String responseBody = response.getBody().asString(); + Assert.assertTrue(responseBody.contains(queryParam)); // Fails if not found - TestUtils.unregisterRoute(requestUrlBase + routePath); + // Unregister the route + removeRoute(queryParam); async.complete(); } @@ -259,27 +263,29 @@ public void testRouteListing_ValidQueryParam(TestContext context) { @Test public void testRouteListing_NonMatchingQueryParam(TestContext context) { Async async = context.async(); - delete(); - initSettings(); + delete(); // Clean up before the test + initSettings(); // Initialize routing rules String nonMatchingQueryParam = "nonMatchingQuery"; + String queryParam = "other"; 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); + String requestUrl = requestUrlBase + routePath; - // Send GET request with non-matching query param - given().queryParam("q", nonMatchingQueryParam) + // Register a route using the addRoute method + addRoute(queryParam, true, true); + assertResponse(get(requestUrlBase), new String[]{queryParam+"/"}); + // Send GET request with a non-matching query param + Response response = given().queryParam("q", nonMatchingQueryParam) .when().get(requestUrl) .then().assertThat().statusCode(200) - .body("routes", Matchers.empty()); + .extract().response(); - // Validate response - checkGETStatusCodeWithAwait(requestUrl, 200); + // Assert the response does not contain the non-matching query param + Assert.assertFalse("Non-matching query param should not be found in response", + response.getBody().asString().contains(nonMatchingQueryParam)); - TestUtils.unregisterRoute(requestUrlBase + routePath); + // Unregister the route + removeRoute(queryParam); async.complete(); } @@ -290,8 +296,8 @@ public void testRouteListing_NonMatchingQueryParam(TestContext context) { @Test public void testRouteListing_NoRoutesRegistered(TestContext context) { Async async = context.async(); - delete(); - initSettings(); + delete(); // Ensure there's no previous data + initSettings(); // Initialize routing rules String queryParam = "someQuery"; String routePath = "/routes"; @@ -300,15 +306,20 @@ public void testRouteListing_NoRoutesRegistered(TestContext context) { // No routes registered // Send GET request with a query param - given().queryParam("q", queryParam) + Response response = given().queryParam("q", queryParam) .when().get(requestUrl) .then().assertThat().statusCode(200) - .body("routes", Matchers.empty()); + .extract().response(); + + // Print the body of the response for debugging + System.out.println("Response body: " + response.getBody().asString()); - // Validate response - checkGETStatusCodeWithAwait(requestUrl, 200); + // Assert that the response body is empty or does not contain routes + Assert.assertTrue("No routes should be registered", + response.getBody().asString().contains("routes")); async.complete(); } + } From c392df5f3c7b32b28ca272686784f5101944f08a Mon Sep 17 00:00:00 2001 From: almeidast Date: Mon, 21 Oct 2024 19:31:38 +0100 Subject: [PATCH 21/47] Fix README_hook.md --- gateleen-hook/README_hook.md | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/gateleen-hook/README_hook.md b/gateleen-hook/README_hook.md index 5a1cc9e5..d2964a9f 100644 --- a/gateleen-hook/README_hook.md +++ b/gateleen-hook/README_hook.md @@ -257,10 +257,7 @@ The response will contain the matching listeners. If no match is found, an empty ```json { "listeners": [ - { - "destination": "/path/to/destination", - "methods": ["GET", "POST"] - } + "first+playground+server+test+nemo+origin+b" ] } ``` From 19bc63dbdd823c690acdc3278b2cb48bb81c2970 Mon Sep 17 00:00:00 2001 From: almeidast Date: Tue, 22 Oct 2024 14:56:11 +0100 Subject: [PATCH 22/47] Recreate and fix tests --- .../swisspush/gateleen/hook/ListenerTest.java | 154 +++++++++++------- .../gateleen/hook/RouteListingTest.java | 12 +- 2 files changed, 97 insertions(+), 69 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 9f1edd05..4298ad3a 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 @@ -6,11 +6,13 @@ import io.restassured.RestAssured; import io.restassured.http.Header; import io.restassured.http.Headers; +import io.restassured.response.Response; import io.vertx.core.json.JsonObject; import io.vertx.ext.unit.Async; import io.vertx.ext.unit.TestContext; import io.vertx.ext.unit.junit.VertxUnitRunner; import org.awaitility.Awaitility; +import org.junit.Assert; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -953,22 +955,34 @@ public void testHookHandleSearch_ListenerPathWithValidQueryParam(TestContext con delete(); initRoutingRules(); - String queryParam = "testQuery"; - String listenerPath = "/_hooks/listeners"; - String requestUrl = requestUrlBase + listenerPath + "?q=" + queryParam; + // Settings + String subresource = "validQueryParameter"; + String listenerName = "myListener"; - // Register a listener - TestUtils.registerListener(requestUrlBase + listenerPath, targetUrlBase, new String[]{"GET", "POST"}, null); + String registerUrlListener1 = requestUrlBase + "/" + subresource + TestUtils.getHookListenersUrlSuffix() + listenerName; + String targetListener1 = targetUrlBase + "/" + listenerName; + String[] methodsListener1 = new String[]{"PUT", "DELETE", "POST"}; + final String targetUrlListener1 = targetUrlBase + "/" + listenerName; - // Send GET request - given().queryParam("q", queryParam) - .when().get(requestUrl) - .then().assertThat().statusCode(200); + final String requestUrl = requestUrlBase + "/" + subresource; - // Validate the response - checkGETStatusCodeWithAwait(requestUrl, 200); + delete(requestUrl); + delete(targetUrlListener1); + + //Sending request, one listener hooked + TestUtils.registerListener(registerUrlListener1, targetListener1, methodsListener1, null, null, + null, null, "x-foo: (A|B)"); - TestUtils.unregisterListener(requestUrlBase + listenerPath); + // Verify that the listener was correctly registered + Response response = given() + .queryParam("q", listenerName) + .when().get(registerUrlListener1) + .then().assertThat().statusCode(200) + .extract().response(); + + // Assert that the response contains the expected query param + Assert.assertTrue(response.getBody().asString().contains(listenerName)); // Fails if not found + TestUtils.unregisterListener(registerUrlListener1); async.complete(); } @@ -983,26 +997,44 @@ public void testHookHandleSearch_ListenerPathWithNonMatchingQueryParam(TestConte delete(); initRoutingRules(); - String nonMatchingQueryParam = "nonMatchingQuery"; - String listenerPath = "/_hooks/listeners"; - String requestUrl = requestUrlBase + listenerPath + "?q=" + nonMatchingQueryParam; + // Settings + String subresource = "matchingQueryParam"; + String listenerName = "myListener"; - // Register a listener with a different query param - String differentQueryParam = "differentQuery"; - TestUtils.registerListener(requestUrlBase + listenerPath + "?q=" + differentQueryParam, targetUrlBase, new String[]{"GET", "POST"}, null); + String registerUrlListener1 = requestUrlBase + "/" + subresource + TestUtils.getHookListenersUrlSuffix() + listenerName; + String targetListener1 = targetUrlBase + "/" + listenerName; + String[] methodsListener1 = new String[]{"PUT", "DELETE", "POST"}; + final String targetUrlListener1 = targetUrlBase + "/" + listenerName + "/" + "test"; - // Send GET request with non-matching query param - given().queryParam("q", nonMatchingQueryParam) - .when().get(requestUrl) - .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 + final String requestUrl = requestUrlBase + "/" + subresource + "/" + "test"; - // Validate that the response is 200 and the result is an empty array - checkGETStatusCodeWithAwait(requestUrl, 200); + delete(requestUrl); + delete(targetUrlListener1); - // Unregister the listener - TestUtils.unregisterListener(requestUrlBase + listenerPath); + TestUtils.registerListener(registerUrlListener1, targetListener1, methodsListener1, null, null, + null, null, "x-foo: (A|B)"); + + // Verify that the listener was correctly registered + Response response = given() + .queryParam("q", listenerName) + .when().get(registerUrlListener1) + .then().assertThat().statusCode(200) + .extract().response(); + + // Assert that the response contains the expected query param + Assert.assertTrue(response.getBody().asString().contains(listenerName)); // Fails if not found + + listenerName="nonMatchingQueryParam"; + // Verify that the listener search with a no registered listener + response = given() + .queryParam("q", listenerName) + .when().get(registerUrlListener1) + .then().assertThat().statusCode(200) + .extract().response(); + + // Assert that the response contains the expected query param + Assert.assertFalse(response.getBody().asString().contains(listenerName)); // Fails if not found + TestUtils.unregisterListener(registerUrlListener1); async.complete(); } @@ -1019,20 +1051,17 @@ public void testHookHandleSearch_NoListenersRegistered(TestContext context) { String queryParam = "someQuery"; String listenerPath = "/_hooks/listeners"; - String requestUrl = requestUrlBase + listenerPath + "?q=" + queryParam; - - // No listeners registered + String requestUrl = requestUrlBase + listenerPath; - // Send GET request with a query param - given().queryParam("q", queryParam) + // Verify that the listener search with a no registered listener + Response response = 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); + .then().assertThat().statusCode(200) + .extract().response(); + // Assert that the response contains the expected query param + Assert.assertFalse(response.getBody().asString().contains(queryParam)); // Fails if not found async.complete(); } @@ -1047,19 +1076,8 @@ public void testHookHandleSearch_ListenerPathInvalidParam(TestContext context) { 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(); } @@ -1073,22 +1091,34 @@ public void testHookHandleSearch_NoQueryParameter(TestContext context) { delete(); initRoutingRules(); - String queryParam = ""; - String listenerPath = "/_hooks/listeners"; - String requestUrl = requestUrlBase + listenerPath + "?q=" + queryParam; + // Settings + String subresource = "validQueryParameter"; + String listenerName = ""; - // Register a listener - TestUtils.registerListener(requestUrlBase + listenerPath, targetUrlBase, new String[]{"GET", "POST"}, null); + String registerUrlListener1 = requestUrlBase + "/" + subresource + TestUtils.getHookListenersUrlSuffix() + listenerName; + String targetListener1 = targetUrlBase + "/" + listenerName; + String[] methodsListener1 = new String[]{"PUT", "DELETE", "POST"}; + final String targetUrlListener1 = targetUrlBase + "/" + listenerName; - // Send GET request - given().queryParam("q", queryParam) - .when().get(requestUrl) - .then().assertThat().statusCode(400); + final String requestUrl = requestUrlBase + "/" + subresource; - // Validate the response - checkGETStatusCodeWithAwait(requestUrl, 400); + delete(requestUrl); + delete(targetUrlListener1); + + //Sending request, one listener hooked + TestUtils.registerListener(registerUrlListener1, targetListener1, methodsListener1, null, null, + null, null, "x-foo: (A|B)"); + + // Verify that the listener was correctly registered + Response response = given() + .queryParam("q", listenerName) + .when().get(registerUrlListener1) + .then().assertThat().statusCode(400) + .extract().response(); - TestUtils.unregisterListener(requestUrlBase + listenerPath); + // Assert that the response contains the expected query param + Assert.assertTrue(response.getBody().asString().contains("Bad Request")); // Fails if not found + TestUtils.unregisterListener(registerUrlListener1); async.complete(); } 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 06f27d52..1cd93712 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 @@ -243,7 +243,7 @@ public void testRouteListing_ValidQueryParam(TestContext context) { // Verify that the route was correctly registered Response response = given() .queryParam("q", queryParam) - .when().get(requestUrl + "?q=" + queryParam) + .when().get(requestUrl ) .then().assertThat().statusCode(200) .extract().response(); @@ -274,6 +274,7 @@ public void testRouteListing_NonMatchingQueryParam(TestContext context) { // Register a route using the addRoute method addRoute(queryParam, true, true); assertResponse(get(requestUrlBase), new String[]{queryParam+"/"}); + // Send GET request with a non-matching query param Response response = given().queryParam("q", nonMatchingQueryParam) .when().get(requestUrl) @@ -301,7 +302,7 @@ public void testRouteListing_NoRoutesRegistered(TestContext context) { String queryParam = "someQuery"; String routePath = "/routes"; - String requestUrl = requestUrlBase + routePath + "?q=" + queryParam; + String requestUrl = requestUrlBase + routePath; // No routes registered @@ -311,12 +312,9 @@ public void testRouteListing_NoRoutesRegistered(TestContext context) { .then().assertThat().statusCode(200) .extract().response(); - // Print the body of the response for debugging - System.out.println("Response body: " + response.getBody().asString()); - // Assert that the response body is empty or does not contain routes - Assert.assertTrue("No routes should be registered", - response.getBody().asString().contains("routes")); + Assert.assertFalse("No routes should be registered", + response.getBody().asString().contains(queryParam)); async.complete(); } From 13b99c208630512d3a2347d7ad21a73d8eed76ca Mon Sep 17 00:00:00 2001 From: almeidast Date: Tue, 22 Oct 2024 15:01:30 +0100 Subject: [PATCH 23/47] Fix Url for listeners search --- gateleen-hook/README_hook.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gateleen-hook/README_hook.md b/gateleen-hook/README_hook.md index d2964a9f..fc3b70fe 100644 --- a/gateleen-hook/README_hook.md +++ b/gateleen-hook/README_hook.md @@ -248,7 +248,7 @@ Gateleen allows searching for listeners and routes using the query parameter `q` Search for listeners based on a query parameter like this: ``` -GET http://myserver:7012/gateleen/server/listenertest/_hooks/listeners/listener/1?q=testQuery +GET http://myserver:7012/gateleen/server/listenertest/_hooks/listeners?q=testQuery ``` The response will contain the matching listeners. If no match is found, an empty list is returned: From ad49bda4604ec8c763e3dd2275c08f015e58bc9b Mon Sep 17 00:00:00 2001 From: almeidast Date: Tue, 22 Oct 2024 15:20:00 +0100 Subject: [PATCH 24/47] Add test with more listeners to check if search works as expected --- .../swisspush/gateleen/hook/ListenerTest.java | 54 +++++++++++++++++++ 1 file changed, 54 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 4298ad3a..6baac56a 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 @@ -1123,4 +1123,58 @@ public void testHookHandleSearch_NoQueryParameter(TestContext context) { async.complete(); } + @Test + public void testHookHandleSearch_ReturnsTwoOutOfThreeListeners(TestContext context) { + Async async = context.async(); + delete(); + initRoutingRules(); + + // Settings for the three listeners + String subresource = "multiListenerTest"; + String listenerName1 = "listenerOne"; + String listenerName2 = "listenerTwo"; + String listenerName3 = "NoMatchThree"; + + String registerUrlListener1 = requestUrlBase + "/" + subresource + TestUtils.getHookListenersUrlSuffix() + listenerName1; + String registerUrlListener2 = requestUrlBase + "/" + subresource + TestUtils.getHookListenersUrlSuffix() + listenerName2; + String registerUrlListener3 = requestUrlBase + "/" + subresource + TestUtils.getHookListenersUrlSuffix() + listenerName3; + + String targetListener1 = targetUrlBase + "/" + listenerName1; + String targetListener2 = targetUrlBase + "/" + listenerName2; + String targetListener3 = targetUrlBase + "/" + listenerName3; + + String[] methodsListener = new String[]{"PUT", "DELETE", "POST"}; + + delete(registerUrlListener1); + delete(registerUrlListener2); + delete(registerUrlListener3); + + // Adding the three listeners + TestUtils.registerListener(registerUrlListener1, targetListener1, methodsListener, null, null, + null, null, "x-foo: (A|B)"); + TestUtils.registerListener(registerUrlListener2, targetListener2, methodsListener, null, null, + null, null, "x-foo: (A|B)"); + TestUtils.registerListener(registerUrlListener3, targetListener3, methodsListener, null, null, + null, null, "x-foo: (A|B)"); + // Perform a search for the listeners that should return only listenerOne and listenerTwo + Response response = given() + .queryParam("q", "listener") + .when().get(requestUrlBase + "/" + subresource + TestUtils.getHookListenersUrlSuffix()) + .then().assertThat().statusCode(200) + .extract().response(); + + // Assert that the search contains listenerOne and listenerTwo but not listenerThree + Assert.assertTrue(response.getBody().asString().contains(listenerName1)); + Assert.assertTrue(response.getBody().asString().contains(listenerName2)); + Assert.assertFalse(response.getBody().asString().contains(listenerName3)); + + // Unregister the listeners + TestUtils.unregisterListener(registerUrlListener1); + TestUtils.unregisterListener(registerUrlListener2); + TestUtils.unregisterListener(registerUrlListener3); + + async.complete(); + } + + } From 8a2a7cf05989d845506a47921bb708a42af96b57 Mon Sep 17 00:00:00 2001 From: almeidast Date: Tue, 22 Oct 2024 16:28:18 +0100 Subject: [PATCH 25/47] Improve tests --- gateleen-hook/README_hook.md | 4 ++-- .../org/swisspush/gateleen/hook/RouteListingTest.java | 10 ++++++++++ 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/gateleen-hook/README_hook.md b/gateleen-hook/README_hook.md index fc3b70fe..87721086 100644 --- a/gateleen-hook/README_hook.md +++ b/gateleen-hook/README_hook.md @@ -248,7 +248,7 @@ Gateleen allows searching for listeners and routes using the query parameter `q` Search for listeners based on a query parameter like this: ``` -GET http://myserver:7012/gateleen/server/listenertest/_hooks/listeners?q=testQuery +GET http://myserver:7012/gateleen/server/listenertest/_hooks/listeners?q=test ``` The response will contain the matching listeners. If no match is found, an empty list is returned: @@ -273,7 +273,7 @@ The response will contain the matching listeners. If no match is found, an empty Similarly, you can search for routes using a query parameter: ``` -GET http://myserver:7012/gateleen/server/listenertest/_hooks/routes?q=testRoute +GET http://myserver:7012/gateleen/server/routetest/_hooks/routes?q=test ``` The response contains the matching routes, or an empty list if no match is found. 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 1cd93712..4bf69279 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 @@ -285,6 +285,16 @@ public void testRouteListing_NonMatchingQueryParam(TestContext context) { Assert.assertFalse("Non-matching query param should not be found in response", response.getBody().asString().contains(nonMatchingQueryParam)); + // Send GET request with a matching query param + response = given().queryParam("q", queryParam) + .when().get(requestUrl) + .then().assertThat().statusCode(200) + .extract().response(); + + // Assert the response contain the matching query param + Assert.assertTrue("matching query param should be found in response", + response.getBody().asString().contains(queryParam)); + // Unregister the route removeRoute(queryParam); From ac9c7207d862618a5fbba69ae783cb01276a052b Mon Sep 17 00:00:00 2001 From: almeidast Date: Tue, 22 Oct 2024 16:46:33 +0100 Subject: [PATCH 26/47] Improve unit tests --- .../gateleen/hook/HookHandlerTest.java | 58 ------------------- 1 file changed, 58 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 c5e073cc..0fc792bf 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 @@ -669,27 +669,6 @@ public void testHandleListenerSearch_Success() { 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="; - - HttpServerRequest request = mock(HttpServerRequest.class); - HttpServerResponse response = mock(HttpServerResponse.class); - - when(request.uri()).thenReturn(uri); - 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); - - boolean result = hookHandler.handle(routingContext); - - assertFalse(result); - verify(response, never()).end(); - } @Test public void testHandleListenerWithStorageAndSearchSuccess(TestContext context) { @@ -732,43 +711,6 @@ public void testHandleListenerWithStorageAndSearchSuccess(TestContext context) { async.complete(); } - @Test - 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); - - // 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 param - Mockito.when(request.response()).thenReturn(response); - Mockito.when(request.headers()).thenReturn(MultiMap.caseInsensitiveMultiMap()); - - - HttpClient selfClient = Mockito.mock(HttpClient.class); - Mockito.when(selfClient.request(Mockito.any(), Mockito.anyString())).thenReturn(Future.failedFuture(new Exception("Mocked failure"))); - - Mockito.when(routingContext.request()).thenReturn(request); - - // Act - boolean handled = hookHandler.handle(routingContext); - - // Assert - context.assertFalse(handled); - Mockito.verify(response, Mockito.never()).end(); - } - - @Test public void testSearchMultipleListeners_Success(TestContext context) { Vertx vertx = Vertx.vertx(); From 3609204291082e9bc1d456f321701f67bba3d0eb Mon Sep 17 00:00:00 2001 From: almeidast Date: Tue, 22 Oct 2024 17:59:42 +0100 Subject: [PATCH 27/47] improve test url as suggested --- gateleen-hook/README_hook.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gateleen-hook/README_hook.md b/gateleen-hook/README_hook.md index 87721086..c754d133 100644 --- a/gateleen-hook/README_hook.md +++ b/gateleen-hook/README_hook.md @@ -248,7 +248,7 @@ Gateleen allows searching for listeners and routes using the query parameter `q` Search for listeners based on a query parameter like this: ``` -GET http://myserver:7012/gateleen/server/listenertest/_hooks/listeners?q=test +GET http://myserver:7012/playground/server/hooks/v1/registrations/listeners?q=test ``` The response will contain the matching listeners. If no match is found, an empty list is returned: @@ -273,7 +273,7 @@ The response will contain the matching listeners. If no match is found, an empty Similarly, you can search for routes using a query parameter: ``` -GET http://myserver:7012/gateleen/server/routetest/_hooks/routes?q=test +GET http://myserver:7012/playground/server/hooks/v1/registrations/routes/?q=test ``` The response contains the matching routes, or an empty list if no match is found. From 76adc3408fdb2ccd63930eed5c20c6b8e0c501f8 Mon Sep 17 00:00:00 2001 From: almeidast Date: Thu, 24 Oct 2024 14:14:14 +0100 Subject: [PATCH 28/47] move integration testes to the right project improve validation url to preserve the previous functionality --- .../swisspush/gateleen/hook/HookHandler.java | 34 +-- .../gateleen/hook/HookHandlerTest.java | 262 ++---------------- 2 files changed, 41 insertions(+), 255 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 4fd633a4..867a5cd5 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 @@ -158,7 +158,8 @@ public class HookHandler implements LoggableResource { private int routeMultiplier; private final QueueSplitter queueSplitter; - + private final String routeBase; + private final String listenerBase; /** * Creates a new HookHandler. @@ -291,6 +292,8 @@ public HookHandler(Vertx vertx, HttpClient selfClient, final ResourceStorage use this.queueSplitter = queueSplitter; String hookSchema = ResourcesUtils.loadResource("gateleen_hooking_schema_hook", true); jsonSchemaHook = JsonSchemaFactory.getInstance().getSchema(hookSchema); + this.listenerBase = hookRootUri + HOOK_LISTENER_STORAGE_PATH; + this.routeBase = hookRootUri + HOOK_ROUTE_STORAGE_PATH; } public void init() { @@ -385,9 +388,6 @@ private void registerCleanupHandler(Handler readyHandler) { private void loadStoredRoutes(Handler readyHandler) { log.debug("loadStoredRoutes"); - // load the names of the routes from the hookStorage - final String routeBase = hookRootUri + HOOK_ROUTE_STORAGE_PATH; - hookStorage.get(routeBase, buffer -> { if (buffer != null) { JsonObject listOfRoutes = new JsonObject(buffer.toString()); @@ -432,8 +432,6 @@ private void loadStoredRoutes(Handler readyHandler) { private void loadStoredListeners(final Handler readyHandler) { log.debug("loadStoredListeners"); - // load the names of the listener from the hookStorage - final String listenerBase = hookRootUri + HOOK_LISTENER_STORAGE_PATH; hookStorage.get(listenerBase, buffer -> { if (buffer != null) { JsonObject listOfListeners = new JsonObject(buffer.toString()); @@ -575,22 +573,16 @@ public boolean handle(final RoutingContext ctx) { // 1. Check if the request method is GET if (request.method() == HttpMethod.GET) { - String queryParam = request.getParam("q"); - // 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)) { + if (!request.params().isEmpty()) { + String queryParam = request.getParam("q"); + String normalizedRequestUri = requestUri.replaceAll("/$", ""); + if (normalizedRequestUri.contains(listenerBase.replaceAll("/$", ""))) { handleListenerSearch(queryParam, request.response()); return true; - } else if (requestUri.contains(ROUTES_KEY)) { + } else if (normalizedRequestUri.contains(routeBase.replaceAll("/$", ""))) { 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; - } } } @@ -651,8 +643,8 @@ private void handleRouteSearch(String queryParam, HttpServerResponse response) { */ private void handleSearch(Map repository, Function getDestination, String queryParam, String resultKey, HttpServerResponse response) { - if (repository == null || getDestination == null || resultKey == null || queryParam.isEmpty()) { - response.setStatusCode(400).end("Bad Request: One or more required parameters are missing or null"); + if (repository == null || getDestination == null || resultKey == null || queryParam == null || queryParam.isEmpty()) { + response.setStatusCode(StatusCode.BAD_REQUEST.getStatusCode()).end("Bad Request: One or more required parameters are missing or null"); return; } @@ -671,9 +663,7 @@ private void handleSearch(Map repository, Function get String encodedResult = result.encode(); response.putHeader(CONTENT_TYPE_HEADER, CONTENT_TYPE_JSON); - response.putHeader("Content-Length", String.valueOf(encodedResult.length())); - response.write(encodedResult); - response.end(); + response.end(encodedResult); } 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 0fc792bf..cb0e1d25 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 @@ -647,7 +647,7 @@ public void hookRegistration_RouteWithRouteMultiplier(TestContext testContext) t public void testHandleListenerSearch_Success() { // Arrange String queryParam = "validQueryParam"; - String uri = "registrations/listeners/?q=" + queryParam; + String uri = "hookRootURI/registrations/listeners/?q=" + queryParam; HttpServerRequest request = mock(HttpServerRequest.class); HttpServerResponse response = mock(HttpServerResponse.class); @@ -666,252 +666,48 @@ public void testHandleListenerSearch_Success() { boolean result = hookHandler.handle(routingContext); // Calls the public `handle` method // Assert - assertTrue(result); // Ensure the handler returns true for a valid listener search + assertTrue(result); verify(response, times(1)).end(); // Ensure `response.end()` was called } @Test - 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); + public void testHandleListenerWithStorageAndSearchSuccess() { + // Arrange + String queryParam = "validQueryParam"; + String uri = "hookRootURI/registrations/listeners/?q=" + queryParam; - 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\" } }"); + HttpServerRequest request = mock(HttpServerRequest.class); + HttpServerResponse response = mock(HttpServerResponse.class); - // Mock RoutingContext and its behavior - RoutingContext routingContext = Mockito.mock(RoutingContext.class); - HttpServerRequest request = Mockito.mock(HttpServerRequest.class); - HttpServerResponse response = Mockito.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); - // 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); + // Use ArgumentCaptor para capturar o conteúdo da resposta + ArgumentCaptor responseCaptor = ArgumentCaptor.forClass(String.class); - Mockito.when(routingContext.request()).thenReturn(request); + // Mock RoutingContext + RoutingContext routingContext = mock(RoutingContext.class); + when(routingContext.request()).thenReturn(request); - // Async handler to check the result - Async async = context.async(); + // Act + boolean result = hookHandler.handle(routingContext); // Chama o método público `handle` - // Act: Call the hookHandler.handle method - boolean handled = hookHandler.handle(routingContext); + // Assert + assertTrue(result); // Certifique-se de que o handler retornou true - // Assert that it was handled - context.assertTrue(handled); + // Capture o valor passado para end() + verify(response).end(responseCaptor.capture()); - // Verify that the response ended correctly (simulating a successful response) - Mockito.verify(response, Mockito.times(1)).end(); - async.complete(); + // Verifique o conteúdo da resposta + String actualResponse = responseCaptor.getValue(); + assertNotNull(actualResponse); + assertEquals("{\"listeners\":[]}", actualResponse); // Ajuste conforme o comportamento esperado } - @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 8096031af5e9bb4cca53fe234ec4006edcdd7835 Mon Sep 17 00:00:00 2001 From: almeidast Date: Mon, 28 Oct 2024 11:42:04 +0000 Subject: [PATCH 29/47] improve integration tests --- .../swisspush/gateleen/hook/ListenerTest.java | 35 +++++++++---------- .../gateleen/hook/RouteListingTest.java | 30 +++++++++------- 2 files changed, 34 insertions(+), 31 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 6baac56a..8565141e 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 @@ -43,6 +43,7 @@ public class ListenerTest extends AbstractTest { private final static int WIREMOCK_PORT = 8881; private String requestUrlBase; private String targetUrlBase; + private String searchUrlBase; @Rule public WireMockRule wireMockRule = new WireMockRule(WIREMOCK_PORT); @@ -56,6 +57,8 @@ public void initRestAssured() { requestUrlBase = "/tests/gateleen/monitoredresource"; targetUrlBase = "http://localhost:" + MAIN_PORT + SERVER_ROOT + "/tests/gateleen/targetresource"; + searchUrlBase = "http://localhost:" + MAIN_PORT + SERVER_ROOT + "/hooks/v1/registrations/listeners"; + } /** @@ -956,17 +959,16 @@ public void testHookHandleSearch_ListenerPathWithValidQueryParam(TestContext con initRoutingRules(); // Settings - String subresource = "validQueryParameter"; + String subresource = "matchingQueryParam"; String listenerName = "myListener"; - String registerUrlListener1 = requestUrlBase + "/" + subresource + TestUtils.getHookListenersUrlSuffix() + listenerName; + String registerUrlListener1 = requestUrlBase + "/" + subresource + TestUtils.getHookListenersUrlSuffix() + listenerName; String targetListener1 = targetUrlBase + "/" + listenerName; String[] methodsListener1 = new String[]{"PUT", "DELETE", "POST"}; final String targetUrlListener1 = targetUrlBase + "/" + listenerName; - final String requestUrl = requestUrlBase + "/" + subresource; - delete(requestUrl); + delete(searchUrlBase); delete(targetUrlListener1); //Sending request, one listener hooked @@ -976,11 +978,12 @@ public void testHookHandleSearch_ListenerPathWithValidQueryParam(TestContext con // Verify that the listener was correctly registered Response response = given() .queryParam("q", listenerName) - .when().get(registerUrlListener1) + .when().get(searchUrlBase) .then().assertThat().statusCode(200) .extract().response(); // Assert that the response contains the expected query param + System.out.println(response.getBody().asString()); Assert.assertTrue(response.getBody().asString().contains(listenerName)); // Fails if not found TestUtils.unregisterListener(registerUrlListener1); @@ -1006,9 +1009,8 @@ public void testHookHandleSearch_ListenerPathWithNonMatchingQueryParam(TestConte String[] methodsListener1 = new String[]{"PUT", "DELETE", "POST"}; final String targetUrlListener1 = targetUrlBase + "/" + listenerName + "/" + "test"; - final String requestUrl = requestUrlBase + "/" + subresource + "/" + "test"; - delete(requestUrl); + delete(searchUrlBase); delete(targetUrlListener1); TestUtils.registerListener(registerUrlListener1, targetListener1, methodsListener1, null, null, @@ -1017,7 +1019,7 @@ public void testHookHandleSearch_ListenerPathWithNonMatchingQueryParam(TestConte // Verify that the listener was correctly registered Response response = given() .queryParam("q", listenerName) - .when().get(registerUrlListener1) + .when().get(searchUrlBase) .then().assertThat().statusCode(200) .extract().response(); @@ -1028,7 +1030,7 @@ public void testHookHandleSearch_ListenerPathWithNonMatchingQueryParam(TestConte // Verify that the listener search with a no registered listener response = given() .queryParam("q", listenerName) - .when().get(registerUrlListener1) + .when().get(searchUrlBase) .then().assertThat().statusCode(200) .extract().response(); @@ -1050,13 +1052,12 @@ public void testHookHandleSearch_NoListenersRegistered(TestContext context) { initRoutingRules(); String queryParam = "someQuery"; - String listenerPath = "/_hooks/listeners"; - String requestUrl = requestUrlBase + listenerPath; + String listenerPath = "/hooks/listeners"; // Verify that the listener search with a no registered listener Response response = given() .queryParam("q", queryParam) - .when().get(requestUrl) + .when().get(searchUrlBase) .then().assertThat().statusCode(200) .extract().response(); @@ -1074,7 +1075,7 @@ public void testHookHandleSearch_ListenerPathInvalidParam(TestContext context) { String queryParam = "testQuery"; String listenerPath = "/_hooks/listeners"; - String requestUrl = requestUrlBase + listenerPath + "?www=" + queryParam; + String requestUrl = searchUrlBase+ "?www=" + queryParam; // Validate the response checkGETStatusCodeWithAwait(requestUrl, 400); @@ -1100,9 +1101,7 @@ public void testHookHandleSearch_NoQueryParameter(TestContext context) { String[] methodsListener1 = new String[]{"PUT", "DELETE", "POST"}; final String targetUrlListener1 = targetUrlBase + "/" + listenerName; - final String requestUrl = requestUrlBase + "/" + subresource; - - delete(requestUrl); + delete(searchUrlBase); delete(targetUrlListener1); //Sending request, one listener hooked @@ -1112,7 +1111,7 @@ public void testHookHandleSearch_NoQueryParameter(TestContext context) { // Verify that the listener was correctly registered Response response = given() .queryParam("q", listenerName) - .when().get(registerUrlListener1) + .when().get(searchUrlBase) .then().assertThat().statusCode(400) .extract().response(); @@ -1159,7 +1158,7 @@ public void testHookHandleSearch_ReturnsTwoOutOfThreeListeners(TestContext conte // Perform a search for the listeners that should return only listenerOne and listenerTwo Response response = given() .queryParam("q", "listener") - .when().get(requestUrlBase + "/" + subresource + TestUtils.getHookListenersUrlSuffix()) + .when().get(searchUrlBase) .then().assertThat().statusCode(200) .extract().response(); 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 4bf69279..6fe83dee 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 @@ -27,7 +27,7 @@ public class RouteListingTest extends AbstractTest { private String requestUrlBase; private String targetUrlBase; private String parentKey; - + private String searchUrlBase; /** * Overwrite RestAssured configuration @@ -39,6 +39,7 @@ public void initRestAssured() { parentKey = "routesource"; requestUrlBase = "/tests/gateleen/" + parentKey; targetUrlBase = "http://localhost:" + MAIN_PORT + SERVER_ROOT + "/tests/gateleen/routetarget"; + searchUrlBase = "http://localhost:" + MAIN_PORT + SERVER_ROOT + "/hooks/v1/registrations/routes"; } @@ -236,14 +237,14 @@ public void testRouteListing_ValidQueryParam(TestContext context) { String queryParam = "routeTests"; String routePath = "/routes"; - String requestUrl = requestUrlBase + routePath; + addRoute(queryParam, true, true); // Verify that the route was correctly registered Response response = given() .queryParam("q", queryParam) - .when().get(requestUrl ) + .when().get(searchUrlBase ) .then().assertThat().statusCode(200) .extract().response(); @@ -268,16 +269,23 @@ public void testRouteListing_NonMatchingQueryParam(TestContext context) { String nonMatchingQueryParam = "nonMatchingQuery"; String queryParam = "other"; - String routePath = "/routes"; - String requestUrl = requestUrlBase + routePath; // Register a route using the addRoute method addRoute(queryParam, true, true); assertResponse(get(requestUrlBase), new String[]{queryParam+"/"}); // Send GET request with a non-matching query param - Response response = given().queryParam("q", nonMatchingQueryParam) - .when().get(requestUrl) + Response response = given().queryParam("q", queryParam) + .when().get(searchUrlBase) + .then().assertThat().statusCode(200) + .extract().response(); + + Assert.assertTrue("Query param should be found in response", + response.getBody().asString().contains(queryParam)); + + // Send GET request with a non-matching query param + response = given().queryParam("q", nonMatchingQueryParam) + .when().get(searchUrlBase) .then().assertThat().statusCode(200) .extract().response(); @@ -287,7 +295,7 @@ public void testRouteListing_NonMatchingQueryParam(TestContext context) { // Send GET request with a matching query param response = given().queryParam("q", queryParam) - .when().get(requestUrl) + .when().get(searchUrlBase) .then().assertThat().statusCode(200) .extract().response(); @@ -311,14 +319,10 @@ public void testRouteListing_NoRoutesRegistered(TestContext context) { initSettings(); // Initialize routing rules String queryParam = "someQuery"; - String routePath = "/routes"; - String requestUrl = requestUrlBase + routePath; - // No routes registered - // Send GET request with a query param Response response = given().queryParam("q", queryParam) - .when().get(requestUrl) + .when().get(searchUrlBase) .then().assertThat().statusCode(200) .extract().response(); From 775704d2770229721b2a975efbdf486b9002ec24 Mon Sep 17 00:00:00 2001 From: almeidast Date: Mon, 28 Oct 2024 14:40:56 +0000 Subject: [PATCH 30/47] Improve and optimize the code --- .../swisspush/gateleen/hook/HookHandler.java | 23 ++++++++----------- 1 file changed, 10 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 867a5cd5..a313279d 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 @@ -74,8 +74,7 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; -import static io.vertx.core.http.HttpMethod.DELETE; -import static io.vertx.core.http.HttpMethod.PUT; +import static io.vertx.core.http.HttpMethod.*; import static org.swisspush.gateleen.core.util.HttpRequestHeader.CONTENT_LENGTH; /** @@ -572,17 +571,15 @@ public boolean handle(final RoutingContext ctx) { } // 1. Check if the request method is GET - if (request.method() == HttpMethod.GET) { - if (!request.params().isEmpty()) { - String queryParam = request.getParam("q"); - String normalizedRequestUri = requestUri.replaceAll("/$", ""); - if (normalizedRequestUri.contains(listenerBase.replaceAll("/$", ""))) { - handleListenerSearch(queryParam, request.response()); - return true; - } else if (normalizedRequestUri.contains(routeBase.replaceAll("/$", ""))) { - handleRouteSearch(queryParam, request.response()); - return true; - } + if (requestMethod == GET && !request.params().isEmpty()) { + String queryParam = request.getParam("q"); + String normalizedRequestUri = requestUri.replaceAll("/$", ""); + if (normalizedRequestUri.contains(listenerBase.replaceAll("/$", ""))) { + handleListenerSearch(queryParam, request.response()); + return true; + } else if (normalizedRequestUri.contains(routeBase.replaceAll("/$", ""))) { + handleRouteSearch(queryParam, request.response()); + return true; } } From 97cbb6e3d0e1cd2dd846bf260357fc938ffb294b Mon Sep 17 00:00:00 2001 From: almeidast Date: Mon, 28 Oct 2024 23:16:30 +0000 Subject: [PATCH 31/47] Create new unit tests --- .../gateleen/hook/HookHandlerTest.java | 239 ++++++++++++++---- 1 file changed, 193 insertions(+), 46 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 cb0e1d25..149ddf57 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 @@ -1,10 +1,12 @@ package org.swisspush.gateleen.hook; +import io.netty.handler.codec.http.QueryStringDecoder; import io.vertx.codegen.annotations.Nullable; import io.vertx.core.*; import io.vertx.core.buffer.Buffer; import io.vertx.core.buffer.impl.BufferImpl; import io.vertx.core.http.*; +import io.vertx.core.http.impl.headers.HeadersMultiMap; import io.vertx.core.json.JsonArray; import io.vertx.core.json.JsonObject; import io.vertx.core.net.HostAndPort; @@ -17,11 +19,13 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; +import org.mockito.InOrder; import org.mockito.Mockito; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.swisspush.gateleen.core.http.*; import org.swisspush.gateleen.core.storage.MockResourceStorage; +import org.swisspush.gateleen.core.util.StatusCode; import org.swisspush.gateleen.hook.reducedpropagation.ReducedPropagationManager; import org.swisspush.gateleen.logging.LogAppenderRepository; import org.swisspush.gateleen.logging.LoggingResourceManager; @@ -52,6 +56,8 @@ @RunWith(VertxUnitRunner.class) public class HookHandlerTest { + public static final String CONTENT_TYPE_HEADER = "Content-Type"; + public static final String CONTENT_TYPE_JSON = "application/json"; private static final String HOOK_ROOT_URI = "hookRootURI/"; private static final Logger logger = LoggerFactory.getLogger(HookHandlerTest.class); private Vertx vertx; @@ -80,10 +86,8 @@ public void setUp() { monitoringHandler = mock(MonitoringHandler.class); requestQueue = mock(RequestQueue.class); reducedPropagationManager = mock(ReducedPropagationManager.class); - - - hookHandler = new HookHandler(vertx, httpClient, storage, loggingResourceManager, logAppenderRepository, monitoringHandler, - "userProfilePath", HOOK_ROOT_URI, requestQueue, false, reducedPropagationManager); + hookHandler = new HookHandler(vertx, httpClient, storage, loggingResourceManager, logAppenderRepository, + monitoringHandler, "userProfilePath", "hookRootURI/", requestQueue, false, reducedPropagationManager); hookHandler.init(); } @@ -644,69 +648,181 @@ public void hookRegistration_RouteWithRouteMultiplier(TestContext testContext) t } @Test - public void testHandleListenerSearch_Success() { - // Arrange - String queryParam = "validQueryParam"; - String uri = "hookRootURI/registrations/listeners/?q=" + queryParam; + public void testHandleGETRequestWithRouteSearch() { + // Mocking necessary components + HttpServerRequest mockRequest = mock(HttpServerRequest.class); + HttpServerResponse mockResponse = mock(HttpServerResponse.class); + RoutingContext routingContext = mock(RoutingContext.class); - HttpServerRequest request = mock(HttpServerRequest.class); - HttpServerResponse response = mock(HttpServerResponse.class); + // Setting up a real MultiMap to simulate parameters + MultiMap params = MultiMap.caseInsensitiveMultiMap(); + params.add("q", "routeId"); - // 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); + // Configuring mocks + when(routingContext.request()).thenReturn(mockRequest); + when(mockRequest.method()).thenReturn(HttpMethod.GET); + when(mockRequest.uri()).thenReturn("/hookRootURI/registrations/routes"); + when(mockRequest.response()).thenReturn(mockResponse); + when(mockRequest.params()).thenReturn(params); - // Mock RoutingContext - RoutingContext routingContext = mock(RoutingContext.class); - when(routingContext.request()).thenReturn(request); + // Capture status code and response from mockResponse + ArgumentCaptor responseCaptor = ArgumentCaptor.forClass(String.class); + when(mockResponse.setStatusCode(anyInt())).thenReturn(mockResponse); + when(mockResponse.end(responseCaptor.capture())).thenReturn(Future.succeededFuture()); - // Act - boolean result = hookHandler.handle(routingContext); // Calls the public `handle` method + // Execute the method + boolean result = hookHandler.handle(routingContext); - // Assert + // Verifications + verify(mockRequest, times(1)).method(); + verify(mockRequest, times(1)).uri(); assertTrue(result); - verify(response, times(1)).end(); // Ensure `response.end()` was called + + // Check the 'q' parameter value and expected status code + assertEquals("routeId", params.get("q")); + verify(mockResponse).setStatusCode(200); + + // Verify the captured JSON response content + String jsonResponse = responseCaptor.getValue(); + assertNotNull(jsonResponse); + assertTrue(jsonResponse.contains("\"routes\":")); + + // Additional debug output + System.out.println("Parameter 'q' in params: " + params.get("q")); + System.out.println("Response body: " + jsonResponse); } @Test - public void testHandleListenerWithStorageAndSearchSuccess() { - // Arrange - String queryParam = "validQueryParam"; - String uri = "hookRootURI/registrations/listeners/?q=" + queryParam; + public void testHandleGETRequestWithoutSearchParams() { + // Mocking necessary components + HttpServerRequest mockRequest = mock(HttpServerRequest.class); + HttpServerResponse mockResponse = mock(HttpServerResponse.class); + RoutingContext routingContext = mock(RoutingContext.class); - HttpServerRequest request = mock(HttpServerRequest.class); - HttpServerResponse response = mock(HttpServerResponse.class); + // Setting up an empty MultiMap to simulate no parameters + MultiMap params = MultiMap.caseInsensitiveMultiMap(); - // 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); + // Configuring mocks + when(routingContext.request()).thenReturn(mockRequest); + when(mockRequest.method()).thenReturn(HttpMethod.GET); + when(mockRequest.uri()).thenReturn("/hookRootURI/registrations/listeners"); + when(mockRequest.response()).thenReturn(mockResponse); + when(mockRequest.params()).thenReturn(params); - // Use ArgumentCaptor para capturar o conteúdo da resposta + // Capture status code and response from mockResponse ArgumentCaptor responseCaptor = ArgumentCaptor.forClass(String.class); + when(mockResponse.setStatusCode(anyInt())).thenReturn(mockResponse); + when(mockResponse.end(responseCaptor.capture())).thenReturn(Future.succeededFuture()); + + // Execute the method + boolean result = hookHandler.handle(routingContext); + + // Verifications + verify(mockRequest, times(1)).method(); + verify(mockRequest, times(1)).uri(); + assertTrue(result); + + // Check that the parameters MultiMap is empty + assertTrue(params.isEmpty()); + verify(mockResponse).setStatusCode(400); - // Mock RoutingContext + // Verify captured response content + String jsonResponse = responseCaptor.getValue(); + assertNotNull(jsonResponse); + assertTrue(jsonResponse.contains("Bad Request")); + + // Additional debug output + System.out.println("Params is empty: " + params.isEmpty()); + System.out.println("Response body: " + jsonResponse); + } + + @Test + public void testHandleGETRequestWithInvalidParam() { + // Mocking necessary components + HttpServerRequest mockRequest = mock(HttpServerRequest.class); + HttpServerResponse mockResponse = mock(HttpServerResponse.class); RoutingContext routingContext = mock(RoutingContext.class); - when(routingContext.request()).thenReturn(request); - // Act - boolean result = hookHandler.handle(routingContext); // Chama o método público `handle` + // Setting up a real MultiMap to simulate an invalid parameter + MultiMap params = MultiMap.caseInsensitiveMultiMap(); + params.add("invalid", "value"); + + // Configuring mocks + when(routingContext.request()).thenReturn(mockRequest); + when(mockRequest.method()).thenReturn(HttpMethod.GET); + when(mockRequest.uri()).thenReturn("/hookRootURI/registrations/listeners"); + when(mockRequest.response()).thenReturn(mockResponse); + when(mockRequest.params()).thenReturn(params); + + // Capture status code and response from mockResponse + ArgumentCaptor responseCaptor = ArgumentCaptor.forClass(String.class); + when(mockResponse.setStatusCode(anyInt())).thenReturn(mockResponse); + when(mockResponse.end(responseCaptor.capture())).thenReturn(Future.succeededFuture()); + + // Execute the method + boolean result = hookHandler.handle(routingContext); + + // Verifications + verify(mockRequest, times(1)).method(); + verify(mockRequest, times(1)).uri(); + assertTrue(result); - // Assert - assertTrue(result); // Certifique-se de que o handler retornou true + // Expecting status code 400 due to invalid parameter + verify(mockResponse).setStatusCode(400); - // Capture o valor passado para end() - verify(response).end(responseCaptor.capture()); + // Verify captured response content + String jsonResponse = responseCaptor.getValue(); + assertNotNull(jsonResponse); + assertTrue(jsonResponse.contains("Bad Request")); - // Verifique o conteúdo da resposta - String actualResponse = responseCaptor.getValue(); - assertNotNull(actualResponse); - assertEquals("{\"listeners\":[]}", actualResponse); // Ajuste conforme o comportamento esperado + // Additional debug output + System.out.println("Invalid params: " + params); + System.out.println("Response body: " + jsonResponse); } + @Test + public void testHandleGETRequestWithEmptyParam() { + // Mocking necessary components + HttpServerRequest mockRequest = mock(HttpServerRequest.class); + HttpServerResponse mockResponse = mock(HttpServerResponse.class); + RoutingContext routingContext = mock(RoutingContext.class); + + // Setting up a real MultiMap to simulate an empty 'q' parameter + MultiMap params = MultiMap.caseInsensitiveMultiMap(); + params.add("q", ""); + + // Configuring mocks + when(routingContext.request()).thenReturn(mockRequest); + when(mockRequest.method()).thenReturn(HttpMethod.GET); + when(mockRequest.uri()).thenReturn("/hookRootURI/registrations/listeners"); + when(mockRequest.response()).thenReturn(mockResponse); + when(mockRequest.params()).thenReturn(params); + + // Capture status code and response from mockResponse + ArgumentCaptor responseCaptor = ArgumentCaptor.forClass(String.class); + when(mockResponse.setStatusCode(anyInt())).thenReturn(mockResponse); + when(mockResponse.end(responseCaptor.capture())).thenReturn(Future.succeededFuture()); + + // Execute the method + boolean result = hookHandler.handle(routingContext); + + // Verifications + verify(mockRequest, times(1)).method(); + verify(mockRequest, times(1)).uri(); + assertTrue(result); + + // Expecting status code 400 due to empty 'q' parameter + verify(mockResponse).setStatusCode(400); + + // Verify captured response content + String jsonResponse = responseCaptor.getValue(); + assertNotNull(jsonResponse); + assertTrue(jsonResponse.contains("Bad Request")); + + // Additional debug output + System.out.println("Empty 'q' param: " + params.get("q")); + System.out.println("Response body: " + jsonResponse); + } /////////////////////////////////////////////////////////////////////////////// @@ -981,4 +1097,35 @@ public void addHeader(String headerName, String headerValue) { headers.add(headerName, headerValue); } } + private static class Request extends DummyHttpServerRequest { + private MultiMap headers; + private HttpMethod httpMethod; + private String uri; + private HttpServerResponse response; + + public Request(HttpMethod httpMethod, String uri, MultiMap headers, HttpServerResponse response) { + this.httpMethod = httpMethod; + this.uri = uri; + this.headers = headers; + this.response = response; + } + + @Override public HttpMethod method() { + return httpMethod; + } + @Override public String uri() { + return uri; + } + @Override public HttpServerResponse response() { return response; } + @Override public MultiMap headers() { return headers; } + @Override public HttpServerRequest pause() { return this; } + @Override public HttpServerRequest resume() { return this; } + } + + private static class Response extends DummyHttpServerResponse { + private MultiMap headers = MultiMap.caseInsensitiveMultiMap(); + + @Override + public MultiMap headers() { return headers; } + } } From db98244884b5edfec45ac758d4b5667c285b636e Mon Sep 17 00:00:00 2001 From: almeidast Date: Tue, 29 Oct 2024 16:05:38 +0000 Subject: [PATCH 32/47] improve and implement new unit testes to cover new hookHandlerSearch create a buildRouteConfig create GETRequest --- .../gateleen/hook/HookHandlerTest.java | 358 +++++++++++------- 1 file changed, 227 insertions(+), 131 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 149ddf57..ce47578c 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 @@ -1,12 +1,10 @@ package org.swisspush.gateleen.hook; -import io.netty.handler.codec.http.QueryStringDecoder; import io.vertx.codegen.annotations.Nullable; import io.vertx.core.*; import io.vertx.core.buffer.Buffer; import io.vertx.core.buffer.impl.BufferImpl; import io.vertx.core.http.*; -import io.vertx.core.http.impl.headers.HeadersMultiMap; import io.vertx.core.json.JsonArray; import io.vertx.core.json.JsonObject; import io.vertx.core.net.HostAndPort; @@ -19,7 +17,6 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; -import org.mockito.InOrder; import org.mockito.Mockito; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -34,13 +31,11 @@ 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.*; @@ -55,9 +50,6 @@ */ @RunWith(VertxUnitRunner.class) public class HookHandlerTest { - - public static final String CONTENT_TYPE_HEADER = "Content-Type"; - public static final String CONTENT_TYPE_JSON = "application/json"; private static final String HOOK_ROOT_URI = "hookRootURI/"; private static final Logger logger = LoggerFactory.getLogger(HookHandlerTest.class); private Vertx vertx; @@ -118,7 +110,28 @@ private JsonObject buildListenerConfig(JsonObject queueingStrategy, String devic return config; } - private JsonObject buildListenerConfigWithHeadersFilter(JsonObject queueingStrategy, String deviceId, String headersFilter){ + private JsonObject buildRouteConfig(String routeId) { + JsonObject config = new JsonObject(); + config.put("requesturl", "/playground/server/tests/"+ routeId+"/_hooks/routes/http/push/" ); + config.put("expirationTime", "2017-01-03T14:15:53.277"); + + JsonObject hook = new JsonObject(); + hook.put("destination", "/playground/server/push/v1/routes/" + routeId); + hook.put("methods", new JsonArray(Collections.singletonList("PUT"))); + hook.put("timeout", 42); + hook.put("connectionPoolSize", 10); + JsonObject staticHeaders = new JsonObject(); + staticHeaders.put("x-custom-header", "route-header-value"); + hook.put("staticHeaders", staticHeaders); + config.put("hook", hook); + return config; + } + private void setRouteStorageEntryAndTriggerUpdate(JsonObject routeConfig) { + storage.putMockData("pathToRouteResource", routeConfig.encode()); + vertx.eventBus().request("gateleen.hook-route-insert", "pathToRouteResource"); + } + + private JsonObject buildListenerConfigWithHeadersFilter(JsonObject queueingStrategy, String deviceId, String headersFilter) { JsonObject config = buildListenerConfig(queueingStrategy, deviceId); config.getJsonObject("hook").put("headersFilter", headersFilter); return config; @@ -400,13 +413,13 @@ public void hookRegistration_RouteWithTimeout(TestContext testContext) { final MultiMap requestHeaders = MultiMap.caseInsensitiveMultiMap(); final Buffer requestBody = new BufferImpl(); requestBody.setBytes(0, ("{" + - " \"methods\": [ \"PUT\" , \"DELETE\" ]," + - " \"destination\": \"/an/example/destination/\"," + - " \"timeout\": 42" + - "}").getBytes()); + " \"methods\": [ \"PUT\" , \"DELETE\" ]," + + " \"destination\": \"/an/example/destination/\"," + + " \"timeout\": 42" + + "}").getBytes()); request = createSimpleRequest(HttpMethod.PUT, "/gateleen/example/_hooks/route/http/my-service/my-hook", - requestHeaders, requestBody, statusCodePtr, statusMessagePtr + requestHeaders, requestBody, statusCodePtr, statusMessagePtr ); } @@ -647,25 +660,20 @@ public void hookRegistration_RouteWithRouteMultiplier(TestContext testContext) t vertx.eventBus().request("gateleen.hook-route-remove", "pathToRouterResource"); } + @Test - public void testHandleGETRequestWithRouteSearch() { - // Mocking necessary components - HttpServerRequest mockRequest = mock(HttpServerRequest.class); + public void testHandleGETRequestWithEmptyParam() { + // Define URI and configures the request with an empty 'q' parameter + String uri = "/hookRootURI/registrations/listeners"; HttpServerResponse mockResponse = mock(HttpServerResponse.class); - RoutingContext routingContext = mock(RoutingContext.class); - - // Setting up a real MultiMap to simulate parameters - MultiMap params = MultiMap.caseInsensitiveMultiMap(); - params.add("q", "routeId"); + GETRequest request = new GETRequest(uri, mockResponse); + request.addParameter("q", ""); // Empty parameter to simulate bad request - // Configuring mocks - when(routingContext.request()).thenReturn(mockRequest); - when(mockRequest.method()).thenReturn(HttpMethod.GET); - when(mockRequest.uri()).thenReturn("/hookRootURI/registrations/routes"); - when(mockRequest.response()).thenReturn(mockResponse); - when(mockRequest.params()).thenReturn(params); + // Mock RoutingContext + RoutingContext routingContext = mock(RoutingContext.class); + when(routingContext.request()).thenReturn(request); - // Capture status code and response from mockResponse + // Capture response content ArgumentCaptor responseCaptor = ArgumentCaptor.forClass(String.class); when(mockResponse.setStatusCode(anyInt())).thenReturn(mockResponse); when(mockResponse.end(responseCaptor.capture())).thenReturn(Future.succeededFuture()); @@ -674,157 +682,173 @@ public void testHandleGETRequestWithRouteSearch() { boolean result = hookHandler.handle(routingContext); // Verifications - verify(mockRequest, times(1)).method(); - verify(mockRequest, times(1)).uri(); - assertTrue(result); - - // Check the 'q' parameter value and expected status code - assertEquals("routeId", params.get("q")); - verify(mockResponse).setStatusCode(200); + verify(mockResponse).setStatusCode(400); // Verify status 400 due to empty 'q' parameter + assertTrue(result); // Ensure the handler returned true - // Verify the captured JSON response content + // Verify captured response content String jsonResponse = responseCaptor.getValue(); assertNotNull(jsonResponse); - assertTrue(jsonResponse.contains("\"routes\":")); - - // Additional debug output - System.out.println("Parameter 'q' in params: " + params.get("q")); - System.out.println("Response body: " + jsonResponse); + assertTrue(jsonResponse.contains("Bad Request")); // Confirm the response contains "Bad Request" } + @Test - public void testHandleGETRequestWithoutSearchParams() { - // Mocking necessary components - HttpServerRequest mockRequest = mock(HttpServerRequest.class); + public void testHandleGETRequestWithListenersSearchSingleResult() throws InterruptedException { + // Define URI and configure GET request with specific search parameter + String singleListener= "mySingleListener"; + String uri = "/hookRootURI/registrations/listeners"; HttpServerResponse mockResponse = mock(HttpServerResponse.class); - RoutingContext routingContext = mock(RoutingContext.class); + GETRequest request = new GETRequest(uri, mockResponse); + request.addParameter("q", singleListener); - // Setting up an empty MultiMap to simulate no parameters - MultiMap params = MultiMap.caseInsensitiveMultiMap(); - // Configuring mocks - when(routingContext.request()).thenReturn(mockRequest); - when(mockRequest.method()).thenReturn(HttpMethod.GET); - when(mockRequest.uri()).thenReturn("/hookRootURI/registrations/listeners"); - when(mockRequest.response()).thenReturn(mockResponse); - when(mockRequest.params()).thenReturn(params); + setListenerStorageEntryAndTriggerUpdate(buildListenerConfigWithHeadersFilter(null, singleListener, "x-foo: (A|B)")); + // wait a moment to let the listener be registered + Thread.sleep(500); + // Mock RoutingContext and configure response capture + RoutingContext routingContext = mock(RoutingContext.class); + when(routingContext.request()).thenReturn(request); - // Capture status code and response from mockResponse ArgumentCaptor responseCaptor = ArgumentCaptor.forClass(String.class); when(mockResponse.setStatusCode(anyInt())).thenReturn(mockResponse); when(mockResponse.end(responseCaptor.capture())).thenReturn(Future.succeededFuture()); - // Execute the method + // Execute the handler boolean result = hookHandler.handle(routingContext); - - // Verifications - verify(mockRequest, times(1)).method(); - verify(mockRequest, times(1)).uri(); assertTrue(result); - // Check that the parameters MultiMap is empty - assertTrue(params.isEmpty()); - verify(mockResponse).setStatusCode(400); - - // Verify captured response content + // Validate JSON response content for matching listener String jsonResponse = responseCaptor.getValue(); assertNotNull(jsonResponse); - assertTrue(jsonResponse.contains("Bad Request")); - - // Additional debug output - System.out.println("Params is empty: " + params.isEmpty()); - System.out.println("Response body: " + jsonResponse); + assertTrue(jsonResponse.contains(singleListener)); } @Test - public void testHandleGETRequestWithInvalidParam() { - // Mocking necessary components - HttpServerRequest mockRequest = mock(HttpServerRequest.class); + public void testHandleGETRequestWithListenersSearchMultipleResults() throws InterruptedException { + // Define the URI and set up the GET request with a broader search parameter for multiple listeners + String uri = "/hookRootURI/registrations/listeners"; HttpServerResponse mockResponse = mock(HttpServerResponse.class); + GETRequest request = new GETRequest(uri, mockResponse); + request.addParameter("q", "listener"); // Search parameter that should match multiple listeners + + // Add multiple listeners to the MockResourceStorage using the expected configuration and register them + String listenerId1 = "listener112222"; + String listenerId2 = "listener222133"; + setListenerStorageEntryAndTriggerUpdate(buildListenerConfigWithHeadersFilter(null, listenerId1, "x-foo: (A|B)")); + Thread.sleep(500); + setListenerStorageEntryAndTriggerUpdate(buildListenerConfigWithHeadersFilter(null, listenerId2, "x-foo: (A|B)")); + Thread.sleep(500); + + // Mock the RoutingContext and set up the response capture RoutingContext routingContext = mock(RoutingContext.class); - - // Setting up a real MultiMap to simulate an invalid parameter - MultiMap params = MultiMap.caseInsensitiveMultiMap(); - params.add("invalid", "value"); - - // Configuring mocks - when(routingContext.request()).thenReturn(mockRequest); - when(mockRequest.method()).thenReturn(HttpMethod.GET); - when(mockRequest.uri()).thenReturn("/hookRootURI/registrations/listeners"); - when(mockRequest.response()).thenReturn(mockResponse); - when(mockRequest.params()).thenReturn(params); - - // Capture status code and response from mockResponse + when(routingContext.request()).thenReturn(request); ArgumentCaptor responseCaptor = ArgumentCaptor.forClass(String.class); when(mockResponse.setStatusCode(anyInt())).thenReturn(mockResponse); when(mockResponse.end(responseCaptor.capture())).thenReturn(Future.succeededFuture()); - // Execute the method + // Execute the handler boolean result = hookHandler.handle(routingContext); - - // Verifications - verify(mockRequest, times(1)).method(); - verify(mockRequest, times(1)).uri(); assertTrue(result); - // Expecting status code 400 due to invalid parameter - verify(mockResponse).setStatusCode(400); - - // Verify captured response content + // Validate the JSON response content for multiple matching listeners String jsonResponse = responseCaptor.getValue(); assertNotNull(jsonResponse); - assertTrue(jsonResponse.contains("Bad Request")); - - // Additional debug output - System.out.println("Invalid params: " + params); - System.out.println("Response body: " + jsonResponse); + assertTrue(jsonResponse.contains(listenerId1)); + assertTrue(jsonResponse.contains(listenerId2)); } + @Test - public void testHandleGETRequestWithEmptyParam() { - // Mocking necessary components - HttpServerRequest mockRequest = mock(HttpServerRequest.class); + public void testHandleGETRequestWithRoutesSearchEmptyResult() { + // Define URI and configure request with specific 'q' parameter for routes search + String uri = "/hookRootURI/registrations/routes"; HttpServerResponse mockResponse = mock(HttpServerResponse.class); - RoutingContext routingContext = mock(RoutingContext.class); + GETRequest request = new GETRequest(uri, mockResponse); + request.addParameter("q", "routeNotFound"); // Parameter that should result in no matches - // Setting up a real MultiMap to simulate an empty 'q' parameter - MultiMap params = MultiMap.caseInsensitiveMultiMap(); - params.add("q", ""); + // No routes are added to MockResourceStorage to simulate empty result + storage.putMockData("hookRootURI/registrations/routes", new JsonArray().encode()); - // Configuring mocks - when(routingContext.request()).thenReturn(mockRequest); - when(mockRequest.method()).thenReturn(HttpMethod.GET); - when(mockRequest.uri()).thenReturn("/hookRootURI/registrations/listeners"); - when(mockRequest.response()).thenReturn(mockResponse); - when(mockRequest.params()).thenReturn(params); + // Mock RoutingContext and configure response capture + RoutingContext routingContext = mock(RoutingContext.class); + when(routingContext.request()).thenReturn(request); - // Capture status code and response from mockResponse ArgumentCaptor responseCaptor = ArgumentCaptor.forClass(String.class); when(mockResponse.setStatusCode(anyInt())).thenReturn(mockResponse); when(mockResponse.end(responseCaptor.capture())).thenReturn(Future.succeededFuture()); - // Execute the method + // Execute the handler boolean result = hookHandler.handle(routingContext); // Verifications - verify(mockRequest, times(1)).method(); - verify(mockRequest, times(1)).uri(); assertTrue(result); - // Expecting status code 400 due to empty 'q' parameter - verify(mockResponse).setStatusCode(400); + // Verify response content with empty result + String jsonResponse = responseCaptor.getValue(); + assertNotNull(jsonResponse); + assertEquals("{\"routes\":[]}", jsonResponse); + } + @Test + public void testHandleGETRequestWithRoutesSearchMultipleResults() throws InterruptedException { + // Define the URI and set up the GET request with a broad search parameter for multiple routes + String uri = "/hookRootURI/registrations/routes"; + HttpServerResponse mockResponse = mock(HttpServerResponse.class); + GETRequest request = new GETRequest(uri, mockResponse); + request.addParameter("q", "route"); // Search parameter that should match multiple routes + + // Add multiple routes to the MockResourceStorage using the expected configuration and register them + String routeId1 = "route12345"; + String routeId2 = "route67890"; + setRouteStorageEntryAndTriggerUpdate(buildRouteConfig(routeId1)); + Thread.sleep(500); + setRouteStorageEntryAndTriggerUpdate(buildRouteConfig( routeId2)); + Thread.sleep(500); + + // Mock the RoutingContext and set up the response capture + RoutingContext routingContext = mock(RoutingContext.class); + when(routingContext.request()).thenReturn(request); + ArgumentCaptor responseCaptor = ArgumentCaptor.forClass(String.class); + when(mockResponse.setStatusCode(anyInt())).thenReturn(mockResponse); + when(mockResponse.end(responseCaptor.capture())).thenReturn(Future.succeededFuture()); + + // Execute the handler + boolean result = hookHandler.handle(routingContext); + assertTrue(result); - // Verify captured response content + // Validate the JSON response content for multiple matching routes String jsonResponse = responseCaptor.getValue(); assertNotNull(jsonResponse); - assertTrue(jsonResponse.contains("Bad Request")); + assertTrue(jsonResponse.contains(routeId1)); + assertTrue(jsonResponse.contains(routeId2)); + } - // Additional debug output - System.out.println("Empty 'q' param: " + params.get("q")); - System.out.println("Response body: " + jsonResponse); + @Test + public void testHandleListenerWithStorageAndEmptyList() { + // Set up the URI for listeners registration + String uri = "hookRootURI/registrations/listeners"; + HttpServerResponse response = mock(HttpServerResponse.class); + GETRequest request = new GETRequest(uri,response) ; + // Add a valid query parameter for the listener search + request.addParameter("q", "validQueryParam"); + + // Capture the response output + ArgumentCaptor responseCaptor = ArgumentCaptor.forClass(String.class); + RoutingContext routingContext = mock(RoutingContext.class); + when(routingContext.request()).thenReturn(request); + + // Execute the handler and validate the response + boolean result = hookHandler.handle(routingContext); + + assertTrue(result); // Ensure the handler returned true + verify(response).end(responseCaptor.capture()); + // Validate the response JSON for an empty listener list + String actualResponse = responseCaptor.getValue(); + assertNotNull(actualResponse); + assertEquals("{\"listeners\":[]}", actualResponse); // Response should contain an empty list } + /////////////////////////////////////////////////////////////////////////////// // Helpers /////////////////////////////////////////////////////////////////////////////// @@ -1097,6 +1121,7 @@ public void addHeader(String headerName, String headerValue) { headers.add(headerName, headerValue); } } + private static class Request extends DummyHttpServerRequest { private MultiMap headers; private HttpMethod httpMethod; @@ -1110,22 +1135,93 @@ public Request(HttpMethod httpMethod, String uri, MultiMap headers, HttpServerRe this.response = response; } - @Override public HttpMethod method() { + @Override + public HttpMethod method() { return httpMethod; } - @Override public String uri() { + + @Override + public String uri() { return uri; } - @Override public HttpServerResponse response() { return response; } - @Override public MultiMap headers() { return headers; } - @Override public HttpServerRequest pause() { return this; } - @Override public HttpServerRequest resume() { return this; } + + @Override + public HttpServerResponse response() { + return response; + } + + @Override + public MultiMap headers() { + return headers; + } + + @Override + public HttpServerRequest pause() { + return this; + } + + @Override + public HttpServerRequest resume() { + return this; + } } private static class Response extends DummyHttpServerResponse { private MultiMap headers = MultiMap.caseInsensitiveMultiMap(); @Override - public MultiMap headers() { return headers; } + public MultiMap headers() { + return headers; + } + } + + static class GETRequest extends DummyHttpServerRequest { + MultiMap headers = MultiMap.caseInsensitiveMultiMap(); + MultiMap params = MultiMap.caseInsensitiveMultiMap(); + private HttpServerResponse response; + private String uri; + + public GETRequest(String uri, HttpServerResponse response) { + this.uri = uri; + this.response = response; + } + + @Override + public HttpMethod method() { + return HttpMethod.GET; + } + + @Override + public String uri() { + return uri; + } + + @Override + public MultiMap headers() { + return headers; + } + + @Override + public MultiMap params() { + return params; + } + + @Override + public HttpServerResponse response() { + return response; + } + + @Override + public String getParam(String paramName) { + return params.get(paramName); + } + + public void addHeader(String headerName, String headerValue) { + headers.add(headerName, headerValue); + } + + public void addParameter(String paramName, String paramValue) { + params.add(paramName, paramValue); + } } } From f921c3bdd4eaeb5b1e15acb62ac687b87389276b Mon Sep 17 00:00:00 2001 From: almeidast Date: Wed, 30 Oct 2024 15:01:34 +0000 Subject: [PATCH 33/47] improve the implementation and remove unused methods --- .../swisspush/gateleen/hook/HookHandler.java | 12 ++- .../gateleen/hook/HookHandlerTest.java | 83 +++---------------- 2 files changed, 17 insertions(+), 78 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 a313279d..59999174 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,7 +570,6 @@ public boolean handle(final RoutingContext ctx) { } } - // 1. Check if the request method is GET if (requestMethod == GET && !request.params().isEmpty()) { String queryParam = request.getParam("q"); String normalizedRequestUri = requestUri.replaceAll("/$", ""); @@ -628,11 +627,10 @@ 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. + * Output a JSON response with the matched results. + * If parameter queryParam is empty or null a 400 Bad Request is returned. + * All params cannot be null + * @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. @@ -640,7 +638,7 @@ private void handleRouteSearch(String queryParam, HttpServerResponse response) { */ private void handleSearch(Map repository, Function getDestination, String queryParam, String resultKey, HttpServerResponse response) { - if (repository == null || getDestination == null || resultKey == null || queryParam == null || queryParam.isEmpty()) { + if (queryParam == null || queryParam.isEmpty()) { response.setStatusCode(StatusCode.BAD_REQUEST.getStatusCode()).end("Bad Request: One or more required parameters are missing or null"); return; } 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 ce47578c..90249c1f 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,7 +8,6 @@ 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; @@ -120,6 +119,7 @@ private JsonObject buildRouteConfig(String routeId) { hook.put("methods", new JsonArray(Collections.singletonList("PUT"))); hook.put("timeout", 42); hook.put("connectionPoolSize", 10); + JsonObject staticHeaders = new JsonObject(); staticHeaders.put("x-custom-header", "route-header-value"); hook.put("staticHeaders", staticHeaders); @@ -662,7 +662,7 @@ public void hookRegistration_RouteWithRouteMultiplier(TestContext testContext) t @Test - public void testHandleGETRequestWithEmptyParam() { + public void testHandleGETRequestWithEmptyParam(TestContext testContext) { // Define URI and configures the request with an empty 'q' parameter String uri = "/hookRootURI/registrations/listeners"; HttpServerResponse mockResponse = mock(HttpServerResponse.class); @@ -670,7 +670,6 @@ public void testHandleGETRequestWithEmptyParam() { request.addParameter("q", ""); // Empty parameter to simulate bad request // Mock RoutingContext - RoutingContext routingContext = mock(RoutingContext.class); when(routingContext.request()).thenReturn(request); // Capture response content @@ -678,17 +677,17 @@ public void testHandleGETRequestWithEmptyParam() { when(mockResponse.setStatusCode(anyInt())).thenReturn(mockResponse); when(mockResponse.end(responseCaptor.capture())).thenReturn(Future.succeededFuture()); - // Execute the method + // Execute the Handler boolean result = hookHandler.handle(routingContext); - // Verifications - verify(mockResponse).setStatusCode(400); // Verify status 400 due to empty 'q' parameter - assertTrue(result); // Ensure the handler returned true - + // Verify status 400 due to empty 'q' parameter + verify(mockResponse).setStatusCode(StatusCode.BAD_REQUEST.getStatusCode()); + testContext.assertTrue(result); // Verify captured response content String jsonResponse = responseCaptor.getValue(); - assertNotNull(jsonResponse); - assertTrue(jsonResponse.contains("Bad Request")); // Confirm the response contains "Bad Request" + testContext.assertNotNull(jsonResponse); + // Confirm the response contains "Bad Request" + testContext.assertTrue(jsonResponse.contains("Bad Request")); } @@ -706,7 +705,6 @@ public void testHandleGETRequestWithListenersSearchSingleResult() throws Interru // wait a moment to let the listener be registered Thread.sleep(500); // Mock RoutingContext and configure response capture - RoutingContext routingContext = mock(RoutingContext.class); when(routingContext.request()).thenReturn(request); ArgumentCaptor responseCaptor = ArgumentCaptor.forClass(String.class); @@ -740,7 +738,6 @@ public void testHandleGETRequestWithListenersSearchMultipleResults() throws Inte Thread.sleep(500); // Mock the RoutingContext and set up the response capture - RoutingContext routingContext = mock(RoutingContext.class); when(routingContext.request()).thenReturn(request); ArgumentCaptor responseCaptor = ArgumentCaptor.forClass(String.class); when(mockResponse.setStatusCode(anyInt())).thenReturn(mockResponse); @@ -764,13 +761,12 @@ public void testHandleGETRequestWithRoutesSearchEmptyResult() { String uri = "/hookRootURI/registrations/routes"; HttpServerResponse mockResponse = mock(HttpServerResponse.class); GETRequest request = new GETRequest(uri, mockResponse); - request.addParameter("q", "routeNotFound"); // Parameter that should result in no matches + request.addParameter("q", "routeNotFound"); // No routes are added to MockResourceStorage to simulate empty result storage.putMockData("hookRootURI/registrations/routes", new JsonArray().encode()); // Mock RoutingContext and configure response capture - RoutingContext routingContext = mock(RoutingContext.class); when(routingContext.request()).thenReturn(request); ArgumentCaptor responseCaptor = ArgumentCaptor.forClass(String.class); @@ -805,7 +801,6 @@ public void testHandleGETRequestWithRoutesSearchMultipleResults() throws Interru Thread.sleep(500); // Mock the RoutingContext and set up the response capture - RoutingContext routingContext = mock(RoutingContext.class); when(routingContext.request()).thenReturn(request); ArgumentCaptor responseCaptor = ArgumentCaptor.forClass(String.class); when(mockResponse.setStatusCode(anyInt())).thenReturn(mockResponse); @@ -828,19 +823,18 @@ public void testHandleListenerWithStorageAndEmptyList() { String uri = "hookRootURI/registrations/listeners"; HttpServerResponse response = mock(HttpServerResponse.class); GETRequest request = new GETRequest(uri,response) ; - // Add a valid query parameter for the listener search request.addParameter("q", "validQueryParam"); // Capture the response output ArgumentCaptor responseCaptor = ArgumentCaptor.forClass(String.class); - RoutingContext routingContext = mock(RoutingContext.class); when(routingContext.request()).thenReturn(request); // Execute the handler and validate the response boolean result = hookHandler.handle(routingContext); - assertTrue(result); // Ensure the handler returned true + assertTrue(result); verify(response).end(responseCaptor.capture()); + // Validate the response JSON for an empty listener list String actualResponse = responseCaptor.getValue(); assertNotNull(actualResponse); @@ -1122,59 +1116,6 @@ public void addHeader(String headerName, String headerValue) { } } - private static class Request extends DummyHttpServerRequest { - private MultiMap headers; - private HttpMethod httpMethod; - private String uri; - private HttpServerResponse response; - - public Request(HttpMethod httpMethod, String uri, MultiMap headers, HttpServerResponse response) { - this.httpMethod = httpMethod; - this.uri = uri; - this.headers = headers; - this.response = response; - } - - @Override - public HttpMethod method() { - return httpMethod; - } - - @Override - public String uri() { - return uri; - } - - @Override - public HttpServerResponse response() { - return response; - } - - @Override - public MultiMap headers() { - return headers; - } - - @Override - public HttpServerRequest pause() { - return this; - } - - @Override - public HttpServerRequest resume() { - return this; - } - } - - private static class Response extends DummyHttpServerResponse { - private MultiMap headers = MultiMap.caseInsensitiveMultiMap(); - - @Override - public MultiMap headers() { - return headers; - } - } - static class GETRequest extends DummyHttpServerRequest { MultiMap headers = MultiMap.caseInsensitiveMultiMap(); MultiMap params = MultiMap.caseInsensitiveMultiMap(); From 0f1c5e7d9afe4fe266710e1cad70efcecb935ab2 Mon Sep 17 00:00:00 2001 From: almeidast Date: Thu, 31 Oct 2024 08:38:25 +0000 Subject: [PATCH 34/47] improve code --- .../test/java/org/swisspush/gateleen/hook/HookHandlerTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 90249c1f..4881a47e 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 @@ -78,7 +78,7 @@ public void setUp() { requestQueue = mock(RequestQueue.class); reducedPropagationManager = mock(ReducedPropagationManager.class); hookHandler = new HookHandler(vertx, httpClient, storage, loggingResourceManager, logAppenderRepository, - monitoringHandler, "userProfilePath", "hookRootURI/", requestQueue, false, reducedPropagationManager); + monitoringHandler, "userProfilePath", HOOK_ROOT_URI, requestQueue, false, reducedPropagationManager); hookHandler.init(); } From 8e3d799e72735a0ee96411b55b2c10459cc71495 Mon Sep 17 00:00:00 2001 From: almeidast Date: Mon, 4 Nov 2024 21:09:07 +0000 Subject: [PATCH 35/47] Add integration tests to compare results between current routes search and new implementation to search with parameters Add more unit tests --- .../gateleen/hook/HookHandlerTest.java | 85 +++++++++++++++++++ .../gateleen/hook/RouteListingTest.java | 53 ++++++++++-- 2 files changed, 132 insertions(+), 6 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 4881a47e..6531bb60 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 @@ -841,6 +841,91 @@ public void testHandleListenerWithStorageAndEmptyList() { assertEquals("{\"listeners\":[]}", actualResponse); // Response should contain an empty list } + @Test + public void testHandleGETRequestWithExtraParam(TestContext testContext) { + // Define URI and configure the request with an extra parameter besides 'q' + String uri = "/hookRootURI/registrations/listeners"; + HttpServerResponse mockResponse = mock(HttpServerResponse.class); + GETRequest request = new GETRequest(uri, mockResponse); + request.addParameter("q", "validQueryParam"); + request.addParameter("extra", "notAllowedParam"); // Extra parameter, not allowed + + // Mock the RoutingContext + when(routingContext.request()).thenReturn(request); + + // Capture the response content + ArgumentCaptor responseCaptor = ArgumentCaptor.forClass(String.class); + when(mockResponse.setStatusCode(anyInt())).thenReturn(mockResponse); + when(mockResponse.end(responseCaptor.capture())).thenReturn(Future.succeededFuture()); + + // Execute the Handler + boolean result = hookHandler.handle(routingContext); + + // Verify status 400 due to the extra parameter + verify(mockResponse).setStatusCode(StatusCode.BAD_REQUEST.getStatusCode()); + testContext.assertTrue(result); + + // Verify captured response content + String jsonResponse = responseCaptor.getValue(); + testContext.assertNotNull(jsonResponse); + // Confirm that the response contains "Bad Request" + testContext.assertTrue(jsonResponse.contains("Bad Request")); + } + + @Test + public void testHandleGETRequestWithTrailingSlash(TestContext testContext) { + // Define URI with trailing slash and configure the request + String uri = "/hookRootURI/registrations/listeners/"; + HttpServerResponse mockResponse = mock(HttpServerResponse.class); + GETRequest request = new GETRequest(uri, mockResponse); + request.addParameter("q", "validQueryParam"); + + // Mock the RoutingContext + when(routingContext.request()).thenReturn(request); + + // Capture the response content + ArgumentCaptor responseCaptor = ArgumentCaptor.forClass(String.class); + when(mockResponse.end(responseCaptor.capture())).thenReturn(Future.succeededFuture()); + + // Execute the Handler + boolean result = hookHandler.handle(routingContext); + + // Verify the result contains an empty listeners list + testContext.assertTrue(result); + String jsonResponse = responseCaptor.getValue(); + testContext.assertNotNull(jsonResponse); + testContext.assertEquals("{\"listeners\":[]}", jsonResponse); + } + + @Test + public void testHandleGETRequestWithInvalidParam(TestContext testContext) { + // Define URI with an invalid parameter different from 'q' + String uri = "/hookRootURI/registrations/listeners"; + HttpServerResponse mockResponse = mock(HttpServerResponse.class); + GETRequest request = new GETRequest(uri, mockResponse); + request.addParameter("invalidParam", "someValue"); // Invalid parameter, not 'q' + + // Mock the RoutingContext + when(routingContext.request()).thenReturn(request); + + // Capture the response content + ArgumentCaptor responseCaptor = ArgumentCaptor.forClass(String.class); + when(mockResponse.setStatusCode(anyInt())).thenReturn(mockResponse); + when(mockResponse.end(responseCaptor.capture())).thenReturn(Future.succeededFuture()); + + // Execute the Handler + boolean result = hookHandler.handle(routingContext); + + // Verify status 400 due to invalid parameter + verify(mockResponse).setStatusCode(StatusCode.BAD_REQUEST.getStatusCode()); + testContext.assertTrue(result); + + // Verify captured response content + String jsonResponse = responseCaptor.getValue(); + testContext.assertNotNull(jsonResponse); + // Confirm that the response contains "Bad Request" + testContext.assertTrue(jsonResponse.contains("Bad Request")); + } /////////////////////////////////////////////////////////////////////////////// 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 6fe83dee..20395e6d 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 @@ -313,22 +313,63 @@ public void testRouteListing_NonMatchingQueryParam(TestContext context) { * Test for route listing when no routes are registered. */ @Test - public void testRouteListing_NoRoutesRegistered(TestContext context) { + public void testSearchRouteListing_WhenNoRoutesRegistered(TestContext context) { Async async = context.async(); delete(); // Ensure there's no previous data initSettings(); // Initialize routing rules String queryParam = "someQuery"; - // No routes registered - // Send GET request with a query param + + // Send GET request with a query param when no routes are registered Response response = given().queryParam("q", queryParam) .when().get(searchUrlBase) .then().assertThat().statusCode(200) .extract().response(); - // Assert that the response body is empty or does not contain routes - Assert.assertFalse("No routes should be registered", - response.getBody().asString().contains(queryParam)); + // Parse response body as JSON + JsonObject jsonResponse = new JsonObject(response.getBody().asString()); + + // Validate that "routes" exists and is an empty array + Assert.assertTrue("Expected 'routes' to be an empty array", + jsonResponse.containsKey("routes") && jsonResponse.getJsonArray("routes").isEmpty()); + + async.complete(); + } + + @Test + public void testRouteListing_WithAndWithoutQueryParam_SingleMatch(TestContext context) { + Async async = context.async(); + delete(); // Clear any existing data before starting the test + initSettings(); // Initialize routing rules + + String routeName = "singleRoute"; + addRoute(routeName, true, true); // Add a route that will be the only matching result + + // Perform search without 'q' parameter + Response responseWithoutParam = get(searchUrlBase) + .then().assertThat().statusCode(200) + .extract().response(); + + // Perform search with 'q' parameter matching the route name + Response responseWithParam = given().queryParam("q", routeName) + .when().get(searchUrlBase) + .then().assertThat().statusCode(200) + .extract().response(); + + // Extract response bodies as strings for comparison + String responseBodyWithoutParam = responseWithoutParam.getBody().asString(); + String responseBodyWithParam = responseWithParam.getBody().asString(); + + // Verify that both responses are identical + Assert.assertEquals("Responses should be identical with and without 'q' when only one matching route exists", + responseBodyWithoutParam, responseBodyWithParam); + + // Ensure the route name is present in both responses + Assert.assertTrue(responseBodyWithoutParam.contains(routeName)); + Assert.assertTrue(responseBodyWithParam.contains(routeName)); + + // Clean up by removing the registered route after the test + removeRoute(routeName); async.complete(); } From 4a4fbffc034fa112be3ac528e2f59e365666eac4 Mon Sep 17 00:00:00 2001 From: almeidast Date: Wed, 13 Nov 2024 18:56:42 +0000 Subject: [PATCH 36/47] Added validations for route and listener search using parameters Fix route search with parameters Update readme with more information about search with query parameters create a hookIdentify to get a requestUrl when search using parameters Add more unit and integrate testes improve code implementations --- gateleen-hook/README_hook.md | 24 +- .../swisspush/gateleen/hook/HookHandler.java | 56 +++-- .../org/swisspush/gateleen/hook/Route.java | 8 +- .../swisspush/gateleen/hook/ListenerTest.java | 227 +++++++++--------- .../gateleen/hook/RouteListingTest.java | 82 ++----- 5 files changed, 209 insertions(+), 188 deletions(-) diff --git a/gateleen-hook/README_hook.md b/gateleen-hook/README_hook.md index c754d133..62a863ed 100644 --- a/gateleen-hook/README_hook.md +++ b/gateleen-hook/README_hook.md @@ -244,11 +244,13 @@ hookHandler.enableResourceLogging(true); Gateleen allows searching for listeners and routes using the query parameter `q`. This simplifies filtering the registered hooks based on query parameters. +The search will be based on the value registered of the destination property + ### Listener Search with `q` Search for listeners based on a query parameter like this: ``` -GET http://myserver:7012/playground/server/hooks/v1/registrations/listeners?q=test +GET http://myserver:7012/playground/server/hooks/v1/registrations/listeners?q=mylistener ``` The response will contain the matching listeners. If no match is found, an empty list is returned: @@ -257,7 +259,7 @@ The response will contain the matching listeners. If no match is found, an empty ```json { "listeners": [ - "first+playground+server+test+nemo+origin+b" + "first+playground+server+test+nemo+origin+mylistener" ] } ``` @@ -273,7 +275,23 @@ The response will contain the matching listeners. If no match is found, an empty Similarly, you can search for routes using a query parameter: ``` -GET http://myserver:7012/playground/server/hooks/v1/registrations/routes/?q=test +GET http://myserver:7012/playground/server/hooks/v1/registrations/routes/?q=myroute ``` The response contains the matching routes, or an empty list if no match is found. + +**Example response with matches:** +```json +{ + "routes": [ + "first+playground+server+test+nemo+origin+myroute" + ] +} + +``` +**Example response with no matches:** +```json +{ + "routes": [] +} +``` \ No newline at end of file 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 59999174..f6a8288b 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 @@ -159,6 +159,8 @@ public class HookHandler implements LoggableResource { private final QueueSplitter queueSplitter; private final String routeBase; private final String listenerBase; + private final String normalizedRouteBase; + private final String normalizedListenerBase; /** * Creates a new HookHandler. @@ -293,6 +295,9 @@ public HookHandler(Vertx vertx, HttpClient selfClient, final ResourceStorage use jsonSchemaHook = JsonSchemaFactory.getInstance().getSchema(hookSchema); this.listenerBase = hookRootUri + HOOK_LISTENER_STORAGE_PATH; this.routeBase = hookRootUri + HOOK_ROUTE_STORAGE_PATH; + this.normalizedListenerBase = this.listenerBase.replaceAll("/+$", ""); + this.normalizedRouteBase = this.routeBase.replaceAll("/+$", ""); + } public void init() { @@ -544,7 +549,7 @@ public void registerListenerRegistrationHandler(Handler readyHandler) { public boolean handle(final RoutingContext ctx) { HttpServerRequest request = ctx.request(); boolean consumed = false; - var requestUri = request.uri(); + var requestUri = request.uri().replaceAll("/+$", "");; /* * 1) Un- / Register Listener / Routes */ @@ -572,11 +577,15 @@ public boolean handle(final RoutingContext ctx) { if (requestMethod == GET && !request.params().isEmpty()) { String queryParam = request.getParam("q"); - String normalizedRequestUri = requestUri.replaceAll("/$", ""); - if (normalizedRequestUri.contains(listenerBase.replaceAll("/$", ""))) { + if (request.params().size() > 1 || (queryParam == null) || (queryParam.isEmpty())) { + request.response().setStatusCode(StatusCode.BAD_REQUEST.getStatusCode()) + .end("Bad Request: Only the 'q' parameter is allowed and can't be empty or null"); + return true; + } + if (requestUri.contains(normalizedListenerBase)) { handleListenerSearch(queryParam, request.response()); return true; - } else if (normalizedRequestUri.contains(routeBase.replaceAll("/$", ""))) { + } else if (requestUri.contains(normalizedRouteBase)) { handleRouteSearch(queryParam, request.response()); return true; } @@ -617,7 +626,7 @@ private void handleListenerSearch(String queryParam, HttpServerResponse response private void handleRouteSearch(String queryParam, HttpServerResponse response) { handleSearch( - routeRepository.getRoutes(), + routeRepository.getRoutes().entrySet().stream().collect(Collectors.toMap(entry -> entry.getValue().getHookIdentify(), Map.Entry::getValue)), route -> route.getHook().getDestination(), queryParam, ROUTES_KEY, @@ -637,18 +646,12 @@ 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 (queryParam == null || queryParam.isEmpty()) { - response.setStatusCode(StatusCode.BAD_REQUEST.getStatusCode()).end("Bad Request: One or more required parameters are missing or null"); - return; - } - JsonArray matchingResults = new JsonArray(); repository.forEach((key, value) -> { String destination = getDestination.apply(value); if (destination != null && destination.contains(queryParam)) { - matchingResults.add(key); + matchingResults.add(convertToStoragePattern(key)); } }); @@ -1490,7 +1493,7 @@ private void registerListener(Buffer buffer) { target = hook.getDestination(); } else { String urlPattern = hookRootUri + LISTENER_HOOK_TARGET_PATH + target; - routeRepository.addRoute(urlPattern, createRoute(urlPattern, hook)); + routeRepository.addRoute(urlPattern, createRoute(urlPattern, hook, requestUrl)); if (log.isTraceEnabled()) { log.trace("external target, add route for urlPattern: {}", urlPattern); @@ -1672,12 +1675,14 @@ private void registerRoute(Buffer buffer) { } boolean mustCreateNewRoute = true; + + Route existingRoute = routeRepository.getRoutes().get(routedUrl); if (existingRoute != null) { mustCreateNewRoute = mustCreateNewRouteForHook(existingRoute, hook); } if (mustCreateNewRoute) { - routeRepository.addRoute(routedUrl, createRoute(routedUrl, hook)); + routeRepository.addRoute(routedUrl, createRoute(routedUrl , hook, requestUrl)); } else { // see comment in #mustCreateNewRouteForHook() existingRoute.getRule().setHeaderFunction(hook.getHeaderFunction()); @@ -1686,6 +1691,25 @@ private void registerRoute(Buffer buffer) { monitoringHandler.updateRoutesCount(routeRepository.getRoutes().size()); } + /** + * Extracts the route name segment from the given request URL after the HOOKS_ROUTE_URI_PART. + * Returns an empty string if there is no route segment to extract. + * + * @param requestUrl the full URL of the request + * @return the extracted route name or an empty string if no route name is present + */ + private String extractRouteName(String requestUrl) { + int startIdx = requestUrl.indexOf(HOOKS_ROUTE_URI_PART); + if (requestUrl.isEmpty() || startIdx == -1) { + return ""; + } + startIdx += HOOKS_ROUTE_URI_PART.length(); + if (startIdx < requestUrl.length()) { + return requestUrl.substring(startIdx); + } + return ""; + } + /** * check if an existing route must be thrown away because the new Hook does not match the config of the existing Route * @@ -1730,9 +1754,9 @@ private boolean headersFilterPatternEquals(Pattern headersFilterPatternLeft, Pat * @param hook hook * @return Route */ - private Route createRoute(String urlPattern, HttpHook hook) { + private Route createRoute(String urlPattern, HttpHook hook, String hookIdentify) { return new Route(vertx, userProfileStorage, loggingResourceManager, logAppenderRepository, monitoringHandler, - userProfilePath, hook, urlPattern, selfClient); + userProfilePath, hook, urlPattern, selfClient,hookIdentify); } /** diff --git a/gateleen-hook/src/main/java/org/swisspush/gateleen/hook/Route.java b/gateleen-hook/src/main/java/org/swisspush/gateleen/hook/Route.java index 85080e44..6c61308a 100755 --- a/gateleen-hook/src/main/java/org/swisspush/gateleen/hook/Route.java +++ b/gateleen-hook/src/main/java/org/swisspush/gateleen/hook/Route.java @@ -49,6 +49,7 @@ public class Route { private MonitoringHandler monitoringHandler; private String userProfilePath; private ResourceStorage storage; + private String hookIdentify; private String urlPattern; private HttpHook httpHook; @@ -79,7 +80,7 @@ public class Route { * @param urlPattern - this can be a listener or a normal urlPattern (eg. for a route) */ public Route(Vertx vertx, ResourceStorage storage, LoggingResourceManager loggingResourceManager, LogAppenderRepository logAppenderRepository, - MonitoringHandler monitoringHandler, String userProfilePath, HttpHook httpHook, String urlPattern, HttpClient selfClient) { + MonitoringHandler monitoringHandler, String userProfilePath, HttpHook httpHook, String urlPattern, HttpClient selfClient, String hookIdentify) { this.vertx = vertx; this.storage = storage; this.loggingResourceManager = loggingResourceManager; @@ -89,6 +90,7 @@ public Route(Vertx vertx, ResourceStorage storage, LoggingResourceManager loggin this.httpHook = httpHook; this.urlPattern = urlPattern; this.selfClient = selfClient; + this.hookIdentify = hookIdentify; createRule(); @@ -273,4 +275,8 @@ public void cleanup() { public HttpHook getHook() { return httpHook; } + + public String getHookIdentify() { + return hookIdentify; + } } 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 8565141e..92f8ae9d 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 @@ -44,10 +44,16 @@ public class ListenerTest extends AbstractTest { private String requestUrlBase; private String targetUrlBase; private String searchUrlBase; + private String defaultRegisterUrlListener; + private String defaultTargetListener; + private String[] defaultMethodsListener; + private final String defaultQueryParam = "defaultQueryParam"; + private final String defaultListenerName = "defaultListener"; @Rule public WireMockRule wireMockRule = new WireMockRule(WIREMOCK_PORT); + /** * Overwrite RestAssured configuration */ @@ -59,6 +65,11 @@ public void initRestAssured() { targetUrlBase = "http://localhost:" + MAIN_PORT + SERVER_ROOT + "/tests/gateleen/targetresource"; searchUrlBase = "http://localhost:" + MAIN_PORT + SERVER_ROOT + "/hooks/v1/registrations/listeners"; + defaultRegisterUrlListener = requestUrlBase + "/" + defaultQueryParam + TestUtils.getHookListenersUrlSuffix() + defaultListenerName; + defaultTargetListener = targetUrlBase + "/" + defaultListenerName; + defaultMethodsListener = new String[]{"PUT", "DELETE", "POST"}; + + } /** @@ -948,103 +959,56 @@ 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.
- * requestUrl: http://localhost:7012/playground/server/hooks/v1/registrations/listeners/?q=testQuery - */ - @Test - public void testHookHandleSearch_ListenerPathWithValidQueryParam(TestContext context) { - Async async = context.async(); + private Response searchWithQueryParam(String searchParam, String queryParam, int expectedStatusCode ) { + return given() + .queryParam(searchParam, queryParam) + .when().get(searchUrlBase ) + .then().assertThat().statusCode(expectedStatusCode) + .extract().response(); + } + private void registerDefaultListener() { delete(); initRoutingRules(); - - // Settings - String subresource = "matchingQueryParam"; - String listenerName = "myListener"; - - String registerUrlListener1 = requestUrlBase + "/" + subresource + TestUtils.getHookListenersUrlSuffix() + listenerName; - String targetListener1 = targetUrlBase + "/" + listenerName; - String[] methodsListener1 = new String[]{"PUT", "DELETE", "POST"}; - final String targetUrlListener1 = targetUrlBase + "/" + listenerName; - - delete(searchUrlBase); - delete(targetUrlListener1); - - //Sending request, one listener hooked - TestUtils.registerListener(registerUrlListener1, targetListener1, methodsListener1, null, null, + delete(defaultTargetListener); + TestUtils.registerListener(defaultRegisterUrlListener, defaultTargetListener, defaultMethodsListener, null, null, null, null, "x-foo: (A|B)"); - - // Verify that the listener was correctly registered - Response response = given() - .queryParam("q", listenerName) - .when().get(searchUrlBase) - .then().assertThat().statusCode(200) - .extract().response(); - - // Assert that the response contains the expected query param - System.out.println(response.getBody().asString()); - Assert.assertTrue(response.getBody().asString().contains(listenerName)); // Fails if not found - TestUtils.unregisterListener(registerUrlListener1); - - async.complete(); } - - /** - * Test for hookHandleSearch with listener storage path and valid query param but no match found.
- * requestUrl: http://localhost:7012/playground/server/hooks/v1/registrations/listeners/?q=nonMatchingQuery - */ @Test - public void testHookHandleSearch_ListenerPathWithNonMatchingQueryParam(TestContext context) { + public void testSearchListenerWithValidAndInvalidSearchParam(TestContext context) { Async async = context.async(); - delete(); - initRoutingRules(); + registerDefaultListener(); - // Settings - String subresource = "matchingQueryParam"; - String listenerName = "myListener"; + searchWithQueryParam("wq", defaultListenerName, 400); + searchWithQueryParam("q", "", 400); - String registerUrlListener1 = requestUrlBase + "/" + subresource + TestUtils.getHookListenersUrlSuffix() + listenerName; - String targetListener1 = targetUrlBase + "/" + listenerName; - String[] methodsListener1 = new String[]{"PUT", "DELETE", "POST"}; - final String targetUrlListener1 = targetUrlBase + "/" + listenerName + "/" + "test"; + Response response = searchWithQueryParam("q",defaultListenerName,200); + Assert.assertTrue(response.getBody().asString().contains(defaultListenerName)); // Fails if not found + TestUtils.unregisterListener(defaultRegisterUrlListener); - delete(searchUrlBase); - delete(targetUrlListener1); + async.complete(); + } - TestUtils.registerListener(registerUrlListener1, targetListener1, methodsListener1, null, null, - null, null, "x-foo: (A|B)"); + @Test + public void testSearchListenerWithNonMatchingQueryParam(TestContext context) { + Async async = context.async(); + registerDefaultListener(); // Verify that the listener was correctly registered - Response response = given() - .queryParam("q", listenerName) - .when().get(searchUrlBase) - .then().assertThat().statusCode(200) - .extract().response(); - - // Assert that the response contains the expected query param - Assert.assertTrue(response.getBody().asString().contains(listenerName)); // Fails if not found + Response response = searchWithQueryParam("q",defaultListenerName,200); + Assert.assertTrue(response.getBody().asString().contains(defaultListenerName)); - listenerName="nonMatchingQueryParam"; + String localListenerName="nonMatchingQueryParam"; // Verify that the listener search with a no registered listener - response = given() - .queryParam("q", listenerName) - .when().get(searchUrlBase) - .then().assertThat().statusCode(200) - .extract().response(); + response = searchWithQueryParam("q",localListenerName,200); + Assert.assertFalse(response.getBody().asString().contains(localListenerName)); - // Assert that the response contains the expected query param - Assert.assertFalse(response.getBody().asString().contains(listenerName)); // Fails if not found - TestUtils.unregisterListener(registerUrlListener1); + TestUtils.unregisterListener(defaultRegisterUrlListener); async.complete(); } - /** - * Test for hookHandleSearch with listener storage path and valid query param but no listeners registered.
- * requestUrl: http://localhost:7012/playground/server/hooks/v1/registrations/listeners/?q=someQuery - */ @Test public void testHookHandleSearch_NoListenersRegistered(TestContext context) { Async async = context.async(); @@ -1052,21 +1016,19 @@ public void testHookHandleSearch_NoListenersRegistered(TestContext context) { initRoutingRules(); String queryParam = "someQuery"; - String listenerPath = "/hooks/listeners"; // Verify that the listener search with a no registered listener - Response response = given() - .queryParam("q", queryParam) - .when().get(searchUrlBase) - .then().assertThat().statusCode(200) - .extract().response(); + Response response = searchWithQueryParam("q",queryParam,200); + + // Parse response body as JSON + JsonObject jsonResponse = new JsonObject(response.getBody().asString()); // Assert that the response contains the expected query param - Assert.assertFalse(response.getBody().asString().contains(queryParam)); // Fails if not found + Assert.assertTrue("Expected 'listeners' to be an empty array", + jsonResponse.containsKey("listeners") && jsonResponse.getJsonArray("listeners").isEmpty()); async.complete(); } - @Test public void testHookHandleSearch_ListenerPathInvalidParam(TestContext context) { Async async = context.async(); @@ -1074,7 +1036,6 @@ public void testHookHandleSearch_ListenerPathInvalidParam(TestContext context) { initRoutingRules(); String queryParam = "testQuery"; - String listenerPath = "/_hooks/listeners"; String requestUrl = searchUrlBase+ "?www=" + queryParam; // Validate the response @@ -1082,42 +1043,54 @@ public void testHookHandleSearch_ListenerPathInvalidParam(TestContext context) { 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) { + public void testHookHandleSearch_ListenerTwoParam(TestContext context) { Async async = context.async(); delete(); initRoutingRules(); - // Settings - String subresource = "validQueryParameter"; - String listenerName = ""; + String queryParam = "testQuery"; + String requestUrl = searchUrlBase+ "?q=" + queryParam+"&www=" + queryParam;; - String registerUrlListener1 = requestUrlBase + "/" + subresource + TestUtils.getHookListenersUrlSuffix() + listenerName; - String targetListener1 = targetUrlBase + "/" + listenerName; - String[] methodsListener1 = new String[]{"PUT", "DELETE", "POST"}; - final String targetUrlListener1 = targetUrlBase + "/" + listenerName; + // Validate the response + checkGETStatusCodeWithAwait(requestUrl, 400); + async.complete(); + } - delete(searchUrlBase); - delete(targetUrlListener1); + @Test + public void testHookHandleSearch_UseSlashInSearchUrlBase(TestContext context) { + Async async = context.async(); + delete(); + initRoutingRules(); - //Sending request, one listener hooked - TestUtils.registerListener(registerUrlListener1, targetListener1, methodsListener1, null, null, - null, null, "x-foo: (A|B)"); + registerDefaultListener(); - // Verify that the listener was correctly registered Response response = given() - .queryParam("q", listenerName) - .when().get(searchUrlBase) - .then().assertThat().statusCode(400) - .extract().response(); + .queryParam("q", defaultListenerName) + .when().get(searchUrlBase +"/") + .then().assertThat().statusCode(200) + .extract().response(); + + // Assert that the response contains the expected query param + Assert.assertTrue(response.getBody().asString().contains(defaultListenerName)); // Fails if not found + + TestUtils.unregisterListener(defaultRegisterUrlListener); + + async.complete(); + } + + @Test + public void testHookHandleSearch_EmptyQueryParameter(TestContext context) { + Async async = context.async(); + delete(); + initRoutingRules(); + registerDefaultListener(); + + Response response = searchWithQueryParam("q","",400); // Assert that the response contains the expected query param Assert.assertTrue(response.getBody().asString().contains("Bad Request")); // Fails if not found - TestUtils.unregisterListener(registerUrlListener1); + TestUtils.unregisterListener(defaultRegisterUrlListener); async.complete(); } @@ -1156,9 +1129,10 @@ public void testHookHandleSearch_ReturnsTwoOutOfThreeListeners(TestContext conte TestUtils.registerListener(registerUrlListener3, targetListener3, methodsListener, null, null, null, null, "x-foo: (A|B)"); // Perform a search for the listeners that should return only listenerOne and listenerTwo + // added in searchUrlBase a '/' to validate if also works Response response = given() .queryParam("q", "listener") - .when().get(searchUrlBase) + .when().get(searchUrlBase+"/") .then().assertThat().statusCode(200) .extract().response(); @@ -1175,5 +1149,40 @@ public void testHookHandleSearch_ReturnsTwoOutOfThreeListeners(TestContext conte async.complete(); } + /** + * Test to verify that the search for listeners returns identical results + * when performed with and without a query parameter if only one matching listener exists. + * Registers a listener and performs searches, confirming that both responses match. + */ + @Test + public void testSearchListener_IdenticalResultsWithAndWithoutQueryParam(TestContext context) { + Async async = context.async(); + registerDefaultListener(); + // Perform search with 'q' parameter matching the listener name + Response responseWithParam = searchWithQueryParam("q",defaultListenerName,200); + + // Perform search without 'q' parameter + Response responseWithoutParam = given().when().get(searchUrlBase) + .then().assertThat().statusCode(200) + .extract().response(); + + // Extract response bodies as strings for comparison + String responseBodyWithoutParam = responseWithoutParam.getBody().asString(); + String responseBodyWithParam = responseWithParam.getBody().asString(); + + // Verify that both responses are identical + Assert.assertEquals("Responses should be identical with and without 'q' when only one matching listener exists", + responseBodyWithoutParam, responseBodyWithParam); + + // Ensure the listener name is present in both responses + Assert.assertTrue("Listener name should be present in response without parameter", + responseBodyWithoutParam.contains(defaultListenerName)); + Assert.assertTrue("Listener name should be present in response with parameter", + responseBodyWithParam.contains(defaultListenerName)); + + // Clean up by removing the registered listener after the test + TestUtils.unregisterListener(defaultRegisterUrlListener); + async.complete(); + } } 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 20395e6d..4ad868ba 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,7 +15,6 @@ import org.swisspush.gateleen.TestUtils; import static io.restassured.RestAssured.*; -import static org.swisspush.gateleen.TestUtils.checkGETStatusCodeWithAwait; /** * Test class for the hook route feature. @@ -196,9 +195,6 @@ public void testListingWithoutStaticAndDynamicCollections(TestContext context) { async.complete(); } - - - private void removeRoute(String name) { String route = requestUrlBase + "/" + name + TestUtils.getHookRouteUrlSuffix(); TestUtils.unregisterRoute(route); @@ -215,7 +211,6 @@ private void addRoute(String name, boolean collection, boolean listable) { TestUtils.registerRoute(route, target, methods, null, collection, listable); } - private void assertResponse(final Response response, final String[] expectedArray) { Assert.assertEquals(200, response.statusCode()); String bodyString = response.getBody().asString(); @@ -226,43 +221,42 @@ private void assertResponse(final Response response, final String[] expectedArra Assert.assertThat(array, Matchers.contains(expectedArray)); } - /** - * Test for route listing with a valid query parameter. - */ + private Response searchWithQueryParam(String searchParam, String queryParam, int expectedStatusCode ) { + return given() + .queryParam(searchParam, queryParam) + .when().get(searchUrlBase ) + .then().assertThat().statusCode(expectedStatusCode) + .extract().response(); + } + @Test - public void testRouteListing_ValidQueryParam(TestContext context) { + public void testHookHandleSearch_WithValidAndInvalidSearchParam(TestContext context) { Async async = context.async(); delete(); // Remove any pre-existing data initSettings(); // Initialize routing rules String queryParam = "routeTests"; - String routePath = "/routes"; - addRoute(queryParam, true, true); // Verify that the route was correctly registered - Response response = given() - .queryParam("q", queryParam) - .when().get(searchUrlBase ) - .then().assertThat().statusCode(200) - .extract().response(); + searchWithQueryParam("w", queryParam, 400); + searchWithQueryParam("q", "", 400); + + // Verify that the route was correctly registered + Response response = searchWithQueryParam("q", queryParam, 200); // Assert that the response contains the expected query param String responseBody = response.getBody().asString(); - Assert.assertTrue(responseBody.contains(queryParam)); // Fails if not found - + Assert.assertTrue(responseBody.contains(queryParam)); // Unregister the route removeRoute(queryParam); async.complete(); } - /** - * Test for route listing with a non-matching query parameter. - */ @Test - public void testRouteListing_NonMatchingQueryParam(TestContext context) { + public void testHookHandleSearch_RouteNonMatchingQueryParam(TestContext context) { Async async = context.async(); delete(); // Clean up before the test initSettings(); // Initialize routing rules @@ -275,45 +269,24 @@ public void testRouteListing_NonMatchingQueryParam(TestContext context) { assertResponse(get(requestUrlBase), new String[]{queryParam+"/"}); // Send GET request with a non-matching query param - Response response = given().queryParam("q", queryParam) - .when().get(searchUrlBase) - .then().assertThat().statusCode(200) - .extract().response(); + Response response = searchWithQueryParam("q", queryParam, 200); Assert.assertTrue("Query param should be found in response", response.getBody().asString().contains(queryParam)); // Send GET request with a non-matching query param - response = given().queryParam("q", nonMatchingQueryParam) - .when().get(searchUrlBase) - .then().assertThat().statusCode(200) - .extract().response(); + response = searchWithQueryParam("q", nonMatchingQueryParam, 200); // Assert the response does not contain the non-matching query param Assert.assertFalse("Non-matching query param should not be found in response", response.getBody().asString().contains(nonMatchingQueryParam)); - // Send GET request with a matching query param - response = given().queryParam("q", queryParam) - .when().get(searchUrlBase) - .then().assertThat().statusCode(200) - .extract().response(); - - // Assert the response contain the matching query param - Assert.assertTrue("matching query param should be found in response", - response.getBody().asString().contains(queryParam)); - - // Unregister the route removeRoute(queryParam); - async.complete(); } - /** - * Test for route listing when no routes are registered. - */ @Test - public void testSearchRouteListing_WhenNoRoutesRegistered(TestContext context) { + public void testHookHandleSearch_RouteWhenNoRoutesRegistered(TestContext context) { Async async = context.async(); delete(); // Ensure there's no previous data initSettings(); // Initialize routing rules @@ -321,14 +294,10 @@ public void testSearchRouteListing_WhenNoRoutesRegistered(TestContext context) { String queryParam = "someQuery"; // Send GET request with a query param when no routes are registered - Response response = given().queryParam("q", queryParam) - .when().get(searchUrlBase) - .then().assertThat().statusCode(200) - .extract().response(); + Response response = searchWithQueryParam("q", queryParam, 200); // Parse response body as JSON JsonObject jsonResponse = new JsonObject(response.getBody().asString()); - // Validate that "routes" exists and is an empty array Assert.assertTrue("Expected 'routes' to be an empty array", jsonResponse.containsKey("routes") && jsonResponse.getJsonArray("routes").isEmpty()); @@ -337,12 +306,12 @@ public void testSearchRouteListing_WhenNoRoutesRegistered(TestContext context) { } @Test - public void testRouteListing_WithAndWithoutQueryParam_SingleMatch(TestContext context) { + public void testHookHandleSearch_ReturnsIdenticalResultsWithAndWithoutSearchQueryParam(TestContext context) { Async async = context.async(); delete(); // Clear any existing data before starting the test initSettings(); // Initialize routing rules - String routeName = "singleRoute"; + String routeName = "GenericSingleRoute"; addRoute(routeName, true, true); // Add a route that will be the only matching result // Perform search without 'q' parameter @@ -351,10 +320,7 @@ public void testRouteListing_WithAndWithoutQueryParam_SingleMatch(TestContext co .extract().response(); // Perform search with 'q' parameter matching the route name - Response responseWithParam = given().queryParam("q", routeName) - .when().get(searchUrlBase) - .then().assertThat().statusCode(200) - .extract().response(); + Response responseWithParam = searchWithQueryParam("q", routeName, 200); // Extract response bodies as strings for comparison String responseBodyWithoutParam = responseWithoutParam.getBody().asString(); @@ -373,6 +339,4 @@ public void testRouteListing_WithAndWithoutQueryParam_SingleMatch(TestContext co async.complete(); } - - } From 27f561fb910a1891806765eb7b3e5fca3d787eea Mon Sep 17 00:00:00 2001 From: almeidast Date: Thu, 14 Nov 2024 09:32:48 +0000 Subject: [PATCH 37/47] Add validation to check if is valid parameters for search request Clear unused code. --- .../swisspush/gateleen/hook/HookHandler.java | 41 ++++++------------- 1 file changed, 13 insertions(+), 28 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 f6a8288b..321a9a0b 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 @@ -549,7 +549,7 @@ public void registerListenerRegistrationHandler(Handler readyHandler) { public boolean handle(final RoutingContext ctx) { HttpServerRequest request = ctx.request(); boolean consumed = false; - var requestUri = request.uri().replaceAll("/+$", "");; + var requestUri = request.uri().replaceAll("/+$", ""); /* * 1) Un- / Register Listener / Routes */ @@ -577,15 +577,10 @@ public boolean handle(final RoutingContext ctx) { if (requestMethod == GET && !request.params().isEmpty()) { String queryParam = request.getParam("q"); - if (request.params().size() > 1 || (queryParam == null) || (queryParam.isEmpty())) { - request.response().setStatusCode(StatusCode.BAD_REQUEST.getStatusCode()) - .end("Bad Request: Only the 'q' parameter is allowed and can't be empty or null"); - return true; - } - if (requestUri.contains(normalizedListenerBase)) { + if (requestUri.contains(normalizedListenerBase) && checkValidParams(request,queryParam)) { handleListenerSearch(queryParam, request.response()); return true; - } else if (requestUri.contains(normalizedRouteBase)) { + } else if (requestUri.contains(normalizedRouteBase) && checkValidParams(request,queryParam)) { handleRouteSearch(queryParam, request.response()); return true; } @@ -614,6 +609,15 @@ public boolean handle(final RoutingContext ctx) { } } + private boolean checkValidParams (HttpServerRequest request, String queryParam){ + if (request.params().size() > 1 || (queryParam == null) || (queryParam.isEmpty())) { + request.response().setStatusCode(StatusCode.BAD_REQUEST.getStatusCode()) + .end("Bad Request: Only the 'q' parameter is allowed and can't be empty or null"); + return true; + } + return false; + } + private void handleListenerSearch(String queryParam, HttpServerResponse response) { handleSearch( listenerRepository.getListeners().stream().collect(Collectors.toMap(Listener::getListenerId, listener -> listener)), @@ -646,8 +650,8 @@ 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) { - JsonArray matchingResults = new JsonArray(); + JsonArray matchingResults = new JsonArray(); repository.forEach((key, value) -> { String destination = getDestination.apply(value); if (destination != null && destination.contains(queryParam)) { @@ -1691,25 +1695,6 @@ private void registerRoute(Buffer buffer) { monitoringHandler.updateRoutesCount(routeRepository.getRoutes().size()); } - /** - * Extracts the route name segment from the given request URL after the HOOKS_ROUTE_URI_PART. - * Returns an empty string if there is no route segment to extract. - * - * @param requestUrl the full URL of the request - * @return the extracted route name or an empty string if no route name is present - */ - private String extractRouteName(String requestUrl) { - int startIdx = requestUrl.indexOf(HOOKS_ROUTE_URI_PART); - if (requestUrl.isEmpty() || startIdx == -1) { - return ""; - } - startIdx += HOOKS_ROUTE_URI_PART.length(); - if (startIdx < requestUrl.length()) { - return requestUrl.substring(startIdx); - } - return ""; - } - /** * check if an existing route must be thrown away because the new Hook does not match the config of the existing Route * From 044ca922d73cc9a81ee98ede809a21703023c61e Mon Sep 17 00:00:00 2001 From: almeidast Date: Thu, 14 Nov 2024 09:52:27 +0000 Subject: [PATCH 38/47] Move specific validations --- .../swisspush/gateleen/hook/HookHandler.java | 39 +++++++++---------- 1 file changed, 18 insertions(+), 21 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 321a9a0b..c86329f3 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 @@ -577,11 +577,11 @@ public boolean handle(final RoutingContext ctx) { if (requestMethod == GET && !request.params().isEmpty()) { String queryParam = request.getParam("q"); - if (requestUri.contains(normalizedListenerBase) && checkValidParams(request,queryParam)) { - handleListenerSearch(queryParam, request.response()); + if (requestUri.contains(normalizedListenerBase) ) { + handleListenerSearch(queryParam, request); return true; - } else if (requestUri.contains(normalizedRouteBase) && checkValidParams(request,queryParam)) { - handleRouteSearch(queryParam, request.response()); + } else if (requestUri.contains(normalizedRouteBase) ) { + handleRouteSearch(queryParam, request); return true; } } @@ -609,32 +609,23 @@ public boolean handle(final RoutingContext ctx) { } } - private boolean checkValidParams (HttpServerRequest request, String queryParam){ - if (request.params().size() > 1 || (queryParam == null) || (queryParam.isEmpty())) { - request.response().setStatusCode(StatusCode.BAD_REQUEST.getStatusCode()) - .end("Bad Request: Only the 'q' parameter is allowed and can't be empty or null"); - return true; - } - return false; - } - - private void handleListenerSearch(String queryParam, HttpServerResponse response) { + private void handleListenerSearch(String queryParam, HttpServerRequest request) { handleSearch( listenerRepository.getListeners().stream().collect(Collectors.toMap(Listener::getListenerId, listener -> listener)), listener -> listener.getHook().getDestination(), queryParam, LISTENERS_KEY, - response + request ); } - private void handleRouteSearch(String queryParam, HttpServerResponse response) { + private void handleRouteSearch(String queryParam, HttpServerRequest request) { handleSearch( routeRepository.getRoutes().entrySet().stream().collect(Collectors.toMap(entry -> entry.getValue().getHookIdentify(), Map.Entry::getValue)), route -> route.getHook().getDestination(), queryParam, ROUTES_KEY, - response + request ); } @@ -647,9 +638,14 @@ private void handleRouteSearch(String queryParam, HttpServerResponse response) { * @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. + * @param request The HTTP request to make a specific validations and return the results. */ - private void handleSearch(Map repository, Function getDestination, String queryParam, String resultKey, HttpServerResponse response) { + private void handleSearch(Map repository, Function getDestination, String queryParam, String resultKey, HttpServerRequest request) { + if (request.params().size() > 1 || (queryParam == null) || (queryParam.isEmpty())) { + request.response().setStatusCode(StatusCode.BAD_REQUEST.getStatusCode()) + .end("Bad Request: Only the 'q' parameter is allowed and can't be empty or null"); + return ; + } JsonArray matchingResults = new JsonArray(); repository.forEach((key, value) -> { @@ -664,11 +660,12 @@ private void handleSearch(Map repository, Function get String encodedResult = result.encode(); - response.putHeader(CONTENT_TYPE_HEADER, CONTENT_TYPE_JSON); - response.end(encodedResult); + request.response().putHeader(CONTENT_TYPE_HEADER, CONTENT_TYPE_JSON); + request.response().end(encodedResult); } + /** * Create a listing of routes in the given parent. This happens * only if we have a GET request, the routes are listable and From 9956ebad4ad2af9677b92dcf56a403b1ac79add0 Mon Sep 17 00:00:00 2001 From: almeidast Date: Thu, 14 Nov 2024 09:57:19 +0000 Subject: [PATCH 39/47] improve code --- .../test/java/org/swisspush/gateleen/hook/ListenerTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 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 92f8ae9d..10a8ff95 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 @@ -47,7 +47,6 @@ public class ListenerTest extends AbstractTest { private String defaultRegisterUrlListener; private String defaultTargetListener; private String[] defaultMethodsListener; - private final String defaultQueryParam = "defaultQueryParam"; private final String defaultListenerName = "defaultListener"; @Rule @@ -65,7 +64,8 @@ public void initRestAssured() { targetUrlBase = "http://localhost:" + MAIN_PORT + SERVER_ROOT + "/tests/gateleen/targetresource"; searchUrlBase = "http://localhost:" + MAIN_PORT + SERVER_ROOT + "/hooks/v1/registrations/listeners"; - defaultRegisterUrlListener = requestUrlBase + "/" + defaultQueryParam + TestUtils.getHookListenersUrlSuffix() + defaultListenerName; + String defaultQueryParam = "defaultQueryParam"; + defaultRegisterUrlListener = requestUrlBase + "/" + defaultQueryParam + TestUtils.getHookListenersUrlSuffix() + defaultListenerName; defaultTargetListener = targetUrlBase + "/" + defaultListenerName; defaultMethodsListener = new String[]{"PUT", "DELETE", "POST"}; @@ -1050,7 +1050,7 @@ public void testHookHandleSearch_ListenerTwoParam(TestContext context) { initRoutingRules(); String queryParam = "testQuery"; - String requestUrl = searchUrlBase+ "?q=" + queryParam+"&www=" + queryParam;; + String requestUrl = searchUrlBase+ "?q=" + queryParam+"&www=" + queryParam; // Validate the response checkGETStatusCodeWithAwait(requestUrl, 400); From 4fb6453c6507908f47599c1c33e0323a76b7cf5d Mon Sep 17 00:00:00 2001 From: almeidast Date: Thu, 14 Nov 2024 15:18:46 +0000 Subject: [PATCH 40/47] Fix comments improve testes organize code remove unused variables --- .../swisspush/gateleen/hook/HookHandler.java | 26 +-- .../gateleen/hook/HookHandlerTest.java | 34 ++-- .../swisspush/gateleen/hook/ListenerTest.java | 187 +++++++++--------- .../gateleen/hook/RouteListingTest.java | 76 ++++--- 4 files changed, 157 insertions(+), 166 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 c86329f3..62be31e9 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 @@ -31,12 +31,7 @@ import org.swisspush.gateleen.core.logging.LoggableResource; import org.swisspush.gateleen.core.logging.RequestLogger; import org.swisspush.gateleen.core.storage.ResourceStorage; -import org.swisspush.gateleen.core.util.CollectionContentComparator; -import org.swisspush.gateleen.core.util.HttpHeaderUtil; -import org.swisspush.gateleen.core.util.HttpRequestHeader; -import org.swisspush.gateleen.core.util.HttpServerRequestUtil; -import org.swisspush.gateleen.core.util.ResourcesUtils; -import org.swisspush.gateleen.core.util.StatusCode; +import org.swisspush.gateleen.core.util.*; import org.swisspush.gateleen.hook.queueingstrategy.DefaultQueueingStrategy; import org.swisspush.gateleen.hook.queueingstrategy.DiscardPayloadQueueingStrategy; import org.swisspush.gateleen.hook.queueingstrategy.QueueingStrategy; @@ -576,12 +571,11 @@ public boolean handle(final RoutingContext ctx) { } if (requestMethod == GET && !request.params().isEmpty()) { - String queryParam = request.getParam("q"); if (requestUri.contains(normalizedListenerBase) ) { - handleListenerSearch(queryParam, request); + handleListenerSearch(request); return true; } else if (requestUri.contains(normalizedRouteBase) ) { - handleRouteSearch(queryParam, request); + handleRouteSearch(request); return true; } } @@ -609,21 +603,19 @@ public boolean handle(final RoutingContext ctx) { } } - private void handleListenerSearch(String queryParam, HttpServerRequest request) { + private void handleListenerSearch(HttpServerRequest request) { handleSearch( listenerRepository.getListeners().stream().collect(Collectors.toMap(Listener::getListenerId, listener -> listener)), listener -> listener.getHook().getDestination(), - queryParam, LISTENERS_KEY, request ); } - private void handleRouteSearch(String queryParam, HttpServerRequest request) { + private void handleRouteSearch(HttpServerRequest request) { handleSearch( routeRepository.getRoutes().entrySet().stream().collect(Collectors.toMap(entry -> entry.getValue().getHookIdentify(), Map.Entry::getValue)), route -> route.getHook().getDestination(), - queryParam, ROUTES_KEY, request ); @@ -636,12 +628,12 @@ private void handleRouteSearch(String queryParam, HttpServerRequest request) { * All params cannot be null * @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 request The HTTP request to make a specific validations and return the results. */ - private void handleSearch(Map repository, Function getDestination, String queryParam, String resultKey, HttpServerRequest request) { - if (request.params().size() > 1 || (queryParam == null) || (queryParam.isEmpty())) { + private void handleSearch(Map repository, Function getDestination, String resultKey, HttpServerRequest request) { + String queryParam = request.getParam("q"); + if (request.params().size() > 1 || StringUtils.isEmpty(queryParam)) { request.response().setStatusCode(StatusCode.BAD_REQUEST.getStatusCode()) .end("Bad Request: Only the 'q' parameter is allowed and can't be empty or null"); return ; @@ -1738,7 +1730,7 @@ private boolean headersFilterPatternEquals(Pattern headersFilterPatternLeft, Pat */ private Route createRoute(String urlPattern, HttpHook hook, String hookIdentify) { return new Route(vertx, userProfileStorage, loggingResourceManager, logAppenderRepository, monitoringHandler, - userProfilePath, hook, urlPattern, selfClient,hookIdentify); + userProfilePath, hook, urlPattern, selfClient, hookIdentify); } /** 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 6531bb60..4a8dd393 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 @@ -703,7 +703,7 @@ public void testHandleGETRequestWithListenersSearchSingleResult() throws Interru setListenerStorageEntryAndTriggerUpdate(buildListenerConfigWithHeadersFilter(null, singleListener, "x-foo: (A|B)")); // wait a moment to let the listener be registered - Thread.sleep(500); + Thread.sleep(200); // Mock RoutingContext and configure response capture when(routingContext.request()).thenReturn(request); @@ -727,15 +727,18 @@ public void testHandleGETRequestWithListenersSearchMultipleResults() throws Inte String uri = "/hookRootURI/registrations/listeners"; HttpServerResponse mockResponse = mock(HttpServerResponse.class); GETRequest request = new GETRequest(uri, mockResponse); - request.addParameter("q", "listener"); // Search parameter that should match multiple listeners + request.addParameter("q", "myListener"); // Search parameter that should match multiple listeners // Add multiple listeners to the MockResourceStorage using the expected configuration and register them - String listenerId1 = "listener112222"; - String listenerId2 = "listener222133"; + String listenerId1 = "myListener112222"; + String listenerId2 = "myListener222133"; + String notMatchListener = "notMatchListener"; setListenerStorageEntryAndTriggerUpdate(buildListenerConfigWithHeadersFilter(null, listenerId1, "x-foo: (A|B)")); - Thread.sleep(500); + Thread.sleep(200); setListenerStorageEntryAndTriggerUpdate(buildListenerConfigWithHeadersFilter(null, listenerId2, "x-foo: (A|B)")); - Thread.sleep(500); + Thread.sleep(200); + setListenerStorageEntryAndTriggerUpdate(buildListenerConfigWithHeadersFilter(null, notMatchListener, "x-foo: (A|B)")); + Thread.sleep(200); // Mock the RoutingContext and set up the response capture when(routingContext.request()).thenReturn(request); @@ -752,6 +755,7 @@ public void testHandleGETRequestWithListenersSearchMultipleResults() throws Inte assertNotNull(jsonResponse); assertTrue(jsonResponse.contains(listenerId1)); assertTrue(jsonResponse.contains(listenerId2)); + assertFalse(jsonResponse.contains(notMatchListener)); } @@ -790,15 +794,18 @@ public void testHandleGETRequestWithRoutesSearchMultipleResults() throws Interru String uri = "/hookRootURI/registrations/routes"; HttpServerResponse mockResponse = mock(HttpServerResponse.class); GETRequest request = new GETRequest(uri, mockResponse); - request.addParameter("q", "route"); // Search parameter that should match multiple routes + request.addParameter("q", "valid"); // Search parameter that should match multiple routes // Add multiple routes to the MockResourceStorage using the expected configuration and register them - String routeId1 = "route12345"; - String routeId2 = "route67890"; + String routeId1 = "valid12345"; + String routeId2 = "valid67890"; + String notPreset = "notPreset"; setRouteStorageEntryAndTriggerUpdate(buildRouteConfig(routeId1)); - Thread.sleep(500); + Thread.sleep(200); setRouteStorageEntryAndTriggerUpdate(buildRouteConfig( routeId2)); - Thread.sleep(500); + Thread.sleep(200); + setRouteStorageEntryAndTriggerUpdate(buildRouteConfig( notPreset)); + Thread.sleep(200); // Mock the RoutingContext and set up the response capture when(routingContext.request()).thenReturn(request); @@ -815,6 +822,7 @@ public void testHandleGETRequestWithRoutesSearchMultipleResults() throws Interru assertNotNull(jsonResponse); assertTrue(jsonResponse.contains(routeId1)); assertTrue(jsonResponse.contains(routeId2)); + assertFalse(jsonResponse.contains(notPreset)); } @Test @@ -1242,10 +1250,6 @@ public String getParam(String paramName) { return params.get(paramName); } - public void addHeader(String headerName, String headerValue) { - headers.add(headerName, headerValue); - } - public void addParameter(String paramName, String paramValue) { params.add(paramName, paramValue); } 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 10a8ff95..5677403b 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 @@ -888,92 +888,6 @@ public void testRequestForwardingForTwoListenerAtSameResourceButDifferentHeaders async.complete(); } - /** - * Checks if the DELETE request gets a response - * with the given status code. - * - * @param requestUrl - * @param statusCode - */ - private void checkDELETEStatusCode(String requestUrl, int statusCode) { - delete(requestUrl).then().assertThat().statusCode(statusCode); - } - - /** - * Checks if the DELETE request gets a response - * with the given status code. - * - * @param requestUrl - * @param statusCode - * @param headers - */ - private void checkDELETEStatusCode(String requestUrl, int statusCode, Headers headers) { - with().headers(headers).delete(requestUrl).then().assertThat().statusCode(statusCode); - } - - /** - * Checks if the PUT request gets a response - * with the given status code. - * - * @param requestUrl - * @param body - * @param statusCode - */ - private void checkPUTStatusCode(String requestUrl, String body, int statusCode) { - with().body(body).put(requestUrl).then().assertThat().statusCode(statusCode); - } - - /** - * Checks if the PUT request gets a response - * with the given status code. - * - * @param requestUrl - * @param body - * @param statusCode - * @param headers - */ - private void checkPUTStatusCode(String requestUrl, String body, int statusCode, Headers headers) { - with().headers(headers).body(body).put(requestUrl).then().assertThat().statusCode(statusCode); - } - - /** - * Checks if the GET request for the - * resource gets a response with - * the given status code. - * - * @param request - * @param statusCode - */ - private void checkGETStatusCodeWithAwait(final String request, final Integer statusCode) { - await().atMost(FIVE_SECONDS).until(() -> String.valueOf(when().get(request).getStatusCode()), equalTo(String.valueOf(statusCode))); - } - - /** - * Checks if the GET request of the - * given resource returns the wished body. - * - * @param requestUrl - * @param body - */ - private void checkGETBodyWithAwait(final String requestUrl, final String body) { - await().atMost(TEN_SECONDS).until(() -> when().get(requestUrl).then().extract().body().asString(), equalTo(body)); - } - - private Response searchWithQueryParam(String searchParam, String queryParam, int expectedStatusCode ) { - return given() - .queryParam(searchParam, queryParam) - .when().get(searchUrlBase ) - .then().assertThat().statusCode(expectedStatusCode) - .extract().response(); - } - private void registerDefaultListener() { - delete(); - initRoutingRules(); - delete(searchUrlBase); - delete(defaultTargetListener); - TestUtils.registerListener(defaultRegisterUrlListener, defaultTargetListener, defaultMethodsListener, null, null, - null, null, "x-foo: (A|B)"); - } @Test public void testSearchListenerWithValidAndInvalidSearchParam(TestContext context) { Async async = context.async(); @@ -984,7 +898,7 @@ public void testSearchListenerWithValidAndInvalidSearchParam(TestContext context Response response = searchWithQueryParam("q",defaultListenerName,200); - Assert.assertTrue(response.getBody().asString().contains(defaultListenerName)); // Fails if not found + Assert.assertTrue(response.getBody().asString().contains(defaultListenerName)); TestUtils.unregisterListener(defaultRegisterUrlListener); async.complete(); @@ -1023,7 +937,6 @@ public void testHookHandleSearch_NoListenersRegistered(TestContext context) { // Parse response body as JSON JsonObject jsonResponse = new JsonObject(response.getBody().asString()); - // Assert that the response contains the expected query param Assert.assertTrue("Expected 'listeners' to be an empty array", jsonResponse.containsKey("listeners") && jsonResponse.getJsonArray("listeners").isEmpty()); async.complete(); @@ -1066,13 +979,13 @@ public void testHookHandleSearch_UseSlashInSearchUrlBase(TestContext context) { registerDefaultListener(); Response response = given() - .queryParam("q", defaultListenerName) - .when().get(searchUrlBase +"/") - .then().assertThat().statusCode(200) - .extract().response(); + .queryParam("q", defaultListenerName) + .when().get(searchUrlBase +"/") + .then().assertThat().statusCode(200) + .extract().response(); // Assert that the response contains the expected query param - Assert.assertTrue(response.getBody().asString().contains(defaultListenerName)); // Fails if not found + Assert.assertTrue(response.getBody().asString().contains(defaultListenerName)); TestUtils.unregisterListener(defaultRegisterUrlListener); @@ -1088,8 +1001,7 @@ public void testHookHandleSearch_EmptyQueryParameter(TestContext context) { Response response = searchWithQueryParam("q","",400); - // Assert that the response contains the expected query param - Assert.assertTrue(response.getBody().asString().contains("Bad Request")); // Fails if not found + Assert.assertTrue(response.getBody().asString().contains("Bad Request")); TestUtils.unregisterListener(defaultRegisterUrlListener); async.complete(); @@ -1185,4 +1097,89 @@ public void testSearchListener_IdenticalResultsWithAndWithoutQueryParam(TestCont TestUtils.unregisterListener(defaultRegisterUrlListener); async.complete(); } + + /** + * Checks if the DELETE request gets a response + * with the given status code. + * + * @param requestUrl + * @param statusCode + */ + private void checkDELETEStatusCode(String requestUrl, int statusCode) { + delete(requestUrl).then().assertThat().statusCode(statusCode); + } + + /** + * Checks if the DELETE request gets a response + * with the given status code. + * + * @param requestUrl + * @param statusCode + * @param headers + */ + private void checkDELETEStatusCode(String requestUrl, int statusCode, Headers headers) { + with().headers(headers).delete(requestUrl).then().assertThat().statusCode(statusCode); + } + + /** + * Checks if the PUT request gets a response + * with the given status code. + * + * @param requestUrl + * @param body + * @param statusCode + */ + private void checkPUTStatusCode(String requestUrl, String body, int statusCode) { + with().body(body).put(requestUrl).then().assertThat().statusCode(statusCode); + } + + /** + * Checks if the PUT request gets a response + * with the given status code. + * + * @param requestUrl + * @param body + * @param statusCode + * @param headers + */ + private void checkPUTStatusCode(String requestUrl, String body, int statusCode, Headers headers) { + with().headers(headers).body(body).put(requestUrl).then().assertThat().statusCode(statusCode); + } + + /** + * Checks if the GET request for the + * resource gets a response with + * the given status code. + * + * @param request + * @param statusCode + */ + private void checkGETStatusCodeWithAwait(final String request, final Integer statusCode) { + await().atMost(FIVE_SECONDS).until(() -> String.valueOf(when().get(request).getStatusCode()), equalTo(String.valueOf(statusCode))); + } + + /** + * Checks if the GET request of the + * given resource returns the wished body. + * + * @param requestUrl + * @param body + */ + private void checkGETBodyWithAwait(final String requestUrl, final String body) { + await().atMost(TEN_SECONDS).until(() -> when().get(requestUrl).then().extract().body().asString(), equalTo(body)); + } + + private Response searchWithQueryParam(String searchParam, String queryParam, int expectedStatusCode ) { + return given() + .queryParam(searchParam, queryParam) + .when().get(searchUrlBase ) + .then().assertThat().statusCode(expectedStatusCode) + .extract().response(); + } + private void registerDefaultListener() { + delete(); + initRoutingRules(); + TestUtils.registerListener(defaultRegisterUrlListener, defaultTargetListener, defaultMethodsListener, null, null, + null, null, "x-foo: (A|B)"); + } } 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 4ad868ba..16193b24 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 @@ -195,40 +195,6 @@ public void testListingWithoutStaticAndDynamicCollections(TestContext context) { async.complete(); } - private void removeRoute(String name) { - String route = requestUrlBase + "/" + name + TestUtils.getHookRouteUrlSuffix(); - TestUtils.unregisterRoute(route); - } - - private void addRoute(String name, boolean collection, boolean listable) { - String route = requestUrlBase + "/" + name + TestUtils.getHookRouteUrlSuffix(); - String target = targetUrlBase + "/" + name; - String[] methods = new String[]{"GET", "PUT", "DELETE", "POST"}; - - // just for security reasons (unregister route) - TestUtils.unregisterRoute(route); - - TestUtils.registerRoute(route, target, methods, null, collection, listable); - } - - private void assertResponse(final Response response, final String[] expectedArray) { - Assert.assertEquals(200, response.statusCode()); - String bodyString = response.getBody().asString(); - System.out.println("BODY => " + bodyString + " <="); - JsonObject body = new JsonObject(bodyString); - JsonArray array = body.getJsonArray(parentKey); - Assert.assertEquals(expectedArray.length, array.size()); - Assert.assertThat(array, Matchers.contains(expectedArray)); - } - - private Response searchWithQueryParam(String searchParam, String queryParam, int expectedStatusCode ) { - return given() - .queryParam(searchParam, queryParam) - .when().get(searchUrlBase ) - .then().assertThat().statusCode(expectedStatusCode) - .extract().response(); - } - @Test public void testHookHandleSearch_WithValidAndInvalidSearchParam(TestContext context) { Async async = context.async(); @@ -268,18 +234,16 @@ public void testHookHandleSearch_RouteNonMatchingQueryParam(TestContext context) addRoute(queryParam, true, true); assertResponse(get(requestUrlBase), new String[]{queryParam+"/"}); - // Send GET request with a non-matching query param Response response = searchWithQueryParam("q", queryParam, 200); - Assert.assertTrue("Query param should be found in response", response.getBody().asString().contains(queryParam)); - // Send GET request with a non-matching query param response = searchWithQueryParam("q", nonMatchingQueryParam, 200); - - // Assert the response does not contain the non-matching query param Assert.assertFalse("Non-matching query param should not be found in response", response.getBody().asString().contains(nonMatchingQueryParam)); + JsonObject jsonResponse = new JsonObject(response.getBody().asString()); + Assert.assertTrue("Expected 'routes' to be an empty array", + jsonResponse.containsKey("routes") && jsonResponse.getJsonArray("routes").isEmpty()); removeRoute(queryParam); async.complete(); @@ -339,4 +303,38 @@ public void testHookHandleSearch_ReturnsIdenticalResultsWithAndWithoutSearchQuer async.complete(); } + + private void removeRoute(String name) { + String route = requestUrlBase + "/" + name + TestUtils.getHookRouteUrlSuffix(); + TestUtils.unregisterRoute(route); + } + + private void addRoute(String name, boolean collection, boolean listable) { + String route = requestUrlBase + "/" + name + TestUtils.getHookRouteUrlSuffix(); + String target = targetUrlBase + "/" + name; + String[] methods = new String[]{"GET", "PUT", "DELETE", "POST"}; + + // just for security reasons (unregister route) + TestUtils.unregisterRoute(route); + + TestUtils.registerRoute(route, target, methods, null, collection, listable); + } + + private void assertResponse(final Response response, final String[] expectedArray) { + Assert.assertEquals(200, response.statusCode()); + String bodyString = response.getBody().asString(); + System.out.println("BODY => " + bodyString + " <="); + JsonObject body = new JsonObject(bodyString); + JsonArray array = body.getJsonArray(parentKey); + Assert.assertEquals(expectedArray.length, array.size()); + Assert.assertThat(array, Matchers.contains(expectedArray)); + } + + private Response searchWithQueryParam(String searchParam, String queryParam, int expectedStatusCode ) { + return given() + .queryParam(searchParam, queryParam) + .when().get(searchUrlBase ) + .then().assertThat().statusCode(expectedStatusCode) + .extract().response(); + } } From 12f4d783752cbfe4d256a8afbb3eb15f53dc3bcf Mon Sep 17 00:00:00 2001 From: almeidast Date: Thu, 14 Nov 2024 20:05:26 +0000 Subject: [PATCH 41/47] optimize testes creating reusable functions --- .../gateleen/hook/HookHandlerTest.java | 93 +++++++++++-------- .../gateleen/hook/RouteListingTest.java | 2 - 2 files changed, 54 insertions(+), 41 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 4a8dd393..6d9021fd 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 @@ -50,6 +50,8 @@ @RunWith(VertxUnitRunner.class) public class HookHandlerTest { private static final String HOOK_ROOT_URI = "hookRootURI/"; + private static final String HOOK_LISTENER_URI = "/"+ HOOK_ROOT_URI + "registrations/listeners"; + String HOOK_ROUTE_URI = "/"+ HOOK_ROOT_URI + "registrations/routes"; private static final Logger logger = LoggerFactory.getLogger(HookHandlerTest.class); private Vertx vertx; private HttpClient httpClient; @@ -63,7 +65,7 @@ public class HookHandlerTest { private HookHandler hookHandler; private RoutingContext routingContext; - + private HttpServerResponse mockResponse; @Before public void setUp() { @@ -77,6 +79,7 @@ public void setUp() { monitoringHandler = mock(MonitoringHandler.class); requestQueue = mock(RequestQueue.class); reducedPropagationManager = mock(ReducedPropagationManager.class); + mockResponse = mock(HttpServerResponse.class); hookHandler = new HookHandler(vertx, httpClient, storage, loggingResourceManager, logAppenderRepository, monitoringHandler, "userProfilePath", HOOK_ROOT_URI, requestQueue, false, reducedPropagationManager); hookHandler.init(); @@ -664,9 +667,7 @@ public void hookRegistration_RouteWithRouteMultiplier(TestContext testContext) t @Test public void testHandleGETRequestWithEmptyParam(TestContext testContext) { // Define URI and configures the request with an empty 'q' parameter - String uri = "/hookRootURI/registrations/listeners"; - HttpServerResponse mockResponse = mock(HttpServerResponse.class); - GETRequest request = new GETRequest(uri, mockResponse); + GETRequest request = new GETRequest(HOOK_LISTENER_URI, mockResponse); request.addParameter("q", ""); // Empty parameter to simulate bad request // Mock RoutingContext @@ -690,14 +691,33 @@ public void testHandleGETRequestWithEmptyParam(TestContext testContext) { testContext.assertTrue(jsonResponse.contains("Bad Request")); } + @Test + public void testHandleGETRequestWithNoParam(TestContext testContext) { + GETRequest request = new GETRequest(HOOK_LISTENER_URI, mockResponse); + when(routingContext.request()).thenReturn(request); + + boolean result = hookHandler.handle(routingContext); + + testContext.assertFalse(result); + } + + @Test + public void testHandleGETRequestWithWrongRoute(TestContext testContext) { + String wrongUri = "/hookRootURI/registrati/listeners"; + GETRequest request = new GETRequest(wrongUri, mockResponse); + request.addParameter("q", "value"); + when(routingContext.request()).thenReturn(request); + + boolean result = hookHandler.handle(routingContext); + + testContext.assertFalse(result); + } @Test public void testHandleGETRequestWithListenersSearchSingleResult() throws InterruptedException { // Define URI and configure GET request with specific search parameter String singleListener= "mySingleListener"; - String uri = "/hookRootURI/registrations/listeners"; - HttpServerResponse mockResponse = mock(HttpServerResponse.class); - GETRequest request = new GETRequest(uri, mockResponse); + GETRequest request = new GETRequest(HOOK_LISTENER_URI, mockResponse); request.addParameter("q", singleListener); @@ -724,9 +744,7 @@ public void testHandleGETRequestWithListenersSearchSingleResult() throws Interru @Test public void testHandleGETRequestWithListenersSearchMultipleResults() throws InterruptedException { // Define the URI and set up the GET request with a broader search parameter for multiple listeners - String uri = "/hookRootURI/registrations/listeners"; - HttpServerResponse mockResponse = mock(HttpServerResponse.class); - GETRequest request = new GETRequest(uri, mockResponse); + GETRequest request = new GETRequest(HOOK_LISTENER_URI, mockResponse); request.addParameter("q", "myListener"); // Search parameter that should match multiple listeners // Add multiple listeners to the MockResourceStorage using the expected configuration and register them @@ -762,13 +780,11 @@ public void testHandleGETRequestWithListenersSearchMultipleResults() throws Inte @Test public void testHandleGETRequestWithRoutesSearchEmptyResult() { // Define URI and configure request with specific 'q' parameter for routes search - String uri = "/hookRootURI/registrations/routes"; - HttpServerResponse mockResponse = mock(HttpServerResponse.class); - GETRequest request = new GETRequest(uri, mockResponse); + GETRequest request = new GETRequest(HOOK_ROUTE_URI, mockResponse); request.addParameter("q", "routeNotFound"); // No routes are added to MockResourceStorage to simulate empty result - storage.putMockData("hookRootURI/registrations/routes", new JsonArray().encode()); + storage.putMockData(HOOK_ROUTE_URI, new JsonArray().encode()); // Mock RoutingContext and configure response capture when(routingContext.request()).thenReturn(request); @@ -784,16 +800,17 @@ public void testHandleGETRequestWithRoutesSearchEmptyResult() { assertTrue(result); // Verify response content with empty result - String jsonResponse = responseCaptor.getValue(); - assertNotNull(jsonResponse); - assertEquals("{\"routes\":[]}", jsonResponse); + String actualResponse = responseCaptor.getValue(); + assertNotNull(actualResponse); + JsonObject jsonResponse = new JsonObject(actualResponse); + assertTrue("Expected 'routes' to be an empty array", + jsonResponse.containsKey("routes") && jsonResponse.getJsonArray("routes").isEmpty()); } + @Test public void testHandleGETRequestWithRoutesSearchMultipleResults() throws InterruptedException { // Define the URI and set up the GET request with a broad search parameter for multiple routes - String uri = "/hookRootURI/registrations/routes"; - HttpServerResponse mockResponse = mock(HttpServerResponse.class); - GETRequest request = new GETRequest(uri, mockResponse); + GETRequest request = new GETRequest(HOOK_ROUTE_URI, mockResponse); request.addParameter("q", "valid"); // Search parameter that should match multiple routes // Add multiple routes to the MockResourceStorage using the expected configuration and register them @@ -828,9 +845,7 @@ public void testHandleGETRequestWithRoutesSearchMultipleResults() throws Interru @Test public void testHandleListenerWithStorageAndEmptyList() { // Set up the URI for listeners registration - String uri = "hookRootURI/registrations/listeners"; - HttpServerResponse response = mock(HttpServerResponse.class); - GETRequest request = new GETRequest(uri,response) ; + GETRequest request = new GETRequest(HOOK_LISTENER_URI,mockResponse) ; request.addParameter("q", "validQueryParam"); // Capture the response output @@ -841,20 +856,18 @@ public void testHandleListenerWithStorageAndEmptyList() { boolean result = hookHandler.handle(routingContext); assertTrue(result); - verify(response).end(responseCaptor.capture()); + verify(mockResponse).end(responseCaptor.capture()); // Validate the response JSON for an empty listener list String actualResponse = responseCaptor.getValue(); - assertNotNull(actualResponse); - assertEquals("{\"listeners\":[]}", actualResponse); // Response should contain an empty list + assertEmptyResult(actualResponse); + } @Test public void testHandleGETRequestWithExtraParam(TestContext testContext) { // Define URI and configure the request with an extra parameter besides 'q' - String uri = "/hookRootURI/registrations/listeners"; - HttpServerResponse mockResponse = mock(HttpServerResponse.class); - GETRequest request = new GETRequest(uri, mockResponse); + GETRequest request = new GETRequest(HOOK_LISTENER_URI, mockResponse); request.addParameter("q", "validQueryParam"); request.addParameter("extra", "notAllowedParam"); // Extra parameter, not allowed @@ -883,9 +896,7 @@ public void testHandleGETRequestWithExtraParam(TestContext testContext) { @Test public void testHandleGETRequestWithTrailingSlash(TestContext testContext) { // Define URI with trailing slash and configure the request - String uri = "/hookRootURI/registrations/listeners/"; - HttpServerResponse mockResponse = mock(HttpServerResponse.class); - GETRequest request = new GETRequest(uri, mockResponse); + GETRequest request = new GETRequest(HOOK_LISTENER_URI, mockResponse); request.addParameter("q", "validQueryParam"); // Mock the RoutingContext @@ -901,16 +912,13 @@ public void testHandleGETRequestWithTrailingSlash(TestContext testContext) { // Verify the result contains an empty listeners list testContext.assertTrue(result); String jsonResponse = responseCaptor.getValue(); - testContext.assertNotNull(jsonResponse); - testContext.assertEquals("{\"listeners\":[]}", jsonResponse); + assertEmptyResult(jsonResponse); } @Test public void testHandleGETRequestWithInvalidParam(TestContext testContext) { // Define URI with an invalid parameter different from 'q' - String uri = "/hookRootURI/registrations/listeners"; - HttpServerResponse mockResponse = mock(HttpServerResponse.class); - GETRequest request = new GETRequest(uri, mockResponse); + GETRequest request = new GETRequest(HOOK_LISTENER_URI, mockResponse); request.addParameter("invalidParam", "someValue"); // Invalid parameter, not 'q' // Mock the RoutingContext @@ -1172,6 +1180,13 @@ private Buffer toBuffer(JsonObject jsonObject) { return buffer; } + private static void assertEmptyResult(String actualResponse) { + assertNotNull(actualResponse); + JsonObject jsonResponse = new JsonObject(actualResponse); + assertTrue("Expected 'listeners' to be an empty array", + jsonResponse.containsKey("listeners") && jsonResponse.getJsonArray("listeners").isEmpty()); + } + static class PUTRequest extends DummyHttpServerRequest { MultiMap headers = MultiMap.caseInsensitiveMultiMap(); @@ -1212,8 +1227,8 @@ public void addHeader(String headerName, String headerValue) { static class GETRequest extends DummyHttpServerRequest { MultiMap headers = MultiMap.caseInsensitiveMultiMap(); MultiMap params = MultiMap.caseInsensitiveMultiMap(); - private HttpServerResponse response; - private String uri; + private final HttpServerResponse response; + private final String uri; public GETRequest(String uri, HttpServerResponse response) { this.uri = uri; 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 16193b24..a61332da 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 @@ -239,8 +239,6 @@ public void testHookHandleSearch_RouteNonMatchingQueryParam(TestContext context) response.getBody().asString().contains(queryParam)); response = searchWithQueryParam("q", nonMatchingQueryParam, 200); - Assert.assertFalse("Non-matching query param should not be found in response", - response.getBody().asString().contains(nonMatchingQueryParam)); JsonObject jsonResponse = new JsonObject(response.getBody().asString()); Assert.assertTrue("Expected 'routes' to be an empty array", jsonResponse.containsKey("routes") && jsonResponse.getJsonArray("routes").isEmpty()); From d7272ec3f08527a4fd6bf8bf85ad8d6b1349554a Mon Sep 17 00:00:00 2001 From: almeidast Date: Thu, 14 Nov 2024 20:57:13 +0000 Subject: [PATCH 42/47] Fix testHandleGETRequestWithTrailingSlash --- .../test/java/org/swisspush/gateleen/hook/HookHandlerTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 6d9021fd..a0c7a63d 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 @@ -896,7 +896,7 @@ public void testHandleGETRequestWithExtraParam(TestContext testContext) { @Test public void testHandleGETRequestWithTrailingSlash(TestContext testContext) { // Define URI with trailing slash and configure the request - GETRequest request = new GETRequest(HOOK_LISTENER_URI, mockResponse); + GETRequest request = new GETRequest(HOOK_LISTENER_URI+"/", mockResponse); request.addParameter("q", "validQueryParam"); // Mock the RoutingContext From 8c47a2a224586352fdea5e11106ed643b05418ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Weber?= Date: Mon, 18 Nov 2024 08:37:47 +0100 Subject: [PATCH 43/47] #311 some cleanup --- .../swisspush/gateleen/hook/HookHandler.java | 30 +++++++------------ .../org/swisspush/gateleen/hook/Route.java | 13 ++++---- .../gateleen/hook/HookHandlerTest.java | 11 +++---- .../swisspush/gateleen/hook/ListenerTest.java | 4 +-- .../gateleen/hook/RouteListingTest.java | 6 ++-- 5 files changed, 26 insertions(+), 38 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 62be31e9..6999ed58 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 @@ -10,12 +10,7 @@ import io.vertx.core.Vertx; import io.vertx.core.buffer.Buffer; import io.vertx.core.eventbus.Message; -import io.vertx.core.http.HttpClient; -import io.vertx.core.http.HttpClientRequest; -import io.vertx.core.http.HttpClientResponse; -import io.vertx.core.http.HttpMethod; -import io.vertx.core.http.HttpServerRequest; -import io.vertx.core.http.HttpServerResponse; +import io.vertx.core.http.*; import io.vertx.core.http.impl.headers.HeadersMultiMap; import io.vertx.core.json.DecodeException; import io.vertx.core.json.JsonArray; @@ -120,8 +115,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 CONTENT_TYPE_HEADER = "content-type"; - private final Comparator collectionContentComparator; private static final Logger log = LoggerFactory.getLogger(HookHandler.class); @@ -544,7 +537,7 @@ public void registerListenerRegistrationHandler(Handler readyHandler) { public boolean handle(final RoutingContext ctx) { HttpServerRequest request = ctx.request(); boolean consumed = false; - var requestUri = request.uri().replaceAll("/+$", ""); + var requestUri = request.uri(); /* * 1) Un- / Register Listener / Routes */ @@ -614,7 +607,7 @@ private void handleListenerSearch(HttpServerRequest request) { private void handleRouteSearch(HttpServerRequest request) { handleSearch( - routeRepository.getRoutes().entrySet().stream().collect(Collectors.toMap(entry -> entry.getValue().getHookIdentify(), Map.Entry::getValue)), + routeRepository.getRoutes().entrySet().stream().collect(Collectors.toMap(entry -> entry.getValue().getHookDisplayText(), Map.Entry::getValue)), route -> route.getHook().getDestination(), ROUTES_KEY, request @@ -634,8 +627,9 @@ private void handleRouteSearch(HttpServerRequest request) { private void handleSearch(Map repository, Function getDestination, String resultKey, HttpServerRequest request) { String queryParam = request.getParam("q"); if (request.params().size() > 1 || StringUtils.isEmpty(queryParam)) { - request.response().setStatusCode(StatusCode.BAD_REQUEST.getStatusCode()) - .end("Bad Request: Only the 'q' parameter is allowed and can't be empty or null"); + request.response().setStatusCode(StatusCode.BAD_REQUEST.getStatusCode()); + request.response().setStatusMessage(StatusCode.BAD_REQUEST.getStatusMessage()); + request.response().end("Only the 'q' parameter is allowed and can't be empty or null"); return ; } @@ -652,12 +646,10 @@ private void handleSearch(Map repository, Function get String encodedResult = result.encode(); - request.response().putHeader(CONTENT_TYPE_HEADER, CONTENT_TYPE_JSON); + request.response().putHeader(HttpHeaders.CONTENT_TYPE, CONTENT_TYPE_JSON); request.response().end(encodedResult); } - - /** * Create a listing of routes in the given parent. This happens * only if we have a GET request, the routes are listable and @@ -1669,13 +1661,12 @@ private void registerRoute(Buffer buffer) { boolean mustCreateNewRoute = true; - Route existingRoute = routeRepository.getRoutes().get(routedUrl); if (existingRoute != null) { mustCreateNewRoute = mustCreateNewRouteForHook(existingRoute, hook); } if (mustCreateNewRoute) { - routeRepository.addRoute(routedUrl, createRoute(routedUrl , hook, requestUrl)); + routeRepository.addRoute(routedUrl, createRoute(routedUrl, hook, requestUrl)); } else { // see comment in #mustCreateNewRouteForHook() existingRoute.getRule().setHeaderFunction(hook.getHeaderFunction()); @@ -1726,11 +1717,12 @@ private boolean headersFilterPatternEquals(Pattern headersFilterPatternLeft, Pat * * @param urlPattern urlPattern * @param hook hook + * @param hookDisplayText text used for display only like in API * @return Route */ - private Route createRoute(String urlPattern, HttpHook hook, String hookIdentify) { + private Route createRoute(String urlPattern, HttpHook hook, String hookDisplayText) { return new Route(vertx, userProfileStorage, loggingResourceManager, logAppenderRepository, monitoringHandler, - userProfilePath, hook, urlPattern, selfClient, hookIdentify); + userProfilePath, hook, urlPattern, selfClient, hookDisplayText); } /** diff --git a/gateleen-hook/src/main/java/org/swisspush/gateleen/hook/Route.java b/gateleen-hook/src/main/java/org/swisspush/gateleen/hook/Route.java index 6c61308a..2aba542c 100755 --- a/gateleen-hook/src/main/java/org/swisspush/gateleen/hook/Route.java +++ b/gateleen-hook/src/main/java/org/swisspush/gateleen/hook/Route.java @@ -49,7 +49,7 @@ public class Route { private MonitoringHandler monitoringHandler; private String userProfilePath; private ResourceStorage storage; - private String hookIdentify; + private String hookDisplayText; private String urlPattern; private HttpHook httpHook; @@ -79,8 +79,9 @@ public class Route { * @param httpHook httpHook * @param urlPattern - this can be a listener or a normal urlPattern (eg. for a route) */ - public Route(Vertx vertx, ResourceStorage storage, LoggingResourceManager loggingResourceManager, LogAppenderRepository logAppenderRepository, - MonitoringHandler monitoringHandler, String userProfilePath, HttpHook httpHook, String urlPattern, HttpClient selfClient, String hookIdentify) { + public Route(Vertx vertx, ResourceStorage storage, LoggingResourceManager loggingResourceManager, + LogAppenderRepository logAppenderRepository, MonitoringHandler monitoringHandler, String userProfilePath, + HttpHook httpHook, String urlPattern, HttpClient selfClient, String hookDisplayText) { this.vertx = vertx; this.storage = storage; this.loggingResourceManager = loggingResourceManager; @@ -90,7 +91,7 @@ public Route(Vertx vertx, ResourceStorage storage, LoggingResourceManager loggin this.httpHook = httpHook; this.urlPattern = urlPattern; this.selfClient = selfClient; - this.hookIdentify = hookIdentify; + this.hookDisplayText = hookDisplayText; createRule(); @@ -276,7 +277,7 @@ public HttpHook getHook() { return httpHook; } - public String getHookIdentify() { - return hookIdentify; + public String getHookDisplayText() { + return hookDisplayText; } } 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 a0c7a63d..2d148c3f 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 @@ -688,7 +688,7 @@ public void testHandleGETRequestWithEmptyParam(TestContext testContext) { String jsonResponse = responseCaptor.getValue(); testContext.assertNotNull(jsonResponse); // Confirm the response contains "Bad Request" - testContext.assertTrue(jsonResponse.contains("Bad Request")); + testContext.assertTrue(jsonResponse.contains("Only the 'q' parameter is allowed and can't be empty or null")); } @Test @@ -720,7 +720,6 @@ public void testHandleGETRequestWithListenersSearchSingleResult() throws Interru GETRequest request = new GETRequest(HOOK_LISTENER_URI, mockResponse); request.addParameter("q", singleListener); - setListenerStorageEntryAndTriggerUpdate(buildListenerConfigWithHeadersFilter(null, singleListener, "x-foo: (A|B)")); // wait a moment to let the listener be registered Thread.sleep(200); @@ -776,7 +775,6 @@ public void testHandleGETRequestWithListenersSearchMultipleResults() throws Inte assertFalse(jsonResponse.contains(notMatchListener)); } - @Test public void testHandleGETRequestWithRoutesSearchEmptyResult() { // Define URI and configure request with specific 'q' parameter for routes search @@ -861,7 +859,6 @@ public void testHandleListenerWithStorageAndEmptyList() { // Validate the response JSON for an empty listener list String actualResponse = responseCaptor.getValue(); assertEmptyResult(actualResponse); - } @Test @@ -890,13 +887,13 @@ public void testHandleGETRequestWithExtraParam(TestContext testContext) { String jsonResponse = responseCaptor.getValue(); testContext.assertNotNull(jsonResponse); // Confirm that the response contains "Bad Request" - testContext.assertTrue(jsonResponse.contains("Bad Request")); + testContext.assertTrue(jsonResponse.contains("Only the 'q' parameter is allowed and can't be empty or null")); } @Test public void testHandleGETRequestWithTrailingSlash(TestContext testContext) { // Define URI with trailing slash and configure the request - GETRequest request = new GETRequest(HOOK_LISTENER_URI+"/", mockResponse); + GETRequest request = new GETRequest(HOOK_LISTENER_URI + "/", mockResponse); request.addParameter("q", "validQueryParam"); // Mock the RoutingContext @@ -940,7 +937,7 @@ public void testHandleGETRequestWithInvalidParam(TestContext testContext) { String jsonResponse = responseCaptor.getValue(); testContext.assertNotNull(jsonResponse); // Confirm that the response contains "Bad Request" - testContext.assertTrue(jsonResponse.contains("Bad Request")); + testContext.assertTrue(jsonResponse.contains("Only the 'q' parameter is allowed and can't be empty or null")); } 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 5677403b..116a5afa 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 @@ -68,8 +68,6 @@ public void initRestAssured() { defaultRegisterUrlListener = requestUrlBase + "/" + defaultQueryParam + TestUtils.getHookListenersUrlSuffix() + defaultListenerName; defaultTargetListener = targetUrlBase + "/" + defaultListenerName; defaultMethodsListener = new String[]{"PUT", "DELETE", "POST"}; - - } /** @@ -1172,7 +1170,7 @@ private void checkGETBodyWithAwait(final String requestUrl, final String body) { private Response searchWithQueryParam(String searchParam, String queryParam, int expectedStatusCode ) { return given() .queryParam(searchParam, queryParam) - .when().get(searchUrlBase ) + .when().get(searchUrlBase) .then().assertThat().statusCode(expectedStatusCode) .extract().response(); } 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 a61332da..48256f05 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 @@ -328,10 +328,10 @@ private void assertResponse(final Response response, final String[] expectedArra Assert.assertThat(array, Matchers.contains(expectedArray)); } - private Response searchWithQueryParam(String searchParam, String queryParam, int expectedStatusCode ) { + private Response searchWithQueryParam(String queryParamName, String queryParamValue, int expectedStatusCode ) { return given() - .queryParam(searchParam, queryParam) - .when().get(searchUrlBase ) + .queryParam(queryParamName, queryParamValue) + .when().get(searchUrlBase) .then().assertThat().statusCode(expectedStatusCode) .extract().response(); } From b88a5f648f3adf4f5451a32833bd0cc4e5fb5d14 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Weber?= Date: Mon, 18 Nov 2024 09:02:05 +0100 Subject: [PATCH 44/47] #311 some cleanup --- .../src/test/java/org/swisspush/gateleen/hook/ListenerTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 116a5afa..081483a2 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 @@ -999,7 +999,7 @@ public void testHookHandleSearch_EmptyQueryParameter(TestContext context) { Response response = searchWithQueryParam("q","",400); - Assert.assertTrue(response.getBody().asString().contains("Bad Request")); + Assert.assertTrue(response.getBody().asString().contains("Only the 'q' parameter is allowed and can't be empty or null")); TestUtils.unregisterListener(defaultRegisterUrlListener); async.complete(); From ec4bcc6f19e21bc997519f709abf76e83e8d38e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Weber?= Date: Wed, 4 Dec 2024 16:29:23 +0100 Subject: [PATCH 45/47] #615 Made MonitoringHandler optional and added the same metrics with micrometer --- gateleen-expansion/README_expansion.md | 26 ++- gateleen-expansion/pom.xml | 4 + .../gateleen/expansion/ExpansionHandler.java | 41 ++++- .../expansion/ExpansionHandlerTest.java | 1 - gateleen-hook/README_hook.md | 22 ++- gateleen-hook/pom.xml | 4 + .../swisspush/gateleen/hook/HookHandler.java | 74 ++++++-- .../org/swisspush/gateleen/hook/Route.java | 10 +- .../gateleen/queue/queuing/QueueClient.java | 18 +- .../queue/queuing/QueueProcessor.java | 12 +- .../queue/queuing/QueuingHandler.java | 6 +- .../queue/queuing/QueueClientTest.java | 25 +++ gateleen-routing/README_routing.md | 29 +++- gateleen-routing/pom.xml | 4 + .../gateleen/routing/AbstractForwarder.java | 24 ++- .../swisspush/gateleen/routing/Forwarder.java | 71 ++++++-- .../gateleen/routing/NullForwarder.java | 33 +++- .../swisspush/gateleen/routing/Router.java | 10 +- .../gateleen/routing/RouterBuilder.java | 9 + .../gateleen/routing/StorageForwarder.java | 160 +++++++++++------- .../gateleen/routing/RouterTest.java | 59 ++++++- .../gateleen/scheduler/Scheduler.java | 29 ++-- .../gateleen/scheduler/SchedulerFactory.java | 3 +- .../scheduler/SchedulerResourceManager.java | 9 +- pom.xml | 6 + 25 files changed, 550 insertions(+), 139 deletions(-) diff --git a/gateleen-expansion/README_expansion.md b/gateleen-expansion/README_expansion.md index 2afb4053..b057390c 100644 --- a/gateleen-expansion/README_expansion.md +++ b/gateleen-expansion/README_expansion.md @@ -120,4 +120,28 @@ For more information about the StorageExpand feature see the [vertx-rest-storage This allows you to create one octet-stream containing each json resource in the given collection (see expand feature). Basically it works exactly the same way as the default expand feature works, except that it does not set an eTag for the request. -> Attention: No eTag header is created / returned when this feature is used! \ No newline at end of file +> Attention: No eTag header is created / returned when this feature is used! + +### Micrometer metrics +The expansion feature is monitored with micrometer. The following metrics are available: +* gateleen_expand_requests_total +* gateleen_storage_expand_requests_total + +For `expand_requests_total` additional tags are provided to specify the expand level. + +Example metrics: + +``` +# HELP gateleen_expand_requests_total +# TYPE gateleen_expand_requests_total counter +gateleen_expand_requests_total{level="1",} 23677.0 +gateleen_expand_requests_total{level="2",} 2350.0 +gateleen_expand_requests_total{level="3",} 77.0 +gateleen_expand_requests_total{level="4",} 0.0 +gateleen_expand_requests_total{level="0",} 0.0 +# HELP gateleen_storage_expand_requests_total +# TYPE gateleen_storage_expand_requests_total counter +gateleen_storage_expand_requests_total 37.0 +``` + +To enable the metrics, set a `MeterRegistry` instance by calling `setMeterRegistry(MeterRegistry meterRegistry)` method in `ExpansionHandler` class. \ No newline at end of file diff --git a/gateleen-expansion/pom.xml b/gateleen-expansion/pom.xml index 80528935..565ac09d 100644 --- a/gateleen-expansion/pom.xml +++ b/gateleen-expansion/pom.xml @@ -22,6 +22,10 @@ org.swisspush rest-storage + + io.micrometer + micrometer-core + diff --git a/gateleen-expansion/src/main/java/org/swisspush/gateleen/expansion/ExpansionHandler.java b/gateleen-expansion/src/main/java/org/swisspush/gateleen/expansion/ExpansionHandler.java index 59f309d4..09408a85 100755 --- a/gateleen-expansion/src/main/java/org/swisspush/gateleen/expansion/ExpansionHandler.java +++ b/gateleen-expansion/src/main/java/org/swisspush/gateleen/expansion/ExpansionHandler.java @@ -1,5 +1,7 @@ package org.swisspush.gateleen.expansion; +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.MeterRegistry; import io.vertx.core.AsyncResult; import io.vertx.core.Handler; import io.vertx.core.MultiMap; @@ -27,10 +29,7 @@ import org.swisspush.gateleen.routing.RuleFeaturesProvider; import org.swisspush.gateleen.routing.RuleProvider; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import java.util.concurrent.atomic.AtomicInteger; import static org.swisspush.gateleen.core.util.StatusCode.INTERNAL_SERVER_ERROR; @@ -89,6 +88,10 @@ public class ExpansionHandler implements RuleChangesObserver { private static final int DECREMENT_BY_ONE = 1; private static final int MAX_RECURSION_LEVEL = 0; + private static final String EXPAND_REQUEST_METRIC = "gateleen.expand.requests"; + private static final String STORAGE_EXPAND_REQUEST_METRIC = "gateleen.storage.expand.requests"; + private static final String LEVEL = "level"; + public static final String MAX_EXPANSION_LEVEL_SOFT_PROPERTY = "max.expansion.level.soft"; public static final String MAX_EXPANSION_LEVEL_HARD_PROPERTY = "max.expansion.level.hard"; public static final String MAX_SUBREQUEST_PROPERTY = "max.expansion.subrequests"; @@ -123,6 +126,9 @@ public class ExpansionHandler implements RuleChangesObserver { private RuleFeaturesProvider ruleFeaturesProvider = new RuleFeaturesProvider(new ArrayList<>()); + private final Map counterMap = new HashMap<>(); + private Counter storageExpandCounter; + /** * Creates a new instance of the ExpansionHandler. * @@ -176,6 +182,26 @@ public int getMaxSubRequestCount() { return maxSubRequestCount; } + public void setMeterRegistry(MeterRegistry meterRegistry) { + counterMap.clear(); + if(meterRegistry != null) { + counterMap.put(0, Counter.builder(EXPAND_REQUEST_METRIC).tag(LEVEL, "0").register(meterRegistry)); + counterMap.put(1, Counter.builder(EXPAND_REQUEST_METRIC).tag(LEVEL, "1").register(meterRegistry)); + counterMap.put(2, Counter.builder(EXPAND_REQUEST_METRIC).tag(LEVEL, "2").register(meterRegistry)); + counterMap.put(3, Counter.builder(EXPAND_REQUEST_METRIC).tag(LEVEL, "3").register(meterRegistry)); + counterMap.put(4, Counter.builder(EXPAND_REQUEST_METRIC).tag(LEVEL, "4").register(meterRegistry)); + + storageExpandCounter = Counter.builder(STORAGE_EXPAND_REQUEST_METRIC).register(meterRegistry); + } + } + + private void incrementExpandReqCount(int level) { + Counter counter = counterMap.get(level); + if(counter != null) { + counter.increment(); + } + } + /** * Initialize the lists which defines, when which parameter * is removed (if any). @@ -363,6 +389,8 @@ private void handleExpansionRequest(final HttpServerRequest req, final Recursive log.debug("constructed uri for request: {}", targetUri); Integer finalExpandLevel = expandLevel; + incrementExpandReqCount(finalExpandLevel); + httpClient.request(HttpMethod.GET, targetUri).onComplete(asyncReqResult -> { if (asyncReqResult.failed()) { log.warn("Failed request to {}: {}", targetUri, asyncReqResult.cause()); @@ -505,6 +533,11 @@ private void makeStorageExpandRequest(final String targetUri, final List subReso Logger log = RequestLoggerFactory.getLogger(ExpansionHandler.class, req); HttpMethod reqMethod = HttpMethod.POST; String reqUri = targetUri + "?storageExpand=true"; + + if(storageExpandCounter != null) { + storageExpandCounter.increment(); + } + httpClient.request(reqMethod, reqUri).onComplete(asyncResult -> { if (asyncResult.failed()) { log.warn("Failed request to {}", reqUri, asyncResult.cause()); diff --git a/gateleen-expansion/src/test/java/org/swisspush/gateleen/expansion/ExpansionHandlerTest.java b/gateleen-expansion/src/test/java/org/swisspush/gateleen/expansion/ExpansionHandlerTest.java index daef5a28..bb365d1e 100644 --- a/gateleen-expansion/src/test/java/org/swisspush/gateleen/expansion/ExpansionHandlerTest.java +++ b/gateleen-expansion/src/test/java/org/swisspush/gateleen/expansion/ExpansionHandlerTest.java @@ -46,7 +46,6 @@ public class ExpansionHandlerTest { public void setUp() { vertx = Vertx.vertx(); httpClient = Mockito.mock(HttpClient.class); - // Mockito.when(httpClient.request(any(HttpMethod.class), anyString(), Matchers.>any())).thenReturn(Mockito.mock(HttpClientRequest.class)); storage = new MockResourceStorage(); } diff --git a/gateleen-hook/README_hook.md b/gateleen-hook/README_hook.md index 62a863ed..9ecc6849 100644 --- a/gateleen-hook/README_hook.md +++ b/gateleen-hook/README_hook.md @@ -163,7 +163,7 @@ PUT http://myserver:7012/gateleen/everything/_hooks/listeners/http/myexample "destination": "/gateleen/example/thePosition", "filter": "/gateleen/everything/.*/position.*", "headers": [ - { "header":"X-Expire-After", "value":"3600", mode:"complete"} + { "header":"X-Expire-After", "value":"3600", "mode":"complete"} ], "headersFilter": "x-foo: (A|B)" } @@ -294,4 +294,22 @@ The response contains the matching routes, or an empty list if no match is found { "routes": [] } -``` \ No newline at end of file +``` + +## Micrometer metrics +The hook feature is monitored with micrometer. The following metrics are available: +* gateleen_listener_count +* gateleen_routes_count + +Example metrics: + +``` +# HELP gateleen_listener_count Amount of listener hooks currently registered +# TYPE gateleen_listener_count gauge +gateleen_listener_count 577.0 +# HELP gateleen_routes_count Amount of route hooks currently registered +# TYPE gateleen_routes_count gauge +gateleen_routes_count 15.0 +``` + +To enable the metrics, set a `MeterRegistry` instance by calling `setMeterRegistry(MeterRegistry meterRegistry)` method in `HookHandler` class. \ No newline at end of file diff --git a/gateleen-hook/pom.xml b/gateleen-hook/pom.xml index 3e75cb6b..5931d2bc 100644 --- a/gateleen-hook/pom.xml +++ b/gateleen-hook/pom.xml @@ -17,6 +17,10 @@ gateleen-queue ${project.version} + + io.micrometer + micrometer-core + 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 6999ed58..c0f42fff 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 @@ -5,6 +5,8 @@ import com.networknt.schema.JsonSchema; import com.networknt.schema.JsonSchemaFactory; import com.networknt.schema.ValidationMessage; +import io.micrometer.core.instrument.Gauge; +import io.micrometer.core.instrument.MeterRegistry; import io.vertx.core.AsyncResult; import io.vertx.core.Handler; import io.vertx.core.Vertx; @@ -59,6 +61,7 @@ import java.util.Optional; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; import java.util.function.Consumer; import java.util.function.Function; import java.util.regex.Pattern; @@ -150,6 +153,10 @@ public class HookHandler implements LoggableResource { private final String normalizedRouteBase; private final String normalizedListenerBase; + private final AtomicLong listenerCount = new AtomicLong(0); + private final AtomicLong routesCount = new AtomicLong(0); + private MeterRegistry meterRegistry; + /** * Creates a new HookHandler. * @@ -162,7 +169,7 @@ public class HookHandler implements LoggableResource { * @param hookRootUri hookRootUri */ public HookHandler(Vertx vertx, HttpClient selfClient, final ResourceStorage storage, - LoggingResourceManager loggingResourceManager, LogAppenderRepository logAppenderRepository, MonitoringHandler monitoringHandler, + LoggingResourceManager loggingResourceManager, LogAppenderRepository logAppenderRepository, @Nullable MonitoringHandler monitoringHandler, String userProfilePath, String hookRootUri) { this(vertx, selfClient, storage, loggingResourceManager, logAppenderRepository, monitoringHandler, userProfilePath, hookRootUri, new QueueClient(vertx, monitoringHandler)); @@ -182,14 +189,14 @@ public HookHandler(Vertx vertx, HttpClient selfClient, final ResourceStorage sto * @param requestQueue requestQueue */ public HookHandler(Vertx vertx, HttpClient selfClient, final ResourceStorage storage, - LoggingResourceManager loggingResourceManager, LogAppenderRepository logAppenderRepository, MonitoringHandler monitoringHandler, + LoggingResourceManager loggingResourceManager, LogAppenderRepository logAppenderRepository, @Nullable MonitoringHandler monitoringHandler, String userProfilePath, String hookRootUri, RequestQueue requestQueue) { this(vertx, selfClient, storage, loggingResourceManager, logAppenderRepository, monitoringHandler, userProfilePath, hookRootUri, requestQueue, false); } public HookHandler(Vertx vertx, HttpClient selfClient, final ResourceStorage storage, - LoggingResourceManager loggingResourceManager, LogAppenderRepository logAppenderRepository, MonitoringHandler monitoringHandler, + LoggingResourceManager loggingResourceManager, LogAppenderRepository logAppenderRepository, @Nullable MonitoringHandler monitoringHandler, String userProfilePath, String hookRootUri, RequestQueue requestQueue, boolean listableRoutes) { this(vertx, selfClient, storage, loggingResourceManager, logAppenderRepository, monitoringHandler, userProfilePath, hookRootUri, requestQueue, false, null); @@ -210,7 +217,7 @@ public HookHandler(Vertx vertx, HttpClient selfClient, final ResourceStorage sto * @param reducedPropagationManager reducedPropagationManager */ public HookHandler(Vertx vertx, HttpClient selfClient, final ResourceStorage storage, - LoggingResourceManager loggingResourceManager, LogAppenderRepository logAppenderRepository, MonitoringHandler monitoringHandler, + LoggingResourceManager loggingResourceManager, LogAppenderRepository logAppenderRepository, @Nullable MonitoringHandler monitoringHandler, String userProfilePath, String hookRootUri, RequestQueue requestQueue, boolean listableRoutes, @Nullable ReducedPropagationManager reducedPropagationManager) { this(vertx, selfClient, storage, loggingResourceManager, logAppenderRepository, monitoringHandler, userProfilePath, hookRootUri, @@ -218,7 +225,7 @@ public HookHandler(Vertx vertx, HttpClient selfClient, final ResourceStorage sto } public HookHandler(Vertx vertx, HttpClient selfClient, final ResourceStorage userProfileStorage, - LoggingResourceManager loggingResourceManager, LogAppenderRepository logAppenderRepository, MonitoringHandler monitoringHandler, + LoggingResourceManager loggingResourceManager, LogAppenderRepository logAppenderRepository, @Nullable MonitoringHandler monitoringHandler, String userProfilePath, String hookRootUri, RequestQueue requestQueue, boolean listableRoutes, ReducedPropagationManager reducedPropagationManager, @Nullable Handler doneHandler, ResourceStorage hookStorage) { this(vertx, selfClient, userProfileStorage, loggingResourceManager, logAppenderRepository, monitoringHandler, userProfilePath, hookRootUri, @@ -226,7 +233,7 @@ public HookHandler(Vertx vertx, HttpClient selfClient, final ResourceStorage use } public HookHandler(Vertx vertx, HttpClient selfClient, final ResourceStorage userProfileStorage, - LoggingResourceManager loggingResourceManager, LogAppenderRepository logAppenderRepository, MonitoringHandler monitoringHandler, + LoggingResourceManager loggingResourceManager, LogAppenderRepository logAppenderRepository, @Nullable MonitoringHandler monitoringHandler, String userProfilePath, String hookRootUri, RequestQueue requestQueue, boolean listableRoutes, ReducedPropagationManager reducedPropagationManager, @Nullable Handler doneHandler, ResourceStorage hookStorage, int routeMultiplier) { @@ -256,7 +263,7 @@ public HookHandler(Vertx vertx, HttpClient selfClient, final ResourceStorage use * parallel operation. */ public HookHandler(Vertx vertx, HttpClient selfClient, final ResourceStorage userProfileStorage, - LoggingResourceManager loggingResourceManager, LogAppenderRepository logAppenderRepository, MonitoringHandler monitoringHandler, + LoggingResourceManager loggingResourceManager, LogAppenderRepository logAppenderRepository, @Nullable MonitoringHandler monitoringHandler, String userProfilePath, String hookRootUri, RequestQueue requestQueue, boolean listableRoutes, ReducedPropagationManager reducedPropagationManager, @Nullable Handler doneHandler, ResourceStorage hookStorage, int routeMultiplier, @Nonnull QueueSplitter queueSplitter) { @@ -317,6 +324,16 @@ public void handle(Void aVoid) { initMethods.forEach(handlerConsumer -> handlerConsumer.accept(readyHandler)); } + public void setMeterRegistry(MeterRegistry meterRegistry) { + this.meterRegistry = meterRegistry; + if(meterRegistry != null) { + Gauge.builder("gateleen.listener.count", listenerCount, AtomicLong::get) + .description("Amount of listener hooks currently registered").register(meterRegistry); + Gauge.builder("gateleen.routes.count", routesCount, AtomicLong::get) + .description("Amount of route hooks currently registered").register(meterRegistry); + } + } + @Override public void enableResourceLogging(boolean resourceLoggingEnabled) { this.logHookConfigurationResourceChanges = resourceLoggingEnabled; @@ -361,8 +378,15 @@ private void registerCleanupHandler(Handler readyHandler) { routeRepository.removeRoute(key); } } - monitoringHandler.updateListenerCount(listenerRepository.size()); - monitoringHandler.updateRoutesCount(routeRepository.getRoutes().size()); + if(meterRegistry != null) { + listenerCount.set(listenerRepository.size()); + routesCount.set(routeRepository.getRoutes().size()); + } + + if(monitoringHandler != null) { + monitoringHandler.updateListenerCount(listenerRepository.size()); + monitoringHandler.updateRoutesCount(routeRepository.getRoutes().size()); + } log.trace("done"); }); @@ -1352,7 +1376,12 @@ private void unregisterRoute(String requestUrl) { log.debug("Unregister route {}", routedUrl); routeRepository.removeRoute(routedUrl); - monitoringHandler.updateRoutesCount(routeRepository.getRoutes().size()); + if(meterRegistry != null) { + routesCount.set(routeRepository.getRoutes().size()); + } + if(monitoringHandler != null) { + monitoringHandler.updateRoutesCount(routeRepository.getRoutes().size()); + } } /** @@ -1367,7 +1396,12 @@ private void unregisterListener(String requestUrl) { routeRepository.removeRoute(hookRootUri + LISTENER_HOOK_TARGET_PATH + getListenerUrlSegment(requestUrl)); listenerRepository.removeListener(listenerId); - monitoringHandler.updateListenerCount(listenerRepository.size()); + if(meterRegistry != null) { + listenerCount.set(listenerRepository.size()); + } + if(monitoringHandler != null) { + monitoringHandler.updateListenerCount(listenerRepository.size()); + } } /** @@ -1491,7 +1525,12 @@ private void registerListener(Buffer buffer) { // create and add a new listener (or update an already existing listener) listenerRepository.addListener(new Listener(listenerId, getMonitoredUrlSegment(requestUrl), target, hook)); - monitoringHandler.updateListenerCount(listenerRepository.size()); + if(meterRegistry != null) { + listenerCount.set(listenerRepository.size()); + } + if(monitoringHandler != null) { + monitoringHandler.updateListenerCount(listenerRepository.size()); + } } /** @@ -1672,7 +1711,12 @@ private void registerRoute(Buffer buffer) { existingRoute.getRule().setHeaderFunction(hook.getHeaderFunction()); existingRoute.getHook().setExpirationTime(hook.getExpirationTime().orElse(null)); } - monitoringHandler.updateRoutesCount(routeRepository.getRoutes().size()); + if(meterRegistry != null) { + routesCount.set(routeRepository.getRoutes().size()); + } + if(monitoringHandler != null) { + monitoringHandler.updateRoutesCount(routeRepository.getRoutes().size()); + } } /** @@ -1721,8 +1765,10 @@ private boolean headersFilterPatternEquals(Pattern headersFilterPatternLeft, Pat * @return Route */ private Route createRoute(String urlPattern, HttpHook hook, String hookDisplayText) { - return new Route(vertx, userProfileStorage, loggingResourceManager, logAppenderRepository, monitoringHandler, + Route route = new Route(vertx, userProfileStorage, loggingResourceManager, logAppenderRepository, monitoringHandler, userProfilePath, hook, urlPattern, selfClient, hookDisplayText); + route.setMeterRegistry(meterRegistry); + return route; } /** diff --git a/gateleen-hook/src/main/java/org/swisspush/gateleen/hook/Route.java b/gateleen-hook/src/main/java/org/swisspush/gateleen/hook/Route.java index 2aba542c..d7068c2a 100755 --- a/gateleen-hook/src/main/java/org/swisspush/gateleen/hook/Route.java +++ b/gateleen-hook/src/main/java/org/swisspush/gateleen/hook/Route.java @@ -1,6 +1,8 @@ package org.swisspush.gateleen.hook; -import io.vertx.codegen.annotations.Nullable; +import javax.annotation.Nullable; + +import io.micrometer.core.instrument.MeterRegistry; import io.vertx.core.Handler; import io.vertx.core.Vertx; import io.vertx.core.buffer.Buffer; @@ -80,7 +82,7 @@ public class Route { * @param urlPattern - this can be a listener or a normal urlPattern (eg. for a route) */ public Route(Vertx vertx, ResourceStorage storage, LoggingResourceManager loggingResourceManager, - LogAppenderRepository logAppenderRepository, MonitoringHandler monitoringHandler, String userProfilePath, + LogAppenderRepository logAppenderRepository, @Nullable MonitoringHandler monitoringHandler, String userProfilePath, HttpHook httpHook, String urlPattern, HttpClient selfClient, String hookDisplayText) { this.vertx = vertx; this.storage = storage; @@ -100,6 +102,10 @@ public Route(Vertx vertx, ResourceStorage storage, LoggingResourceManager loggin createForwarder(); } + public void setMeterRegistry(MeterRegistry meterRegistry) { + forwarder.setMeterRegistry(meterRegistry); + } + /** * Creates the forwarder for this hook. */ diff --git a/gateleen-queue/src/main/java/org/swisspush/gateleen/queue/queuing/QueueClient.java b/gateleen-queue/src/main/java/org/swisspush/gateleen/queue/queuing/QueueClient.java index c85c9484..7251ffbd 100755 --- a/gateleen-queue/src/main/java/org/swisspush/gateleen/queue/queuing/QueueClient.java +++ b/gateleen-queue/src/main/java/org/swisspush/gateleen/queue/queuing/QueueClient.java @@ -13,6 +13,8 @@ import org.swisspush.gateleen.core.util.StatusCode; import org.swisspush.gateleen.monitoring.MonitoringHandler; +import javax.annotation.Nullable; + import static org.swisspush.redisques.util.RedisquesAPI.*; /** @@ -32,7 +34,7 @@ public class QueueClient implements RequestQueue { * @param vertx vertx * @param monitoringHandler monitoringHandler */ - public QueueClient(Vertx vertx, MonitoringHandler monitoringHandler) { + public QueueClient(Vertx vertx, @Nullable MonitoringHandler monitoringHandler) { this.vertx = vertx; this.monitoringHandler = monitoringHandler; } @@ -109,7 +111,7 @@ public void lockedEnqueue(HttpRequest queuedRequest, String queue, String lockRe vertx.eventBus().request(getRedisquesAddress(), buildLockedEnqueueOperation(queue, queuedRequest.toJsonObject().put(QUEUE_TIMESTAMP, System.currentTimeMillis()).encode(), lockRequestedBy), (Handler>>) event -> { - if (OK.equals(event.result().body().getString(STATUS))) { + if (OK.equals(event.result().body().getString(STATUS)) && monitoringHandler != null) { monitoringHandler.updateLastUsedQueueSizeInformation(queue); monitoringHandler.updateEnqueue(); } @@ -172,8 +174,10 @@ public Future enqueueFuture(HttpRequest queuedRequest, String queue) { queuedRequest.toJsonObject().put(QUEUE_TIMESTAMP, System.currentTimeMillis()).encode()), (Handler>>) event -> { if (OK.equals(event.result().body().getString(STATUS))) { - monitoringHandler.updateLastUsedQueueSizeInformation(queue); - monitoringHandler.updateEnqueue(); + if(monitoringHandler != null) { + monitoringHandler.updateLastUsedQueueSizeInformation(queue); + monitoringHandler.updateEnqueue(); + } promise.complete(); } else { promise.fail(event.result().body().getString(MESSAGE)); @@ -216,8 +220,10 @@ private void enqueue(final HttpServerRequest request, HttpRequest queuedRequest, vertx.eventBus().request(getRedisquesAddress(), buildEnqueueOperation(queue, queuedRequest.toJsonObject().put(QUEUE_TIMESTAMP, System.currentTimeMillis()).encode()), (Handler>>) event -> { if (OK.equals(event.result().body().getString(STATUS))) { - monitoringHandler.updateLastUsedQueueSizeInformation(queue); - monitoringHandler.updateEnqueue(); + if(monitoringHandler != null) { + monitoringHandler.updateLastUsedQueueSizeInformation(queue); + monitoringHandler.updateEnqueue(); + } if (request != null) { ResponseStatusCodeLogUtil.info(request, StatusCode.ACCEPTED, QueueClient.class); diff --git a/gateleen-queue/src/main/java/org/swisspush/gateleen/queue/queuing/QueueProcessor.java b/gateleen-queue/src/main/java/org/swisspush/gateleen/queue/queuing/QueueProcessor.java index 7e2a8214..8c68d077 100755 --- a/gateleen-queue/src/main/java/org/swisspush/gateleen/queue/queuing/QueueProcessor.java +++ b/gateleen-queue/src/main/java/org/swisspush/gateleen/queue/queuing/QueueProcessor.java @@ -25,6 +25,8 @@ import org.swisspush.gateleen.queue.queuing.circuitbreaker.util.QueueCircuitState; import org.swisspush.gateleen.queue.queuing.circuitbreaker.util.QueueResponseType; +import javax.annotation.Nullable; + import static io.vertx.core.Future.failedFuture; import static io.vertx.core.Future.succeededFuture; import static io.vertx.core.buffer.Buffer.buffer; @@ -49,18 +51,18 @@ public class QueueProcessor { private Logger log = LoggerFactory.getLogger(QueueProcessor.class); - public QueueProcessor(final Vertx vertx, final HttpClient httpClient, final MonitoringHandler monitoringHandler) { + public QueueProcessor(final Vertx vertx, final HttpClient httpClient, @Nullable final MonitoringHandler monitoringHandler) { this(vertx, httpClient, monitoringHandler, null); } - public QueueProcessor(final Vertx vertx, final HttpClient httpClient, final MonitoringHandler monitoringHandler, QueueCircuitBreaker queueCircuitBreaker) { + public QueueProcessor(final Vertx vertx, final HttpClient httpClient, @Nullable final MonitoringHandler monitoringHandler, QueueCircuitBreaker queueCircuitBreaker) { this(vertx, httpClient, monitoringHandler, queueCircuitBreaker, newGateleenThriftyExceptionFactory(), true); } public QueueProcessor( Vertx vertx, HttpClient httpClient, - MonitoringHandler monitoringHandler, + @Nullable MonitoringHandler monitoringHandler, QueueCircuitBreaker queueCircuitBreaker, GateleenExceptionFactory exceptionFactory, boolean immediatelyStartQueueProcessing @@ -275,7 +277,9 @@ private void executeQueuedRequest(Message message, Logger logger, Ht } message.reply(new JsonObject().put(STATUS, OK)); performCircuitBreakerActions(queueName, queuedRequest, SUCCESS, state); - monitoringHandler.updateDequeue(); + if(monitoringHandler != null) { + monitoringHandler.updateDequeue(); + } } else if (QueueRetryUtil.retryQueueItem(queuedRequest.getHeaders(), statusCode, logger)) { logger.info("Failed queued request to {}: {} {}", queuedRequest.getUri(), statusCode, response.statusMessage()); message.reply(new JsonObject().put(STATUS, ERROR).put(MESSAGE, statusCode + " " + response.statusMessage())); diff --git a/gateleen-queue/src/main/java/org/swisspush/gateleen/queue/queuing/QueuingHandler.java b/gateleen-queue/src/main/java/org/swisspush/gateleen/queue/queuing/QueuingHandler.java index e95c0fac..9b342f49 100755 --- a/gateleen-queue/src/main/java/org/swisspush/gateleen/queue/queuing/QueuingHandler.java +++ b/gateleen-queue/src/main/java/org/swisspush/gateleen/queue/queuing/QueuingHandler.java @@ -14,6 +14,8 @@ import org.swisspush.gateleen.queue.queuing.splitter.NoOpQueueSplitter; import org.swisspush.gateleen.queue.queuing.splitter.QueueSplitter; +import javax.annotation.Nullable; + import static org.swisspush.redisques.util.RedisquesAPI.buildCheckOperation; /** @@ -42,7 +44,7 @@ public QueuingHandler( Vertx vertx, RedisProvider redisProvider, HttpServerRequest request, - MonitoringHandler monitoringHandler + @Nullable MonitoringHandler monitoringHandler ) { this(vertx, redisProvider, request, new QueueClient(vertx, monitoringHandler), new NoOpQueueSplitter()); } @@ -51,7 +53,7 @@ public QueuingHandler( Vertx vertx, RedisProvider redisProvider, HttpServerRequest request, - MonitoringHandler monitoringHandler, + @Nullable MonitoringHandler monitoringHandler, QueueSplitter queueSplitter ) { this( diff --git a/gateleen-queue/src/test/java/org/swisspush/gateleen/queue/queuing/QueueClientTest.java b/gateleen-queue/src/test/java/org/swisspush/gateleen/queue/queuing/QueueClientTest.java index feb879e1..de0675b4 100644 --- a/gateleen-queue/src/test/java/org/swisspush/gateleen/queue/queuing/QueueClientTest.java +++ b/gateleen-queue/src/test/java/org/swisspush/gateleen/queue/queuing/QueueClientTest.java @@ -69,6 +69,31 @@ public void testEnqueueFuture(TestContext context){ Mockito.verify(monitoringHandler, Mockito.timeout(1000).times(1)).updateEnqueue(); } + @Test + public void testEnqueueFutureNotUpdatingMonitoringHandlerWhenNotProvided(TestContext context){ + Async async = context.async(); + + // create a QueueClient without monitoringHandler + queueClient = new QueueClient(vertx, null); + + /* + * consume event bus messages directed to redisques and verify message content. + * reply with 'success' for enqueuing + */ + vertx.eventBus().localConsumer(Address.redisquesAddress(), (Handler>) message -> { + validateMessage(context, message, QueueOperation.enqueue, "myQueue"); + message.reply(new JsonObject().put(STATUS, OK)); + }); + + HttpRequest request = new HttpRequest(HttpMethod.PUT, "/targetUri", MultiMap.caseInsensitiveMultiMap(), Buffer.buffer("{\"key\":\"value\"}").getBytes()); + queueClient.enqueueFuture(request, "myQueue").onComplete(event -> { + context.assertTrue(event.succeeded()); + async.complete(); + }); + + Mockito.verifyNoInteractions(monitoringHandler); + } + @Test public void testEnqueueFutureNotUpdatingMonitoringHandlerOnRedisquesFail(TestContext context){ Async async = context.async(); diff --git a/gateleen-routing/README_routing.md b/gateleen-routing/README_routing.md index d89614af..7886d452 100644 --- a/gateleen-routing/README_routing.md +++ b/gateleen-routing/README_routing.md @@ -134,4 +134,31 @@ Examples: } } ``` -Each request header entry is validated in the format `: `, so you are able to filter for request header names and values. \ No newline at end of file +Each request header entry is validated in the format `: `, so you are able to filter for request header names and values. + +## Micrometer metrics +The routing feature is monitored with micrometer. The following metrics are available: +* gateleen_forwarded_requests_total + +Additional tags are provided to split the forward count into sub counts. + +| tag | description | +|------------|-------------------------------------------------------------------------------------------------------------------| +| metricName | The `metricName` property from the corresponding routing rule. With this, you are able to count requests per rule | +| type | Describes where the request was forwarded to. Possible values are `local`, `external` and `null` | + + +Example metrics: + +``` +# HELP gateleen_forwarded_requests_total Number of forwarded requests +# TYPE gateleen_forwarded_requests_total counter +gateleen_forwarded_requests_total{metricName="infotool_v1_informations",type="external",} 678765.0 +gateleen_forwarded_requests_total{metricName="amp-mutation",type="local",} 8875.0 +gateleen_forwarded_requests_total{metricName="edds-profile-houseservice-v2",type="storage",} 7777.0 +gateleen_forwarded_requests_total{metricName="password-reset",type="external",} 4.0 +gateleen_forwarded_requests_total{metricName="inventory-v1-session-start",type="external",} 0.0 +gateleen_forwarded_requests_total{metricName="edds-azb-catdir",type="null",} 5577.0 +``` + +To enable the metrics, set a `MeterRegistry` instance by calling `withMeterRegistry(MeterRegistry meterRegistry)` method in `RouterBuilder` class. \ No newline at end of file diff --git a/gateleen-routing/pom.xml b/gateleen-routing/pom.xml index 26f4bfce..91cfa062 100644 --- a/gateleen-routing/pom.xml +++ b/gateleen-routing/pom.xml @@ -27,6 +27,10 @@ gateleen-monitoring ${project.version} + + io.micrometer + micrometer-core + diff --git a/gateleen-routing/src/main/java/org/swisspush/gateleen/routing/AbstractForwarder.java b/gateleen-routing/src/main/java/org/swisspush/gateleen/routing/AbstractForwarder.java index 7cab72a0..96c57d8f 100644 --- a/gateleen-routing/src/main/java/org/swisspush/gateleen/routing/AbstractForwarder.java +++ b/gateleen-routing/src/main/java/org/swisspush/gateleen/routing/AbstractForwarder.java @@ -1,5 +1,8 @@ package org.swisspush.gateleen.routing; +import javax.annotation.Nullable; + +import io.micrometer.core.instrument.MeterRegistry; import io.vertx.core.Handler; import io.vertx.core.http.HttpServerRequest; import io.vertx.ext.web.RoutingContext; @@ -18,14 +21,25 @@ public abstract class AbstractForwarder implements Handler { protected final LoggingResourceManager loggingResourceManager; protected final LogAppenderRepository logAppenderRepository; protected final MonitoringHandler monitoringHandler; + protected final String metricNameTag; + + public static final String FORWARDER_METRIC_NAME = "gateleen.forwarded.requests"; + public static final String FORWARDER_METRIC_DESCRIPTION = "gateleen.forwarded.requests"; + public static final String FORWARDER_METRIC_TAG_TYPE = "type"; + public static final String FORWARDER_METRIC_TAG_METRICNAME = "metricName"; + public static final String FORWARDER_NO_METRICNAME = "no-metric-name"; - public AbstractForwarder(Rule rule, LoggingResourceManager loggingResourceManager, LogAppenderRepository logAppenderRepository, MonitoringHandler monitoringHandler) { + public AbstractForwarder(Rule rule, LoggingResourceManager loggingResourceManager, LogAppenderRepository logAppenderRepository, @Nullable MonitoringHandler monitoringHandler) { this.rule = rule; this.loggingResourceManager = loggingResourceManager; this.logAppenderRepository = logAppenderRepository; this.monitoringHandler = monitoringHandler; + + this.metricNameTag = rule.getMetricName() != null ? rule.getMetricName() : FORWARDER_NO_METRICNAME; } + protected abstract void setMeterRegistry(MeterRegistry meterRegistry); + protected boolean doHeadersFilterMatch(final HttpServerRequest request) { final Logger log = RequestLoggerFactory.getLogger(getClass(), request); @@ -64,4 +78,12 @@ protected void respondError(HttpServerRequest req, StatusCode statusCode) { log.warn("IllegalStateException while sending error response for {}", req.uri(), ex); } } + + protected boolean isRequestToExternalTarget(String target) { + boolean isInternalRequest = false; + if (target != null) { + isInternalRequest = target.contains("localhost") || target.contains("127.0.0.1"); + } + return !isInternalRequest; + } } diff --git a/gateleen-routing/src/main/java/org/swisspush/gateleen/routing/Forwarder.java b/gateleen-routing/src/main/java/org/swisspush/gateleen/routing/Forwarder.java index 3a049add..e59ca45e 100755 --- a/gateleen-routing/src/main/java/org/swisspush/gateleen/routing/Forwarder.java +++ b/gateleen-routing/src/main/java/org/swisspush/gateleen/routing/Forwarder.java @@ -1,5 +1,7 @@ package org.swisspush.gateleen.routing; +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.MeterRegistry; import io.netty.channel.ConnectTimeoutException; import io.netty.handler.codec.http.HttpResponseStatus; import io.vertx.core.*; @@ -64,10 +66,12 @@ public class Forwarder extends AbstractForwarder { private static final int STATUS_CODE_2XX = 2; private static final Logger LOG = LoggerFactory.getLogger(Forwarder.class); + private Counter localForwardCounter; + private Counter externalForwardCounter; public Forwarder(Vertx vertx, HttpClient client, Rule rule, final ResourceStorage storage, - LoggingResourceManager loggingResourceManager, LogAppenderRepository logAppenderRepository, MonitoringHandler monitoringHandler, - String userProfilePath, @Nullable AuthStrategy authStrategy) { + LoggingResourceManager loggingResourceManager, LogAppenderRepository logAppenderRepository, + @Nullable MonitoringHandler monitoringHandler, String userProfilePath, @Nullable AuthStrategy authStrategy) { super(rule, loggingResourceManager, logAppenderRepository, monitoringHandler); this.vertx = vertx; this.client = client; @@ -82,6 +86,29 @@ public Forwarder(Vertx vertx, HttpClient client, Rule rule, final ResourceStorag this.authStrategy = authStrategy; } + /** + * Sets the MeterRegistry for this Forwarder. + * If the provided MeterRegistry is not null, it initializes the forwardCounter + * with the appropriate metric name, description, and tags. + * + * @param meterRegistry the MeterRegistry to set + */ + @Override + public void setMeterRegistry(MeterRegistry meterRegistry) { + if (meterRegistry != null) { + localForwardCounter = Counter.builder(FORWARDER_METRIC_NAME) + .description(FORWARDER_METRIC_DESCRIPTION) + .tag(FORWARDER_METRIC_TAG_TYPE, "local") + .tag(FORWARDER_METRIC_TAG_METRICNAME, metricNameTag) + .register(meterRegistry); + externalForwardCounter = Counter.builder(FORWARDER_METRIC_NAME) + .description(FORWARDER_METRIC_DESCRIPTION) + .tag(FORWARDER_METRIC_TAG_TYPE, "external") + .tag(FORWARDER_METRIC_TAG_METRICNAME, metricNameTag) + .register(meterRegistry); + } + } + private Map createProfileHeaderValues(JsonObject profile, Logger log) { Map profileValues = new HashMap<>(); if (rule.getProfile() != null) { @@ -148,15 +175,20 @@ public void handle(final RoutingContext ctx, final Buffer bodyData, @Nullable fi port = rule.getPort(); } target = rule.getHost() + ":" + port; - monitoringHandler.updateRequestsMeter(target, req.uri()); - monitoringHandler.updateRequestPerRuleMonitoring(req, rule.getMetricName()); + + handleForwardMetrics(target); + + if (monitoringHandler != null) { + monitoringHandler.updateRequestsMeter(target, req.uri()); + monitoringHandler.updateRequestPerRuleMonitoring(req, rule.getMetricName()); + } final String targetUri = urlPattern.matcher(req.uri()).replaceFirst(rule.getPath()).replaceAll("\\/\\/", "/"); log.debug("Forwarding request: {} to {}://{} with rule {}", req.uri(), rule.getScheme(), target + targetUri, rule.getRuleIdentifier()); final String userId = extractUserId(req, log); req.pause(); // pause the request to avoid problems with starting another async request (storage) maybeAuthenticate(rule).onComplete(event -> { - if(event.failed()) { + if (event.failed()) { req.resume(); log.error("Failed to authenticate request. Cause: {}", event.cause().getMessage()); respondError(req, StatusCode.UNAUTHORIZED); @@ -184,6 +216,13 @@ public void handle(final RoutingContext ctx, final Buffer bodyData, @Nullable fi }); } + private void handleForwardMetrics(String target) { + Counter counter = isRequestToExternalTarget(target) ? externalForwardCounter : localForwardCounter; + if (counter != null) { + counter.increment(); + } + } + /** * Returns the userId defined in the on-behalf-of-header if provided, the userId from user-header otherwise. * @@ -240,8 +279,12 @@ private void handleRequest(final HttpServerRequest req, final Buffer bodyData, f final String uniqueId = req.headers().get("x-rp-unique_id"); final String timeout = req.headers().get("x-timeout"); - final long startTime = monitoringHandler.startRequestMetricTracking(rule.getMetricName(), req.uri()); + Long startTime = null; + if (monitoringHandler != null) { + startTime = monitoringHandler.startRequestMetricTracking(rule.getMetricName(), req.uri()); + } + Long finalStartTime = startTime; client.request(req.method(), port, rule.getHost(), targetUri, new Handler<>() { @Override public void handle(AsyncResult event) { @@ -256,7 +299,7 @@ public void handle(AsyncResult event) { return; } HttpClientRequest cReq = event.result(); - final Handler> cResHandler = getAsyncHttpClientResponseHandler(req, targetUri, log, profileHeaderMap, loggingHandler, startTime, afterHandler); + final Handler> cResHandler = getAsyncHttpClientResponseHandler(req, targetUri, log, profileHeaderMap, loggingHandler, finalStartTime, afterHandler); cReq.response(cResHandler); if (timeout != null) { @@ -290,7 +333,7 @@ public void handle(AsyncResult event) { return; } - installExceptionHandler(req, targetUri, startTime, cReq); + installExceptionHandler(req, targetUri, finalStartTime, cReq); /* * If no bodyData is available @@ -421,9 +464,11 @@ private void setProfileHeaders(Logger log, Map profileHeaderMap, } } - private void installExceptionHandler(final HttpServerRequest req, final String targetUri, final long startTime, HttpClientRequest cReq) { + private void installExceptionHandler(final HttpServerRequest req, final String targetUri, final Long startTime, HttpClientRequest cReq) { cReq.exceptionHandler(exception -> { - monitoringHandler.stopRequestMetricTracking(rule.getMetricName(), startTime, req.uri()); + if (monitoringHandler != null && startTime != null) { + monitoringHandler.stopRequestMetricTracking(rule.getMetricName(), startTime, req.uri()); + } if (exception instanceof TimeoutException) { error("Timeout", req, targetUri); respondError(req, StatusCode.TIMEOUT); @@ -445,7 +490,7 @@ private void installExceptionHandler(final HttpServerRequest req, final String t }); } - private Handler> getAsyncHttpClientResponseHandler(final HttpServerRequest req, final String targetUri, final Logger log, final Map profileHeaderMap, final LoggingHandler loggingHandler, final long startTime, @Nullable final Handler afterHandler) { + private Handler> getAsyncHttpClientResponseHandler(final HttpServerRequest req, final String targetUri, final Logger log, final Map profileHeaderMap, final LoggingHandler loggingHandler, @Nullable final Long startTime, @Nullable final Handler afterHandler) { return asyncResult -> { HttpClientResponse cRes = asyncResult.result(); if (asyncResult.failed()) { @@ -462,7 +507,9 @@ private Handler> getAsyncHttpClientResponseHandl } return; } - monitoringHandler.stopRequestMetricTracking(rule.getMetricName(), startTime, req.uri()); + if (monitoringHandler != null) { + monitoringHandler.stopRequestMetricTracking(rule.getMetricName(), startTime, req.uri()); + } loggingHandler.setResponse(cRes); req.response().setStatusCode(cRes.statusCode()); req.response().setStatusMessage(cRes.statusMessage()); diff --git a/gateleen-routing/src/main/java/org/swisspush/gateleen/routing/NullForwarder.java b/gateleen-routing/src/main/java/org/swisspush/gateleen/routing/NullForwarder.java index 8d1b8fb4..2224c9d2 100755 --- a/gateleen-routing/src/main/java/org/swisspush/gateleen/routing/NullForwarder.java +++ b/gateleen-routing/src/main/java/org/swisspush/gateleen/routing/NullForwarder.java @@ -1,5 +1,7 @@ package org.swisspush.gateleen.routing; +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.MeterRegistry; import io.vertx.core.MultiMap; import io.vertx.core.buffer.Buffer; import io.vertx.core.eventbus.EventBus; @@ -17,6 +19,8 @@ import org.swisspush.gateleen.logging.LoggingResourceManager; import org.swisspush.gateleen.monitoring.MonitoringHandler; +import javax.annotation.Nullable; + /** * Consumes requests without forwarding them anywhere. * @@ -25,12 +29,31 @@ public class NullForwarder extends AbstractForwarder { private EventBus eventBus; + private Counter forwardCounter; - public NullForwarder(Rule rule, LoggingResourceManager loggingResourceManager, LogAppenderRepository logAppenderRepository, MonitoringHandler monitoringHandler, EventBus eventBus) { + public NullForwarder(Rule rule, LoggingResourceManager loggingResourceManager, LogAppenderRepository logAppenderRepository, @Nullable MonitoringHandler monitoringHandler, EventBus eventBus) { super(rule, loggingResourceManager, logAppenderRepository, monitoringHandler); this.eventBus = eventBus; } + /** + * Sets the MeterRegistry for this NullForwarder. + * If the provided MeterRegistry is not null, it initializes the forwardCounter + * with the appropriate metric name, description, and tags. + * + * @param meterRegistry the MeterRegistry to set + */ + @Override + public void setMeterRegistry(MeterRegistry meterRegistry) { + if(meterRegistry != null) { + forwardCounter = Counter.builder(FORWARDER_METRIC_NAME) + .description(FORWARDER_METRIC_DESCRIPTION) + .tag(FORWARDER_METRIC_TAG_METRICNAME, metricNameTag) + .tag(FORWARDER_METRIC_TAG_TYPE, "null") + .register(meterRegistry); + } + } + @Override public void handle(final RoutingContext ctx) { final Logger log = RequestLoggerFactory.getLogger(NullForwarder.class, ctx.request()); @@ -40,7 +63,13 @@ public void handle(final RoutingContext ctx) { return; } - monitoringHandler.updateRequestPerRuleMonitoring(ctx.request(), rule.getMetricName()); + if(forwardCounter != null) { + forwardCounter.increment(); + } + + if(monitoringHandler != null) { + monitoringHandler.updateRequestPerRuleMonitoring(ctx.request(), rule.getMetricName()); + } final LoggingHandler loggingHandler = new LoggingHandler(loggingResourceManager, logAppenderRepository, ctx.request(), eventBus); log.debug("Not forwarding request: {} with rule {}", ctx.request().uri(), rule.getRuleIdentifier()); final HeadersMultiMap requestHeaders = new HeadersMultiMap(); diff --git a/gateleen-routing/src/main/java/org/swisspush/gateleen/routing/Router.java b/gateleen-routing/src/main/java/org/swisspush/gateleen/routing/Router.java index 7c430f2e..80add04a 100755 --- a/gateleen-routing/src/main/java/org/swisspush/gateleen/routing/Router.java +++ b/gateleen-routing/src/main/java/org/swisspush/gateleen/routing/Router.java @@ -1,5 +1,6 @@ package org.swisspush.gateleen.routing; +import io.micrometer.core.instrument.MeterRegistry; import io.vertx.core.Handler; import io.vertx.core.Vertx; import io.vertx.core.buffer.Buffer; @@ -62,6 +63,7 @@ public class Router implements Refreshable, LoggableResource, ConfigurationResou private final LoggingResourceManager loggingResourceManager; private final LogAppenderRepository logAppenderRepository; private final MonitoringHandler monitoringHandler; + private final MeterRegistry meterRegistry; private final Logger log = LoggerFactory.getLogger(Router.class); private final Logger cleanupLogger = LoggerFactory.getLogger(Router.class.getName() + "Cleanup"); private final Vertx vertx; @@ -108,7 +110,8 @@ public static RouterBuilder builder() { final Map properties, LoggingResourceManager loggingResourceManager, LogAppenderRepository logAppenderRepository, - MonitoringHandler monitoringHandler, + @Nullable MonitoringHandler monitoringHandler, + MeterRegistry meterRegistry, HttpClient selfClient, String serverPath, String rulesPath, @@ -126,6 +129,7 @@ public static RouterBuilder builder() { this.loggingResourceManager = loggingResourceManager; this.logAppenderRepository = logAppenderRepository; this.monitoringHandler = monitoringHandler; + this.meterRegistry = meterRegistry; this.selfClient = selfClient; this.vertx = vertx; this.sharedData = vertx.sharedData().getLocalMap(ROUTER_STATE_MAP); @@ -322,7 +326,7 @@ private void createForwarders(List rules, io.vertx.ext.web.Router newRoute * is null. */ AuthStrategy authStrategy = selectAuthStrategy(rule); - Handler forwarder; + AbstractForwarder forwarder; if (rule.getPath() == null) { forwarder = new NullForwarder(rule, loggingResourceManager, logAppenderRepository, monitoringHandler, vertx.eventBus()); @@ -339,6 +343,8 @@ private void createForwarders(List rules, io.vertx.ext.web.Router newRoute newClients.add(client); } + forwarder.setMeterRegistry(meterRegistry); + if (rule.getMethods() == null) { log.info("Installing {} forwarder for all methods: {}", rule.getScheme().toUpperCase(), rule.getUrlPattern()); newRouter.routeWithRegex(rule.getUrlPattern()).handler(forwarder); diff --git a/gateleen-routing/src/main/java/org/swisspush/gateleen/routing/RouterBuilder.java b/gateleen-routing/src/main/java/org/swisspush/gateleen/routing/RouterBuilder.java index f7ef9685..59e290ad 100644 --- a/gateleen-routing/src/main/java/org/swisspush/gateleen/routing/RouterBuilder.java +++ b/gateleen-routing/src/main/java/org/swisspush/gateleen/routing/RouterBuilder.java @@ -1,5 +1,6 @@ package org.swisspush.gateleen.routing; +import io.micrometer.core.instrument.MeterRegistry; import io.vertx.core.Handler; import io.vertx.core.Vertx; import io.vertx.core.http.HttpClient; @@ -37,6 +38,7 @@ public class RouterBuilder { private LoggingResourceManager loggingResourceManager; private LogAppenderRepository logAppenderRepository; private MonitoringHandler monitoringHandler; + private MeterRegistry meterRegistry; private HttpClient selfClient; private String serverPath; private String rulesPath; @@ -98,6 +100,7 @@ public Router build() { loggingResourceManager, logAppenderRepository, monitoringHandler, + meterRegistry, selfClient, serverPath, rulesPath, @@ -183,6 +186,12 @@ public RouterBuilder withMonitoringHandler(MonitoringHandler monitoringHandler) return this; } + public RouterBuilder withMeterRegistry(MeterRegistry meterRegistry) { + ensureNotBuilt(); + this.meterRegistry = meterRegistry; + return this; + } + public RouterBuilder withSelfClient(HttpClient selfClient) { ensureNotBuilt(); this.selfClient = selfClient; diff --git a/gateleen-routing/src/main/java/org/swisspush/gateleen/routing/StorageForwarder.java b/gateleen-routing/src/main/java/org/swisspush/gateleen/routing/StorageForwarder.java index 1e48c221..c7324cf4 100755 --- a/gateleen-routing/src/main/java/org/swisspush/gateleen/routing/StorageForwarder.java +++ b/gateleen-routing/src/main/java/org/swisspush/gateleen/routing/StorageForwarder.java @@ -1,5 +1,7 @@ package org.swisspush.gateleen.routing; +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.MeterRegistry; import io.netty.handler.codec.http.HttpResponseStatus; import io.vertx.core.AsyncResult; import io.vertx.core.Handler; @@ -28,6 +30,7 @@ import org.swisspush.gateleen.logging.LoggingResourceManager; import org.swisspush.gateleen.monitoring.MonitoringHandler; +import javax.annotation.Nullable; import java.util.regex.Pattern; /** @@ -43,8 +46,10 @@ public class StorageForwarder extends AbstractForwarder { private CORSHandler corsHandler; private GateleenExceptionFactory gateleenExceptionFactory; + private Counter forwardCounter; + public StorageForwarder(EventBus eventBus, Rule rule, LoggingResourceManager loggingResourceManager, - LogAppenderRepository logAppenderRepository, MonitoringHandler monitoringHandler, + LogAppenderRepository logAppenderRepository, @Nullable MonitoringHandler monitoringHandler, GateleenExceptionFactory gateleenExceptionFactory) { super(rule, loggingResourceManager, logAppenderRepository, monitoringHandler); this.eventBus = eventBus; @@ -54,6 +59,24 @@ public StorageForwarder(EventBus eventBus, Rule rule, LoggingResourceManager log this.gateleenExceptionFactory = gateleenExceptionFactory; } + /** + * Sets the MeterRegistry for this StorageForwarder. + * If the provided MeterRegistry is not null, it initializes the forwardCounter + * with the appropriate metric name, description, and tags. + * + * @param meterRegistry the MeterRegistry to set + */ + @Override + public void setMeterRegistry(MeterRegistry meterRegistry) { + if (meterRegistry != null) { + forwardCounter = Counter.builder(FORWARDER_METRIC_NAME) + .description(FORWARDER_METRIC_DESCRIPTION) + .tag(FORWARDER_METRIC_TAG_METRICNAME, metricNameTag) + .tag(FORWARDER_METRIC_TAG_TYPE, "storage") + .register(meterRegistry); + } + } + @Override public void handle(final RoutingContext ctx) { final LoggingHandler loggingHandler = new LoggingHandler(loggingResourceManager, logAppenderRepository, ctx.request(), this.eventBus); @@ -65,9 +88,17 @@ public void handle(final RoutingContext ctx) { return; } - monitoringHandler.updateRequestsMeter("localhost", ctx.request().uri()); - monitoringHandler.updateRequestPerRuleMonitoring(ctx.request(), rule.getMetricName()); - final long startTime = monitoringHandler.startRequestMetricTracking(rule.getMetricName(), ctx.request().uri()); + Long startTime = null; + + if (forwardCounter != null) { + forwardCounter.increment(); + } + + if (monitoringHandler != null) { + monitoringHandler.updateRequestsMeter("localhost", ctx.request().uri()); + monitoringHandler.updateRequestPerRuleMonitoring(ctx.request(), rule.getMetricName()); + startTime = monitoringHandler.startRequestMetricTracking(rule.getMetricName(), ctx.request().uri()); + } log.debug("Forwarding {} request: {} to storage {} {} with rule {}", ctx.request().method(), ctx.request().uri(), rule.getStorage(), targetUri, rule.getRuleIdentifier()); final HeadersMultiMap requestHeaders = new HeadersMultiMap(); @@ -92,68 +123,71 @@ public void handle(final RoutingContext ctx) { loggingHandler.appendRequestPayload(buffer, requestHeaders); requestBuffer.appendBuffer(buffer); }); + Long finalStartTime = startTime; ctx.request().endHandler(event -> eventBus.request(address, requestBuffer, new DeliveryOptions().setSendTimeout(10000), (Handler>>) result -> { - HttpServerResponse response = ctx.response(); - monitoringHandler.stopRequestMetricTracking(rule.getMetricName(), startTime, ctx.request().uri()); - if (result.failed()) { - String statusMessage = "Storage request for " + ctx.request().uri() + " failed with message: " + result.cause().getMessage(); - response.setStatusCode(StatusCode.INTERNAL_SERVER_ERROR.getStatusCode()); - response.setStatusMessage(statusMessage); - response.end(); - log.error("{}", statusMessage, gateleenExceptionFactory.newException(result.cause())); - } else { - Buffer buffer = result.result().body(); - int headerLength = buffer.getInt(0); - JsonObject responseJson = new JsonObject(buffer.getString(4, headerLength + 4)); - JsonArray headers = responseJson.getJsonArray("headers"); - MultiMap responseHeaders = null; - if (headers != null && !headers.isEmpty()) { - responseHeaders = JsonMultiMap.fromJson(headers); - - setUniqueIdHeader(responseHeaders); - - ctx.response().headers().setAll(responseHeaders); - } - corsHandler.handle(ctx.request()); - int statusCode = responseJson.getInteger("statusCode"); - - // translate with header info - int translatedStatus = Translator.translateStatusCode(statusCode, ctx.request().headers()); - - // nothing changed? - if (statusCode == translatedStatus) { - translatedStatus = Translator.translateStatusCode(statusCode, rule, log); - } - - boolean translated = statusCode != translatedStatus; - - // set the statusCode (if nothing hapend, it will remain the same) - statusCode = translatedStatus; - - response.setStatusCode(statusCode); - String statusMessage; - if (translated) { - statusMessage = HttpResponseStatus.valueOf(statusCode).reasonPhrase(); - response.setStatusMessage(statusMessage); - } else { - statusMessage = responseJson.getString("statusMessage"); - if (statusMessage != null) { - response.setStatusMessage(statusMessage); - } - } - Buffer data = buffer.getBuffer(4 + headerLength, buffer.length()); - response.headers().set("content-length", "" + data.length()); - response.write(data); - response.end(); - ResponseStatusCodeLogUtil.debug(ctx.request(), StatusCode.fromCode(statusCode), StorageForwarder.class); - if (responseHeaders != null) { - loggingHandler.appendResponsePayload(data, responseHeaders); - } - loggingHandler.log(ctx.request().uri(), ctx.request().method(), statusCode, statusMessage, - requestHeaders, responseHeaders != null ? responseHeaders : new HeadersMultiMap()); - } + HttpServerResponse response = ctx.response(); + if (monitoringHandler != null) { + monitoringHandler.stopRequestMetricTracking(rule.getMetricName(), finalStartTime, ctx.request().uri()); + } + if (result.failed()) { + String statusMessage = "Storage request for " + ctx.request().uri() + " failed with message: " + result.cause().getMessage(); + response.setStatusCode(StatusCode.INTERNAL_SERVER_ERROR.getStatusCode()); + response.setStatusMessage(statusMessage); + response.end(); + log.error("{}", statusMessage, gateleenExceptionFactory.newException(result.cause())); + } else { + Buffer buffer = result.result().body(); + int headerLength = buffer.getInt(0); + JsonObject responseJson = new JsonObject(buffer.getString(4, headerLength + 4)); + JsonArray headers = responseJson.getJsonArray("headers"); + MultiMap responseHeaders = null; + if (headers != null && !headers.isEmpty()) { + responseHeaders = JsonMultiMap.fromJson(headers); + + setUniqueIdHeader(responseHeaders); + + ctx.response().headers().setAll(responseHeaders); + } + corsHandler.handle(ctx.request()); + int statusCode = responseJson.getInteger("statusCode"); + + // translate with header info + int translatedStatus = Translator.translateStatusCode(statusCode, ctx.request().headers()); + + // nothing changed? + if (statusCode == translatedStatus) { + translatedStatus = Translator.translateStatusCode(statusCode, rule, log); + } + + boolean translated = statusCode != translatedStatus; + + // set the statusCode (if nothing hapend, it will remain the same) + statusCode = translatedStatus; + + response.setStatusCode(statusCode); + String statusMessage; + if (translated) { + statusMessage = HttpResponseStatus.valueOf(statusCode).reasonPhrase(); + response.setStatusMessage(statusMessage); + } else { + statusMessage = responseJson.getString("statusMessage"); + if (statusMessage != null) { + response.setStatusMessage(statusMessage); + } + } + Buffer data = buffer.getBuffer(4 + headerLength, buffer.length()); + response.headers().set("content-length", "" + data.length()); + response.write(data); + response.end(); + ResponseStatusCodeLogUtil.debug(ctx.request(), StatusCode.fromCode(statusCode), StorageForwarder.class); + if (responseHeaders != null) { + loggingHandler.appendResponsePayload(data, responseHeaders); + } + loggingHandler.log(ctx.request().uri(), ctx.request().method(), statusCode, statusMessage, + requestHeaders, responseHeaders != null ? responseHeaders : new HeadersMultiMap()); + } })); } diff --git a/gateleen-routing/src/test/java/org/swisspush/gateleen/routing/RouterTest.java b/gateleen-routing/src/test/java/org/swisspush/gateleen/routing/RouterTest.java index b1184d66..2d789b95 100755 --- a/gateleen-routing/src/test/java/org/swisspush/gateleen/routing/RouterTest.java +++ b/gateleen-routing/src/test/java/org/swisspush/gateleen/routing/RouterTest.java @@ -1,6 +1,9 @@ package org.swisspush.gateleen.routing; import com.google.common.collect.ImmutableMap; +import io.micrometer.core.instrument.Counter; +import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.simple.SimpleMeterRegistry; import io.vertx.core.Handler; import io.vertx.core.MultiMap; import io.vertx.core.Vertx; @@ -28,10 +31,7 @@ import org.swisspush.gateleen.logging.LoggingResourceManager; import org.swisspush.gateleen.monitoring.MonitoringHandler; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Map; -import java.util.Set; +import java.util.*; /** * Tests for the Router class @@ -45,6 +45,7 @@ public class RouterTest { private Map properties; private LoggingResourceManager loggingResourceManager; private MonitoringHandler monitoringHandler; + private MeterRegistry meterRegistry; private HttpClient httpClient; private String serverUrl; private String rulesPath; @@ -143,6 +144,7 @@ public void setUp() { Mockito.when(loggingResourceManager.getLoggingResource()).thenReturn(new LoggingResource()); monitoringHandler = Mockito.mock(MonitoringHandler.class); httpClient = Mockito.mock(HttpClient.class); + meterRegistry = new SimpleMeterRegistry(); serverUrl = "/gateleen/server"; rulesPath = serverUrl + "/admin/v1/routing/rules"; @@ -160,6 +162,7 @@ private RouterBuilder routerBuilder() { .withProperties(properties) .withLoggingResourceManager(loggingResourceManager) .withMonitoringHandler(monitoringHandler) + .withMeterRegistry(meterRegistry) .withSelfClient(httpClient) .withServerPath(serverUrl) .withRulesPath(rulesPath) @@ -168,6 +171,12 @@ private RouterBuilder routerBuilder() { .withStoragePort(storagePort); } + private void assertNoCountersIncremented(TestContext context) { + for (Counter counter : meterRegistry.get(AbstractForwarder.FORWARDER_METRIC_NAME).counters()) { + context.assertEquals(0.0, counter.count(), "No counter should have been incremented"); + } + } + @Test public void testRequestHopValidationLimitNotYetReached(TestContext context) { storage = new MockResourceStorage(ImmutableMap.of(rulesPath, RULES_WITH_HOPS, serverUrl + "/loop/4/resource", RANDOM_RESOURCE)); @@ -240,6 +249,9 @@ public DummyHttpServerResponse response() { GETRandomResourceRequest request = new GETRandomResourceRequest(); router.route(request); + Counter counter = meterRegistry.get(AbstractForwarder.FORWARDER_METRIC_NAME).tag(AbstractForwarder.FORWARDER_METRIC_TAG_METRICNAME, "loop_4").counter(); + context.assertEquals(1.0, counter.count(), "Counter for `loop_4` rule should have been incremented by 1"); + context.assertEquals("1", request.headers().get("x-hops"), "x-hops header should have value 1"); context.assertEquals(StatusCode.OK.getStatusCode(), request.response().getStatusCode(), "StatusCode should be 200"); context.assertEquals(StatusCode.OK.getStatusMessage(), request.response().getStatusMessage(), "StatusMessage should be OK"); @@ -317,6 +329,8 @@ public DummyHttpServerResponse response() { GETRandomResourceRequest request = new GETRandomResourceRequest(); router.route(request); + assertNoCountersIncremented(context); + context.assertEquals("1", request.headers().get("x-hops"), "x-hops header should have value 1"); context.assertEquals(StatusCode.INTERNAL_SERVER_ERROR.getStatusCode(), request.response().getStatusCode(), "StatusCode should be 500"); context.assertEquals("Request hops limit exceeded", request.response().getStatusMessage(), "StatusMessage should be 'Request hops limit exceeded'"); @@ -404,6 +418,9 @@ public DummyHttpServerResponse response() { context.assertEquals("6", request.headers().get("x-hops"), "x-hops header should have value 6"); context.assertEquals(StatusCode.INTERNAL_SERVER_ERROR.getStatusCode(), request.response().getStatusCode(), "StatusCode should be 500"); context.assertEquals("Request hops limit exceeded", request.response().getStatusMessage(), "StatusMessage should be 'Request hops limit exceeded'"); + + Counter counter = meterRegistry.get(AbstractForwarder.FORWARDER_METRIC_NAME).tag(AbstractForwarder.FORWARDER_METRIC_TAG_METRICNAME, "loop_4").counter(); + context.assertEquals(5.0, counter.count(), "Counter for `loop_4` rule should have been incremented by 5"); } @Test @@ -478,6 +495,9 @@ public DummyHttpServerResponse response() { context.assertNull(request.headers().get("x-hops"), "No x-hops header should be present"); context.assertEquals(StatusCode.OK.getStatusCode(), request.response().getStatusCode(), "StatusCode should be 200"); context.assertEquals(StatusCode.OK.getStatusMessage(), request.response().getStatusMessage(), "StatusMessage should be OK"); + + Counter counter = meterRegistry.get(AbstractForwarder.FORWARDER_METRIC_NAME).tag(AbstractForwarder.FORWARDER_METRIC_TAG_METRICNAME, "loop_4").counter(); + context.assertEquals(20.0, counter.count(), "Counter for `loop_4` rule should have been incremented by 20"); } @@ -488,6 +508,8 @@ public void testRouterConstructionValidConfiguration(TestContext context) { Router router = routerBuilder().withProperties(properties).build(); context.assertFalse(router.isRoutingBroken(), "Routing should not be broken"); context.assertNull(router.getRoutingBrokenMessage(), "RoutingBrokenMessage should be null"); + + assertNoCountersIncremented(context); } @Test @@ -496,6 +518,8 @@ public void testRouterConstructionWithMissingProperty(TestContext context) { context.assertTrue(router.isRoutingBroken(), "Routing should be broken because of missing properties entry"); context.assertNotNull(router.getRoutingBrokenMessage(), "RoutingBrokenMessage should contain 'gateleen.test.prop.1' property"); context.assertTrue(router.getRoutingBrokenMessage().contains("gateleen.test.prop.1"), "RoutingBrokenMessage should contain 'gateleen.test.prop.1' property"); + + assertNoCountersIncremented(context); } @Test @@ -538,6 +562,8 @@ public HttpServerResponse response() { router.route(new UpdateRulesWithValidResourceRequest()); context.assertFalse(router.isRoutingBroken(), "Routing should not be broken anymore"); context.assertNull(router.getRoutingBrokenMessage(), "RoutingBrokenMessage should be null"); + + assertNoCountersIncremented(context); } @Test @@ -578,6 +604,8 @@ public MultiMap headers() { context.assertEquals(StatusCode.OK.getStatusCode(), request.response().getStatusCode(), "StatusCode should be 200"); context.assertEquals(StatusCode.OK.getStatusMessage(), request.response().getStatusMessage(), "StatusMessage should be OK"); context.assertEquals(RULES_WITH_MISSING_PROPS, request.response().getResultBuffer(), "RoutingRules should be returned as result"); + + assertNoCountersIncremented(context); } @Test @@ -619,6 +647,8 @@ public MultiMap headers() { context.assertEquals(StatusCode.INTERNAL_SERVER_ERROR.getStatusMessage(), request.response().getStatusMessage(), "StatusMessage should be Internal Server Error"); context.assertTrue(request.response().getResultBuffer().contains("Routing is broken"), "Routing is broken message should be returned"); context.assertTrue(request.response().getResultBuffer().contains("gateleen.test.prop.1"), "The message should contain 'gateleen.test.prop.1' in the message"); + + assertNoCountersIncremented(context); } @Test @@ -742,6 +772,9 @@ public HttpServerRequest pause() { router.route(requestRandomResource); context.assertFalse(router.isRoutingBroken(), "Routing should not be broken anymore"); context.assertNull(router.getRoutingBrokenMessage(), "RoutingBrokenMessage should be null"); + + Counter counter = meterRegistry.get(AbstractForwarder.FORWARDER_METRIC_NAME).tag(AbstractForwarder.FORWARDER_METRIC_TAG_TYPE, "local").counter(); + context.assertEquals(1.0, counter.count(), "Counter should have been incremented by 1"); } @Test @@ -793,6 +826,8 @@ public DummyHttpServerResponse response() { context.assertEquals(StatusCode.OK.getStatusCode(), request.response().getStatusCode(), "StatusCode should be 200"); context.assertEquals(StatusCode.OK.getStatusMessage(), request.response().getStatusMessage(), "StatusMessage should be OK"); context.assertEquals(ts, new JsonObject(request.response().getResultBuffer()).getLong("ts")); + + assertNoCountersIncremented(context); } @Test @@ -848,6 +883,7 @@ public DummyHttpServerResponse response() { router.route(request); context.assertEquals(StatusCode.NOT_FOUND.getStatusCode(), request.response().getStatusCode(), "StatusCode should be 404"); + assertNoCountersIncremented(context); } @Test @@ -871,6 +907,9 @@ public void testStorageRequestWithHeadersFilterPresent(TestContext context) { context.assertEquals(StatusCode.OK.getStatusCode(), request.response().getStatusCode(), "StatusCode should be 200"); context.assertEquals(StatusCode.OK.getStatusMessage(), request.response().getStatusMessage(), "StatusMessage should be OK"); + + Counter counter = meterRegistry.get(AbstractForwarder.FORWARDER_METRIC_NAME).tag(AbstractForwarder.FORWARDER_METRIC_TAG_METRICNAME, "forward_storage").counter(); + context.assertEquals(1.0, counter.count(), "Counter for `forward_storage` rule should have been incremented by 1"); } @Test @@ -892,6 +931,8 @@ public void testStorageRequestWithHeadersFilterAbsent(TestContext context) { router.route(request); context.assertEquals(StatusCode.NOT_FOUND.getStatusCode(), request.response().getStatusCode(), "StatusCode should be 404"); + + assertNoCountersIncremented(context); } @Test @@ -914,6 +955,8 @@ public void testNullForwarderRequestWithHeadersFilterNotMatching(TestContext con router.route(request); context.assertEquals(StatusCode.NOT_FOUND.getStatusCode(), request.response().getStatusCode(), "StatusCode should be 404"); + + assertNoCountersIncremented(context); } @Test @@ -938,6 +981,9 @@ public void testNullForwarderRequestWithHeadersFilterPresent(TestContext context context.assertEquals(StatusCode.OK.getStatusCode(), request.response().getStatusCode(), "StatusCode should be 200"); context.assertEquals(StatusCode.OK.getStatusMessage(), request.response().getStatusMessage(), "StatusMessage should be OK"); + + Counter counter = meterRegistry.get(AbstractForwarder.FORWARDER_METRIC_NAME).tag(AbstractForwarder.FORWARDER_METRIC_TAG_METRICNAME, "forward_null").counter(); + context.assertEquals(1.0, counter.count(), "Counter for `forward_null` rule should have been incremented by 1"); } @Test @@ -963,6 +1009,9 @@ public void testForwarderRequestWithHeadersFilterPresent(TestContext context) { // we expect a status code 500 because of a NullPointerException in the test setup // however, this means that the headersFilter evaluation did not return a 400 Bad Request context.assertEquals(StatusCode.INTERNAL_SERVER_ERROR.getStatusCode(), request.response().getStatusCode(), "StatusCode should be 500"); + + Counter counter = meterRegistry.get(AbstractForwarder.FORWARDER_METRIC_NAME).tag(AbstractForwarder.FORWARDER_METRIC_TAG_METRICNAME, "forward_backend").counter(); + context.assertEquals(1.0, counter.count(), "Counter for `forward_backend` rule should have been incremented by 1"); } @Test @@ -986,6 +1035,8 @@ public void testForwarderRequestWithHeadersFilterNotMatching(TestContext context router.route(request); context.assertEquals(StatusCode.NOT_FOUND.getStatusCode(), request.response().getStatusCode(), "StatusCode should be 404"); + + assertNoCountersIncremented(context); } private DummyHttpServerRequest buildRequest(HttpMethod method, String uri, MultiMap headers, Buffer body, DummyHttpServerResponse response) { diff --git a/gateleen-scheduler/src/main/java/org/swisspush/gateleen/scheduler/Scheduler.java b/gateleen-scheduler/src/main/java/org/swisspush/gateleen/scheduler/Scheduler.java index 65ab5a30..6577b694 100755 --- a/gateleen-scheduler/src/main/java/org/swisspush/gateleen/scheduler/Scheduler.java +++ b/gateleen-scheduler/src/main/java/org/swisspush/gateleen/scheduler/Scheduler.java @@ -15,6 +15,7 @@ import org.swisspush.gateleen.queue.expiry.ExpiryCheckHandler; import org.swisspush.gateleen.queue.queuing.QueueClient; +import javax.annotation.Nullable; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Collections; @@ -48,17 +49,17 @@ public class Scheduler { private Logger log; public Scheduler( - Vertx vertx, - String redisquesAddress, - RedisProvider redisProvider, - GateleenExceptionFactory exceptionFactory, - String name, - String cronExpression, - List requests, - MonitoringHandler monitoringHandler, - int maxRandomOffset, - boolean executeOnStartup, - boolean executeOnReload + Vertx vertx, + String redisquesAddress, + RedisProvider redisProvider, + GateleenExceptionFactory exceptionFactory, + String name, + String cronExpression, + List requests, + @Nullable MonitoringHandler monitoringHandler, + int maxRandomOffset, + boolean executeOnStartup, + boolean executeOnReload ) throws ParseException { this.vertx = vertx; this.redisquesAddress = redisquesAddress; @@ -138,7 +139,9 @@ public void stop() { private void trigger() { for (final HttpRequest request : requests) { - monitoringHandler.updateEnqueue(); + if (monitoringHandler != null) { + monitoringHandler.updateEnqueue(); + } if (log.isTraceEnabled()) { log.trace("Triggering request " + request.toJsonObject().encodePrettily()); @@ -156,7 +159,7 @@ private void trigger() { if (event.failed()) { if (log.isWarnEnabled()) { log.warn("Could not enqueue request '{}' '{}'", queueName, request.getUri(), - exceptionFactory.newException("eventBus.request('" + redisquesAddress + "', enqueOp) failed", event.cause())); + exceptionFactory.newException("eventBus.request('" + redisquesAddress + "', enqueOp) failed", event.cause())); } return; } diff --git a/gateleen-scheduler/src/main/java/org/swisspush/gateleen/scheduler/SchedulerFactory.java b/gateleen-scheduler/src/main/java/org/swisspush/gateleen/scheduler/SchedulerFactory.java index 1a5c34f3..7639bbf0 100755 --- a/gateleen-scheduler/src/main/java/org/swisspush/gateleen/scheduler/SchedulerFactory.java +++ b/gateleen-scheduler/src/main/java/org/swisspush/gateleen/scheduler/SchedulerFactory.java @@ -15,6 +15,7 @@ import org.swisspush.gateleen.validation.ValidationException; import org.swisspush.gateleen.validation.Validator; +import javax.annotation.Nullable; import java.nio.charset.Charset; import java.text.ParseException; import java.util.ArrayList; @@ -54,7 +55,7 @@ public SchedulerFactory( Vertx vertx, RedisProvider redisProvider, GateleenExceptionFactory exceptionFactory, - MonitoringHandler monitoringHandler, + @Nullable MonitoringHandler monitoringHandler, String schedulersSchema, String redisquesAddress ) { diff --git a/gateleen-scheduler/src/main/java/org/swisspush/gateleen/scheduler/SchedulerResourceManager.java b/gateleen-scheduler/src/main/java/org/swisspush/gateleen/scheduler/SchedulerResourceManager.java index da8bbbe1..a953151c 100755 --- a/gateleen-scheduler/src/main/java/org/swisspush/gateleen/scheduler/SchedulerResourceManager.java +++ b/gateleen-scheduler/src/main/java/org/swisspush/gateleen/scheduler/SchedulerResourceManager.java @@ -21,6 +21,7 @@ import org.swisspush.gateleen.monitoring.MonitoringHandler; import org.swisspush.gateleen.validation.ValidationException; +import javax.annotation.Nullable; import java.util.Collections; import java.util.List; import java.util.Map; @@ -44,17 +45,17 @@ public class SchedulerResourceManager implements Refreshable, LoggableResource { private boolean logConfigurationResourceChanges = false; public SchedulerResourceManager(Vertx vertx, RedisProvider redisProvider, final ResourceStorage storage, - MonitoringHandler monitoringHandler, String schedulersUri) { + @Nullable MonitoringHandler monitoringHandler, String schedulersUri) { this(vertx, redisProvider, storage, monitoringHandler, schedulersUri, null); } public SchedulerResourceManager(Vertx vertx, RedisProvider redisProvider, final ResourceStorage storage, - MonitoringHandler monitoringHandler, String schedulersUri, Map props) { + @Nullable MonitoringHandler monitoringHandler, String schedulersUri, Map props) { this(vertx, redisProvider, storage, monitoringHandler, schedulersUri, props, Address.redisquesAddress()); } public SchedulerResourceManager(Vertx vertx, RedisProvider redisProvider, final ResourceStorage storage, - MonitoringHandler monitoringHandler, String schedulersUri, Map props, + @Nullable MonitoringHandler monitoringHandler, String schedulersUri, Map props, String redisquesAddress) { this(vertx, redisProvider, newGateleenThriftyExceptionFactory(), storage, monitoringHandler, schedulersUri, props, redisquesAddress, Collections.emptyMap()); } @@ -64,7 +65,7 @@ public SchedulerResourceManager( RedisProvider redisProvider, GateleenExceptionFactory exceptionFactory, ResourceStorage storage, - MonitoringHandler monitoringHandler, + @Nullable MonitoringHandler monitoringHandler, String schedulersUri, Map props, String redisquesAddress, diff --git a/pom.xml b/pom.xml index b67508a7..cd834c44 100644 --- a/pom.xml +++ b/pom.xml @@ -74,6 +74,7 @@ 2.4.10 2.12.6 2.9.0 + 1.12.13 5.8.0 3.0.0 0.1.15 @@ -358,6 +359,11 @@ log4j-slf4j2-impl ${log4j-slf4j2.version} + + io.micrometer + micrometer-core + ${micrometer.version} + org.swisspush redisques From ba5f9991984306b73c3339f7b3991f636d1fb899 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Weber?= Date: Thu, 5 Dec 2024 16:46:43 +0100 Subject: [PATCH 46/47] #615 Added duration metrics of forwarded requests --- gateleen-routing/README_routing.md | 36 +++++++++---- .../gateleen/routing/AbstractForwarder.java | 19 +++---- .../swisspush/gateleen/routing/Forwarder.java | 53 +++++++++++++------ .../gateleen/routing/NullForwarder.java | 4 +- .../gateleen/routing/StorageForwarder.java | 31 +++++++++-- .../gateleen/routing/RouterTest.java | 16 +++--- 6 files changed, 109 insertions(+), 50 deletions(-) diff --git a/gateleen-routing/README_routing.md b/gateleen-routing/README_routing.md index 7886d452..3a50831a 100644 --- a/gateleen-routing/README_routing.md +++ b/gateleen-routing/README_routing.md @@ -138,27 +138,43 @@ Each request header entry is validated in the format `: `, so you ar ## Micrometer metrics The routing feature is monitored with micrometer. The following metrics are available: -* gateleen_forwarded_requests_total +* gateleen_forwarded_total +* gateleen_forwarded_seconds +* gateleen_forwarded_seconds_max +* gateleen_forwarded_seconds_count +* gateleen_forwarded_seconds_sum Additional tags are provided to split the forward count into sub counts. | tag | description | |------------|-------------------------------------------------------------------------------------------------------------------| | metricName | The `metricName` property from the corresponding routing rule. With this, you are able to count requests per rule | -| type | Describes where the request was forwarded to. Possible values are `local`, `external` and `null` | +| type | Describes where the request was forwarded to. Possible values are `local`, `external` and `null` | +| quantile | Values of `0.75` and `0.95` for percentile durations of requests | Example metrics: ``` -# HELP gateleen_forwarded_requests_total Number of forwarded requests -# TYPE gateleen_forwarded_requests_total counter -gateleen_forwarded_requests_total{metricName="infotool_v1_informations",type="external",} 678765.0 -gateleen_forwarded_requests_total{metricName="amp-mutation",type="local",} 8875.0 -gateleen_forwarded_requests_total{metricName="edds-profile-houseservice-v2",type="storage",} 7777.0 -gateleen_forwarded_requests_total{metricName="password-reset",type="external",} 4.0 -gateleen_forwarded_requests_total{metricName="inventory-v1-session-start",type="external",} 0.0 -gateleen_forwarded_requests_total{metricName="edds-azb-catdir",type="null",} 5577.0 +# HELP gateleen_forwarded_total Amount of forwarded requests +# TYPE gateleen_forwarded_total counter +gateleen_forwarded_total{metricName="storage-resources",type="storage",} 67565.0 +gateleen_forwarded_total{metricName="infotool_v1_informations",type="external",} 655.0 +gateleen_forwarded_total{metricName="infotool-v1",type="storage",} 4320.0 +# HELP gateleen_forwarded_seconds_max Durations of forwarded requests +# TYPE gateleen_forwarded_seconds_max gauge +gateleen_forwarded_seconds_max{metricName="storage-resources",type="storage",} 8.5515 +gateleen_forwarded_seconds_max{metricName="infotool_v1_informations",type="external",} 3.456 +# HELP gateleen_forwarded_seconds Durations of forwarded requests +# TYPE gateleen_forwarded_seconds summary +gateleen_forwarded_seconds{metricName="storage-resources",type="storage",quantile="0.75",} 6.2158 +gateleen_forwarded_seconds{metricName="storage-resources",type="storage",quantile="0.95",} 8.2123 +gateleen_forwarded_seconds_count{metricName="storage-resources",type="storage",} 67565.0 +gateleen_forwarded_seconds_sum{metricName="storage-resources",type="storage",} 656434.0 +gateleen_forwarded_seconds{metricName="infotool_v1_informations",type="external",quantile="0.75",} 4.2365 +gateleen_forwarded_seconds{metricName="infotool_v1_informations",type="external",quantile="0.95",} 4.8998 +gateleen_forwarded_seconds_count{metricName="infotool_v1_informations",type="external",} 7567.0 +gateleen_forwarded_seconds_sum{metricName="infotool_v1_informations",type="external",} 256324.0 ``` To enable the metrics, set a `MeterRegistry` instance by calling `withMeterRegistry(MeterRegistry meterRegistry)` method in `RouterBuilder` class. \ No newline at end of file diff --git a/gateleen-routing/src/main/java/org/swisspush/gateleen/routing/AbstractForwarder.java b/gateleen-routing/src/main/java/org/swisspush/gateleen/routing/AbstractForwarder.java index 96c57d8f..dccf549b 100644 --- a/gateleen-routing/src/main/java/org/swisspush/gateleen/routing/AbstractForwarder.java +++ b/gateleen-routing/src/main/java/org/swisspush/gateleen/routing/AbstractForwarder.java @@ -23,8 +23,10 @@ public abstract class AbstractForwarder implements Handler { protected final MonitoringHandler monitoringHandler; protected final String metricNameTag; - public static final String FORWARDER_METRIC_NAME = "gateleen.forwarded.requests"; - public static final String FORWARDER_METRIC_DESCRIPTION = "gateleen.forwarded.requests"; + public static final String FORWARDER_COUNT_METRIC_NAME = "gateleen.forwarded"; + public static final String FORWARDER_COUNT_METRIC_DESCRIPTION = "Amount of forwarded requests"; + public static final String FORWARDS_METRIC_NAME = "gateleen.forwarded.seconds"; + public static final String FORWARDS_METRIC_DESCRIPTION = "Durations of forwarded requests"; public static final String FORWARDER_METRIC_TAG_TYPE = "type"; public static final String FORWARDER_METRIC_TAG_METRICNAME = "metricName"; public static final String FORWARDER_NO_METRICNAME = "no-metric-name"; @@ -43,10 +45,10 @@ public AbstractForwarder(Rule rule, LoggingResourceManager loggingResourceManage protected boolean doHeadersFilterMatch(final HttpServerRequest request) { final Logger log = RequestLoggerFactory.getLogger(getClass(), request); - if(rule.getHeadersFilterPattern() != null){ + if (rule.getHeadersFilterPattern() != null) { log.debug("Looking for request headers with pattern {}", rule.getHeadersFilterPattern().pattern()); boolean matchFound = HttpHeaderUtil.hasMatchingHeader(request.headers(), rule.getHeadersFilterPattern()); - if(matchFound) { + if (matchFound) { log.debug("Matching request headers found"); } else { log.debug("No matching request headers found. Looking for the next routing rule"); @@ -79,11 +81,10 @@ protected void respondError(HttpServerRequest req, StatusCode statusCode) { } } - protected boolean isRequestToExternalTarget(String target) { - boolean isInternalRequest = false; - if (target != null) { - isInternalRequest = target.contains("localhost") || target.contains("127.0.0.1"); + protected String getRequestTarget(String target) { + if (target != null && (target.contains("localhost") || target.contains("127.0.0.1"))) { + return "local"; } - return !isInternalRequest; + return "external"; } } diff --git a/gateleen-routing/src/main/java/org/swisspush/gateleen/routing/Forwarder.java b/gateleen-routing/src/main/java/org/swisspush/gateleen/routing/Forwarder.java index e59ca45e..9eb2a718 100755 --- a/gateleen-routing/src/main/java/org/swisspush/gateleen/routing/Forwarder.java +++ b/gateleen-routing/src/main/java/org/swisspush/gateleen/routing/Forwarder.java @@ -2,6 +2,7 @@ import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Timer; import io.netty.channel.ConnectTimeoutException; import io.netty.handler.codec.http.HttpResponseStatus; import io.vertx.core.*; @@ -66,8 +67,9 @@ public class Forwarder extends AbstractForwarder { private static final int STATUS_CODE_2XX = 2; private static final Logger LOG = LoggerFactory.getLogger(Forwarder.class); - private Counter localForwardCounter; - private Counter externalForwardCounter; + private Counter forwardCounter; + private Timer forwardTimer; + private MeterRegistry meterRegistry; public Forwarder(Vertx vertx, HttpClient client, Rule rule, final ResourceStorage storage, LoggingResourceManager loggingResourceManager, LogAppenderRepository logAppenderRepository, @@ -95,16 +97,18 @@ public Forwarder(Vertx vertx, HttpClient client, Rule rule, final ResourceStorag */ @Override public void setMeterRegistry(MeterRegistry meterRegistry) { + this.meterRegistry = meterRegistry; if (meterRegistry != null) { - localForwardCounter = Counter.builder(FORWARDER_METRIC_NAME) - .description(FORWARDER_METRIC_DESCRIPTION) - .tag(FORWARDER_METRIC_TAG_TYPE, "local") + forwardCounter = Counter.builder(FORWARDER_COUNT_METRIC_NAME) + .description(FORWARDER_COUNT_METRIC_DESCRIPTION) + .tag(FORWARDER_METRIC_TAG_TYPE, getRequestTarget(target)) .tag(FORWARDER_METRIC_TAG_METRICNAME, metricNameTag) .register(meterRegistry); - externalForwardCounter = Counter.builder(FORWARDER_METRIC_NAME) - .description(FORWARDER_METRIC_DESCRIPTION) - .tag(FORWARDER_METRIC_TAG_TYPE, "external") + forwardTimer = Timer.builder(FORWARDS_METRIC_NAME) + .description(FORWARDS_METRIC_DESCRIPTION) + .publishPercentiles(0.75, 0.95) .tag(FORWARDER_METRIC_TAG_METRICNAME, metricNameTag) + .tag(FORWARDER_METRIC_TAG_TYPE, getRequestTarget(target)) .register(meterRegistry); } } @@ -176,7 +180,9 @@ public void handle(final RoutingContext ctx, final Buffer bodyData, @Nullable fi } target = rule.getHost() + ":" + port; - handleForwardMetrics(target); + if (forwardCounter != null) { + forwardCounter.increment(); + } if (monitoringHandler != null) { monitoringHandler.updateRequestsMeter(target, req.uri()); @@ -216,10 +222,9 @@ public void handle(final RoutingContext ctx, final Buffer bodyData, @Nullable fi }); } - private void handleForwardMetrics(String target) { - Counter counter = isRequestToExternalTarget(target) ? externalForwardCounter : localForwardCounter; - if (counter != null) { - counter.increment(); + private void handleForwardDurationMetrics(Timer.Sample timerSample) { + if (timerSample != null && forwardTimer != null) { + timerSample.stop(forwardTimer); } } @@ -280,11 +285,19 @@ private void handleRequest(final HttpServerRequest req, final Buffer bodyData, f final String uniqueId = req.headers().get("x-rp-unique_id"); final String timeout = req.headers().get("x-timeout"); Long startTime = null; + + Timer.Sample timerSample = null; + if (meterRegistry != null) { + timerSample = Timer.start(meterRegistry); + } + if (monitoringHandler != null) { startTime = monitoringHandler.startRequestMetricTracking(rule.getMetricName(), req.uri()); } Long finalStartTime = startTime; + Timer.Sample finalTimerSample = timerSample; + client.request(req.method(), port, rule.getHost(), targetUri, new Handler<>() { @Override public void handle(AsyncResult event) { @@ -299,7 +312,7 @@ public void handle(AsyncResult event) { return; } HttpClientRequest cReq = event.result(); - final Handler> cResHandler = getAsyncHttpClientResponseHandler(req, targetUri, log, profileHeaderMap, loggingHandler, finalStartTime, afterHandler); + final Handler> cResHandler = getAsyncHttpClientResponseHandler(req, targetUri, log, profileHeaderMap, loggingHandler, finalStartTime, finalTimerSample, afterHandler); cReq.response(cResHandler); if (timeout != null) { @@ -333,7 +346,7 @@ public void handle(AsyncResult event) { return; } - installExceptionHandler(req, targetUri, finalStartTime, cReq); + installExceptionHandler(req, targetUri, finalStartTime, finalTimerSample, cReq); /* * If no bodyData is available @@ -464,11 +477,14 @@ private void setProfileHeaders(Logger log, Map profileHeaderMap, } } - private void installExceptionHandler(final HttpServerRequest req, final String targetUri, final Long startTime, HttpClientRequest cReq) { + private void installExceptionHandler(final HttpServerRequest req, final String targetUri, final Long startTime, @Nullable Timer.Sample timerSample, HttpClientRequest cReq) { cReq.exceptionHandler(exception -> { if (monitoringHandler != null && startTime != null) { monitoringHandler.stopRequestMetricTracking(rule.getMetricName(), startTime, req.uri()); } + + handleForwardDurationMetrics(timerSample); + if (exception instanceof TimeoutException) { error("Timeout", req, targetUri); respondError(req, StatusCode.TIMEOUT); @@ -490,7 +506,7 @@ private void installExceptionHandler(final HttpServerRequest req, final String t }); } - private Handler> getAsyncHttpClientResponseHandler(final HttpServerRequest req, final String targetUri, final Logger log, final Map profileHeaderMap, final LoggingHandler loggingHandler, @Nullable final Long startTime, @Nullable final Handler afterHandler) { + private Handler> getAsyncHttpClientResponseHandler(final HttpServerRequest req, final String targetUri, final Logger log, final Map profileHeaderMap, final LoggingHandler loggingHandler, @Nullable final Long startTime, @Nullable Timer.Sample timerSample, @Nullable final Handler afterHandler) { return asyncResult -> { HttpClientResponse cRes = asyncResult.result(); if (asyncResult.failed()) { @@ -510,6 +526,9 @@ private Handler> getAsyncHttpClientResponseHandl if (monitoringHandler != null) { monitoringHandler.stopRequestMetricTracking(rule.getMetricName(), startTime, req.uri()); } + + handleForwardDurationMetrics(timerSample); + loggingHandler.setResponse(cRes); req.response().setStatusCode(cRes.statusCode()); req.response().setStatusMessage(cRes.statusMessage()); diff --git a/gateleen-routing/src/main/java/org/swisspush/gateleen/routing/NullForwarder.java b/gateleen-routing/src/main/java/org/swisspush/gateleen/routing/NullForwarder.java index 2224c9d2..bed0d498 100755 --- a/gateleen-routing/src/main/java/org/swisspush/gateleen/routing/NullForwarder.java +++ b/gateleen-routing/src/main/java/org/swisspush/gateleen/routing/NullForwarder.java @@ -46,8 +46,8 @@ public NullForwarder(Rule rule, LoggingResourceManager loggingResourceManager, L @Override public void setMeterRegistry(MeterRegistry meterRegistry) { if(meterRegistry != null) { - forwardCounter = Counter.builder(FORWARDER_METRIC_NAME) - .description(FORWARDER_METRIC_DESCRIPTION) + forwardCounter = Counter.builder(FORWARDER_COUNT_METRIC_NAME) + .description(FORWARDER_COUNT_METRIC_DESCRIPTION) .tag(FORWARDER_METRIC_TAG_METRICNAME, metricNameTag) .tag(FORWARDER_METRIC_TAG_TYPE, "null") .register(meterRegistry); diff --git a/gateleen-routing/src/main/java/org/swisspush/gateleen/routing/StorageForwarder.java b/gateleen-routing/src/main/java/org/swisspush/gateleen/routing/StorageForwarder.java index c7324cf4..32a0f38a 100755 --- a/gateleen-routing/src/main/java/org/swisspush/gateleen/routing/StorageForwarder.java +++ b/gateleen-routing/src/main/java/org/swisspush/gateleen/routing/StorageForwarder.java @@ -2,6 +2,7 @@ import io.micrometer.core.instrument.Counter; import io.micrometer.core.instrument.MeterRegistry; +import io.micrometer.core.instrument.Timer; import io.netty.handler.codec.http.HttpResponseStatus; import io.vertx.core.AsyncResult; import io.vertx.core.Handler; @@ -47,6 +48,10 @@ public class StorageForwarder extends AbstractForwarder { private GateleenExceptionFactory gateleenExceptionFactory; private Counter forwardCounter; + private Timer forwardTimer; + private MeterRegistry meterRegistry; + + private static final String TYPE_STORAGE = "storage"; public StorageForwarder(EventBus eventBus, Rule rule, LoggingResourceManager loggingResourceManager, LogAppenderRepository logAppenderRepository, @Nullable MonitoringHandler monitoringHandler, @@ -68,11 +73,19 @@ public StorageForwarder(EventBus eventBus, Rule rule, LoggingResourceManager log */ @Override public void setMeterRegistry(MeterRegistry meterRegistry) { + this.meterRegistry = meterRegistry; if (meterRegistry != null) { - forwardCounter = Counter.builder(FORWARDER_METRIC_NAME) - .description(FORWARDER_METRIC_DESCRIPTION) + forwardCounter = Counter.builder(FORWARDER_COUNT_METRIC_NAME) + .description(FORWARDER_COUNT_METRIC_DESCRIPTION) .tag(FORWARDER_METRIC_TAG_METRICNAME, metricNameTag) - .tag(FORWARDER_METRIC_TAG_TYPE, "storage") + .tag(FORWARDER_METRIC_TAG_TYPE, TYPE_STORAGE) + .register(meterRegistry); + + forwardTimer = Timer.builder(FORWARDS_METRIC_NAME) + .description(FORWARDS_METRIC_DESCRIPTION) + .publishPercentiles(0.75, 0.95) + .tag(FORWARDER_METRIC_TAG_METRICNAME, metricNameTag) + .tag(FORWARDER_METRIC_TAG_TYPE, TYPE_STORAGE) .register(meterRegistry); } } @@ -90,7 +103,9 @@ public void handle(final RoutingContext ctx) { Long startTime = null; - if (forwardCounter != null) { + Timer.Sample timerSample = null; + if(meterRegistry != null) { + timerSample = Timer.start(meterRegistry); forwardCounter.increment(); } @@ -124,6 +139,9 @@ public void handle(final RoutingContext ctx) { requestBuffer.appendBuffer(buffer); }); Long finalStartTime = startTime; + + Timer.Sample finalTimerSample = timerSample; + ctx.request().endHandler(event -> eventBus.request(address, requestBuffer, new DeliveryOptions().setSendTimeout(10000), (Handler>>) result -> { @@ -131,6 +149,11 @@ public void handle(final RoutingContext ctx) { if (monitoringHandler != null) { monitoringHandler.stopRequestMetricTracking(rule.getMetricName(), finalStartTime, ctx.request().uri()); } + + if(finalTimerSample != null) { + finalTimerSample.stop(forwardTimer); + } + if (result.failed()) { String statusMessage = "Storage request for " + ctx.request().uri() + " failed with message: " + result.cause().getMessage(); response.setStatusCode(StatusCode.INTERNAL_SERVER_ERROR.getStatusCode()); diff --git a/gateleen-routing/src/test/java/org/swisspush/gateleen/routing/RouterTest.java b/gateleen-routing/src/test/java/org/swisspush/gateleen/routing/RouterTest.java index 2d789b95..2c90dfbc 100755 --- a/gateleen-routing/src/test/java/org/swisspush/gateleen/routing/RouterTest.java +++ b/gateleen-routing/src/test/java/org/swisspush/gateleen/routing/RouterTest.java @@ -172,7 +172,7 @@ private RouterBuilder routerBuilder() { } private void assertNoCountersIncremented(TestContext context) { - for (Counter counter : meterRegistry.get(AbstractForwarder.FORWARDER_METRIC_NAME).counters()) { + for (Counter counter : meterRegistry.get(AbstractForwarder.FORWARDER_COUNT_METRIC_NAME).counters()) { context.assertEquals(0.0, counter.count(), "No counter should have been incremented"); } } @@ -249,7 +249,7 @@ public DummyHttpServerResponse response() { GETRandomResourceRequest request = new GETRandomResourceRequest(); router.route(request); - Counter counter = meterRegistry.get(AbstractForwarder.FORWARDER_METRIC_NAME).tag(AbstractForwarder.FORWARDER_METRIC_TAG_METRICNAME, "loop_4").counter(); + Counter counter = meterRegistry.get(AbstractForwarder.FORWARDER_COUNT_METRIC_NAME).tag(AbstractForwarder.FORWARDER_METRIC_TAG_METRICNAME, "loop_4").counter(); context.assertEquals(1.0, counter.count(), "Counter for `loop_4` rule should have been incremented by 1"); context.assertEquals("1", request.headers().get("x-hops"), "x-hops header should have value 1"); @@ -419,7 +419,7 @@ public DummyHttpServerResponse response() { context.assertEquals(StatusCode.INTERNAL_SERVER_ERROR.getStatusCode(), request.response().getStatusCode(), "StatusCode should be 500"); context.assertEquals("Request hops limit exceeded", request.response().getStatusMessage(), "StatusMessage should be 'Request hops limit exceeded'"); - Counter counter = meterRegistry.get(AbstractForwarder.FORWARDER_METRIC_NAME).tag(AbstractForwarder.FORWARDER_METRIC_TAG_METRICNAME, "loop_4").counter(); + Counter counter = meterRegistry.get(AbstractForwarder.FORWARDER_COUNT_METRIC_NAME).tag(AbstractForwarder.FORWARDER_METRIC_TAG_METRICNAME, "loop_4").counter(); context.assertEquals(5.0, counter.count(), "Counter for `loop_4` rule should have been incremented by 5"); } @@ -496,7 +496,7 @@ public DummyHttpServerResponse response() { context.assertEquals(StatusCode.OK.getStatusCode(), request.response().getStatusCode(), "StatusCode should be 200"); context.assertEquals(StatusCode.OK.getStatusMessage(), request.response().getStatusMessage(), "StatusMessage should be OK"); - Counter counter = meterRegistry.get(AbstractForwarder.FORWARDER_METRIC_NAME).tag(AbstractForwarder.FORWARDER_METRIC_TAG_METRICNAME, "loop_4").counter(); + Counter counter = meterRegistry.get(AbstractForwarder.FORWARDER_COUNT_METRIC_NAME).tag(AbstractForwarder.FORWARDER_METRIC_TAG_METRICNAME, "loop_4").counter(); context.assertEquals(20.0, counter.count(), "Counter for `loop_4` rule should have been incremented by 20"); } @@ -773,7 +773,7 @@ public HttpServerRequest pause() { context.assertFalse(router.isRoutingBroken(), "Routing should not be broken anymore"); context.assertNull(router.getRoutingBrokenMessage(), "RoutingBrokenMessage should be null"); - Counter counter = meterRegistry.get(AbstractForwarder.FORWARDER_METRIC_NAME).tag(AbstractForwarder.FORWARDER_METRIC_TAG_TYPE, "local").counter(); + Counter counter = meterRegistry.get(AbstractForwarder.FORWARDER_COUNT_METRIC_NAME).tag(AbstractForwarder.FORWARDER_METRIC_TAG_TYPE, "local").counter(); context.assertEquals(1.0, counter.count(), "Counter should have been incremented by 1"); } @@ -908,7 +908,7 @@ public void testStorageRequestWithHeadersFilterPresent(TestContext context) { context.assertEquals(StatusCode.OK.getStatusCode(), request.response().getStatusCode(), "StatusCode should be 200"); context.assertEquals(StatusCode.OK.getStatusMessage(), request.response().getStatusMessage(), "StatusMessage should be OK"); - Counter counter = meterRegistry.get(AbstractForwarder.FORWARDER_METRIC_NAME).tag(AbstractForwarder.FORWARDER_METRIC_TAG_METRICNAME, "forward_storage").counter(); + Counter counter = meterRegistry.get(AbstractForwarder.FORWARDER_COUNT_METRIC_NAME).tag(AbstractForwarder.FORWARDER_METRIC_TAG_METRICNAME, "forward_storage").counter(); context.assertEquals(1.0, counter.count(), "Counter for `forward_storage` rule should have been incremented by 1"); } @@ -982,7 +982,7 @@ public void testNullForwarderRequestWithHeadersFilterPresent(TestContext context context.assertEquals(StatusCode.OK.getStatusCode(), request.response().getStatusCode(), "StatusCode should be 200"); context.assertEquals(StatusCode.OK.getStatusMessage(), request.response().getStatusMessage(), "StatusMessage should be OK"); - Counter counter = meterRegistry.get(AbstractForwarder.FORWARDER_METRIC_NAME).tag(AbstractForwarder.FORWARDER_METRIC_TAG_METRICNAME, "forward_null").counter(); + Counter counter = meterRegistry.get(AbstractForwarder.FORWARDER_COUNT_METRIC_NAME).tag(AbstractForwarder.FORWARDER_METRIC_TAG_METRICNAME, "forward_null").counter(); context.assertEquals(1.0, counter.count(), "Counter for `forward_null` rule should have been incremented by 1"); } @@ -1010,7 +1010,7 @@ public void testForwarderRequestWithHeadersFilterPresent(TestContext context) { // however, this means that the headersFilter evaluation did not return a 400 Bad Request context.assertEquals(StatusCode.INTERNAL_SERVER_ERROR.getStatusCode(), request.response().getStatusCode(), "StatusCode should be 500"); - Counter counter = meterRegistry.get(AbstractForwarder.FORWARDER_METRIC_NAME).tag(AbstractForwarder.FORWARDER_METRIC_TAG_METRICNAME, "forward_backend").counter(); + Counter counter = meterRegistry.get(AbstractForwarder.FORWARDER_COUNT_METRIC_NAME).tag(AbstractForwarder.FORWARDER_METRIC_TAG_METRICNAME, "forward_backend").counter(); context.assertEquals(1.0, counter.count(), "Counter for `forward_backend` rule should have been incremented by 1"); } From 06e4ed9f61a1d3cbd61d4241a506b7b2fe83e011 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Marc-Andr=C3=A9=20Weber?= Date: Mon, 9 Dec 2024 15:08:30 +0100 Subject: [PATCH 47/47] #615 Added `x-originally-queued` header to queued requests to distinguish them from `enqueuing` requests --- .../org/swisspush/gateleen/queue/queuing/QueuingHandler.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/gateleen-queue/src/main/java/org/swisspush/gateleen/queue/queuing/QueuingHandler.java b/gateleen-queue/src/main/java/org/swisspush/gateleen/queue/queuing/QueuingHandler.java index 9b342f49..00421e93 100755 --- a/gateleen-queue/src/main/java/org/swisspush/gateleen/queue/queuing/QueuingHandler.java +++ b/gateleen-queue/src/main/java/org/swisspush/gateleen/queue/queuing/QueuingHandler.java @@ -26,6 +26,7 @@ public class QueuingHandler implements Handler { public static final String QUEUE_HEADER = "x-queue"; + public static final String ORIGINALLY_QUEUED_HEADER = "x-originally-queued"; public static final String DUPLICATE_CHECK_HEADER = "x-duplicate-check"; private final RequestQueue requestQueue; @@ -86,6 +87,9 @@ public void handle(final Buffer buffer) { // Remove the queue header to avoid feedback loop headers.remove(QUEUE_HEADER); + // Add a header to indicate that this request was initially queued + headers.add(ORIGINALLY_QUEUED_HEADER, "true"); + if (headers.names().contains(DUPLICATE_CHECK_HEADER)) { DuplicateCheckHandler.checkDuplicateRequest(redisProvider, request.uri(), buffer, headers.get(DUPLICATE_CHECK_HEADER), requestIsDuplicate -> { if (requestIsDuplicate) {