WrightPay: Cross-Border Payments Test Architecture
A comprehensive quality engineering case study validating an enterprise cross-border payment platform from the Next.js frontend to NestJS microservices, BullMQ settlement queues, and PostgreSQL ledger transactions.
Live API / Swagger — protected endpoints require authentication.
System Architecture & Transfer Lifecycle
WrightPay simulates multi-currency cross-border money movement. Testing it requires understanding the exact data and control flow across distributed components.
How Tests Attach to WrightPay's Distributed System
2. Backend REST API Layer(NestJS / TypeScript / Express)
Route handling, JWT auth guards, DTO validation pipes, controller orchestration.
State Invariants Asserted at this Layer:
- ✓Strict Bearer token validation and user ownership checks
- ✓Deterministic status codes (200, 201, 400, 401, 404, 409)
- ✓UUID path parameter validation via ParseUUIDPipe
WrightPay Transfer Execution Lifecycle
Trace how a cross-border payment transitions from client initiation to database locking, queue handoff, and background settlement.
Pessimistic Debit & Persistence
State Invariant & Mechanism
Source wallet row locked. Balance checked >= sendAmount + 25.00 EUR fee. Wallet debited and transaction row inserted with status 'PENDING'.
Failure Path & Defect Analysis
400 Insufficient Funds if balance < total deduction; TypeORM transaction rolls back.
Financial Domain Specifications
- • Multi-Currency Engine: Base EUR balances with on-the-fly FX conversion across USD, GBP, AED, PLN, and INR.
- • Pessimistic Concurrency: Database uses
SELECT ... FOR UPDATEon wallet rows to prevent race-condition overdrawing. - • Fixed Fee Rule: Flat 25.00 EUR transfer deduction applied strictly to all transfer orders.
Asynchronous Queue Model
- • BullMQ Worker:
TransfersProcessorconsumes jobs from Redis queuetransfersQueue. - • Retry Backoff: Configured with 3 retry attempts with exponential backoff on simulated partner bank timeouts.
- • Idempotency TTL: 24-hour distributed lock key window managed in Redis.
Multi-Layer Test Architecture & Testing Matrix
The testing pyramid is balanced to maximize execution speed and failure localization while minimizing UI test flakiness.
Different Testing Layers Answer Different Questions
API & Integration Testing Tier
Core Engineering Question Answered:
"Do the controllers, database transactions, and auth guards enforce state integrity under HTTP load?"
Key Invariants Verified at this Tier:
- ✓SELECT ... FOR UPDATE serializes concurrent debits (prevents double-spend)
- ✓Strict 401 on missing Bearer tokens
- ✓Idempotency-Key returns cached response on replay
Full Test Matrix (Layer • Tool • System Area • Invariant)
| Layer | Tool | System Area | Purpose & Scope | State / Invariant Tested | Evidence Source | Count |
|---|---|---|---|---|---|---|
| Unit | Jest | Cryptography & Auth | Validate password hashing, token generation, and account access | Argon2 salt generation uniqueness; deterministic token claims | backend/src/modules/auth/auth.service.spec.ts | 14 |
| Unit | Jest | Currency Exchange Engine | Verify multi-currency arithmetic and rate calculations | Triangular conversion exactness; zero IEEE-754 precision drift | backend/src/modules/exchange-rates/exchange-rates.service.spec.ts | 10 |
| Unit | Jest | Card & Payment Validation | Verify card tokenization, safe PAN masking, and lifecycle state | Tokenizes and stores only last-4 digits; strict active/frozen/deactivated lifecycle | backend/src/modules/cards/cards.service.spec.ts | 14 |
| Unit | Jest | Transfer Fees & Ledger | Verify fixed transfer fee deduction and balance checks | Flat 25.00 EUR fee strictly applied to all transfer amounts | backend/src/modules/transfers/transfers.service.spec.ts | 10 |
| API / Integration | Playwright API | Wallets Controller & Service | Validate wallet balances, schema, and currency equivalents | Initial balance 0.00 EUR; multi-currency equivalent calculation; Postgres cross-layer validation | qa/automation/tests/wallet/wallet.spec.ts | 8 |
| API / Integration | Playwright API | Transfers Initiation Endpoint | Validate transfer creation, debit, and queue scheduling | Atomic debit, fixed 25.00 EUR fee, BullMQ enqueue, and PENDING/COMPLETED states in DB | qa/automation/tests/transfers/transfers.spec.ts | 30 |
| API / Integration | Playwright API | Beneficiaries CRUD API | Validate recipient persistence, validation, and deletion | Unique account numbers per user; bank account and UPI validation; 3-beneficiary limit | qa/automation/tests/beneficiaries/beneficiaries.spec.ts | 25 |
| API / Integration | Playwright API | Card Management API | Validate card tokenization, freeze, and unfreeze | Frozen cards reject subsequent payout authorization; safe PAN storage | qa/automation/tests/cards/cards.spec.ts | 19 |
| API / Integration | Playwright API | Authentication & Security | Validate login, registration, and email verification lifecycle | Invalid credentials return 401; unverified emails restricted; JWT token issuance | qa/automation/tests/auth/auth.spec.ts | 19 |
| API / Integration | Playwright API | Exchange Rates Public API | Validate public currency quote endpoints and schemas | Rates return HTTP 200 with all 6 supported fiat currencies; triangular conversion | qa/automation/tests/exchange-rates/exchange-rates.spec.ts | 42 |
| E2E Functional | Playwright | Authentication UI Flows | Verify registration, login, and dashboard redirect | Session persists across refresh; logout clears local storage | qa/automation/tests/ui/auth/auth.spec.ts | 5 |
| E2E Functional | Playwright | Send Money Wizard | Complete multi-step transfer wizard with live quote | Wallet balance updates in real-time on transfer submission | qa/automation/tests/ui/transfers/send-money.spec.ts | 8 |
| E2E Functional | Playwright | Beneficiary Management UI | Add, edit, and select beneficiaries for payout | New beneficiaries appear immediately in transfer selection; form validation | qa/automation/tests/ui/beneficiaries/beneficiaries.spec.ts | 6 |
| E2E Functional | Playwright | Cards Management UI | Add virtual cards and toggle freeze status | Freeze status badge toggles instantly on UI without reload | qa/automation/tests/ui/cards/cards.spec.ts | 6 |
| Accessibility | @axe-core/playwright | All 12 Application Views | Automated scan for WCAG 2.1 AA accessibility violations | Zero critical WCAG AA automated rule violations | qa/automation/tests/ui/accessibility/ (qa/ACCESSIBILITY_AUDIT.md) | 26 |
| Responsive | Playwright Viewports | Layout & Navigation Shell | Verify layout stability at 375px, 390px, 768px, 1024px, 1440px | No horizontal scrollbar; stepper and tables fit viewport | qa/automation/tests/ui/responsive/responsive.spec.ts | 36 |
| Error Handling | Playwright Mocks | Network Fault Resilience | Inject HTTP 400, 404, 409, 500 and network disconnects | UI displays informative error messages; avoids infinite loaders | qa/automation/tests/ui/errors/error-handling.spec.ts | 44 |
| Session & Auth | Playwright | Session Lifecycle & Expiry | Verify 401 response handling, token eviction, and polling cancel | 401 immediately purges storage and cancels active poll loops | qa/automation/tests/ui/session/session-expiry.spec.ts (qa/SESSION_EXPIRY_AUDIT.md) | 46 |
| Concurrency | Playwright / Redis | Idempotency & Parallelism | Simultaneous transfer calls with duplicate and distinct keys | Only 1 execution processed; duplicates return cached response | qa/automation/tests/concurrency/idempotency-race.spec.ts | 6 |
| Regression | Playwright test.fail() | Verified Defect Regression | Continuous assertion of known open defects in CI | Expected failure status asserted; triggers alert if defect closes | qa/automation/tests/defects/ (via playwright.defects.config.ts) | 9 |
Why I Tested It This Way
Demonstrating the technical reasoning, architectural trade-offs, and risk mitigations behind every testing decision.
Built 277 API integration tests covering 100% of controller routes and parameter combinations, keeping the E2E UI suite lean at 49 tests.
Trade-off: Writing 200+ UI tests would provide visual confirmation, but at the cost of 10x slower execution times and frequent locator flakiness.
Risk Mitigated: Eliminates combinatorial explosion of browser tests while proving every HTTP status code (200, 201, 400, 401, 404, 409) executes in under 8 seconds.
> API tests isolate the business domain from rendering quirks. When an API test fails, the defect location is immediately known. UI tests should validate user journeys, not HTTP parameter validation.
Executed direct SQL queries against PostgreSQL in integration tests rather than relying solely on API response JSON.
Trade-off: Couples test code to database schema, requiring maintenance if column names change.
Risk Mitigated: Prevents false-positive tests where an API returns HTTP 201 Created while failing to commit database rows or leaving foreign keys in an inconsistent state.
> In financial software, the database is the source of truth, not the HTTP response payload. Verifying row existence, decimal column types, and foreign key cascades is mandatory for ledger integrity.
Implemented dedicated replay tests transmitting duplicate Idempotency-Key headers with both identical and mutated payloads.
Trade-off: Requires managing Redis test keys and ensuring TTL eviction during test runs.
Risk Mitigated: Guarantees that network retries by users, mobile apps, or reverse proxies never execute duplicate monetary deductions from customer wallets.
> Payment networks are inherently unreliable. If a user clicks 'Transfer' and loses connectivity before the HTTP response arrives, their retry MUST return the existing transaction rather than initiating a second debit.
Fired parallel execution bursts using Promise.all against single wallet resources to verify pessimistic row-level locking.
Trade-off: Concurrent tests require careful isolation to avoid deadlocks in the test database.
Risk Mitigated: Eliminates double-spend exploits where rapid parallel requests read the same balance before either writes the deduction, driving account balances below zero.
> Traditional single-threaded test suites will NEVER catch race conditions. Asserting that PostgreSQL SELECT ... FOR UPDATE serializes concurrent debits is essential for any financial application.
Connected directly to Redis via ioredis during tests to verify lock creation, TTL expiration, and key state transitions.
Trade-off: Introduces external Redis client dependency in the test harness.
Risk Mitigated: Uncovered WP-QA-007, where early failure deleted the idempotency key prematurely, leaving concurrent polling callers hanging for 2.5 seconds.
> Black-box testing cannot verify if a cache key has the proper 24-hour TTL or whether locks are released cleanly after an exception. Direct cache inspection verifies coordination mechanics.
Simulated worker delays, queue backlog bursts, and job completion events to validate decoupled background processing.
Trade-off: Requires asynchronous polling helpers (waitForTransactionStatus) in test specs.
Risk Mitigated: Discovered WP-QA-002: an unhandled Redis enqueue error after a database commit risks permanently stranding transactions in PENDING state.
> Synchronous HTTP endpoints that offload work to background queues must be verified from job scheduling to worker execution. A 201 response only means the request was received, not settled.
Injected SIMULATE_FAILURE flags to force settlement failures and observe worker terminal behavior.
Trade-off: Requires backend support for test-only failure simulation flags.
Risk Mitigated: Directly uncovered flagship defect WP-QA-001: failed asynchronous transfers transition to FAILED status in PostgreSQL, but customer funds remain permanently debited without a refund.
> Happy paths are easy; failure paths are where money is lost. When a payout fails at partner banks, the system MUST execute a compensating transaction to restore wallet balances.
Submitted non-UUID strings ('not-a-uuid', '123', 'NaN') to all URL path parameters across all controllers.
Trade-off: Time investment in negative boundary sweeps.
Risk Mitigated: Uncovered WP-QA-003, where malformed UUIDs bypass NestJS validation and crash PostgreSQL with fatal 22P02 syntax errors, leaking database internals in HTTP 500 responses.
> Robust APIs fail gracefully at the transport layer. A malformed URL should return HTTP 400 Bad Request via NestJS ParseUUIDPipe before a single database query is dispatched.
Validated serialized JSON types against OpenAPI specifications using automated schema validation.
Trade-off: Requires maintaining synced OpenAPI documentation with backend DTOs.
Risk Mitigated: Uncovered WP-QA-004: exchange rates declared as type: number in OpenAPI were serialized as strings by pg, risking crashes in type-strict mobile/frontend clients.
> An API is a binding contract. If backend serialization diverges from published schemas, consumer clients (mobile, frontend, partner gateways) suffer unhandled deserialization crashes.
Executed automated @axe-core/playwright scans across all 12 frontend views and validated keyboard navigation.
Trade-off: Automated a11y tools only detect ~40% of WCAG issues; required supplemental keyboard audits.
Risk Mitigated: Uncovered 9 verified accessibility defects, including missing form label associations on /dashboard/profile and missing focus traps in modal dialogs.
> Financial interfaces must be usable by everyone. If a user with a screen reader cannot identify an input field or get trapped inside a modal dialog, the software fails fundamental regulatory and human standards.
Automated 36 viewport test runs across 375px, 390px, 768px, 1024px, and 1440px widths.
Trade-off: Increases test execution time across multiple device profiles.
Risk Mitigated: Uncovered WP-QA-RESP-001: fixed-width 256px sidebar blocked 68% of screen real estate on mobile phones without a collapsible hamburger menu.
> Over 60% of modern fintech transactions occur on mobile devices. Ensuring tables scroll internally and wizards do not clip is critical for operational usability.
Audited token expiry lifecycles, unauthorized route interception, and polling timer cleanup.
Trade-off: Requires mock token generation and timer manipulation in Playwright.
Risk Mitigated: Uncovered WP-QA-AUTH-001 (React AuthContext desync on 401) and WP-QA-AUTH-002 (polling timers firing infinitely after unauthorized response).
> A client that retains visual authenticated state after its token expires is a major security and UX risk. Polling timers that survive 401s waste server bandwidth and lock user sessions.
Tracked the 9 active defects via Playwright test.fail() annotations in a dedicated regression suite.
Trade-off: Requires writing and maintaining tests for bugs that are not yet fixed in the application code.
Risk Mitigated: Prevents defect forgetting. When a developer fixes the bug, test.fail() turns RED, immediately alerting the team that the bug was resolved and can graduate to passing.
> Disabling failing tests is bad practice. test.fail() documents the expected behavior, proves the defect exists, and ensures zero broken builds on main.
Utilized k6 for isolated performance benchmarks, explicitly classifying results as local-machine containerized evidence rather than production SLA claims.
Trade-off: Running load tests in CI requires isolated runner infrastructure to prevent noisy-neighbor throttling.
Risk Mitigated: Prevents deceptive marketing claims. A local machine running 8,272 req/s demonstrates endpoint efficiency, not cloud autoscaling capacity.
> Performance testing answers 'where does the system bottleneck under stress?'. Functional automation answers 'does the system behave correctly?'. Keeping them distinct preserves engineering integrity.
Idempotency, Concurrency & Distributed Queues
Deep architectural testing into Redis distributed lock mechanics, BullMQ worker failure paths, and PostgreSQL transaction isolation.
Redis Distributed Idempotency & The WP-QA-007 Race Hazard
How WrightPay coordinates concurrent transfers using Redis atomic locks, and the exact race condition discovered during automated concurrency testing.
Request A (Primary)
Idempotency-Key: "trf-998"
T+0ms ArrivesRequest B (Network Retry)
Idempotency-Key: "trf-998"
T+20ms ConcurrentRequest C (Parallel Tab)
Idempotency-Key: "trf-998"
T+35ms ConcurrentRequest A acquires Lock
SET NX succeeds. State set to 'PROCESSING'. Proceeds to PostgreSQL pessimistic wallet debit.
Requests B & C enter Polling Waiter
SET NX returns nil. Secondary callers enter a polling backoff loop (up to 2,500ms) waiting for Request A to write cached response.
What happens when Request A fails early (e.g. invalid recipient or insufficient balance)?
In WrightPay V1, when Request A encounters an early validation error, the catch handler executes await redis.del(key) immediately.
However, Requests B and C are already asleep inside the polling waiter awaiting lock resolution. Because the key vanished completely instead of updating to a terminal state, the polling loop fails to detect resolution and loops for the full 2,500ms timeout, eventually throwing an erroneous 409 Conflict: Concurrent request in progress.
✓ Automated Proof: Concurrency spec asserts response latency and captures the 2,500ms stalling behavior.
PostgreSQL State Invariants
Direct SQL assertions verify that wallet deductions match transfer sums plus fees. When insufficient balance is provided, TypeORM transaction rollback is asserted at the database layer.
Redis Coordination Invariants
Direct ioredis queries assert that keys are written with 24-hour TTL and SHA-256 payload hashes. Replay tests ensure cached HTTP 201 responses are returned without duplicate debits.
BullMQ Queue Invariants
Fault injection tests verify that transient failures trigger exactly 3 worker retries with backoff, ensuring transfer records capture failureReason upon permanent partner rejection.
Authoritative Flagship Defect Dossiers
8 selected representative defect investigations and architectural risks from the 28 confirmed defects register. Each dossier includes complete root-cause analysis, risk assessment, and reproduction code.
Missing Automatic Refund on Asynchronous BullMQ Settlement Failure
Symptom: When an asynchronous transfer reaches a terminal failure state in the BullMQ worker, customer funds remain permanently debited from their EUR balance without automatic restitution.
Upon permanent settlement failure, the system must atomically dispatch a compensating refund transaction, restoring the wallet balance to its pre-debit state (400.00 EUR) with a corresponding ledger audit entry.
Transaction status updates to 'FAILED', but zero wallet balance adjustments occur and zero refund ledger rows are inserted.
Root-Cause Analysis
In backend/src/modules/transfers/transfers.processor.ts, the catch block updates the transaction record with failureReason, but lacks any call to WalletsService.credit() or an automated compensating refund saga.
Step-by-Step Reproduction
- Create authenticated user with funded wallet (400.00 EUR balance).
- Initiate a cross-border transfer of 50.00 EUR with fee 25.00 EUR (total deduction 75.00 EUR).
- Inject SIMULATE_FAILURE in transfer metadata to trigger settlement failure.
- Allow BullMQ worker to exhaust its 3 automatic retry attempts with exponential backoff.
- Inspect transactions table: status is 'FAILED' and failureReason is populated.
- Inspect wallets table: balance remains 325.00 EUR (75.00 EUR permanently missing).
Business & Operational Risk
Direct loss of customer funds without administrative manual intervention. Critical financial compliance violation and customer trust breach.
Engineering Remediation Plan
Implement an automated compensation handler within the worker's terminal failure block: wrap wallet crediting and compensating transaction creation in a dedicated TypeORM transaction.
// Reproduction from qa/automation/tests/defects/wp-qa-001-settlement-refund.spec.ts
test('WP-QA-001: failed async transfer must restore debited wallet balance', async ({ authUser, db }) => {
test.fail(true, 'Known defect: TransfersProcessor does not issue compensating refund on permanent failure');
const initialBalance = 400;
const transfer = await authUser.api.transfers.create({
beneficiaryId,
sourceCurrency: 'EUR',
targetCurrency: 'USD',
sourceAmount: 50,
metadata: { testScenario: 'SIMULATE_FAILURE' }
});
// Wait for BullMQ worker retries to reach terminal FAILED status
await waitForTransactionStatus(db, transfer.id, 'FAILED', 15000);
// Financial Invariant: Customer funds MUST be restored to initial balance
const finalWallet = await db.queryOne('SELECT balance FROM wallets WHERE id = $1', [walletId]);
expect(Number(finalWallet.balance)).toBe(initialBalance); // Fails: 325.00 !== 400.00
});PostgreSQL Commit Precedes BullMQ Enqueue (Dual-Write Window)
Symptom: If Redis suffers a transient network outage or queue memory exhaustion immediately following a database transaction commit, transfers become permanently stranded in 'PENDING_EXECUTION' with no worker pickup.
Malformed Non-UUID Path Parameters Trigger Unhandled HTTP 500
Symptom: Supplying non-UUID values (e.g., 'invalid-uuid-123', 'test', 'NaN') in URL path parameters crashes with HTTP 500 Internal Server Error instead of 400 Bad Request or 404 Not Found.
Serialized Exchange Rate Returns String Conflict With OpenAPI Number Contract
Symptom: Exchange rate endpoints return decimal values serialized as JSON strings (e.g., 'rate': '1.0845') while the declared OpenAPI specification specifies type: number.
Beneficiary Creation Accepts Whitespace-Only Name and Persists Empty String
Symptom: Submitting a beneficiary with a name consisting exclusively of whitespace characters (' ') is accepted and saved into PostgreSQL with name: ''.
Idempotency Lock Key Deletion on Early Failure Stalls Concurrent Callers
Symptom: When a primary transfer request fails early (e.g., validation or balance error), the idempotency lock is deleted prematurely, causing secondary concurrent callers to block for 2.5s and receive a misleading 409 Conflict.
Low 32-Bit Entropy in Transfer Reference Generation Risks Collision Under Volume
Symptom: Transfer references are generated using 32 bits of entropy (crypto.randomBytes(4).toString('hex')), creating mathematical collision hazards under scale.
Frontend Card Creation Sends Uppercase Type Mismatching Backend Enum
Symptom: Submitting a new card from the web UI returns HTTP 400 with error 'cardType must be a valid enum value'.
Specialized QA Audit Findings (Documented in Master Defect Register)
Form label omissions, modal focus trapping gaps, low contrast on currency codes (WCAG AA).
Fixed 256px sidebar blocking mobile viewports, transaction table 2D page bleed.
Backend 500 errors masked as 'No recent transactions' empty states.
React AuthContext token desync, infinite transfer polling loop after 401.
Canonical Hierarchy: 28 Confirmed Findings (27 Functional Defects + 1 API Contract Discrepancy) across backend, frontend, and specialized audits, plus 2 separate Architectural Risks and 14 Documented Observations (qa/DEFECT_INVENTORY.md). The 8 flagship dossiers above represent selected investigations, not the total defect count.
Verified Test Evidence & Execution Telemetry
Concrete execution numbers across the 608 core baseline tests, the 9 known defect regression cases, and k6 performance benchmarks.
Jest Unit Suite
Pure business logic, Argon2 hashing, FX currency math, DTO transformation, and fee calculation algorithms.
Key Invariants Verified:
- •Zero floating point rounding error on multi-currency conversions
- •Deterministic Argon2 password salt/hash verification
- •Valid card Luhn algorithm validation
Backend API & Integration
End-to-end HTTP contract testing across all 8 NestJS REST controllers with PostgreSQL transactions.
Key Invariants Verified:
- •Strict bearer token authentication guards on private endpoints
- •Pessimistic row-level lock (SELECT FOR UPDATE) prevents double-spend
- •TypeORM transaction rollback on balance exhaustion
Frontend Functional / E2E
Multi-step user journey workflows across Next.js UI from authentication through transfer execution.
Key Invariants Verified:
- •Recipient creation, validation, and auto-population in transfer form
- •Real-time exchange rate quote recalculation on amount change
- •Wallet multi-currency equivalent tab rendering
Accessibility (WCAG 2.1 AA)
Automated WCAG 2.1 AA accessibility scans across all 12 application routes.
Key Invariants Verified:
- •Input form control label associations (htmlFor/id matching)
- •Focus trapping within active modal dialogs
- •Color contrast minimum ratio >= 4.5:1 for normal text
Responsive Viewport Suite
Cross-breakpoint visual and interaction testing across 375px, 390px, 768px, 1024px, and 1440px.
Key Invariants Verified:
- •Zero unscrollable horizontal 2D viewport overflow
- •Send Money step wizard layout integrity at 375px
- •Card detail rendering without PAN truncation
Error Handling & Edge Cases
Fault injection and HTTP error simulations validating UI resilience and error state rendering.
Key Invariants Verified:
- •Graceful degradation on backend 500 without crashing React tree
- •Clear error messaging instead of misleading empty states
- •Network dropout handling during transfer polling
Session Expiry & 401 Interceptors
Token lifecycle, automatic eviction, 401 response handling, and polling termination audits.
Key Invariants Verified:
- •Immediate localStorage token purge on 401 response
- •React AuthContext synchronization preventing zombie authenticated states
- •Transfer polling timer cancellation on unauthorized responses
Known Defect Regression Suite
9 automated test specifications asserting verified open defects using test.fail() to track fix readiness without breaking CI.
Key Invariants Verified:
- •WP-QA-001: Async transfer failure wallet refund omission
- •WP-QA-003: Non-UUID path parameter 500 error handling (3 tests)
- •WP-QA-004: Decimal exchange rate type discrepancy
k6 Load Testing: 6 Scenarios, 142,280 Total Requests
Baseline latency profiling against /wallets/me, /transactions, /exchange-rates.
Sustained read concurrency across exchange rate and public configuration endpoints.
Stress testing CPU-intensive Argon2 password hashing pipeline under auth spikes.
Simultaneous transfer creations validating Redis idempotency lock allocation.
Identical Idempotency-Key replays under burst conditions verifying cache hit rate.
Rapid BullMQ job scheduling evaluating Redis queue memory & pipeline depth.
Real Test Code & Project Retrospective
Examine 6 production-grade test specifications and read the engineering retrospective on what worked, scope limitations, and future improvements.
What this test does
Submits a transfer request using Playwright's API request context with Bearer authentication and asserts strict HTTP 201 response schema conformity.
Why it exists
Validates that the HTTP transport layer enforces correct status codes, header responses, and JSON payload serialization before the frontend touches it.
Risk / Bug Prevented
Catches undeclared response property changes, missing authorization header enforcement, and unhandled 500 crashes on missing fields.
import { test, expect } from '../fixtures/api.fixtures';
test('POST /api/v1/transfers creates transfer and returns compliant DTO', async ({ authUser }) => {
const payload = {
beneficiaryId: '3fa85f64-5717-4562-b3fc-2c963f66afa6',
sourceCurrency: 'EUR',
targetCurrency: 'USD',
sourceAmount: 100.00
};
const response = await authUser.api.transfers.create(payload, {
headers: { 'Idempotency-Key': crypto.randomUUID() }
});
expect(response.status()).toBe(201);
const body = await response.json();
expect(body).toMatchObject({
id: expect.stringMatching(/^[0-9a-f-]{36}$/),
status: 'PENDING',
sourceCurrency: 'EUR',
targetCurrency: 'USD',
sourceAmount: 100.00,
fee: 25.00
});
});Key Recommendations
- • Implement the Transactional Outbox Pattern to close the dual-write window between PostgreSQL commit and BullMQ enqueue.
- • Add ParseUUIDPipe validation across all controller route handlers to eliminate unhandled 22P02 database crashes.
- • Synchronize CreateCardDto casing with frontend select values to restore web payment card creation.
Documented Limitations
- • /onboarding flow is currently a client-side mock without actual KYC document verification.
- • Fixed 25.00 EUR fee structure regardless of transfer volume (intended V1 business logic constraint).
- • Single base EUR ledger with real-time FX equivalents rather than a multi-currency double-entry core.
Engineering Retrospective
- • What worked: Using test.fail() kept CI green while maintaining visibility on all 9 open defect regressions.
- • What worked: Direct database and Redis queries caught critical bugs that black-box UI testing would have completely missed.
- • What I would improve: Add containerized WireMock stubs for partner bank clearing networks.