Skip to content

feat(s1): business tests — 5 E2E payment lifecycle flows (STA-116)#116

Merged
Puneethkumarck merged 1 commit into
mainfrom
feature/STA-116-s1-business-tests
Mar 8, 2026
Merged

feat(s1): business tests — 5 E2E payment lifecycle flows (STA-116)#116
Puneethkumarck merged 1 commit into
mainfrom
feature/STA-116-s1-business-tests

Conversation

@Puneethkumarck

@Puneethkumarck Puneethkumarck commented Mar 8, 2026

Copy link
Copy Markdown
Owner

Summary

  • Add 5 E2E business tests for the Payment Orchestrator using Temporal TestWorkflowEnvironment, real DB (TestContainers PostgreSQL), and mocked S2/S6 Feign clients
  • Happy path: payment initiation → compliance pass → FX lock → workflow completion + outbox events
  • Compliance rejection: sanctions hit stops workflow, publishes failed event
  • FX lock failure: insufficient liquidity fails workflow with compensation
  • Cancel after FX lock: cancel signal triggers LIFO compensation (release lock)
  • Idempotency: duplicate idempotency key returns existing payment (200 OK)
  • Fix latent bug: PaymentWorkflowImpl used invalid "FX_LOCKING" state in failed event requests — PaymentState has no such value, causing silent IllegalArgumentException in EventPublishingActivityImpl.mapToEvent()

Test plan

  • All 5 business tests pass (./gradlew :payment-orchestrator:payment-orchestrator:businessTest)
  • Full S1 check passes — 237 tests (unit + integration + business)
  • Spotless formatting applied

Closes STA-116

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Corrected event categorization for FX lock failure scenarios so failure events are classified consistently.
  • Tests

    • Added comprehensive end-to-end payment lifecycle tests covering compliance, FX flows, cancellations, idempotency, and outbox/DB verification.
    • Introduced a base class to enable business (E2E) tests with full test environment wiring.
  • Chores

    • Temporal client configuration can now be toggled via application property.

@Puneethkumarck Puneethkumarck added phase-2 Core Payment Logic service-s1 Orchestrator test Test coverage labels Mar 8, 2026
@coderabbitai

coderabbitai Bot commented Mar 8, 2026

Copy link
Copy Markdown

Walkthrough

Adds a business E2E test base and a comprehensive PaymentLifecycleTest exercising Temporal workflows, TestContainers-backed Postgres/Kafka, and mocked downstream services; gates Temporal client config with a property and corrects event-type labels in FX-failure paths.

Changes

Cohort / File(s) Summary
Test Base
payment-orchestrator/.../AbstractBusinessTest.java
New public abstract test base annotated @Tag("business"), extends AbstractIntegrationTest to reuse TestContainers/security/TestWorkflowEnvironment.
Business E2E Tests
payment-orchestrator/.../PaymentLifecycleTest.java
Adds full end-to-end payment lifecycle tests (happy path, compliance rejection, FX lock failure, cancel-after-lock compensation, idempotency) with Temporal TestWorkflowEnvironment, mocked Compliance/FX services, DB and outbox assertions.
Temporal Config
payment-orchestrator/.../TemporalConfig.java
Annotates TemporalConfig with @ConditionalOnProperty(name="app.temporal.client.enabled", havingValue="true", matchIfMissing=true) to gate Temporal client.
Workflow & Tests
payment-orchestrator/.../PaymentWorkflowImpl.java, payment-orchestrator/.../PaymentWorkflowTest.java
Changes event-type string in FX lock failure paths from FX_LOCKING to COMPLIANCE_CHECK; updates unit test expectation accordingly.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant Client as Client (HTTP)
participant Orchestrator as Payment Orchestrator (Spring)
participant Temporal as Temporal TestWorkflowEnv
participant DB as PostgreSQL
participant Kafka as Kafka (Outbox)
participant Compliance as Mock Compliance
participant FX as Mock FX Engine

Client->>Orchestrator: POST /payments (create)
Orchestrator->>Temporal: start PaymentWorkflow
Temporal->>Compliance: compliance check request
Compliance-->>Temporal: compliance response
Temporal->>FX: request quote & lock
FX-->>Temporal: quote/lock response
Temporal->>DB: persist payment / outbox event
Temporal->>Kafka: publish outbox message
Temporal-->>Orchestrator: workflow completion/status
Orchestrator-->>Client: HTTP response (200/4xx/5xx)

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested labels

feature

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.26% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the main change: adding 5 E2E business tests for payment lifecycle flows with the associated ticket reference.
Description check ✅ Passed The description covers all required sections: Summary with 5 test scenarios and a bug fix, Related Issue (STA-116), Type of Change (New feature), What Changed with specifics, How Was It Tested with comprehensive coverage, and relevant Security/Breaking Changes sections. Checklist items documented with test results.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/STA-116-s1-business-tests

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/PaymentWorkflowImpl.java (1)

167-179: ⚠️ Potential issue | 🟠 Major

Inconsistent state labeling in FX failure events.

At line 148, currentState is set to "FX_LOCKING", yet failure events at lines 168 and 178 use "COMPLIANCE_CHECK". This creates:

  1. Semantic confusion: Event consumers see COMPLIANCE_CHECK source with FX_LOCK_ERROR/FX_LOCK_REJECTED event types
  2. State tracking mismatch: Internal state says FX_LOCKING, events say COMPLIANCE_CHECK

If PaymentState enum lacks FX_LOCKING, add it rather than reusing an unrelated state label.

Proposed fix: Use consistent FX_LOCKING label
         publishEvent(PaymentEventRequest.failed(request.paymentId(),
-                    request.correlationId(), "COMPLIANCE_CHECK", reason, "FX_LOCK_ERROR"));
+                    request.correlationId(), "FX_LOCKING", reason, "FX_LOCK_ERROR"));
         return PaymentResult.failed(request.paymentId(), reason);
     }

     if (fxResult.status() != FxLockResult.FxLockStatus.LOCKED) {
         currentState = "FAILED";
         var reason = "FX rate lock failed: " + fxResult.failureReason();
         log.info("FX lock rejected for paymentId={}: {}",
                 request.paymentId(), fxResult.failureReason());
         publishEvent(PaymentEventRequest.failed(request.paymentId(),
-                    request.correlationId(), "COMPLIANCE_CHECK", reason, "FX_LOCK_REJECTED"));
+                    request.correlationId(), "FX_LOCKING", reason, "FX_LOCK_REJECTED"));

Then add FX_LOCKING to PaymentState enum and update EventPublishingActivityImpl.mapToEvent() accordingly.

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

In
`@payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/PaymentWorkflowImpl.java`
around lines 167 - 179, The FX failure event code in PaymentWorkflowImpl sets
currentState = "FAILED" while publishing events with source "COMPLIANCE_CHECK",
causing mismatch with the earlier currentState "FX_LOCKING"; update the flow to
use a consistent FX_LOCKING state: add FX_LOCKING to the PaymentState enum (if
missing) and ensure EventPublishingActivityImpl.mapToEvent() maps that new
FX_LOCKING value to the correct event source, then change the
PaymentEventRequest.failed calls (and any related uses in PaymentWorkflowImpl)
to use the FX_LOCKING source instead of "COMPLIANCE_CHECK" so events and
internal state align.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@payment-orchestrator/payment-orchestrator/src/business-test/java/com/stablecoin/payments/orchestrator/PaymentLifecycleTest.java`:
- Around line 428-433: The extractPaymentId method currently does fragile manual
string parsing; replace it with a proper JSON parser (e.g., JsonPath or Jackson
ObjectMapper) to reliably extract the paymentId. Update the
extractPaymentId(String jsonResponse) implementation to parse jsonResponse (for
example call JsonPath.read(jsonResponse, "$.paymentId") or use new
ObjectMapper().readTree(jsonResponse).get("paymentId").asText()) and return that
value, ensuring it handles missing/null fields appropriately (throw or return
null) and add any necessary imports for JsonPath or
com.fasterxml.jackson.databind.ObjectMapper.

---

Outside diff comments:
In
`@payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/PaymentWorkflowImpl.java`:
- Around line 167-179: The FX failure event code in PaymentWorkflowImpl sets
currentState = "FAILED" while publishing events with source "COMPLIANCE_CHECK",
causing mismatch with the earlier currentState "FX_LOCKING"; update the flow to
use a consistent FX_LOCKING state: add FX_LOCKING to the PaymentState enum (if
missing) and ensure EventPublishingActivityImpl.mapToEvent() maps that new
FX_LOCKING value to the correct event source, then change the
PaymentEventRequest.failed calls (and any related uses in PaymentWorkflowImpl)
to use the FX_LOCKING source instead of "COMPLIANCE_CHECK" so events and
internal state align.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 7c9dd813-91ce-4d10-b5e9-a7a146746f9c

📥 Commits

Reviewing files that changed from the base of the PR and between 8dafc09 and 741102f.

📒 Files selected for processing (5)
  • payment-orchestrator/payment-orchestrator/src/business-test/java/com/stablecoin/payments/orchestrator/AbstractBusinessTest.java
  • payment-orchestrator/payment-orchestrator/src/business-test/java/com/stablecoin/payments/orchestrator/PaymentLifecycleTest.java
  • payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/application/config/TemporalConfig.java
  • payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/PaymentWorkflowImpl.java
  • payment-orchestrator/payment-orchestrator/src/test/java/com/stablecoin/payments/orchestrator/domain/workflow/PaymentWorkflowTest.java

Comment on lines +428 to +433
private static String extractPaymentId(String jsonResponse) {
var pattern = "\"paymentId\":\"";
var start = jsonResponse.indexOf(pattern) + pattern.length();
var end = jsonResponse.indexOf("\"", start);
return jsonResponse.substring(start, end);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider using JsonPath or ObjectMapper for JSON extraction.

Manual string parsing is fragile if JSON formatting changes (e.g., whitespace, field ordering).

Alternative using JsonPath
private static String extractPaymentId(String jsonResponse) {
    return JsonPath.read(jsonResponse, "$.paymentId");
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@payment-orchestrator/payment-orchestrator/src/business-test/java/com/stablecoin/payments/orchestrator/PaymentLifecycleTest.java`
around lines 428 - 433, The extractPaymentId method currently does fragile
manual string parsing; replace it with a proper JSON parser (e.g., JsonPath or
Jackson ObjectMapper) to reliably extract the paymentId. Update the
extractPaymentId(String jsonResponse) implementation to parse jsonResponse (for
example call JsonPath.read(jsonResponse, "$.paymentId") or use new
ObjectMapper().readTree(jsonResponse).get("paymentId").asText()) and return that
value, ensuring it handles missing/null fields appropriately (throw or return
null) and add any necessary imports for JsonPath or
com.fasterxml.jackson.databind.ObjectMapper.

Add PaymentLifecycleTest with Temporal TestWorkflowEnvironment, real DB
(TestContainers), and mocked S2/S6 Feign clients. Covers happy path,
compliance rejection, FX lock failure, cancel-after-lock compensation,
and idempotency. Fix latent bug: PaymentWorkflowImpl used invalid
"FX_LOCKING" state string in failed event requests (no such PaymentState
enum value), causing silent event publishing failures.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@Puneethkumarck Puneethkumarck force-pushed the feature/STA-116-s1-business-tests branch from 741102f to c8789c3 Compare March 8, 2026 12:52

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/PaymentWorkflowImpl.java (1)

167-179: ⚠️ Potential issue | 🟠 Major

Use FAILED state for FX lock failures, not COMPLIANCE_CHECK.

The event publishing uses "COMPLIANCE_CHECK" as the failedState parameter for FX lock failures (lines 168, 178), but this is semantically misleading. The PaymentState enum contains FX_LOCKED (not FX_LOCKING), while the workflow code at line 148 attempts to use "FX_LOCKING". When FX lock fails, the payment transitions to FAILED state (see line 175); the event should reflect this final state rather than a previous checkpoint.

Pass "FAILED" as the failedState parameter to accurately represent the payment's terminal state:

publishEvent(PaymentEventRequest.failed(request.paymentId(),
        request.correlationId(), "FAILED", reason, "FX_LOCK_ERROR"));

This eliminates the disconnect between the actual state transition and the event metadata. If cross-step error classification is required, extend the event structure instead of reusing stale state names.

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

In
`@payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/PaymentWorkflowImpl.java`
around lines 167 - 179, When FX locking fails in PaymentWorkflowImpl (check
around FxLockResult handling), update the publishEvent calls that currently pass
"COMPLIANCE_CHECK" as the failedState to instead pass "FAILED" so the event
matches the actual terminal state; specifically change the failed-state argument
in the two PaymentEventRequest.failed(...) invocations that follow the FX error
and FX rejection branches (related symbols: publishEvent,
PaymentEventRequest.failed, currentState, FxLockResult, PaymentResult.failed) to
"FAILED".
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@payment-orchestrator/payment-orchestrator/src/business-test/java/com/stablecoin/payments/orchestrator/PaymentLifecycleTest.java`:
- Line 73: The static TEST_ENV field (TestWorkflowEnvironment TEST_ENV) in
PaymentLifecycleTest creates a shared environment that can conflict across test
classes; move its instantiation into the test lifecycle by removing the static
initializer and creating TEST_ENV inside the `@BeforeAll` method (and
nulling/closing it in `@AfterAll`), or alternatively document/annotate that
parallel test execution is unsupported; specifically update the
PaymentLifecycleTest to allocate TestWorkflowEnvironment in the `@BeforeAll` setup
and shut it down in `@AfterAll` to ensure clear ownership and avoid cross-test
interference.
- Around line 289-292: The test in PaymentLifecycleTest currently verifies
fxEngineClient.getQuote(...) was called but doesn't assert that
fxEngineClient.lockRate(...) was attempted; add a verification line such as
then(fxEngineClient).should().lockRate(any(), any()); (placed alongside the
existing then(...) assertions after the action) to ensure lockRate interaction
is covered by the test.

---

Outside diff comments:
In
`@payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/PaymentWorkflowImpl.java`:
- Around line 167-179: When FX locking fails in PaymentWorkflowImpl (check
around FxLockResult handling), update the publishEvent calls that currently pass
"COMPLIANCE_CHECK" as the failedState to instead pass "FAILED" so the event
matches the actual terminal state; specifically change the failed-state argument
in the two PaymentEventRequest.failed(...) invocations that follow the FX error
and FX rejection branches (related symbols: publishEvent,
PaymentEventRequest.failed, currentState, FxLockResult, PaymentResult.failed) to
"FAILED".

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: a7e500fd-9fbb-42fb-88cf-6321e6b56fb8

📥 Commits

Reviewing files that changed from the base of the PR and between 741102f and c8789c3.

📒 Files selected for processing (5)
  • payment-orchestrator/payment-orchestrator/src/business-test/java/com/stablecoin/payments/orchestrator/AbstractBusinessTest.java
  • payment-orchestrator/payment-orchestrator/src/business-test/java/com/stablecoin/payments/orchestrator/PaymentLifecycleTest.java
  • payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/application/config/TemporalConfig.java
  • payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/domain/workflow/PaymentWorkflowImpl.java
  • payment-orchestrator/payment-orchestrator/src/test/java/com/stablecoin/payments/orchestrator/domain/workflow/PaymentWorkflowTest.java

@DisplayName("Payment Lifecycle — Business Tests")
class PaymentLifecycleTest extends AbstractBusinessTest {

private static final TestWorkflowEnvironment TEST_ENV = TestWorkflowEnvironment.newInstance();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Static TestWorkflowEnvironment requires careful lifecycle management.

TEST_ENV is a static field shared across test instances. With @TestInstance(Lifecycle.PER_CLASS), the @BeforeAll/@AfterAll methods correctly manage lifecycle per test class instance. However, if multiple test classes inherit this pattern, parallel execution could conflict.

Consider moving TestWorkflowEnvironment instantiation into the @BeforeAll method for clearer ownership, or document that parallel test execution is unsupported.

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

In
`@payment-orchestrator/payment-orchestrator/src/business-test/java/com/stablecoin/payments/orchestrator/PaymentLifecycleTest.java`
at line 73, The static TEST_ENV field (TestWorkflowEnvironment TEST_ENV) in
PaymentLifecycleTest creates a shared environment that can conflict across test
classes; move its instantiation into the test lifecycle by removing the static
initializer and creating TEST_ENV inside the `@BeforeAll` method (and
nulling/closing it in `@AfterAll`), or alternatively document/annotate that
parallel test execution is unsupported; specifically update the
PaymentLifecycleTest to allocate TestWorkflowEnvironment in the `@BeforeAll` setup
and shut it down in `@AfterAll` to ensure clear ownership and avoid cross-test
interference.

Comment on lines +289 to +292
// Both S2 and S6 were called
then(complianceCheckClient).should().initiateCheck(any());
then(fxEngineClient).should().getQuote(any(), any(), any());
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick | 🔵 Trivial

Consider verifying lockRate was attempted.

The test verifies getQuote was called but doesn't explicitly assert lockRate was invoked before failing. Add:

then(fxEngineClient).should().lockRate(any(), any());
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@payment-orchestrator/payment-orchestrator/src/business-test/java/com/stablecoin/payments/orchestrator/PaymentLifecycleTest.java`
around lines 289 - 292, The test in PaymentLifecycleTest currently verifies
fxEngineClient.getQuote(...) was called but doesn't assert that
fxEngineClient.lockRate(...) was attempted; add a verification line such as
then(fxEngineClient).should().lockRate(any(), any()); (placed alongside the
existing then(...) assertions after the action) to ensure lockRate interaction
is covered by the test.

@Puneethkumarck Puneethkumarck merged commit 1f89c94 into main Mar 8, 2026
14 of 15 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

phase-2 Core Payment Logic service-s1 Orchestrator test Test coverage

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant