Skip to content

fix(gdpr): consent cookie domain + cached page banner + anonymous nonce (#240, #241)#251

Merged
parhumm merged 19 commits intodevelopmentfrom
fix/gdpr-banner-consent-240-241
Mar 20, 2026
Merged

fix(gdpr): consent cookie domain + cached page banner + anonymous nonce (#240, #241)#251
parhumm merged 19 commits intodevelopmentfrom
fix/gdpr-banner-consent-240-241

Conversation

@parhumm
Copy link
Copy Markdown
Contributor

@parhumm parhumm commented Mar 19, 2026

Summary

Fixes two related GDPR consent banner bugs and hardens nonce verification.

Changes

File Change
wp-slimstat.php Add gdpr_cookie_domain param to SlimStatParams
wp-slimstat.js Cookie domain + cached page guard + conditional X-WP-Nonce header
ConsentChangeRestController.php Add nonce verification comment (already verified pre-PR)
GDPRBannerRestController.php Keep nonce required + verification for all users
wp-slimstat.min.js Rebuilt with all JS fixes
composer.json Register test:gdpr-consent in test scripts
tests/gdpr-consent-cookie-test.php 19 assertions: GDPRService ops + handler nonce verification
tests/e2e/gdpr-banner-consent-persistence.spec.ts 8 production-scenario E2E tests

Cached page flow after this PR

  1. User visits cached page (banner HTML baked in, nonce may be stale/empty)
  2. JS checks for existing slimstat_gdpr_consent cookie via inline regex
  3. If cookie exists: removes banner from DOM, returns early
  4. If no cookie: banner shown, user clicks Accept/Deny
  5. JS sets consent cookie with proper domain attribute
  6. JS sends consent-change REST request WITHOUT X-WP-Nonce header (anonymous)
  7. Server-side consent-change may return 403 (nonce required for CSRF protection)
  8. Client-side cookie is the source of truth for consent
  9. Tracking works via /hit endpoint (PR Fix weekly chart not showing today's data #235 nonce skip for tracking)
  10. On next page load, PHP sees cookie and acts accordingly

Test plan

  • Unit tests: 9 suites, 142 total assertions (all pass)
  • E2E tests: 8 scenarios (all pass on fix branch)
  • Baseline comparison: Tests 3-4 fail on development, pass on fix branch
  • Security review: nonce verification restored for all consent endpoints
  • JS review: X-WP-Nonce header skipped when empty (P1 finding)
  • esbuild scope fix: inline cookie check avoids renamed function reference

Fixes #240, fixes #241

Summary by CodeRabbit

  • New Features

    • GDPR banner now persists across cached pages and multi-page navigation, avoiding redundant re-display when consent exists.
    • Consent cookie supports an optional configurable cookie domain to better handle subdomain/hosting setups.
  • Bug Fixes

    • Banner initialization skips when an existing consent cookie is present to prevent flicker on cached content.
  • Tests

    • Added end-to-end and PHP tests covering consent persistence, cookie behavior, domain handling, and nonce enforcement.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 19, 2026

Caution

Review failed

Pull request was closed or merged during review

📝 Walkthrough

Walkthrough

Detects and suppresses GDPR banner when a consent cookie exists on cached pages, makes client-side REST nonce header conditional, adds optional cookie domain support, and adds extensive PHP, e2e, and standalone tests covering consent persistence and nonce behavior.

Changes

Cohort / File(s) Summary
Composer & standalone test
composer.json, tests/gdpr-consent-cookie-test.php
Added test:gdpr-consent script and a new PHP standalone test that stubs WP functions to validate GDPR cookie behavior and REST nonce enforcement.
E2E test suite
tests/e2e/gdpr-banner-consent-persistence.spec.ts
Added comprehensive Playwright spec (8 scenarios) validating banner persistence on cached pages, stale nonces, cookie-domain correctness, and downstream tracking effects.
Client-side banner & consent logic
wp-slimstat.js
Make X-WP-Nonce header conditional (only when nonce non-empty); detect existing consent cookie on init and remove banner early; allow optional gdpr_cookie_domain when writing consent cookie.
Server-side localization param
wp-slimstat.php
Expose new gdpr_cookie_domain to JS localization (uses COOKIE_DOMAIN when defined, else empty).
Controller inline docs
src/Controllers/Rest/ConsentChangeRestController.php, src/Controllers/Rest/GDPRBannerRestController.php
Expanded/clarified comments about nonce verification and expected behavior for anonymous requests; no functional signature changes.

Sequence Diagram(s)

sequenceDiagram
    participant Browser
    participant JS as wp-slimstat.js
    participant REST as /slimstat/v1/consent-change
    participant PHP as ConsentChangeRestController
    participant DB as wp_slim_stats / wp_options

    Browser->>JS: Page load (cached HTML with banner)
    JS->>JS: detect consent cookie? (params.gdpr_cookie_name)
    alt cookie exists
        JS-->>Browser: remove `#slimstat-gdpr-banner` and stop initialization
    else no cookie
        JS-->>Browser: show banner
        Browser->>JS: User clicks Accept/Decline
        JS->>JS: write consent cookie (include domain if params.gdpr_cookie_domain)
        JS->>REST: POST consent-change (include X-WP-Nonce only if nonce non-empty)
        REST->>PHP: handle_consent_change()
        PHP->>PHP: wp_verify_nonce() (applies per controller logic)
        PHP->>DB: record consent change / tracking (if permitted)
        PHP-->>REST: response (200 or 403)
        REST-->>JS: response -> update UI
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Poem

🐰 I hopped through cached pages bright and keen,
Cookies now match domains—no banner between.
Nonces sent when needed, otherwise spared,
Tracking resumes and banners are cared.
A happy rabbit, consent tidy and green! 🥕

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.58% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Title clearly summarizes the main fixes: consent cookie domain, cached page banner, and anonymous nonce handling, directly matching the core changes.
Linked Issues check ✅ Passed PR addresses all coding requirements from #240 and #241: cached-page banner fix, nonce handling for anonymous users, cookie domain alignment, and nonce verification enforcement.
Out of Scope Changes check ✅ Passed All changes are scoped to GDPR consent cookie and banner handling; test infrastructure updates (composer.json, new test files) support the core fixes.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/gdpr-banner-consent-240-241
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/Controllers/Rest/GDPRBannerRestController.php`:
- Around line 52-54: The route in GDPRBannerRestController currently makes the
'nonce' parameter optional and calls GDPRService::setConsent without verifying
it; update the route registration and handler to require a nonce for any
state-changing consent writes (including the blocks around lines 69-81),
validate it with wp_verify_nonce() even for anonymous visitors (do not skip
verification for logged-out users), and perform any additional
capability/context checks before calling GDPRService::setConsent; ensure the
'nonce' param is required in the args schema and that the handler returns a 403
on failed or missing nonce so consent changes cannot be made by cross-site
POSTs.

In `@src/Services/Privacy/ConsentHandler.php`:
- Around line 103-115: The current nonce bypass for anonymous users (in
ConsentHandler::... using get_current_user_id() and wp_verify_nonce) allows
forged POSTs to set banner consent; update the logic to verify the nonce for all
requests that call handleBannerConsent (including anonymous flows forwarded by
TrackingRestController->handleBannerConsent(false, $consent_data)) or otherwise
validate an explicit consent token tied to the client before setting
consent—i.e., remove the early anonymous bypass and ensure
wp_verify_nonce($nonce, 'wp_rest') is performed (or an equivalent cryptographic
check) for banner_consent submissions so anonymous requests cannot upgrade to
PII-tracking without a verified user action.

In `@tests/e2e/gdpr-banner-consent-persistence.spec.ts`:
- Around line 475-490: The test currently only checks that
submitBannerDecision() set the slimstat_gdpr_consent cookie, which doesn’t
verify the stale-nonce POST actually succeeded; update the test after clicking
the accept button (the block using testPage.evaluate and waitForTimeout) to also
assert a successful server-side consent request by using the test helper
isConsentChangeRequest() (or equivalent request recorder) to confirm the consent
POST returned 200 (or that the server recorded the consent change) for the
stale-nonce flow; ensure you reference the existing test helpers (testCtx,
isConsentChangeRequest) and assert the server-side effect in addition to the
cookie check so the test fails when the stale-nonce endpoint returns 403.

In `@tests/gdpr-consent-cookie-test.php`:
- Around line 283-285: Tests 6-9 do not exercise the real endpoint handlers or
production nonce logic; instead of inlining the get_current_user_id() guard,
update the tests to invoke the actual handlers (ConsentHandler,
GDPRBannerRestController, ConsentChangeRestController) by constructing
WP_REST_Request objects and/or populating $_POST with the expected fields, stub
WP functions as needed (e.g., get_current_user_id, wp_verify_nonce,
current_user_can) to simulate both authorized and unauthorized contexts, and
assert that the handlers perform nonce verification and capability checks and
sanitize/escape inputs; ensure you replace the inline guard blocks with calls to
the controller methods so the tests fail if production nonce logic changes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 58e108e7-e022-4bed-b625-57b7435b67f6

📥 Commits

Reviewing files that changed from the base of the PR and between b76927e and 8f03495.

⛔ Files ignored due to path filters (2)
  • wp-slimstat.min.js is excluded by !**/*.min.js
  • wp-slimstat.min.js.map is excluded by !**/*.map, !**/*.min.js.map
📒 Files selected for processing (8)
  • composer.json
  • src/Controllers/Rest/ConsentChangeRestController.php
  • src/Controllers/Rest/GDPRBannerRestController.php
  • src/Services/Privacy/ConsentHandler.php
  • tests/e2e/gdpr-banner-consent-persistence.spec.ts
  • tests/gdpr-consent-cookie-test.php
  • wp-slimstat.js
  • wp-slimstat.php

parhumm added 17 commits March 20, 2026 06:33
Covers GDPRService cookie operations (setConsent domain/path,
hasConsentDecision, getBannerHtml visibility) and verifies the
anonymous-user nonce-skip pattern used by consent REST controllers.
9 test cases (14 assertions):
- GDPRService cookie operations (setConsent, hasConsentDecision, getBannerHtml)
- Nonce skip pattern for anonymous users (verified for REST + AJAX handlers)

Refs #240, #241
Production-realistic E2E tests for issues #240 and #241:
1. Fresh accept — persists across pages + tracking works
2. Fresh deny — persists across pages + no tracking
3. Cached page (real HTML capture) + accept cookie — banner hidden
4. Cached page + deny cookie — banner hidden
5. Stale nonce (injected) — consent 200 not 403
6. Cookie domain matches PHP COOKIE_DOMAIN
7. Multi-page nav — banner never reappears
8. Sanity: fresh visit — banner IS shown

Uses page.request.get() + page.route() for cached page simulation.

Refs #240, #241
1. Pass gdpr_cookie_domain from PHP to JS (wp-slimstat.php)
2. Include domain attribute in JS consent cookie (wp-slimstat.js)
3. Check consent cookie before showing banner on cached pages (wp-slimstat.js)
4. Skip nonce verification for anonymous users on consent endpoints:
   - ConsentChangeRestController
   - GDPRBannerRestController
   - ConsentHandler (handleBannerConsent + handleConsentRevoked)

Fixes #240, fixes #241
Remove unnecessary $user_id variable — call inline for consistency
with ConsentHandler pattern. Found via /simplify review.
The setSlimstatOption() MU-plugin approach returns 400 when auth cookies
are stale. Switch to direct DB manipulation (setOption) which is reliable
regardless of Playwright auth state.

Refs #240, #241
The test site has cookie-law-info (CookieYes) active which renders an
overlay that blocks clicking the SlimStat banner buttons. Pre-set
CookieYes dismissal cookies on all anonymous browser contexts.

Refs #240, #241
CookieYes overlay covers the SlimStat banner even with dismiss cookies.
Use force:true to click through the overlay since we've already asserted
the banner element is visible in the DOM.

Refs #240, #241
Playwright click() requires viewport coordinates even with force:true.
dispatchEvent('click') fires the DOM event directly, bypassing the
CookieYes overlay that covers the SlimStat banner.

Refs #240, #241
dispatchEvent('click') doesn't properly trigger addEventListener handlers
in this context. Use page.evaluate with native DOM .click() instead.

Refs #240, #241
SlimStat JS tracker only enqueues when is_tracking=on. Also ensure
javascript_mode=on and ignore_wp_users=no for proper anonymous tracking.

Refs #240, #241
Includes cookie domain attribute and cached page banner guard.

Refs #240, #241
…ssue

detectSlimStatBanner() is renamed to detectSlimStatBanner2 by esbuild
(due to duplicate definitions in different scopes), causing a
ReferenceError in attachBannerHandlers() that silently kills the
entire function — no show class, no event listeners, no consent flow.

Inline the cookie read using the same regex pattern as getCookieStrict()
to avoid cross-scope function reference issues.

Refs #240, #241
page.evaluate() click doesn't reliably trigger Playwright response
interception. Remove consent-change response checks from Tests 1, 2, 5.
Core behavior (cookie set, banner hidden, tracking works) is verified
by cookie assertions and DB checks instead.

Also change not.toBeAttached() to not.toBeVisible() for more reliable
timing with the 350ms banner removal animation.

Refs #240, #241
The request listener captures requests from page load (before decline).
Reset the counter after decline to only check post-decline behavior.

Refs #240, #241
Revert the anonymous nonce bypass — consent is a state-changing operation.
A cross-site POST without nonce verification could force-accept consent,
enabling PII tracking without genuine user action (GDPR violation).

On cached pages, anonymous consent REST calls return 403, but the JS
cookie still records consent client-side, and tracking works via the
/hit endpoint (PR #235). This is an acceptable trade-off for security.

Changes:
- ConsentChangeRestController: restore nonce verification for all users
- GDPRBannerRestController: restore nonce required + verification
- ConsentHandler: restore check_ajax_referer and wp_verify_nonce
- wp-slimstat.js: skip X-WP-Nonce header when nonce is empty to avoid
  WordPress core rest_cookie_check_errors 403 before handler runs
- Unit tests: replace inline pattern tests with actual handler invocations
- E2E test 5: verify stale nonce causes 403 (correct security behavior)
  while client-side cookie is still set

Refs #240, #241
@parhumm parhumm force-pushed the fix/gdpr-banner-consent-240-241 branch from 8f03495 to beacf08 Compare March 20, 2026 05:35
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/gdpr-consent-cookie-test.php (1)

265-294: Align REST shims with WordPress core API contract.

The shim WP_REST_Request::__construct() accepts an array of params, while WordPress core expects __construct($method, $route, $attributes). The test instantiates it as new WP_REST_Request(['consent' => 'accepted', ...]), which works against the shim but diverges from the real API—if production code ever uses the proper WordPress constructor signature, this test would not catch the mismatch. Similarly, $result->status accesses the property directly; the idiomatic WordPress API is $result->get_status(). Update the shims to match WordPress' actual request construction flow and response getter methods to ensure the test validates the genuine REST API contract.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/gdpr-consent-cookie-test.php` around lines 265 - 294, Update the test
shims to match WordPress core: change WP_REST_Request::__construct to the core
signature (e.g., __construct($method = 'GET', $route = '', $attributes = []))
and store incoming params in the attributes bag so
WP_REST_Request::get_param($key) reads from that attributes array; update
WP_REST_Response to include a public get_status() method returning $this->status
(and keep $status property for compatibility) so tests use $result->get_status()
instead of directly accessing $status. Ensure the shim class names and method
names (WP_REST_Request::__construct, WP_REST_Request::get_param,
WP_REST_Response::get_status) are implemented to mirror the real API.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/gdpr-consent-cookie-test.php`:
- Around line 373-385: The anonymous-nonce tests are still asserting the old
failure behavior; update the tests that use $_stub_user_id == 0 (anonymous) and
$_stub_nonce_valid = false (e.g., the GDPRBannerRestController->handle_consent
calls in Test 6 and the related blocks around lines 400-432) to expect success
instead of WP_Error/false (because nonce checks should be skipped for anonymous
cached-page consent), and add new cases where you set $_stub_user_id to a
non-zero value (logged-in user) with invalid/empty nonces to assert the original
failure behavior (WP_Error/rest_forbidden/403 or false as appropriate); ensure
you reference the same controller method handle_consent and keep the anonymous
vs logged-in distinction explicit in the new assertions.

---

Nitpick comments:
In `@tests/gdpr-consent-cookie-test.php`:
- Around line 265-294: Update the test shims to match WordPress core: change
WP_REST_Request::__construct to the core signature (e.g., __construct($method =
'GET', $route = '', $attributes = [])) and store incoming params in the
attributes bag so WP_REST_Request::get_param($key) reads from that attributes
array; update WP_REST_Response to include a public get_status() method returning
$this->status (and keep $status property for compatibility) so tests use
$result->get_status() instead of directly accessing $status. Ensure the shim
class names and method names (WP_REST_Request::__construct,
WP_REST_Request::get_param, WP_REST_Response::get_status) are implemented to
mirror the real API.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1f6b3d40-18a5-4fa5-9b79-e74624c8bae9

📥 Commits

Reviewing files that changed from the base of the PR and between 8f03495 and beacf08.

⛔ Files ignored due to path filters (2)
  • wp-slimstat.min.js is excluded by !**/*.min.js
  • wp-slimstat.min.js.map is excluded by !**/*.map, !**/*.min.js.map
📒 Files selected for processing (7)
  • composer.json
  • src/Controllers/Rest/ConsentChangeRestController.php
  • src/Controllers/Rest/GDPRBannerRestController.php
  • tests/e2e/gdpr-banner-consent-persistence.spec.ts
  • tests/gdpr-consent-cookie-test.php
  • wp-slimstat.js
  • wp-slimstat.php
✅ Files skipped from review due to trivial changes (3)
  • composer.json
  • src/Controllers/Rest/ConsentChangeRestController.php
  • src/Controllers/Rest/GDPRBannerRestController.php
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/e2e/gdpr-banner-consent-persistence.spec.ts
  • wp-slimstat.js

@parhumm
Copy link
Copy Markdown
Contributor Author

parhumm commented Mar 20, 2026

Test Results: development vs fix/gdpr-banner-consent-240-241

Unit Tests (composer test:all — 9 suites, 149 assertions)

Suite Assertions Status
license-tags 14 PASS
geoip-resolver 24 PASS
geoservice 16 PASS
legacy-sync 7 PASS
lazy-migration 6 PASS
rest-controller 16 PASS
reports-escaping 23 PASS
fingerprint-sanitize 17 PASS
gdpr-consent (14 cases) 26 PASS

gdpr-consent coverage:

  • Tests 1-5: GDPRService cookie ops (setConsent, hasConsentDecision, getBannerHtml)
  • Tests 6-9: GDPRBannerRestController nonce verification (/gdpr/consent route)
  • Tests 10-11: ConsentHandler::handleBannerConsent nonce (AJAX route)
  • Tests 12-13: ConsentChangeRestController nonce (/consent-change route - production path)
  • Test 14: ConsentHandler::handleConsentRevoked nonce (slimstat_consent_revoked AJAX)

E2E Tests (8 scenarios)

# Scenario development fix branch
1 Fresh accept - persists across pages PASS PASS
2 Fresh decline - persists across pages PASS PASS
3 Cached page (real HTML) + accept cookie - banner hidden FAIL PASS
4 Cached page (real HTML) + deny cookie - banner hidden FAIL PASS
5 Stale nonce - client-side consent + server 403 PASS PASS
6 Cookie domain matches COOKIE_DOMAIN PASS PASS
7 Multi-page nav - banner never reappears PASS PASS
8 Fresh visit - banner shown (sanity) PASS PASS
Total 6/8 8/8

esbuild verification

Rebuilt wp-slimstat.min.js from development source: MD5 matches committed version exactly (deterministic build). The fix branch adds +317 bytes — all from our source changes, no esbuild artifacts.

Known limitation (pre-existing, not introduced by this PR)

The deny path on cached pages has no server-side fallback. Both /consent-change and slimstat_consent_revoked require nonce, which fails on cached pages. The "denied" cookie IS set client-side, and on next page load PHP blocks tracking. The accept path has a fallback via /hit (PR #235). This asymmetry is pre-existing.

- WP_REST_Request stub: match core constructor signature (method, route, attributes)
- WP_REST_Response stub: add get_status() method matching core API
- Tests 6-9: explicit $_stub_user_id for anonymous vs logged-in clarity
- Add Test 10-11: ConsentHandler nonce rejection + success with valid nonce
- All tests use actual handler methods, not inline patterns

22 assertions across 11 test cases.
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
tests/gdpr-consent-cookie-test.php (2)

1-12: Missing GPL license header.

Per coding guidelines, all new PHP files require a GPL license header. Add the standard GPLv2+ header after the opening <?php tag.

📝 Proposed fix
 <?php
+/**
+ * WP-Slimstat - GDPR Consent Cookie Tests
+ *
+ * `@package`   wp-slimstat
+ * `@license`   GPL-2.0-or-later
+ * `@link`      https://github.com/wp-slimstat/wp-slimstat
+ */
+
 /**
  * Unit tests for GDPR consent cookie operations and nonce-skip pattern.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/gdpr-consent-cookie-test.php` around lines 1 - 12, Add the standard
GPLv2+ license header immediately after the opening "<?php" tag in
tests/gdpr-consent-cookie-test.php so the file includes the required licensing
boilerplate before the file-level docblock and the existing
declare(strict_types=1); statement; ensure the header matches the project's
standard GPLv2+ text and preserves the existing file comment block that
documents the GDPR tests.

415-428: Add test case for anonymous user with valid nonce.

Tests 8 and 11 verify success for logged-in users with valid nonces, but there's no test verifying that anonymous users can also succeed when they provide a valid nonce. Since the PR enforces nonce verification for all users, this success path should be covered.

🧪 Proposed additional test case

Add this after Test 8 (around line 428):

// ─── Test 8b: GDPRBannerRestController accepts anonymous request with valid nonce ───

$_stub_user_id     = 0; // anonymous
$_stub_nonce_valid = true;
$_setcookie_calls = [];
\wp_slimstat::$settings = ['use_slimstat_banner' => 'on'];

$controller = new \SlimStat\Controllers\Rest\GDPRBannerRestController();
$request = new \WP_REST_Request('POST', '/slimstat/v1/gdpr/consent', ['consent' => 'accepted', 'nonce' => 'valid_nonce']);
$result = $controller->handle_consent($request);

assert_true($result instanceof \WP_REST_Response, 'handle_consent should return WP_REST_Response for anonymous with valid nonce');
assert_same(200, $result->get_status(), 'handle_consent should return 200 for anonymous with valid nonce');
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/gdpr-consent-cookie-test.php` around lines 415 - 428, Add a new test
case after Test 8 that verifies anonymous users succeed with a valid nonce: set
$_stub_user_id = 0 and $_stub_nonce_valid = true, clear $_setcookie_calls and
set \wp_slimstat::$settings = ['use_slimstat_banner' => 'on']; instantiate
\SlimStat\Controllers\Rest\GDPRBannerRestController, create a POST
\WP_REST_Request to '/slimstat/v1/gdpr/consent' with
['consent'=>'accepted','nonce'=>'valid_nonce'], call
$controller->handle_consent($request) and assert the result is a
\WP_REST_Response with get_status() === 200 (and optionally that
$result->data['success'] is truthy) to cover the anonymous-success path enforced
by nonce verification.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@tests/gdpr-consent-cookie-test.php`:
- Around line 1-12: Add the standard GPLv2+ license header immediately after the
opening "<?php" tag in tests/gdpr-consent-cookie-test.php so the file includes
the required licensing boilerplate before the file-level docblock and the
existing declare(strict_types=1); statement; ensure the header matches the
project's standard GPLv2+ text and preserves the existing file comment block
that documents the GDPR tests.
- Around line 415-428: Add a new test case after Test 8 that verifies anonymous
users succeed with a valid nonce: set $_stub_user_id = 0 and $_stub_nonce_valid
= true, clear $_setcookie_calls and set \wp_slimstat::$settings =
['use_slimstat_banner' => 'on']; instantiate
\SlimStat\Controllers\Rest\GDPRBannerRestController, create a POST
\WP_REST_Request to '/slimstat/v1/gdpr/consent' with
['consent'=>'accepted','nonce'=>'valid_nonce'], call
$controller->handle_consent($request) and assert the result is a
\WP_REST_Response with get_status() === 200 (and optionally that
$result->data['success'] is truthy) to cover the anonymous-success path enforced
by nonce verification.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 5d429786-1178-4d15-8f00-06b0093a5fb8

📥 Commits

Reviewing files that changed from the base of the PR and between beacf08 and 4bece03.

📒 Files selected for processing (1)
  • tests/gdpr-consent-cookie-test.php

Tests 12-14 cover the actual production banner path:
- /consent-change endpoint nonce rejection (anonymous + logged-in)
- slimstat_consent_revoked AJAX nonce rejection

Now covers both REST routes (/gdpr/consent + /consent-change) and AJAX.
26 total assertions across 14 test cases.
@parhumm parhumm force-pushed the fix/gdpr-banner-consent-240-241 branch from 6400d07 to 906a6ea Compare March 20, 2026 06:21
@parhumm parhumm merged commit 56a4a9a into development Mar 20, 2026
1 check was pending
@parhumm parhumm deleted the fix/gdpr-banner-consent-240-241 branch March 20, 2026 06:24
@coderabbitai coderabbitai bot mentioned this pull request Mar 20, 2026
7 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant