Skip to content

feat(s4): controller integration tests — 11 TransferController ITs (STA-144)#148

Merged
Puneethkumarck merged 2 commits into
mainfrom
feature/STA-144-s4-application-layer-tests
Mar 9, 2026
Merged

feat(s4): controller integration tests — 11 TransferController ITs (STA-144)#148
Puneethkumarck merged 2 commits into
mainfrom
feature/STA-144-s4-application-layer-tests

Conversation

@Puneethkumarck

@Puneethkumarck Puneethkumarck commented Mar 9, 2026

Copy link
Copy Markdown
Owner

Summary

  • 11 integration tests for all S4 controller endpoints (POST /v1/transfers, GET /v1/transfers/{id}, GET /v1/wallets/{id}/balance)
  • Full-stack tests using TestContainers PostgreSQL + Kafka with fallback adapters (custody engine, chain health, nonce repo)
  • V5 Flyway migration: drops FK constraint on chain_selection_log.transfer_id → chain_transfers — selection log is written before the transfer row exists, so the FK prevented the full submission pipeline
  • Added aTransferRequestJson() fixture for raw JSON transfer request body (testFixtures can't depend on the API module)

Test coverage

Endpoint Tests Scenarios
POST /v1/transfers 6 202 new, 200 idempotent replay, 400 missing fields, 400 invalid amount, 503 insufficient balance, balance reservation verification
GET /v1/transfers/{id} 3 200 found, 404 not found, 400 invalid UUID
GET /v1/wallets/{id}/balance 2 200 found, 404 not found

Total: 408 tests across S4 (355 unit + 53 integration), all passing.

Closes STA-144

Test plan

  • All 408 tests pass (./gradlew :blockchain-custody:blockchain-custody:test :blockchain-custody:blockchain-custody:integrationTest)
  • Spotless check passes
  • Code quality review — no critical findings

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Tests

    • Expanded integration test coverage for transfer API endpoints, including success scenarios, idempotent requests, error handling, and wallet balance verification.
  • Chores

    • Removed database constraint that was blocking valid transfer operations from completing.

…TA-144)

Add full-stack integration tests for all S4 controller endpoints using
TestContainers PostgreSQL, covering POST /v1/transfers (202, 200 idempotent,
400 missing fields, 400 invalid amount, 503 insufficient balance, balance
reservation verification), GET /v1/transfers/{id} (200, 404, 400), and
GET /v1/wallets/{id}/balance (200, 404). Also adds V5 migration to drop
chain_selection_log FK that prevented the selection log from being written
before the transfer row exists.

Closes STA-144

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

coderabbitai Bot commented Mar 9, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@Puneethkumarck has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 0 minutes and 25 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 7cd13fba-ed88-48fb-aa7b-08c05f779da7

📥 Commits

Reviewing files that changed from the base of the PR and between 46fe68f and 3b5aa44.

📒 Files selected for processing (1)
  • blockchain-custody/blockchain-custody/src/integration-test/java/com/stablecoin/payments/custody/application/controller/TransferControllerIT.java

Walkthrough

This PR introduces an integration test suite for the Transfer REST API endpoints with database-backed scenarios, a database migration removing a problematic foreign key constraint on the chain_selection_log table, and a test fixture for generating transfer request payloads.

Changes

Cohort / File(s) Summary
Integration Tests
blockchain-custody/.../TransferControllerIT.java
New test suite exercising POST/GET transfer endpoints and wallet balance endpoints; validates success cases (202/200), idempotency, error handling (400/404/503), and balance reservation behavior.
Database Migration
blockchain-custody/.../db/migration/V5__drop_selection_log_fk.sql
Removes foreign key constraint chain_selection_log_transfer_id_fkey to resolve sequencing issue where chain_selection_log is written before corresponding chain_transfer row exists.
Test Fixtures
blockchain-custody/.../fixtures/ChainTransferFixtures.java
Adds aTransferRequestJson() method returning JSON representation of FORWARD transfer request for test data generation.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~22 minutes

Possibly related PRs

  • PR #147: Directly related—adds integration tests and fixtures exercising the same TransferController endpoints and transfer request payloads covered in this PR.
  • PR #122: Modifies the ChainTransferFixtures test-fixtures class that is extended by this PR's fixture additions.

Suggested labels

test

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.33% 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 reflects the primary change: introducing 11 integration tests for TransferController endpoints with clear reference to the story (STA-144).
Description check ✅ Passed Description covers all template sections with specific test scenarios, endpoints, coverage metrics, and verification of test execution. All critical information is present and well-organized.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/STA-144-s4-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: 2

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

Inline comments:
In
`@blockchain-custody/blockchain-custody/src/integration-test/java/com/stablecoin/payments/custody/application/controller/TransferControllerIT.java`:
- Around line 81-103: The test in TransferControllerIT currently only checks
paymentId and status, allowing duplicate ChainTransfer creation; modify the test
to capture the first response's transferId (from the first mockMvc.perform POST)
using andReturn()/getResponse().getContentAsString() or by querying the transfer
repository (e.g., chainTransferRepository.findByPaymentId(PAYMENT_ID)), then on
the second (idempotent) request assert the returned transferId equals the first
transferId and additionally assert the repository contains exactly one
ChainTransfer for that paymentId (e.g., countByPaymentId or findAllByPaymentId
size == 1) to ensure no duplicate transfer was created.

In
`@blockchain-custody/blockchain-custody/src/testFixtures/java/com/stablecoin/payments/custody/fixtures/ChainTransferFixtures.java`:
- Around line 27-42: The fixture aTransferRequestJson currently hardcodes
PAYMENT_ID and CORRELATION_ID which forces tests to share IDs; add an overloaded
method (e.g., aTransferRequestJson(String paymentId, String correlationId,
String toWalletAddress)) or a small builder helper (e.g.,
TransferRequestFixture.with(paymentId, correlationId, toWalletAddress).json())
so tests can supply mutable fields while keeping the original parameterless
aTransferRequestJson() as a convenience that delegates to the new overload using
PAYMENT_ID, CORRELATION_ID, TO_ADDRESS; update callers to use the overload where
tests need custom IDs and retain the original for explicit replay scenarios.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: c935e815-55f8-4ec6-b888-c2e8686f13c6

📥 Commits

Reviewing files that changed from the base of the PR and between b0d1047 and 46fe68f.

📒 Files selected for processing (3)
  • blockchain-custody/blockchain-custody/src/integration-test/java/com/stablecoin/payments/custody/application/controller/TransferControllerIT.java
  • blockchain-custody/blockchain-custody/src/main/resources/db/migration/V5__drop_selection_log_fk.sql
  • blockchain-custody/blockchain-custody/src/testFixtures/java/com/stablecoin/payments/custody/fixtures/ChainTransferFixtures.java

Comment on lines +81 to +103
@Test
@DisplayName("should return 200 OK for idempotent replay with same paymentId")
void shouldReturn200ForIdempotentReplay() throws Exception {
// given
var wallet = walletRepository.save(anActiveWallet());
var balance = WalletBalance.initialize(wallet.walletId(), CHAIN_BASE, USDC)
.syncFromChain(new BigDecimal("50000.00"), 100L);
walletBalanceRepository.save(balance);

// first request — 202 Accepted
mockMvc.perform(post("/v1/transfers")
.contentType(MediaType.APPLICATION_JSON)
.content(aTransferRequestJson()))
.andExpect(status().isAccepted());

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

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 replay identity, not just HTTP 200.

A broken idempotency path could still create a second ChainTransfer and pass this test because the assertions only check paymentId and status. Capture the first transferId and assert the replay returns that same ID, or assert the repository still contains a single row for the request.

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

In
`@blockchain-custody/blockchain-custody/src/integration-test/java/com/stablecoin/payments/custody/application/controller/TransferControllerIT.java`
around lines 81 - 103, The test in TransferControllerIT currently only checks
paymentId and status, allowing duplicate ChainTransfer creation; modify the test
to capture the first response's transferId (from the first mockMvc.perform POST)
using andReturn()/getResponse().getContentAsString() or by querying the transfer
repository (e.g., chainTransferRepository.findByPaymentId(PAYMENT_ID)), then on
the second (idempotent) request assert the returned transferId equals the first
transferId and additionally assert the repository contains exactly one
ChainTransfer for that paymentId (e.g., countByPaymentId or findAllByPaymentId
size == 1) to ensure no duplicate transfer was created.

Comment on lines +27 to +42
/**
* JSON body for a FORWARD transfer request on Base chain.
*/
public static String aTransferRequestJson() {
return """
{
"paymentId": "%s",
"correlationId": "%s",
"transferType": "FORWARD",
"stablecoin": "USDC",
"amount": "1000.00",
"toWalletAddress": "%s",
"preferredChain": "base"
}
""".formatted(PAYMENT_ID, CORRELATION_ID, TO_ADDRESS);
}

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

Parameterize this request fixture.

This helper bakes the same paymentId and correlationId into every POST scenario. That makes unrelated controller ITs depend on perfect DB cleanup and forces payload duplication when one field needs to vary. Prefer an overload or builder that accepts the mutable fields, and keep the fixed IDs only for the explicit replay case.

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

In
`@blockchain-custody/blockchain-custody/src/testFixtures/java/com/stablecoin/payments/custody/fixtures/ChainTransferFixtures.java`
around lines 27 - 42, The fixture aTransferRequestJson currently hardcodes
PAYMENT_ID and CORRELATION_ID which forces tests to share IDs; add an overloaded
method (e.g., aTransferRequestJson(String paymentId, String correlationId,
String toWalletAddress)) or a small builder helper (e.g.,
TransferRequestFixture.with(paymentId, correlationId, toWalletAddress).json())
so tests can supply mutable fields while keeping the original parameterless
aTransferRequestJson() as a convenience that delegates to the new overload using
PAYMENT_ID, CORRELATION_ID, TO_ADDRESS; update callers to use the overload where
tests need custom IDs and retain the original for explicit replay scenarios.

…o duplicates (STA-144)

Capture first response transferId via andReturn(), assert second response
returns the same transferId, and verify repository contains exactly one
transfer for the paymentId.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@Puneethkumarck Puneethkumarck merged commit 99e0f45 into main Mar 9, 2026
12 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-s4 Blockchain

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant