Skip to content

feat(s3): application layer tests — 19 controller ITs + FK bug fix (STA-128)#138

Merged
Puneethkumarck merged 3 commits into
mainfrom
feature/STA-128-s3-application-layer-tests
Mar 9, 2026
Merged

feat(s3): application layer tests — 19 controller ITs + FK bug fix (STA-128)#138
Puneethkumarck merged 3 commits into
mainfrom
feature/STA-128-s3-application-layer-tests

Conversation

@Puneethkumarck

@Puneethkumarck Puneethkumarck commented Mar 9, 2026

Copy link
Copy Markdown
Owner

Summary

  • Add 19 controller integration tests: CollectionControllerIT (14) + StripeWebhookControllerIT (5)
  • Fix FK constraint bug in CollectionCommandHandler — save collection order before PspTransaction
  • Fix null rawResponse handling in PspTransactionPersistenceMapper
  • Add aCollectionRequest() and aRefundRequest() test fixtures

Test Coverage

Test Class Tests Scenarios
CollectionControllerIT 14 POST 201/200/400, GET by ID 200/404/400, GET by paymentId 200/404/400, refund 201/409/422/404
StripeWebhookControllerIT 5 succeeded 200, failed 200, missing sig 400, blank sig 400, not found 404

S3 totals: 245 unit + 40 IT = 285 tests

Bug Fix

CollectionCommandHandler.initiateCollection() saved PspTransaction before CollectionOrder, violating the FK constraint (psp_transactions.collection_id → collection_orders.id). Reordered to save the order first.

Test plan

  • All 19 new controller ITs pass with TestContainers PostgreSQL + Kafka
  • All 21 existing persistence ITs still pass (no regression)
  • All 245 unit tests pass
  • Compilation verified

Closes STA-128

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Collection API: create, retrieve, and query collections; refunds for collected payments; Stripe webhook endpoint for payment events; improved idempotency for payment initiation.
  • Tests

    • New integration tests for collection flows (creation, idempotency, validation, domain errors) and Stripe webhook processing including signature validation and error cases.
  • Chores

    • Added/updated test fixtures and integration-test configuration to support new tests.

…TA-128)

Add CollectionControllerIT (14 tests) and StripeWebhookControllerIT (5 tests)
covering all HTTP scenarios: POST 201/200/400, GET 200/404/400, refund
201/409/422/404, webhook 200/400/404.

Fix CollectionCommandHandler save ordering — collection order must be persisted
before PspTransaction to satisfy FK constraint. Fix PspTransactionPersistenceMapper
null rawResponse handling.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@Puneethkumarck Puneethkumarck added phase-3 Value Movement MVP service-s3 On-Ramp labels Mar 9, 2026
@coderabbitai

coderabbitai Bot commented Mar 9, 2026

Copy link
Copy Markdown

Walkthrough

Adds integration tests for collections and Stripe webhooks, introduces test-fixtures dependency and API request fixtures, sets PSP provider to "dev" for integration tests, adds idempotencyKey to PspPaymentRequest and propagates it to Stripe adapter, persists collection order earlier, and defaults null PspTransaction.rawResponse to "{}".

Changes

Cohort / File(s) Summary
Build & Config
fiat-on-ramp/fiat-on-ramp/build.gradle.kts, fiat-on-ramp/fiat-on-ramp/src/integration-test/resources/application-integration-test.yml
Added testFixturesImplementation(project(":fiat-on-ramp:fiat-on-ramp-api")); set app.psp.provider: dev for integration tests.
Integration Tests
fiat-on-ramp/fiat-on-ramp/src/integration-test/java/com/stablecoin/payments/onramp/application/controller/CollectionControllerIT.java, fiat-on-ramp/fiat-on-ramp/src/integration-test/java/com/stablecoin/payments/onramp/application/controller/StripeWebhookControllerIT.java
New integration test classes covering Collection endpoints (POST/GET/refunds, idempotency, validation, domain errors) and Stripe webhook endpoint (signature validation, event handling, missing-order handling).
Test Fixtures
fiat-on-ramp/fiat-on-ramp/src/testFixtures/java/com/stablecoin/payments/onramp/fixtures/CollectionOrderFixtures.java, fiat-on-ramp/fiat-on-ramp/src/testFixtures/java/com/stablecoin/payments/onramp/fixtures/RefundFixtures.java
Added API request factory methods aCollectionRequest() and aRefundRequest() for test DTO creation; imported API DTOs.
Domain API & Tests
fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/port/PspPaymentRequest.java, fiat-on-ramp/fiat-on-ramp/src/test/java/.../CollectionCommandHandlerTest.java, fiat-on-ramp/fiat-on-ramp/src/test/java/.../StripePspAdapterTest.java
Added idempotencyKey field to PspPaymentRequest record and updated unit tests to pass/verify the new parameter and header.
Provider Adapter
fiat-on-ramp/fiat-on-ramp/src/main/java/.../infrastructure/provider/stripe/StripePspAdapter.java
Send Idempotency-Key header conditionally when idempotencyKey is present; request spec variable introduced to apply header.
Domain Service & Persistence
fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/service/CollectionCommandHandler.java, fiat-on-ramp/fiat-on-ramp/src/main/java/.../persistence/mapper/PspTransactionPersistenceMapper.java
Persist collection order earlier (before creating PspTransaction) and remove final re-save; map null txn.rawResponse() to "{}" when building entity to ensure non-null stored value.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant Client
participant CollectionController
participant CollectionCommandHandler
participant CollectionOrderRepo
participant PspAdapter
participant PspTransactionRepo
participant OutboxPublisher

Client->>CollectionController: POST /v1/collections
CollectionController->>CollectionCommandHandler: initiateCollection(request)
CollectionCommandHandler->>CollectionOrderRepo: save(new CollectionOrder)        <!-- persisted early -->
CollectionCommandHandler->>PspAdapter: initiatePayment(PspPaymentRequest{..., idempotencyKey})
PspAdapter->>PspTransactionRepo: save(PspTransaction with FK to Order)
CollectionCommandHandler->>OutboxPublisher: publish(CollectionInitiatedEvent)
CollectionCommandHandler-->>CollectionController: return result
CollectionController-->>Client: 201 Created / 200 OK

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

Suggested labels

test, feature

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 10.34% 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 Title accurately describes the main changes: 19 controller integration tests and FK bug fix in S3 service.
Description check ✅ Passed Description covers all required template sections: summary, test coverage details, bug fix explanation, and comprehensive test plan with verification 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-128-s3-application-layer-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: 6

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

Inline comments:
In `@fiat-on-ramp/fiat-on-ramp/build.gradle.kts`:
- Around line 114-116: The test-fixtures module pulls in the whole API via
testFixturesImplementation(project(":fiat-on-ramp:fiat-on-ramp-api")), coupling
shared fixtures; to fix, move the two fixtures that depend on API
types—CollectionOrderFixtures and RefundFixtures (which reference
CollectionRequest and RefundRequest)—out of the shared fixtures and into
src/integration-test of the module that needs them, update their package/imports
accordingly, and then remove the
testFixturesImplementation(project(":fiat-on-ramp:fiat-on-ramp-api"))
declaration so the testFixtures module no longer depends on the API.

In
`@fiat-on-ramp/fiat-on-ramp/src/integration-test/java/com/stablecoin/payments/onramp/application/controller/CollectionControllerIT.java`:
- Around line 64-81: The test shouldReturn200ForIdempotentReplay currently only
asserts the paymentId on the second (replay) response; capture and parse the
first response to extract its collectionId and then assert the second response
returns the exact same collectionId to prove idempotency. After performing the
first mockMvc.post to "/v1/collections" with the request, store the response
body and extract the collectionId (e.g., from the JSON), then perform the second
post and assert both jsonPath("$.paymentId", is(PAYMENT_ID.toString())) and
jsonPath("$.collectionId", is(firstCollectionId)) where firstCollectionId is the
value extracted from the first response. Ensure you reference the existing
variables/methods request, mockMvc, objectMapper, PAYMENT_ID and the endpoint
"/v1/collections".

In
`@fiat-on-ramp/fiat-on-ramp/src/integration-test/java/com/stablecoin/payments/onramp/application/controller/StripeWebhookControllerIT.java`:
- Around line 37-71: Update the two tests in StripeWebhookControllerIT
(shouldReturn200ForSucceededWebhook and shouldReturn200ForFailedWebhook) to
assert the persisted state transition: after performing the POST (using
anAwaitingConfirmationOrder(), PSP_REFERENCE and
collectionOrderRepository.save(...)), reload the saved order from
collectionOrderRepository (e.g., via findById or findByCollectionId using
order.collectionId()) and assert its terminal status changed to the expected
value for each case (succeeded → expected terminal success status, failed →
expected terminal failure status) in addition to the existing 200/"received"
assertions.

In
`@fiat-on-ramp/fiat-on-ramp/src/integration-test/resources/application-integration-test.yml`:
- Around line 32-33: The integration test profile currently sets psp provider to
dev which disables real Stripe HMAC verification; for StripeWebhookControllerIT
either switch the test to a Stripe-specific profile instead of using
app.psp.provider=dev or override that property in the test setup to use the
Stripe provider and ensure requests are signed by computing and adding valid
signatures using WebhookFixtures.computeSignature(...); update
StripeWebhookControllerIT to toggle the profile or set the property and sign
payloads so the webhook path performs real HMAC validation.

In
`@fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/service/CollectionCommandHandler.java`:
- Around line 78-79: The code currently calls the PSP after saving the order
inside the same `@Transactional` boundary (collectionOrderRepository.save(order)
in CollectionCommandHandler), which risks PSP side effects being visible if the
transaction later rolls back; either persist/commit the collection order before
calling the PSP or make the PSP call idempotent. Fix by moving the commit point:
either (A) persist and flush/commit the order before the PSP call (e.g.
save+flush or perform the save inside a separate transaction using
Propagation.REQUIRES_NEW for the method that calls
collectionOrderRepository.save(order)) so the FK is durable prior to
initiatePayment(), or (B) generate and pass a stable idempotency key derived
from paymentId/collectionId into the PSP request so retrying initiatePayment()
is safe; update the code paths in CollectionCommandHandler and the PSP
invocation to use the chosen approach.

In
`@fiat-on-ramp/fiat-on-ramp/src/testFixtures/java/com/stablecoin/payments/onramp/fixtures/CollectionOrderFixtures.java`:
- Around line 11-12: The import block change introduced by adding import
com.stablecoin.payments.onramp.api.CollectionRequest in CollectionOrderFixtures
caused a Spotless ordering/spacing violation; restore the expected import
grouping/ordering and spacing (or simply run ./gradlew spotlessApply) to
reformat the imports in CollectionOrderFixtures so they match the project's
Spotless rules before merging.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0765d4f1-f3e6-4e8a-bd0d-c5ad2339f4ad

📥 Commits

Reviewing files that changed from the base of the PR and between 9c1024e and 338b2c3.

📒 Files selected for processing (8)
  • fiat-on-ramp/fiat-on-ramp/build.gradle.kts
  • fiat-on-ramp/fiat-on-ramp/src/integration-test/java/com/stablecoin/payments/onramp/application/controller/CollectionControllerIT.java
  • fiat-on-ramp/fiat-on-ramp/src/integration-test/java/com/stablecoin/payments/onramp/application/controller/StripeWebhookControllerIT.java
  • fiat-on-ramp/fiat-on-ramp/src/integration-test/resources/application-integration-test.yml
  • fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/service/CollectionCommandHandler.java
  • fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/infrastructure/persistence/mapper/PspTransactionPersistenceMapper.java
  • fiat-on-ramp/fiat-on-ramp/src/testFixtures/java/com/stablecoin/payments/onramp/fixtures/CollectionOrderFixtures.java
  • fiat-on-ramp/fiat-on-ramp/src/testFixtures/java/com/stablecoin/payments/onramp/fixtures/RefundFixtures.java

Comment on lines 114 to 116
// Test Fixtures
testFixturesImplementation(project(":fiat-on-ramp:fiat-on-ramp-api"))
testFixturesImplementation("org.assertj:assertj-core")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Show which shared fixtures import API-layer DTOs.
rg -n --glob 'fiat-on-ramp/fiat-on-ramp/src/testFixtures/java/**/*.java' \
  'import com\.stablecoin\.payments\.onramp\.api\.'

Repository: Puneethkumarck/stablebridge-platform

Length of output: 427


Narrow testFixtures scope to domain-only fixtures.

The fiat-on-ramp-api dependency in line 115 couples your published test-fixture module to the API layer. Verification shows only CollectionOrderFixtures and RefundFixtures require CollectionRequest/RefundRequest from the API package. Move these two fixture classes to src/integration-test and remove the API dependency from shared testFixtures to keep the fixture module focused and decoupled.

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

In `@fiat-on-ramp/fiat-on-ramp/build.gradle.kts` around lines 114 - 116, The
test-fixtures module pulls in the whole API via
testFixturesImplementation(project(":fiat-on-ramp:fiat-on-ramp-api")), coupling
shared fixtures; to fix, move the two fixtures that depend on API
types—CollectionOrderFixtures and RefundFixtures (which reference
CollectionRequest and RefundRequest)—out of the shared fixtures and into
src/integration-test of the module that needs them, update their package/imports
accordingly, and then remove the
testFixturesImplementation(project(":fiat-on-ramp:fiat-on-ramp-api"))
declaration so the testFixtures module no longer depends on the API.

Comment on lines +64 to +81
@Test
@DisplayName("should return 200 OK for idempotent replay with same paymentId")
void shouldReturn200ForIdempotentReplay() throws Exception {
// given
var request = aCollectionRequest();

// first request — 201 Created
mockMvc.perform(post("/v1/collections")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andExpect(status().isCreated());

// when — second request with same paymentId
mockMvc.perform(post("/v1/collections")
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsString(request)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.paymentId", is(PAYMENT_ID.toString())));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Assert the same collectionId on replay.

This only proves the second request echoes the same paymentId. A broken handler could still create a second order and return 200 OK. Capture the first response and assert that the replay returns the exact same collectionId.

Suggested tightening
-            mockMvc.perform(post("/v1/collections")
+            var firstResponse = mockMvc.perform(post("/v1/collections")
                             .contentType(MediaType.APPLICATION_JSON)
                             .content(objectMapper.writeValueAsString(request)))
-                    .andExpect(status().isCreated());
+                    .andExpect(status().isCreated())
+                    .andReturn()
+                    .getResponse()
+                    .getContentAsString();
+            var firstCollectionId = objectMapper.readTree(firstResponse).get("collectionId").asText();

             // when — second request with same paymentId
             mockMvc.perform(post("/v1/collections")
                             .contentType(MediaType.APPLICATION_JSON)
                             .content(objectMapper.writeValueAsString(request)))
                     .andExpect(status().isOk())
-                    .andExpect(jsonPath("$.paymentId", is(PAYMENT_ID.toString())));
+                    .andExpect(jsonPath("$.paymentId", is(PAYMENT_ID.toString())))
+                    .andExpect(jsonPath("$.collectionId", is(firstCollectionId)));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@fiat-on-ramp/fiat-on-ramp/src/integration-test/java/com/stablecoin/payments/onramp/application/controller/CollectionControllerIT.java`
around lines 64 - 81, The test shouldReturn200ForIdempotentReplay currently only
asserts the paymentId on the second (replay) response; capture and parse the
first response to extract its collectionId and then assert the second response
returns the exact same collectionId to prove idempotency. After performing the
first mockMvc.post to "/v1/collections" with the request, store the response
body and extract the collectionId (e.g., from the JSON), then perform the second
post and assert both jsonPath("$.paymentId", is(PAYMENT_ID.toString())) and
jsonPath("$.collectionId", is(firstCollectionId)) where firstCollectionId is the
value extracted from the first response. Ensure you reference the existing
variables/methods request, mockMvc, objectMapper, PAYMENT_ID and the endpoint
"/v1/collections".

Comment on lines +37 to +71
@Test
@DisplayName("should return 200 OK for valid succeeded webhook")
void shouldReturn200ForSucceededWebhook() throws Exception {
// given — save an awaiting-confirmation order with matching pspReference
var order = anAwaitingConfirmationOrder();
collectionOrderRepository.save(order);

var rawBody = aSucceededEventJson(PSP_REFERENCE, 100000L, "usd", order.collectionId());

// when/then — fallback validator always accepts signature
mockMvc.perform(post("/internal/webhooks/psp/stripe")
.contentType(MediaType.APPLICATION_JSON)
.header("Stripe-Signature", "t=1234567890,v1=dummy")
.content(rawBody))
.andExpect(status().isOk())
.andExpect(jsonPath("$.status", is("received")));
}

@Test
@DisplayName("should return 200 OK for valid failed webhook")
void shouldReturn200ForFailedWebhook() throws Exception {
// given
var order = anAwaitingConfirmationOrder();
collectionOrderRepository.save(order);

var rawBody = aFailedEventJson(PSP_REFERENCE, order.collectionId());

// when/then
mockMvc.perform(post("/internal/webhooks/psp/stripe")
.contentType(MediaType.APPLICATION_JSON)
.header("Stripe-Signature", "t=1234567890,v1=dummy")
.content(rawBody))
.andExpect(status().isOk())
.andExpect(jsonPath("$.status", is("received")));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Assert the persisted state transition, not just the 200 ACK.

Both success-path tests pass even if the controller accepts the webhook and never mutates the collection. Reload the saved order after the POST and assert the expected terminal status for succeeded vs failed events.

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

In
`@fiat-on-ramp/fiat-on-ramp/src/integration-test/java/com/stablecoin/payments/onramp/application/controller/StripeWebhookControllerIT.java`
around lines 37 - 71, Update the two tests in StripeWebhookControllerIT
(shouldReturn200ForSucceededWebhook and shouldReturn200ForFailedWebhook) to
assert the persisted state transition: after performing the POST (using
anAwaitingConfirmationOrder(), PSP_REFERENCE and
collectionOrderRepository.save(...)), reload the saved order from
collectionOrderRepository (e.g., via findById or findByCollectionId using
order.collectionId()) and assert its terminal status changed to the expected
value for each case (succeeded → expected terminal success status, failed →
expected terminal failure status) in addition to the existing 200/"received"
assertions.

Comment on lines +32 to +33
psp:
provider: dev

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

This profile bypasses real Stripe signature verification.

With app.psp.provider=dev, the Stripe webhook ITs only prove header presence, not Stripe HMAC validation. That leaves the authenticated webhook path untested; use a Stripe-specific test profile for StripeWebhookControllerIT or override this property in that suite and sign payloads via WebhookFixtures.computeSignature(...).

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

In
`@fiat-on-ramp/fiat-on-ramp/src/integration-test/resources/application-integration-test.yml`
around lines 32 - 33, The integration test profile currently sets psp provider
to dev which disables real Stripe HMAC verification; for
StripeWebhookControllerIT either switch the test to a Stripe-specific profile
instead of using app.psp.provider=dev or override that property in the test
setup to use the Stripe provider and ensure requests are signed by computing and
adding valid signatures using WebhookFixtures.computeSignature(...); update
StripeWebhookControllerIT to toggle the profile or set the property and sign
payloads so the webhook path performs real HMAC validation.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

@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

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

Inline comments:
In
`@fiat-on-ramp/fiat-on-ramp/src/testFixtures/java/com/stablecoin/payments/onramp/fixtures/CollectionOrderFixtures.java`:
- Around line 110-126: The aCollectionRequest() method contains duplicated
literal values (e.g., "1000.00", "SEPA", "IBAN", "DE", "EUR", "Stripe", sha256
string) that are already used elsewhere; refactor by replacing these magic
literals with shared constants or reusing existing fixture/value-object
factories so all tests draw from a single source of truth—update
CollectionOrderFixtures.aCollectionRequest() to reference the existing fixture
constants (or a shared Factory) for PAYMENT_ID, CORRELATION_ID, AMOUNT,
CURRENCY, PAYMENT_TYPE, COUNTRY, RECEIVING_CURRENCY, PROVIDER_ID, PROVIDER_NAME,
HASH, BIC, ACCOUNT_TYPE, and ACCOUNT_COUNTRY.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: e1dca934-0a02-481d-9c27-ea42ecf56260

📥 Commits

Reviewing files that changed from the base of the PR and between 338b2c3 and dc4ddff.

📒 Files selected for processing (1)
  • fiat-on-ramp/fiat-on-ramp/src/testFixtures/java/com/stablecoin/payments/onramp/fixtures/CollectionOrderFixtures.java

Comment on lines +110 to +126
public static CollectionRequest aCollectionRequest() {
return new CollectionRequest(
PAYMENT_ID,
CORRELATION_ID,
new BigDecimal("1000.00"),
"USD",
"SEPA",
"DE",
"EUR",
"stripe_001",
"Stripe",
"sha256_abc123def456",
"DEUTDEFF",
"IBAN",
"DE"
);
}

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 extracting magic values to constants for consistency.

The method duplicates literal values ("1000.00", "SEPA", "IBAN", etc.) that are already embedded in other fixture methods. If these ever drift, tests may pass with inconsistent data.

Optional: leverage existing value-object factories for derivation, or extract shared constants.

♻️ Proposed refactor (optional)
+    // Add constants section
+    public static final BigDecimal DEFAULT_AMOUNT = new BigDecimal("1000.00");
+    public static final String DEFAULT_CURRENCY = "USD";
+    public static final String DEFAULT_RAIL_TYPE = PaymentRailType.SEPA.name();
+    public static final String DEFAULT_ACCOUNT_TYPE = AccountType.IBAN.name();

     public static CollectionRequest aCollectionRequest() {
         return new CollectionRequest(
                 PAYMENT_ID,
                 CORRELATION_ID,
-                new BigDecimal("1000.00"),
-                "USD",
-                "SEPA",
+                DEFAULT_AMOUNT,
+                DEFAULT_CURRENCY,
+                DEFAULT_RAIL_TYPE,
                 "DE",
                 "EUR",
                 "stripe_001",
                 "Stripe",
                 "sha256_abc123def456",
                 "DEUTDEFF",
-                "IBAN",
+                DEFAULT_ACCOUNT_TYPE,
                 "DE"
         );
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@fiat-on-ramp/fiat-on-ramp/src/testFixtures/java/com/stablecoin/payments/onramp/fixtures/CollectionOrderFixtures.java`
around lines 110 - 126, The aCollectionRequest() method contains duplicated
literal values (e.g., "1000.00", "SEPA", "IBAN", "DE", "EUR", "Stripe", sha256
string) that are already used elsewhere; refactor by replacing these magic
literals with shared constants or reusing existing fixture/value-object
factories so all tests draw from a single source of truth—update
CollectionOrderFixtures.aCollectionRequest() to reference the existing fixture
constants (or a shared Factory) for PAYMENT_ID, CORRELATION_ID, AMOUNT,
CURRENCY, PAYMENT_TYPE, COUNTRY, RECEIVING_CURRENCY, PROVIDER_ID, PROVIDER_NAME,
HASH, BIC, ACCOUNT_TYPE, and ACCOUNT_COUNTRY.

…rollback

Pass collectionId as Stripe Idempotency-Key header so that if the
transaction rolls back after a successful PSP call, retries are safe.
Addresses CodeRabbit critical review on CollectionCommandHandler.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

@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.

Caution

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

⚠️ Outside diff range comments (1)
fiat-on-ramp/fiat-on-ramp/src/test/java/com/stablecoin/payments/onramp/infrastructure/provider/stripe/StripePspAdapterTest.java (1)

170-177: 🧹 Nitpick | 🔵 Trivial

Good verification of Idempotency-Key header.

Consider adding a test case for when idempotencyKey is null to verify the header is not sent. This would cover the conditional branch in StripePspAdapter.initiatePayment().

Suggested additional test case
`@Test`
`@DisplayName`("should not send Idempotency-Key header when idempotencyKey is null")
void initiatePayment_noIdempotencyKeyHeader_whenNull() {
    wireMock.stubFor(post(urlEqualTo("/v1/payment_intents"))
            .willReturn(aResponse()
                    .withStatus(200)
                    .withHeader("Content-Type", "application/json")
                    .withBody("""
                            {
                              "id": "pi_no_idem",
                              "status": "succeeded",
                              "amount": 25000,
                              "currency": "usd",
                              "client_secret": "pi_no_idem_secret"
                            }
                            """)));

    var requestWithoutIdempotencyKey = new PspPaymentRequest(
            COLLECTION_ID,
            new Money(new BigDecimal("250.00"), "USD"),
            new PaymentRail(PaymentRailType.ACH, "US", "USD"),
            new BankAccount("hash123", "021000021", AccountType.ACH_ROUTING, "US"),
            "stripe",
            null  // no idempotency key
    );

    adapter.initiatePayment(requestWithoutIdempotencyKey);

    wireMock.verify(postRequestedFor(urlEqualTo("/v1/payment_intents"))
            .withoutHeader("Idempotency-Key"));
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@fiat-on-ramp/fiat-on-ramp/src/test/java/com/stablecoin/payments/onramp/infrastructure/provider/stripe/StripePspAdapterTest.java`
around lines 170 - 177, Add a new unit test in StripePspAdapterTest that calls
StripePspAdapter.initiatePayment with a PspPaymentRequest whose idempotencyKey
is null and stubs a successful /v1/payment_intents response; after invoking
adapter.initiatePayment, verify via wireMock.verify that the request to
"/v1/payment_intents" does not include the "Idempotency-Key" header (use
wireMock's withoutHeader matcher). This covers the conditional branch in
StripePspAdapter.initiatePayment that should omit the header when idempotencyKey
is null.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In
`@fiat-on-ramp/fiat-on-ramp/src/test/java/com/stablecoin/payments/onramp/infrastructure/provider/stripe/StripePspAdapterTest.java`:
- Around line 170-177: Add a new unit test in StripePspAdapterTest that calls
StripePspAdapter.initiatePayment with a PspPaymentRequest whose idempotencyKey
is null and stubs a successful /v1/payment_intents response; after invoking
adapter.initiatePayment, verify via wireMock.verify that the request to
"/v1/payment_intents" does not include the "Idempotency-Key" header (use
wireMock's withoutHeader matcher). This covers the conditional branch in
StripePspAdapter.initiatePayment that should omit the header when idempotencyKey
is null.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 6c92b377-326f-4127-9d2b-401c965d1636

📥 Commits

Reviewing files that changed from the base of the PR and between dc4ddff and f93616a.

📒 Files selected for processing (5)
  • fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/port/PspPaymentRequest.java
  • fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/domain/service/CollectionCommandHandler.java
  • fiat-on-ramp/fiat-on-ramp/src/main/java/com/stablecoin/payments/onramp/infrastructure/provider/stripe/StripePspAdapter.java
  • fiat-on-ramp/fiat-on-ramp/src/test/java/com/stablecoin/payments/onramp/domain/service/CollectionCommandHandlerTest.java
  • fiat-on-ramp/fiat-on-ramp/src/test/java/com/stablecoin/payments/onramp/infrastructure/provider/stripe/StripePspAdapterTest.java

@Puneethkumarck Puneethkumarck merged commit a2ee53b into main Mar 9, 2026
12 of 13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

phase-3 Value Movement MVP service-s3 On-Ramp

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant