Skip to content

Apply CodeRabbit review fixes to Back in Stock Notifications alpha#64329

Merged
opr merged 29 commits into
trunkfrom
feature/back-in-stock-notifications
Apr 29, 2026
Merged

Apply CodeRabbit review fixes to Back in Stock Notifications alpha#64329
opr merged 29 commits into
trunkfrom
feature/back-in-stock-notifications

Conversation

@opr

@opr opr commented Apr 22, 2026

Copy link
Copy Markdown
Contributor

Submission Review Guidelines:

Changes proposed in this Pull Request:

Closes RSM-436.

Resolves the must-fix CodeRabbit review comments on the Back in Stock Notifications alpha (WOOCOMMERCE_BIS_ALPHA_ENABLED-gated) that were left unaddressed when the alpha merged. Also drops the pre-WP-6.8 HasherHelper compatibility shim now that WooCommerce requires WP 6.8, and deletes the in-tree sprint scratch docs ahead of merge.

Notable items:

  • Fixes a copy-paste bug in the admin cancel action that wrote an enum into the date_notified column instead of cancellation_source.
  • Splits the previously shared email_link_action_key meta into distinct verification_action_key / unsubscribe_action_key keys so generating one no longer silently invalidates the other. Verification and unsubscribe email URLs now carry an explicit email_link_action parameter for dispatch.
  • Extracts the BIS email-link action tokens (verify / unsubscribe) into named constants on EmailActionController and adds a debug log path for unknown actions.
  • Guards against Factory::get_notification() returning false in the retention cron and privacy eraser, preventing fatals when any notification id is unreadable.
  • Corrects an operator-precedence bug in the background processor ((int) $product_id ?? 0), repairs malformed <option> tags + a missing attribute space in the admin create form, and drops an orphaned sprintf argument (no %s placeholder) in the verified email template.
  • Renames the typo filter woocommerce_customer_stock_notifications_supported_product_stock_statuses..._supported_product_statuses while the feature is still alpha-gated.
  • Removes the pre-WP-6.8 HasherHelper shim now that WooCommerce requires WP 6.8.
  • Deletes the sprint scratch docs (SPRINT.md, CODERABBIT-TRIAGE.md, CODERABBIT-TRIAGE-RAW.md) from src/Internal/StockNotifications/ ahead of merge.

Bug introduced in PR #59947.

How to test the changes in this Pull Request:

Enable the alpha: define( 'WOOCOMMERCE_BIS_ALPHA_ENABLED', true ); in wp-config.php, then enable WooCommerce → Settings → Products → Customer stock notifications → Allow signups.

  1. Sign up for stock notifications on any out-of-stock product as a guest. Expected: success notice referencing the product name by title, a new row in WooCommerce → Notifications with status Active.
  2. Flip the product back to In stock. Expected: the back-in-stock email is dispatched; the notification row transitions to Sent.
  3. Click the unsubscribe link in the email. Expected: status moves to Cancelled, cancellation_source = USER, success notice on the redirect.
  4. From WooCommerce → Notifications, open any Active notification and click "Cancel". Expected: status = Cancelled, cancellation_source = ADMIN, date_cancelled populated, date_notified remains NULL (this was the bug).
  5. Open WooCommerce → Notifications → Add new. The product dropdown should display the select2 placeholder Select product… and a clear (×) button — both were suppressed before the attribute-space fix.

Testing that has already taken place:

Manual end-to-end on a local Studio site (SQLite): signup → return-to-stock → back-in-stock email dispatch → unsubscribe all verified. pnpm --filter=@woocommerce/plugin-woocommerce test:php:env -- --filter 'StockNotifications|Notification' runs 409 tests / 998 assertions green, including the updated NotificationTests and EmailActionControllerTests. phpcs and PHPStan (level 8, PHP 8.2) both clean on the changed paths; the PHPStan baseline shrinks by 8 entries for PrivacyEraser.php now that the null guard is in place.

Milestone

Changelog entry

  • This Pull Request does not require a changelog entry. (Comment required below)
Changelog Entry Comment

Comment

Changelog entry was created manually and is committed in this PR: plugins/woocommerce/changelog/bis-alpha-cr-fixes-and-cleanup (Patch/Dev). Consolidates the previous separate entries (bis-coderabbit-review-fixes, bis-remove-hasherhelper-shim, 64398-bis-email-action-constants) into a single one.

@github-actions github-actions Bot added the plugin: woocommerce Issues related to the WooCommerce Core plugin. label Apr 22, 2026
@opr opr changed the base branch from trunk to feature/bis-rsm-sprint April 23, 2026 15:02
@opr opr force-pushed the feature/back-in-stock-notifications branch from eb9cdff to 453ee2b Compare April 23, 2026 15:20
Avoids 'already defined' warnings when WOOCOMMERCE_BIS_ALPHA_ENABLED is set at the wp-config level (which happens in the tests env via .wp-env.json on downstream branches).
@opr opr marked this pull request as ready for review April 23, 2026 16:54
@opr opr requested a review from a team as a code owner April 23, 2026 16:54
@opr opr requested review from prettyboymp and removed request for a team April 23, 2026 16:54
@coderabbitai

coderabbitai Bot commented Apr 23, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Apr 23, 2026

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Back in Stock Notifications: removed the HasherHelper shim and migrated to WordPress wp_fast_hash/wp_verify_fast_hash; split email_link_action_key into verification_action_key and unsubscribe_action_key; added explicit email_link_action URL routing and controller branching; fixed admin template HTML; added type guards in retention/privacy; updated tests, docs, and PHPStan baseline.

Changes

Cohort / File(s) Summary
Changelog Entries
plugins/woocommerce/changelog/bis-coderabbit-review-fixes, plugins/woocommerce/changelog/bis-remove-hasherhelper-shim, plugins/woocommerce/changelog/64398-bis-email-action-constants
Added changelog files documenting BIS fixes, removal of hashing shim, and extraction of email-action constants and logging.
Hashing & Notification metadata
plugins/woocommerce/src/Internal/StockNotifications/Notification.php, plugins/woocommerce/src/Internal/StockNotifications/Utilities/HasherHelper.php
Removed HasherHelper utility and switched notification key generation/verification to wp_fast_hash/wp_verify_fast_hash; introduced separate verification_action_key and unsubscribe_action_key meta usage.
Email link generation & templates
plugins/woocommerce/src/Internal/StockNotifications/Emails/CustomerStockNotificationEmail.php, .../CustomerStockNotificationVerifyEmail.php, .../CustomerStockNotificationVerifiedEmail.php, plugins/woocommerce/templates/emails/customer-stock-notification-verified.php
Email URLs now include explicit email_link_action param (verify/unsubscribe); verified email template version bumped and footer text simplified.
Email action routing & controller
plugins/woocommerce/src/Internal/StockNotifications/Emails/EmailActionController.php, plugins/woocommerce/tests/php/.../EmailActionControllerTests.php
Controller requires and sanitizes email_link_action, forwards it into validate_and_maybe_process_request(...), uses explicit switch for ACTION_VERIFY/ACTION_UNSUBSCRIBE, adds debug path for unknown actions, and tests updated to pass action and use wp_fast_hash.
Admin UI & Edit flow
plugins/woocommerce/src/Internal/StockNotifications/Admin/NotificationEditPage.php, plugins/woocommerce/src/Internal/StockNotifications/Admin/Templates/html-admin-notification-create.php
Cancel action now calls set_cancellation_source(NotificationCancellationSource::ADMIN); fixed malformed <option> markup and missing space in select attributes.
Data retention & privacy erasure
plugins/woocommerce/src/Internal/StockNotifications/DataRetentionController.php, plugins/woocommerce/src/Internal/StockNotifications/Privacy/PrivacyEraser.php
Added guards verifying Factory::get_notification() returns Notification before delete/mutate; PrivacyEraser blanks verification_action_key and unsubscribe_action_key and uses time() for cancellation timestamp.
Async processor
plugins/woocommerce/src/Internal/StockNotifications/AsyncTasks/NotificationsProcessor.php
Exception logging now casts $product_id to (int) (removed null-coalescing default).
PHPStan baseline
plugins/woocommerce/phpstan-baseline.neon
Removed numerous baselined diagnostics after adding type guards and migrating to native hashing functions.
Tests & bootstrap
plugins/woocommerce/tests/php/.../NotificationTests.php, plugins/woocommerce/tests/php/.../EmailActionControllerTests.php, plugins/woocommerce/tests/legacy/bootstrap.php
Tests updated to use new meta keys and wp_fast_hash; added tests for action routing/guards; bootstrap now defines BIS alpha constant only if undefined.
Documentation & triage
plugins/woocommerce/src/Internal/StockNotifications/SPRINT.md, .../CODERABBIT-TRIAGE.md, .../CODERABBIT-TRIAGE-RAW.md
Added sprint coordination and triage artifacts detailing decisions, remaining work, and reviewer notes.
Misc
.markdownlintignore
Added a Markdown file path to the markdownlint ignore list.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client (email link)
    participant Controller as EmailActionController
    participant Factory as Notification Factory
    participant Notif as Notification
    participant DB as Persistence/DB
    participant Logger as Logger

    rect rgba(135,206,235,0.5)
    Client->>Controller: GET /?notification_id=...&email_link_action=verify|unsubscribe&email_link_action_key=...
    end

    Controller->>Controller: sanitize 'email_link_action' -> $action
    Controller->>Factory: Factory::get_notification($notification_id)
    Factory-->>Controller: Notification|null
    alt Notification exists
        Controller->>Notif: validate_and_maybe_process_request($notification, $action, $key)
        alt action == verify
            Notif->>Notif: wp_verify_fast_hash(...) -> if valid: set status VERIFIED
            Notif->>DB: persist status
        else action == unsubscribe
            Notif->>Notif: wp_verify_fast_hash(...) -> if valid: set status CANCELLED and set cancellation source
            Notif->>DB: persist status and metadata
        else unknown action
            Controller->>Logger: debug log if WP_DEBUG, drop request otherwise
        end
        Notif-->>Controller: result (redirect)
    else Notification not found
        Controller->>Logger: debug/early return
    end
    Controller-->>Client: wp_safe_redirect / response
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Apply CodeRabbit review fixes to Back in Stock Notifications alpha' directly and clearly summarizes the main change—applying review fixes to a specific feature. It is specific, concise, and accurately represents the changeset's primary purpose.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The pull request description is clearly related to the changeset, detailing specific bug fixes, refactoring of email-link action keys, removal of compatibility shims, and documentation cleanup for the Back in Stock Notifications alpha feature.

✏️ 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 feature/back-in-stock-notifications

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (4)
plugins/woocommerce/src/Internal/StockNotifications/Config.php (1)

73-98: Filter rename is acceptable under alpha gating — worth a brief note for the alpha upgrade guide.

Renaming woocommerce_customer_stock_notifications_supported_product_stock_statuseswoocommerce_customer_stock_notifications_supported_product_statuses is a breaking change for any extender that may have hooked into the original (typo'd) name. Since BIS is gated behind WOOCOMMERCE_BIS_ALPHA_ENABLED, this is fine now, but consider calling it out in the alpha change notes so anyone testing the alpha knows to update hook usage.

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

In `@plugins/woocommerce/src/Internal/StockNotifications/Config.php` around lines
73 - 98, The filter name was changed from the typo'd hook to
'woocommerce_customer_stock_notifications_supported_product_statuses' in
Config::get_supported_product_statuses(), which is breaking for extenders;
update the alpha upgrade/changes documentation to call out this rename (original
'woocommerce_customer_stock_notifications_supported_product_stock_statuses' →
new 'woocommerce_customer_stock_notifications_supported_product_statuses'),
mention WOOCOMMERCE_BIS_ALPHA_ENABLED gating, and include a short migration note
so testers know to update any add_filter/remove_filter usages against that hook.
plugins/woocommerce/tests/php/src/Internal/StockNotifications/Emails/EmailActionControllerTests.php (1)

21-55: LGTM — tests correctly reflect the split meta keys, wp_fast_hash migration, and explicit action parameter.

The verification-key test uses the time():hash format (matching the expiration-aware verification_action_key contract) while the unsubscribe test uses a bare hash, consistent with check_unsubscribe_key() not enforcing TTL.

Optional: consider adding negative cases (invalid key, mismatched action e.g. passing 'unsubscribe' against a verification_action_key, and missing notification id) to guard against regressions in the action-routing logic introduced by email_link_action. Not a blocker.

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

In
`@plugins/woocommerce/tests/php/src/Internal/StockNotifications/Emails/EmailActionControllerTests.php`
around lines 21 - 55, Add negative test cases to cover invalid/mismatched action
routing: extend EmailActionControllerTests by adding tests such as
test_process_verification_action_with_invalid_key_returns_no_change,
test_process_unsubscribe_action_with_verification_key_does_not_unsubscribe, and
test_process_action_with_missing_notification_id_handles_gracefully; each should
create a Notification (use Factory or Notification methods), set the opposite or
invalid meta key (use 'verification_action_key' vs 'unsubscribe_action_key' and
wp_fast_hash/time:wp_fast_hash variants), call
EmailActionController::validate_and_maybe_process_request with mismatched action
or an invalid id, and assert that the notification status and
cancellation_source remain unchanged (or that the controller returns/handles the
error as expected).
plugins/woocommerce/src/Internal/StockNotifications/Emails/EmailActionController.php (1)

55-74: Optional: extract action tokens into class constants and consider a default branch.

Two small hardening suggestions for the dispatch surface:

  1. The literal strings 'verify' / 'unsubscribe' are now the contract between URL producers (email templates) and this consumer. A drift in either side silently no-ops. Extracting them as EmailActionController::ACTION_VERIFY / ACTION_UNSUBSCRIBE would make the coupling explicit and greppable.
  2. The switch has no default — unknown/typo'd actions are dropped silently with no log or notice. For an alpha-gated feature the silent fallthrough is fine, but a single wc_get_logger()->debug(...) in a default: branch would make mis-routed emails visible during alpha testing.

Also: $action = '' default on the signature means any existing 2-arg caller (tests?) will now silently do nothing rather than dispatch. If this method is only intended to be driven by maybe_process_email_action, consider making $action required; otherwise leave as-is.

♻️ Proposed refactor sketch
 class EmailActionController {
+    private const ACTION_VERIFY      = 'verify';
+    private const ACTION_UNSUBSCRIBE = 'unsubscribe';
+
     public function __construct() {
         ...
     }
...
-        switch ( $action ) {
-            case 'verify':
+        switch ( $action ) {
+            case self::ACTION_VERIFY:
                 $this->process_verification_action( $notification, $email_link_action_key );
                 break;
-            case 'unsubscribe':
+            case self::ACTION_UNSUBSCRIBE:
                 $this->process_unsubscribe_action( $notification, $email_link_action_key );
                 break;
+            default:
+                // Unknown email_link_action — silently ignore in production, log in debug.
+                if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) {
+                    wc_get_logger()->debug(
+                        sprintf( 'Unknown email_link_action "%s" for notification %d', $action, $notification->get_id() ),
+                        array( 'source' => 'stock-notifications' )
+                    );
+                }
+                break;
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@plugins/woocommerce/src/Internal/StockNotifications/Emails/EmailActionController.php`
around lines 55 - 74, The switch in validate_and_maybe_process_request silently
relies on string literals and lacks a default/logging path; extract the action
tokens into class constants (e.g., ACTION_VERIFY and ACTION_UNSUBSCRIBE) and use
them in the switch to make the contract explicit, add a default: branch that
logs unknown actions using wc_get_logger()->debug(...) so typos or mismatches
are visible, and decide whether $action should be required (if only called by
maybe_process_email_action) or keep the '' default but ensure callers are
updated/tested; update references to process_verification_action and
process_unsubscribe_action to use the new constants.
plugins/woocommerce/src/Internal/StockNotifications/Notification.php (1)

500-502: Optional: consider whether explicit guards are needed for check_unsubscribe_key.

The check_unsubscribe_key method calls wp_verify_fast_hash() with the result of get_meta( 'unsubscribe_action_key' ), which returns '' when the key has never been generated or was cleared. While wp_verify_fast_hash() handles empty strings cleanly without emitting notices, adding an explicit guard would align with defensive programming principles. However, note that the "symmetry" with check_verification_key() is not exact—check_verification_key() guards via a functional check (str_contains() for a colon) rather than an explicit empty/type guard. If adding a guard here, ensure it serves the actual defensive need rather than assumed consistency with a different pattern.

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

In `@plugins/woocommerce/src/Internal/StockNotifications/Notification.php` around
lines 500 - 502, The method check_unsubscribe_key calls wp_verify_fast_hash with
the value returned by get_meta('unsubscribe_action_key') which can be an empty
string; add an explicit defensive guard at the start of check_unsubscribe_key to
return false if get_meta('unsubscribe_action_key') is empty or not a string
before calling wp_verify_fast_hash, referencing the same meta key and keeping
behavior consistent with existing check_verification_key semantics.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@plugins/woocommerce/src/Internal/StockNotifications/Config.php`:
- Around line 73-98: The filter name was changed from the typo'd hook to
'woocommerce_customer_stock_notifications_supported_product_statuses' in
Config::get_supported_product_statuses(), which is breaking for extenders;
update the alpha upgrade/changes documentation to call out this rename (original
'woocommerce_customer_stock_notifications_supported_product_stock_statuses' →
new 'woocommerce_customer_stock_notifications_supported_product_statuses'),
mention WOOCOMMERCE_BIS_ALPHA_ENABLED gating, and include a short migration note
so testers know to update any add_filter/remove_filter usages against that hook.

In
`@plugins/woocommerce/src/Internal/StockNotifications/Emails/EmailActionController.php`:
- Around line 55-74: The switch in validate_and_maybe_process_request silently
relies on string literals and lacks a default/logging path; extract the action
tokens into class constants (e.g., ACTION_VERIFY and ACTION_UNSUBSCRIBE) and use
them in the switch to make the contract explicit, add a default: branch that
logs unknown actions using wc_get_logger()->debug(...) so typos or mismatches
are visible, and decide whether $action should be required (if only called by
maybe_process_email_action) or keep the '' default but ensure callers are
updated/tested; update references to process_verification_action and
process_unsubscribe_action to use the new constants.

In `@plugins/woocommerce/src/Internal/StockNotifications/Notification.php`:
- Around line 500-502: The method check_unsubscribe_key calls
wp_verify_fast_hash with the value returned by
get_meta('unsubscribe_action_key') which can be an empty string; add an explicit
defensive guard at the start of check_unsubscribe_key to return false if
get_meta('unsubscribe_action_key') is empty or not a string before calling
wp_verify_fast_hash, referencing the same meta key and keeping behavior
consistent with existing check_verification_key semantics.

In
`@plugins/woocommerce/tests/php/src/Internal/StockNotifications/Emails/EmailActionControllerTests.php`:
- Around line 21-55: Add negative test cases to cover invalid/mismatched action
routing: extend EmailActionControllerTests by adding tests such as
test_process_verification_action_with_invalid_key_returns_no_change,
test_process_unsubscribe_action_with_verification_key_does_not_unsubscribe, and
test_process_action_with_missing_notification_id_handles_gracefully; each should
create a Notification (use Factory or Notification methods), set the opposite or
invalid meta key (use 'verification_action_key' vs 'unsubscribe_action_key' and
wp_fast_hash/time:wp_fast_hash variants), call
EmailActionController::validate_and_maybe_process_request with mismatched action
or an invalid id, and assert that the notification status and
cancellation_source remain unchanged (or that the controller returns/handles the
error as expected).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 9fe39bd3-cc92-4774-bcee-f9efc39d9da1

📥 Commits

Reviewing files that changed from the base of the PR and between 00bed1a and 4ac4d40.

📒 Files selected for processing (23)
  • .markdownlintignore
  • plugins/woocommerce/changelog/bis-coderabbit-review-fixes
  • plugins/woocommerce/changelog/bis-remove-hasherhelper-shim
  • plugins/woocommerce/phpstan-baseline.neon
  • plugins/woocommerce/src/Internal/StockNotifications/Admin/NotificationEditPage.php
  • plugins/woocommerce/src/Internal/StockNotifications/Admin/Templates/html-admin-notification-create.php
  • plugins/woocommerce/src/Internal/StockNotifications/AsyncTasks/NotificationsProcessor.php
  • plugins/woocommerce/src/Internal/StockNotifications/CODERABBIT-TRIAGE-RAW.md
  • plugins/woocommerce/src/Internal/StockNotifications/CODERABBIT-TRIAGE.md
  • plugins/woocommerce/src/Internal/StockNotifications/Config.php
  • plugins/woocommerce/src/Internal/StockNotifications/DataRetentionController.php
  • plugins/woocommerce/src/Internal/StockNotifications/Emails/CustomerStockNotificationEmail.php
  • plugins/woocommerce/src/Internal/StockNotifications/Emails/CustomerStockNotificationVerifiedEmail.php
  • plugins/woocommerce/src/Internal/StockNotifications/Emails/CustomerStockNotificationVerifyEmail.php
  • plugins/woocommerce/src/Internal/StockNotifications/Emails/EmailActionController.php
  • plugins/woocommerce/src/Internal/StockNotifications/Notification.php
  • plugins/woocommerce/src/Internal/StockNotifications/Privacy/PrivacyEraser.php
  • plugins/woocommerce/src/Internal/StockNotifications/SPRINT.md
  • plugins/woocommerce/src/Internal/StockNotifications/Utilities/HasherHelper.php
  • plugins/woocommerce/templates/emails/customer-stock-notification-verified.php
  • plugins/woocommerce/tests/legacy/bootstrap.php
  • plugins/woocommerce/tests/php/src/Internal/StockNotifications/Emails/EmailActionControllerTests.php
  • plugins/woocommerce/tests/php/src/Internal/StockNotifications/NotificationTests.php
💤 Files with no reviewable changes (2)
  • plugins/woocommerce/src/Internal/StockNotifications/Utilities/HasherHelper.php
  • plugins/woocommerce/phpstan-baseline.neon

@opr opr changed the base branch from feature/bis-rsm-sprint to trunk April 23, 2026 23:22
@opr opr added this to the 10.8.0 milestone Apr 23, 2026
Pulls the 'verify' / 'unsubscribe' tokens shared between the three BIS
email templates and EmailActionController into class constants
(ACTION_VERIFY / ACTION_UNSUBSCRIBE), so a drift on either side of the
URL contract is now a compile-time typo rather than a silent no-op. All
three email templates that produce these links are updated to reference
the same constants.

Adds a default branch to the action switch that logs unknown tokens via
wc_get_logger()->debug when WP_DEBUG is enabled, so mis-routed email
links become visible during alpha testing instead of failing silently.

Expands EmailActionControllerTests with negative cases for: invalid
verification key, mismatched action key family (verification key
present, unsubscribe action requested), missing notification id, and
unknown action token.

Addresses CodeRabbit nitpicks on PR #64329.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
plugins/woocommerce/tests/php/src/Internal/StockNotifications/Emails/EmailActionControllerTests.php (1)

103-111: Optional: prefer expectNotToPerformAssertions() over assertTrue( true ).

The dummy assertion works, but PHPUnit's risky-test checks flag this pattern and the intent ("this call must not throw or mutate") is clearer with $this->expectNotToPerformAssertions(); declared before the act step.

♻️ Suggested tweak
 	public function test_process_action_with_missing_notification_id_handles_gracefully() {
 		$controller = new EmailActionController();
 
 		// Should not throw or emit a notice — the guard in
 		// validate_and_maybe_process_request catches the empty id.
+		$this->expectNotToPerformAssertions();
 		$controller->validate_and_maybe_process_request( 0, 'any-key', 'verify' );
-
-		$this->assertTrue( true );
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@plugins/woocommerce/tests/php/src/Internal/StockNotifications/Emails/EmailActionControllerTests.php`
around lines 103 - 111, Replace the dummy assertion in the
test_process_action_with_missing_notification_id_handles_gracefully test with
PHPUnit's intent API: call $this->expectNotToPerformAssertions() before invoking
EmailActionController::validate_and_maybe_process_request(0, 'any-key',
'verify') so the test expresses "no assertions expected" instead of using
$this->assertTrue(true); keep the same EmailActionController instantiation and
method call.
plugins/woocommerce/src/Internal/StockNotifications/Emails/EmailActionController.php (1)

71-104: Optional: tighten the empty-vs-unknown action distinction.

validate_and_maybe_process_request() still accepts a default $action = '', which will silently fall through to the default branch and emit Unknown email_link_action "" for notification N. The only in-repo caller (maybe_process_email_action) already passes a sanitized non-empty value, so either making the parameter required or branching empty-vs-unknown explicitly would make the log signal more actionable during alpha triage.

♻️ Suggested tweak
-	public function validate_and_maybe_process_request( int $notification_id, string $email_link_action_key, string $action = '' ): void {
-		if ( empty( $email_link_action_key ) || empty( $notification_id ) ) {
+	public function validate_and_maybe_process_request( int $notification_id, string $email_link_action_key, string $action ): void {
+		if ( empty( $email_link_action_key ) || empty( $notification_id ) || empty( $action ) ) {
 			return;
 		}

Feel free to defer — behaviourally this is a no-op in practice because the only public entry point feeds the isset()-guarded GET param through.

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

In
`@plugins/woocommerce/src/Internal/StockNotifications/Emails/EmailActionController.php`
around lines 71 - 104, The method validate_and_maybe_process_request currently
has a default empty string for $action which results in the default switch
branch logging an "Unknown email_link_action" for an empty value; fix by making
the distinction explicit — either remove the default (make $action a required
parameter) or add an early guard like if ( '' === $action ) { return; } (or log
a clearer "empty email_link_action" message) before the switch, and if you make
the signature non-default, update the caller maybe_process_email_action
accordingly to pass the sanitized action.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@plugins/woocommerce/src/Internal/StockNotifications/Emails/EmailActionController.php`:
- Around line 71-104: The method validate_and_maybe_process_request currently
has a default empty string for $action which results in the default switch
branch logging an "Unknown email_link_action" for an empty value; fix by making
the distinction explicit — either remove the default (make $action a required
parameter) or add an early guard like if ( '' === $action ) { return; } (or log
a clearer "empty email_link_action" message) before the switch, and if you make
the signature non-default, update the caller maybe_process_email_action
accordingly to pass the sanitized action.

In
`@plugins/woocommerce/tests/php/src/Internal/StockNotifications/Emails/EmailActionControllerTests.php`:
- Around line 103-111: Replace the dummy assertion in the
test_process_action_with_missing_notification_id_handles_gracefully test with
PHPUnit's intent API: call $this->expectNotToPerformAssertions() before invoking
EmailActionController::validate_and_maybe_process_request(0, 'any-key',
'verify') so the test expresses "no assertions expected" instead of using
$this->assertTrue(true); keep the same EmailActionController instantiation and
method call.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: f540021a-025f-4c94-879f-881211d3fa18

📥 Commits

Reviewing files that changed from the base of the PR and between 4ac4d40 and 967cd98.

📒 Files selected for processing (6)
  • plugins/woocommerce/changelog/64398-bis-email-action-constants
  • plugins/woocommerce/src/Internal/StockNotifications/Emails/CustomerStockNotificationEmail.php
  • plugins/woocommerce/src/Internal/StockNotifications/Emails/CustomerStockNotificationVerifiedEmail.php
  • plugins/woocommerce/src/Internal/StockNotifications/Emails/CustomerStockNotificationVerifyEmail.php
  • plugins/woocommerce/src/Internal/StockNotifications/Emails/EmailActionController.php
  • plugins/woocommerce/tests/php/src/Internal/StockNotifications/Emails/EmailActionControllerTests.php
✅ Files skipped from review due to trivial changes (2)
  • plugins/woocommerce/changelog/64398-bis-email-action-constants
  • plugins/woocommerce/src/Internal/StockNotifications/Emails/CustomerStockNotificationVerifyEmail.php
🚧 Files skipped from review as they are similar to previous changes (1)
  • plugins/woocommerce/src/Internal/StockNotifications/Emails/CustomerStockNotificationEmail.php

- Add missing exit after wp_safe_redirect() in both email-action handlers
  so execution stops once the 302 is queued.

- Guard check_unsubscribe_key against an empty stored hash, mirroring
  check_verification_key semantics. wp_verify_fast_hash tolerates an
  empty string today but the explicit early-return makes intent
  greppable and removes the implicit dependency.

- Coerce the verification-key meta to string before str_contains so a
  stored null/int can never trip a TypeError.

- Reword tests/legacy/bootstrap.php BIS comment to reflect that the
  constant is only set as a default — per-suite overrides still win.

- Parenthesize the expired-verification-key timestamp arithmetic in
  NotificationTests for readability.

- Separate empty vs unknown action in EmailActionController: empty
  means the caller omitted the argument (silent return); unknown keeps
  the debug-log branch so genuinely mis-routed tokens surface.

- Replace assertTrue(true) in the no-assertion happy-path test with
  expectNotToPerformAssertions() to avoid PHPUnit's risky-test warning.

Addresses CodeRabbit nits across PRs #64329, #64367, #64368, #64369,
#64370, #64371 that share files on the feature branch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
plugins/woocommerce/tests/php/src/Internal/StockNotifications/Emails/EmailActionControllerTests.php (1)

56-132: Solid negative-path coverage expansion.

The new tests cover the three important new branches: invalid-key rejection, action/key mismatch (unsubscribe routed at a notification with only a verification key), zero/missing ID, and unknown action token. expectNotToPerformAssertions() is the right call for the intentionally side-effect-free path at lines 103-112.

Optional: you might add one more tiny test for the explicit-empty-$action early return in validate_and_maybe_process_request (e.g. ->validate_and_maybe_process_request( $id, 'test', '' )) to lock down that branch separately from the "unknown token" branch, since they take different code paths in the controller (early return vs. default case with debug log). Not a blocker.

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

In
`@plugins/woocommerce/tests/php/src/Internal/StockNotifications/Emails/EmailActionControllerTests.php`
around lines 56 - 132, Add a tiny test that covers the explicit-empty-$action
early-return branch: create a product and Notification (e.g. status PENDING or
ACTIVE as appropriate), save it, call
EmailActionController::validate_and_maybe_process_request($id, 'test', '') and
assert the notification was not mutated (e.g. compare status before/after). Name
the test method something like
test_process_action_with_empty_action_early_returns to make the intent clear and
ensure it verifies the early-return path is distinct from the unknown-token
branch.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@plugins/woocommerce/tests/php/src/Internal/StockNotifications/Emails/EmailActionControllerTests.php`:
- Around line 56-132: Add a tiny test that covers the explicit-empty-$action
early-return branch: create a product and Notification (e.g. status PENDING or
ACTIVE as appropriate), save it, call
EmailActionController::validate_and_maybe_process_request($id, 'test', '') and
assert the notification was not mutated (e.g. compare status before/after). Name
the test method something like
test_process_action_with_empty_action_early_returns to make the intent clear and
ensure it verifies the early-return path is distinct from the unknown-token
branch.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 34964822-91c4-442f-832d-ac46b268061f

📥 Commits

Reviewing files that changed from the base of the PR and between 967cd98 and 988bcae.

📒 Files selected for processing (5)
  • plugins/woocommerce/src/Internal/StockNotifications/Emails/EmailActionController.php
  • plugins/woocommerce/src/Internal/StockNotifications/Notification.php
  • plugins/woocommerce/tests/legacy/bootstrap.php
  • plugins/woocommerce/tests/php/src/Internal/StockNotifications/Emails/EmailActionControllerTests.php
  • plugins/woocommerce/tests/php/src/Internal/StockNotifications/NotificationTests.php
🚧 Files skipped from review as they are similar to previous changes (2)
  • plugins/woocommerce/src/Internal/StockNotifications/Notification.php
  • plugins/woocommerce/tests/php/src/Internal/StockNotifications/NotificationTests.php

Line-number citations in markdown rot every time the target file is
edited and give a false-precision signal — a reader searching for the
cited line after a few weeks of churn lands on unrelated code. Replace
the remaining ones in SPRINT.md with symbol references (method names
and file stems).

Also tighten the CODERABBIT-TRIAGE uncertainty-notes wording and add a
banner at the top of the triage doc making it explicit that the file
is a pre-GA scratchpad listed in SPRINT.md for deletion, not reference
documentation — so anyone who lands on it via a search doesn't treat
the triage notes as the permanent shape of the code.

Addresses CodeRabbit nits on PRs #64369 (CODERABBIT-TRIAGE wording) and
#64370 (SPRINT.md anchor format).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

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

Inline comments:
In `@plugins/woocommerce/src/Internal/StockNotifications/CODERABBIT-TRIAGE.md`:
- Around line 91-96: Update the summary count in the triage header to match the
breakdown: either change "Of the 11 items flagged" to "Of the 12 items flagged"
or adjust the bucket numbers so 9 + 2 + 1 totals 11; locate the header sentence
that begins "Of the 11 items flagged in the handoff as 'already identified
must-fixes'" and make the counts consistent with the listed bullet breakdown
(NotificationEditPage, Notification, Config, NotificationsProcessor,
DataRetentionController, html-admin-notification-create,
customer-stock-notification-verified, HasherHelper,
CustomerStockNotificationVerifyEmail, and CustomerStockNotificationEmail).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 625b6d42-4fee-433b-a14d-0dab305395f6

📥 Commits

Reviewing files that changed from the base of the PR and between 988bcae and d6b7e95.

📒 Files selected for processing (2)
  • plugins/woocommerce/src/Internal/StockNotifications/CODERABBIT-TRIAGE.md
  • plugins/woocommerce/src/Internal/StockNotifications/SPRINT.md
✅ Files skipped from review due to trivial changes (1)
  • plugins/woocommerce/src/Internal/StockNotifications/SPRINT.md

Comment thread plugins/woocommerce/src/Internal/StockNotifications/CODERABBIT-TRIAGE.md Outdated
The unknown-token test locks down the switch's default: path (debug-log
branch). This adds a separate test for the empty-action early return
that sits before the switch, so the two distinct code paths for
'caller omitted the argument' vs 'caller passed a bogus token' are
each asserted in isolation.

Addresses CodeRabbit nit on PR #64329.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
plugins/woocommerce/tests/php/src/Internal/StockNotifications/Emails/EmailActionControllerTests.php (1)

21-153: Optional: extract a helper to DRY the notification-arrange blocks.

Five of the seven tests repeat the same 6-line arrange block (create product → new Notification → set product id / status / email → update_meta_datasave). A tiny private helper would make each test read as "given a PENDING notification with this key meta, when I call the controller with X, then status is Y."

♻️ Proposed helper extraction
+	/**
+	 * Arrange a persisted notification with a single action-key meta entry.
+	 *
+	 * `@param` string $status       Initial NotificationStatus.
+	 * `@param` string $meta_key     'verification_action_key' or 'unsubscribe_action_key'.
+	 * `@param` string $stored_key   Value to store in the meta (already hashed / time-prefixed by caller).
+	 * `@return` int Saved notification id.
+	 */
+	private function arrange_notification( string $status, string $meta_key, string $stored_key ): int {
+		$product      = WC_Helper_Product::create_simple_product();
+		$notification = new Notification();
+		$notification->set_product_id( $product->get_id() );
+		$notification->set_status( $status );
+		$notification->set_user_email( 'test@example.com' );
+		$notification->update_meta_data( $meta_key, $stored_key );
+		return $notification->save();
+	}

Then each test collapses to, e.g.:

$id = $this->arrange_notification(
    NotificationStatus::PENDING,
    'verification_action_key',
    time() . ':' . wp_fast_hash( 'test' )
);
( new EmailActionController() )->validate_and_maybe_process_request( $id, 'wrong-key', 'verify' );
$this->assertEquals( NotificationStatus::PENDING, Factory::get_notification( $id )->get_status() );

Non-blocking — happy to defer if you prefer to keep each test fully self-contained.

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

In
`@plugins/woocommerce/tests/php/src/Internal/StockNotifications/Emails/EmailActionControllerTests.php`
around lines 21 - 153, Many tests duplicate the same notification setup; add a
private helper (e.g., arrange_notification) to DRY creation and saving of a
Notification so tests become shorter; implement a private method on the test
class that accepts arguments (status, meta_key, meta_value) and performs: create
product via WC_Helper_Product::create_simple_product(), instantiate
Notification, call set_product_id, set_status, set_user_email, update_meta_data,
save and return the saved id; then replace repeated arrange blocks with calls to
this helper and keep assertions and
EmailActionController::validate_and_maybe_process_request calls intact.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@plugins/woocommerce/tests/php/src/Internal/StockNotifications/Emails/EmailActionControllerTests.php`:
- Around line 21-153: Many tests duplicate the same notification setup; add a
private helper (e.g., arrange_notification) to DRY creation and saving of a
Notification so tests become shorter; implement a private method on the test
class that accepts arguments (status, meta_key, meta_value) and performs: create
product via WC_Helper_Product::create_simple_product(), instantiate
Notification, call set_product_id, set_status, set_user_email, update_meta_data,
save and return the saved id; then replace repeated arrange blocks with calls to
this helper and keep assertions and
EmailActionController::validate_and_maybe_process_request calls intact.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: bb1f91d0-262d-4df6-a9b6-74a4cc266d3d

📥 Commits

Reviewing files that changed from the base of the PR and between d6b7e95 and 17beffe.

📒 Files selected for processing (1)
  • plugins/woocommerce/tests/php/src/Internal/StockNotifications/Emails/EmailActionControllerTests.php

opr and others added 2 commits April 27, 2026 09:52
- Correct triage reconciliation count from 11 to 12 (9 + 2 + 1 = 12).
- Extract a private arrange_notification() helper in
  EmailActionControllerTests so each test reads as 'given a notification
  with this status and meta, when the controller is called with X,
  expect status Y' instead of duplicating the 6-line setup block seven
  times.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace 64398-bis-email-action-constants, bis-coderabbit-review-fixes, and bis-remove-hasherhelper-shim with one bis-alpha-cr-fixes-and-cleanup entry summarising the scope of #64329.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@opr opr requested review from xristos3490 and removed request for jorgeatorres and prettyboymp April 27, 2026 12:10
Removes SPRINT.md, CODERABBIT-TRIAGE.md, and CODERABBIT-TRIAGE-RAW.md from src/Internal/StockNotifications/ — these were sprint-internal scratch files always intended for pre-merge cleanup. Updates the bis-alpha-cr-fixes-and-cleanup changelog entry to reflect the deletion.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@opr opr modified the milestones: 10.8.0, 10.9.0 Apr 28, 2026

@xristos3490 xristos3490 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Reviewed the code over huddle. This tests well on my end, and looks good, @opr! Nice touch on removing the HasherHelper!

@opr opr merged commit 35dfa2e into trunk Apr 29, 2026
120 of 122 checks passed
@opr opr deleted the feature/back-in-stock-notifications branch April 29, 2026 10:10
kalessil added a commit that referenced this pull request May 21, 2026
* Trap wp_safe_redirect in EmailActionControllerTests to fix silent PHPUnit halt

PR #64329 added `exit;` after `wp_safe_redirect()` in
EmailActionController::process_verification_action() and
process_unsubscribe_action(). The new EmailActionControllerTests calls
validate_and_maybe_process_request() directly with valid keys, which
now reaches `wp_safe_redirect + exit;` and silently terminates PHPUnit
mid-run with status 0 — CI stays green while the rest of the suite
never executes.

Install the same `wp_redirect` filter trap already used by the sister
NotificationManagementServiceTests so the controller's `exit;` is never
reached during tests. The four happy-path tests (verify success,
verified email dispatch, repeated-verify short-circuit, unsubscribe)
wrap their SUT calls in try/catch and assert the intercept message.
Production code is unchanged — wp_safe_redirect + exit is the correct
WP pattern.

Bug introduced in PR #64329.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* Add changefile(s) from automation for the following project(s): woocommerce

* Monorepo: cleanup.

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-authored-by: woocommercebot <woocommercebot@users.noreply.github.com>
Co-authored-by: Vladimir Reznichenko <kalessil@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

plugin: woocommerce Issues related to the WooCommerce Core plugin.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants