Skip to content

fix: harden transaction status updates and expand email/e2e coverage#228

Merged
zainfathoni merged 8 commits intomainfrom
fix/email-test-assertion
Feb 27, 2026
Merged

fix: harden transaction status updates and expand email/e2e coverage#228
zainfathoni merged 8 commits intomainfrom
fix/email-test-assertion

Conversation

@wheeljackz
Copy link
Copy Markdown
Collaborator

@wheeljackz wheeljackz commented Feb 26, 2026

Summary

  • Add stronger email test coverage by asserting emailProvider.sendEmail is called with the expected payload.
  • Harden transaction status updates with:
    • canUpdateTransactionStatus helper
    • server-side status allowlist validation
    • atomic DB guard to prevent TOCTOU race conditions
    • HTTP 409 Conflict on concurrent update conflicts
  • Improve dashboard transaction UI by disabling the reject button for VERIFIED and REJECTED transactions.
  • Improve E2E reliability in WebKit by using click-before-fill for focus stability.

Why

This PR now covers both testing improvements and transaction update hardening (backend + UI), plus E2E stability fixes. The updated scope clarifies all shipped changes so reviewers can evaluate the full impact.

wheeljackz and others added 5 commits February 21, 2026 08:47
…s called

The existing test had a TODO comment noting that it should assert
emailProvider.sendEmail was called but only verified no exceptions were thrown.

Now uses vi.spyOn to verify:
- emailProvider.sendEmail is called exactly once
- called with the correct recipient address
- called with an html body that includes the magic link URL

email-provider.server.ts already short-circuits in test mode (returns
Promise.resolve without hitting Mailgun), so no mocking of HTTP is needed.
… 11)

Playwright's fill() is supposed to focus the element before typing, but on
WebKit/Safari (used for the iPhone 11 test project), the synthetic onFocus
event is not always reliably dispatched in CI's headless environment.

The Field component tracks 'interacted' state (set on onFocus or onChange)
to decide whether to show validation errors after blur. If onFocus doesn't
fire, 'interacted' stays false and the error message never renders, causing
the E2E assertion to fail with 'element(s) not found'.

Fix: add an explicit .click() before .fill() in both validation tests. A
click is guaranteed to trigger focus events in all browsers, ensuring
'interacted' is set to true before we blur and check for the error.

Applied to both phone-number and name validation tests for consistency.
Copy link
Copy Markdown
Owner

@zainfathoni zainfathoni left a comment

Choose a reason for hiding this comment

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

Thanks for the cleanup and for addressing the email test TODO + WebKit flakiness note. I found one correctness/security gap that should be fixed before merge: the action still trusts any string status from form data and casts it to TransactionStatus, so crafted requests can still transition VERIFIED -> SUBMITTED (or other unintended states). That bypasses the new business rule intent and can leave subscription state inconsistent.

What looks good:

  • Email test now verifies provider handoff with concrete argument checks.
  • WebKit click-before-fill adjustment is a reasonable stabilization step for focus-triggered validation.

Please add strict runtime validation for status (allow only VERIFIED/REJECTED in this action) and expand tests to cover tampered status payloads and forbidden transitions at the action level.

Comment thread app/routes/dashboard.transactions.$transactionId.$action.tsx
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR improves reliability and correctness across tests and transaction status handling by (1) strengthening an email unit test to assert provider invocation, (2) hardening the dashboard transaction reject flow so verified transactions can’t be rejected, and (3) making a couple of E2E interactions more reliable on WebKit.

Changes:

  • Add a Vitest spy assertion to ensure emailProvider.sendEmail is invoked with expected payload.
  • Introduce canUpdateTransactionStatus and apply it in the transaction status action + disable reject UI for verified transactions.
  • Refine TRANSACTION_STATUS typing (as const + derived TransactionStatus) and add unit tests for the new status-transition helper.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
e2e/profile.spec.ts Adds click() before filling fields to improve WebKit focus/validation reliability.
app/utils/transaction-status.ts Adds a centralized rule for allowed transaction status transitions.
app/utils/tests/transaction-status.test.ts Unit tests for canUpdateTransactionStatus.
app/services/tests/email.server.test.ts Adds spy assertions to verify provider handoff occurs.
app/routes/dashboard.transactions.$transactionId.tsx Disables “reject” action in UI when transaction is VERIFIED (or already REJECTED).
app/routes/dashboard.transactions.$transactionId.$action.tsx Enforces “VERIFIED can’t become REJECTED” server-side via the new helper.
app/models/enum.ts Makes TRANSACTION_STATUS literal-typed and derives TransactionStatus from it.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread app/services/__tests__/email.server.test.ts
Comment thread app/routes/dashboard.transactions.$transactionId.$action.tsx
Comment thread app/routes/dashboard.transactions.$transactionId.$action.tsx Outdated
Copy link
Copy Markdown
Owner

@zainfathoni zainfathoni left a comment

Choose a reason for hiding this comment

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

ISSUES FOUND

Thanks for the improvements around transaction status validation and added coverage. I reviewed all changed files and ran checks locally (npm test, npm run lint, npm run type-check).

Blocking issue:

  • npm run type-check currently fails due to the new action test using response.status/response.headers without narrowing from the broad ActionFunction return type. This makes the PR non-mergeable in its current form in a type-checked pipeline.

What looks good:

  • Server-side allowlist for transaction status and transition guard reduce payload tampering risk.
  • UI now disables reject action for already verified transactions, matching backend rules.
  • sendEmail test TODO is now fulfilled with concrete assertions.
  • E2E focus handling adjustments improve WebKit stability.

Please fix the type error in the test and I can re-review quickly.

Copy link
Copy Markdown
Owner

@zainfathoni zainfathoni left a comment

Choose a reason for hiding this comment

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

ISSUES FOUND

I reviewed all changed files in this PR (transaction status validation/UI guard, related unit tests, email test assertion update, and E2E profile tweaks). Most changes improve correctness and coverage, but there is one merge-readiness issue:

  1. The transition check is currently vulnerable to a TOCTOU race. The code validates existingTransaction.status and then performs an unconditional updateTransactionStatus by id. Two concurrent moderator actions can still produce a forbidden VERIFIED -> REJECTED end state despite the guard. Please make the transition enforcement atomic at the DB write boundary (for example, conditional update where id + allowed current statuses, then verify affected row count).

After this is fixed, the rest of the PR looks good from security/correctness/type/test-quality perspective.

Comment thread app/routes/dashboard.transactions.$transactionId.$action.tsx
Copy link
Copy Markdown
Owner

@zainfathoni zainfathoni left a comment

Choose a reason for hiding this comment

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

NO ISSUES FOUND

I reviewed all changed files in this PR (models, routes, utils, unit tests, and E2E spec updates) with focus on correctness, security, TypeScript safety, and test quality.

What I validated:

  • Status transition hardening now enforces server-side allowlisting and blocks forbidden VERIFIED -> REJECTED transitions, including against tampered payloads.
  • updateTransactionStatus uses conditional where filtering with handled P2025 to convert race-condition misses into a controlled null result, which the route now maps to HTTP 409.
  • UI behavior now aligns with backend rules by disabling reject action for already VERIFIED transactions.
  • New tests cover payload tampering rejection, forbidden transition rejection, and allowed transition success; utility-level transition rules are also covered.
  • Email test TODO is fully addressed with explicit spy assertions on invocation and key message fields.
  • E2E profile updates improve WebKit reliability without weakening assertions.

Verification run locally on this branch:

  • npm test passed (116/116)
  • npm run type-check passed
  • npm run lint shows only pre-existing Playwright warnings (no errors).

Merge-readiness: looks good from a code quality and safety standpoint.

Comment thread app/models/transaction.ts
@zainfathoni zainfathoni changed the title test: fulfill email sendEmail TODO — assert emailProvider.sendEmail is called fix: harden transaction status updates and expand email/e2e coverage Feb 27, 2026
@zainfathoni zainfathoni merged commit 81528aa into main Feb 27, 2026
8 checks passed
@zainfathoni zainfathoni deleted the fix/email-test-assertion branch February 27, 2026 21:03
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.

3 participants