From 7b9ba7c944e1f3c9dd846eda457d7996af7bf8e5 Mon Sep 17 00:00:00 2001 From: Saadha123 Date: Fri, 28 Aug 2026 11:51:49 +0530 Subject: [PATCH 1/5] test(ratelimit): add IT scenario for match condition on keyExtraction --- gateway/it/features/ratelimit.feature | 55 +++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/gateway/it/features/ratelimit.feature b/gateway/it/features/ratelimit.feature index a4f2f75b29..570c2f618c 100644 --- a/gateway/it/features/ratelimit.feature +++ b/gateway/it/features/ratelimit.feature @@ -757,6 +757,61 @@ Feature: Rate Limiting When I send a GET request to "http://localhost:8080/ratelimit-peruser/v1.0/user" with header "X-User-ID" value "user-B" Then the response status code should be 429 + Scenario: Match condition restricts a quota to requests with a matching key value + Given I authenticate using basic auth as "admin" + When I deploy this API configuration: + """ + apiVersion: gateway.api-platform.wso2.com/v1 + kind: RestApi + metadata: + name: ratelimit-match-api + spec: + displayName: RateLimit Match API + version: v1.0 + context: /ratelimit-match/$version + upstream: + main: + url: http://sample-backend:9080/api/v1 + operations: + - method: GET + path: /checkout + policies: + - name: advanced-ratelimit + version: v1 + params: + quotas: + - name: guest-and-partner-checkout + limits: + - limit: 3 + duration: "1h" + keyExtraction: + - type: header + key: X-App-ID + match: + type: regex + value: "^(guest-.*|channel-partner)$" + """ + Then the response should be successful + And I wait for the endpoint "http://localhost:8080/ratelimit-match/v1.0/checkout" to be ready + + # A matching client ID (guest-*) is counted and enforced + When I send 3 GET requests to "http://localhost:8080/ratelimit-match/v1.0/checkout" with header "X-App-ID" value "guest-42" + Then the response status code should be 200 + + When I send a GET request to "http://localhost:8080/ratelimit-match/v1.0/checkout" with header "X-App-ID" value "guest-42" + Then the response status code should be 429 + + # A different matching client ID (exact match) gets its own separate bucket + When I send 3 GET requests to "http://localhost:8080/ratelimit-match/v1.0/checkout" with header "X-App-ID" value "channel-partner" + Then the response status code should be 200 + + When I send a GET request to "http://localhost:8080/ratelimit-match/v1.0/checkout" with header "X-App-ID" value "channel-partner" + Then the response status code should be 429 + + # A non-matching client ID bypasses the quota entirely - never throttled, no matter how many requests + When I send 6 GET requests to "http://localhost:8080/ratelimit-match/v1.0/checkout" with header "X-App-ID" value "internal-app-1" + Then the response status code should be 200 + Scenario: Multiple limits per quota - enforces most restrictive limit Given I authenticate using basic auth as "admin" When I deploy this API configuration: From 9c56799ac78dfe2d076848a648bc402bd9b34f21 Mon Sep 17 00:00:00 2001 From: Saadha123 Date: Wed, 2 Sep 2026 13:48:19 +0530 Subject: [PATCH 2/5] test(ratelimit): add IT scenario for authproperty keyExtraction type --- gateway/it/features/ratelimit.feature | 78 +++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/gateway/it/features/ratelimit.feature b/gateway/it/features/ratelimit.feature index 570c2f618c..284b3a93c4 100644 --- a/gateway/it/features/ratelimit.feature +++ b/gateway/it/features/ratelimit.feature @@ -812,6 +812,84 @@ Feature: Rate Limiting When I send 6 GET requests to "http://localhost:8080/ratelimit-match/v1.0/checkout" with header "X-App-ID" value "internal-app-1" Then the response status code should be 200 + Scenario: authproperty keyExtraction with match restricts a quota to requests with a matching JWT claim + Given I authenticate using basic auth as "admin" + When I deploy this API configuration: + """ + apiVersion: gateway.api-platform.wso2.com/v1 + kind: RestApi + metadata: + name: ratelimit-authproperty-api + spec: + displayName: RateLimit AuthProperty API + version: v1.0 + context: /ratelimit-authproperty/$version + upstream: + main: + url: http://sample-backend:9080/api/v1 + operations: + - method: GET + path: /checkout + policies: + - name: jwt-auth + version: v1 + params: + issuers: + - mock-jwks + - name: advanced-ratelimit + version: v1 + params: + quotas: + - name: guest-and-partner-checkout + limits: + - limit: 3 + duration: "1h" + keyExtraction: + - type: authproperty + key: app_id + match: + type: regex + value: "^(guest-.*|channel-partner)$" + """ + Then the response should be successful + + # A JWT with a matching app_id claim is counted and enforced + When I get a JWT token from the mock JWKS server with issuer "http://mock-jwks:8080/token" and claims "app_id=guest-42" + And I send a GET request to "http://localhost:8080/ratelimit-authproperty/v1.0/checkout" with the JWT token + Then the response status code should be 200 + When I send a GET request to "http://localhost:8080/ratelimit-authproperty/v1.0/checkout" with the JWT token + Then the response status code should be 200 + When I send a GET request to "http://localhost:8080/ratelimit-authproperty/v1.0/checkout" with the JWT token + Then the response status code should be 200 + When I send a GET request to "http://localhost:8080/ratelimit-authproperty/v1.0/checkout" with the JWT token + Then the response status code should be 429 + + # A different matching app_id claim (exact match) gets its own separate bucket + When I get a JWT token from the mock JWKS server with issuer "http://mock-jwks:8080/token" and claims "app_id=channel-partner" + And I send a GET request to "http://localhost:8080/ratelimit-authproperty/v1.0/checkout" with the JWT token + Then the response status code should be 200 + When I send a GET request to "http://localhost:8080/ratelimit-authproperty/v1.0/checkout" with the JWT token + Then the response status code should be 200 + When I send a GET request to "http://localhost:8080/ratelimit-authproperty/v1.0/checkout" with the JWT token + Then the response status code should be 200 + When I send a GET request to "http://localhost:8080/ratelimit-authproperty/v1.0/checkout" with the JWT token + Then the response status code should be 429 + + # A non-matching app_id claim bypasses the quota entirely - never throttled, no matter how many requests + When I get a JWT token from the mock JWKS server with issuer "http://mock-jwks:8080/token" and claims "app_id=internal-app-1" + And I send a GET request to "http://localhost:8080/ratelimit-authproperty/v1.0/checkout" with the JWT token + Then the response status code should be 200 + When I send a GET request to "http://localhost:8080/ratelimit-authproperty/v1.0/checkout" with the JWT token + Then the response status code should be 200 + When I send a GET request to "http://localhost:8080/ratelimit-authproperty/v1.0/checkout" with the JWT token + Then the response status code should be 200 + When I send a GET request to "http://localhost:8080/ratelimit-authproperty/v1.0/checkout" with the JWT token + Then the response status code should be 200 + When I send a GET request to "http://localhost:8080/ratelimit-authproperty/v1.0/checkout" with the JWT token + Then the response status code should be 200 + When I send a GET request to "http://localhost:8080/ratelimit-authproperty/v1.0/checkout" with the JWT token + Then the response status code should be 200 + Scenario: Multiple limits per quota - enforces most restrictive limit Given I authenticate using basic auth as "admin" When I deploy this API configuration: From d1059c051be7e84f45ed522b513172a3ff1076c7 Mon Sep 17 00:00:00 2001 From: Saadha123 Date: Wed, 2 Sep 2026 13:54:07 +0530 Subject: [PATCH 3/5] chore: bump advanced-ratelimit to released v1.2.0 --- gateway/build-manifest.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gateway/build-manifest.yaml b/gateway/build-manifest.yaml index 1524b5423b..78b9cff73b 100644 --- a/gateway/build-manifest.yaml +++ b/gateway/build-manifest.yaml @@ -1,7 +1,7 @@ version: v1 policies: - name: advanced-ratelimit - version: v1.1.2 + version: v1.2.0 gomodule: github.com/wso2/gateway-controllers/policies/advanced-ratelimit@v1 - name: analytics-header-filter version: v1.0.1 From bac419e4ec77388ce2bd1332a7c58edba57c4ba8 Mon Sep 17 00:00:00 2001 From: Saadha123 Date: Thu, 3 Sep 2026 10:40:45 +0530 Subject: [PATCH 4/5] test(ratelimit): add IT scenarios for filter-by-app/count-by-user and per-customer quotas --- gateway/it/features/ratelimit.feature | 152 ++++++++++++++++++++++++++ 1 file changed, 152 insertions(+) diff --git a/gateway/it/features/ratelimit.feature b/gateway/it/features/ratelimit.feature index 284b3a93c4..ad54583af9 100644 --- a/gateway/it/features/ratelimit.feature +++ b/gateway/it/features/ratelimit.feature @@ -890,6 +890,158 @@ Feature: Rate Limiting When I send a GET request to "http://localhost:8080/ratelimit-authproperty/v1.0/checkout" with the JWT token Then the response status code should be 200 + Scenario: Match filters by one JWT claim while the bucket counts by a different JWT claim + Given I authenticate using basic auth as "admin" + When I deploy this API configuration: + """ + apiVersion: gateway.api-platform.wso2.com/v1 + kind: RestApi + metadata: + name: ratelimit-filter-count-api + spec: + displayName: RateLimit Filter-Count API + version: v1.0 + context: /ratelimit-filter-count/$version + upstream: + main: + url: http://sample-backend:9080/api/v1 + operations: + - method: GET + path: /checkout + policies: + - name: jwt-auth + version: v1 + params: + issuers: + - mock-jwks + - name: advanced-ratelimit + version: v1 + params: + quotas: + - name: guests-per-user + limits: + - limit: 3 + duration: "1h" + keyExtraction: + - type: authproperty + key: app_id + match: + type: regex + value: "^123$" + - type: authproperty + key: user_id + """ + Then the response should be successful + + # app_id "123" matches - alice's requests are counted and enforced + When I get a JWT token from the mock JWKS server with issuer "http://mock-jwks:8080/token" and claims "app_id=123,user_id=alice" + And I send a GET request to "http://localhost:8080/ratelimit-filter-count/v1.0/checkout" with the JWT token + Then the response status code should be 200 + When I send a GET request to "http://localhost:8080/ratelimit-filter-count/v1.0/checkout" with the JWT token + Then the response status code should be 200 + When I send a GET request to "http://localhost:8080/ratelimit-filter-count/v1.0/checkout" with the JWT token + Then the response status code should be 200 + When I send a GET request to "http://localhost:8080/ratelimit-filter-count/v1.0/checkout" with the JWT token + Then the response status code should be 429 + + # bob is behind the same matching app_id "123" but gets his own independent counter + When I get a JWT token from the mock JWKS server with issuer "http://mock-jwks:8080/token" and claims "app_id=123,user_id=bob" + And I send a GET request to "http://localhost:8080/ratelimit-filter-count/v1.0/checkout" with the JWT token + Then the response status code should be 200 + When I send a GET request to "http://localhost:8080/ratelimit-filter-count/v1.0/checkout" with the JWT token + Then the response status code should be 200 + When I send a GET request to "http://localhost:8080/ratelimit-filter-count/v1.0/checkout" with the JWT token + Then the response status code should be 200 + When I send a GET request to "http://localhost:8080/ratelimit-filter-count/v1.0/checkout" with the JWT token + Then the response status code should be 429 + + # alice is still blocked - bob's traffic never touched her counter + When I get a JWT token from the mock JWKS server with issuer "http://mock-jwks:8080/token" and claims "app_id=123,user_id=alice" + And I send a GET request to "http://localhost:8080/ratelimit-filter-count/v1.0/checkout" with the JWT token + Then the response status code should be 429 + + # a non-matching app_id bypasses the quota entirely, no matter the user - never throttled + When I get a JWT token from the mock JWKS server with issuer "http://mock-jwks:8080/token" and claims "app_id=456,user_id=alice" + And I send a GET request to "http://localhost:8080/ratelimit-filter-count/v1.0/checkout" with the JWT token + Then the response status code should be 200 + When I send a GET request to "http://localhost:8080/ratelimit-filter-count/v1.0/checkout" with the JWT token + Then the response status code should be 200 + When I send a GET request to "http://localhost:8080/ratelimit-filter-count/v1.0/checkout" with the JWT token + Then the response status code should be 200 + + # alice's app_id "123" counter is still exhausted - the non-matching app-456 traffic above + # did not interfere with it + When I get a JWT token from the mock JWKS server with issuer "http://mock-jwks:8080/token" and claims "app_id=123,user_id=alice" + And I send a GET request to "http://localhost:8080/ratelimit-filter-count/v1.0/checkout" with the JWT token + Then the response status code should be 429 + + Scenario: Independent per-customer quotas each with their own match pattern and limit + Given I authenticate using basic auth as "admin" + When I deploy this API configuration: + """ + apiVersion: gateway.api-platform.wso2.com/v1 + kind: RestApi + metadata: + name: ratelimit-per-customer-api + spec: + displayName: RateLimit Per-Customer API + version: v1.0 + context: /ratelimit-per-customer/$version + upstream: + main: + url: http://sample-backend:9080/api/v1 + operations: + - method: GET + path: /checkout + policies: + - name: advanced-ratelimit + version: v1 + params: + quotas: + - name: customer-abc + limits: + - limit: 2 + duration: "1h" + keyExtraction: + - type: header + key: X-App-ID + match: + type: regex + value: "^abc-.*$" + - name: customer-xyz + limits: + - limit: 3 + duration: "1h" + keyExtraction: + - type: header + key: X-App-ID + match: + type: regex + value: "^xyz-.*$" + """ + Then the response should be successful + And I wait for the endpoint "http://localhost:8080/ratelimit-per-customer/v1.0/checkout" to be ready + + # customer-abc has its own 2/hour limit + When I send 2 GET requests to "http://localhost:8080/ratelimit-per-customer/v1.0/checkout" with header "X-App-ID" value "abc-1" + Then the response status code should be 200 + When I send a GET request to "http://localhost:8080/ratelimit-per-customer/v1.0/checkout" with header "X-App-ID" value "abc-1" + Then the response status code should be 429 + + # customer-xyz has its own separate 3/hour limit, unaffected by customer-abc being exhausted + When I send 3 GET requests to "http://localhost:8080/ratelimit-per-customer/v1.0/checkout" with header "X-App-ID" value "xyz-1" + Then the response status code should be 200 + When I send a GET request to "http://localhost:8080/ratelimit-per-customer/v1.0/checkout" with header "X-App-ID" value "xyz-1" + Then the response status code should be 429 + + # customer-abc is still blocked - customer-xyz's traffic never touched its counter + When I send a GET request to "http://localhost:8080/ratelimit-per-customer/v1.0/checkout" with header "X-App-ID" value "abc-1" + Then the response status code should be 429 + + # a client ID matching neither quota's pattern is never throttled by either one + When I send 6 GET requests to "http://localhost:8080/ratelimit-per-customer/v1.0/checkout" with header "X-App-ID" value "internal-app-1" + Then the response status code should be 200 + Scenario: Multiple limits per quota - enforces most restrictive limit Given I authenticate using basic auth as "admin" When I deploy this API configuration: From 1dcabcdee1ea04a07a1b6d1eba5918b5df6ef8c1 Mon Sep 17 00:00:00 2001 From: Saadha123 Date: Thu, 3 Sep 2026 11:02:30 +0530 Subject: [PATCH 5/5] test(ratelimit): assert a 4th non-matching request also bypasses the quota --- gateway/it/features/ratelimit.feature | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/gateway/it/features/ratelimit.feature b/gateway/it/features/ratelimit.feature index ad54583af9..35453d175c 100644 --- a/gateway/it/features/ratelimit.feature +++ b/gateway/it/features/ratelimit.feature @@ -960,7 +960,9 @@ Feature: Rate Limiting And I send a GET request to "http://localhost:8080/ratelimit-filter-count/v1.0/checkout" with the JWT token Then the response status code should be 429 - # a non-matching app_id bypasses the quota entirely, no matter the user - never throttled + # a non-matching app_id bypasses the quota entirely, no matter the user - never throttled. + # A 4th request beyond the quota's limit of 3 must still be 200, proving this is a true + # bypass and not just a separate 3-request bucket (as bob's case above legitimately is). When I get a JWT token from the mock JWKS server with issuer "http://mock-jwks:8080/token" and claims "app_id=456,user_id=alice" And I send a GET request to "http://localhost:8080/ratelimit-filter-count/v1.0/checkout" with the JWT token Then the response status code should be 200 @@ -968,6 +970,8 @@ Feature: Rate Limiting Then the response status code should be 200 When I send a GET request to "http://localhost:8080/ratelimit-filter-count/v1.0/checkout" with the JWT token Then the response status code should be 200 + When I send a GET request to "http://localhost:8080/ratelimit-filter-count/v1.0/checkout" with the JWT token + Then the response status code should be 200 # alice's app_id "123" counter is still exhausted - the non-matching app-456 traffic above # did not interfere with it