feat(s5): project scaffold — fiat-off-ramp (STA-146)#150
Conversation
|
Warning Rate limit exceeded
⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the 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 configurationConfiguration used: Path: .coderabbit.yaml Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (26)
WalkthroughThis PR introduces a new fiat off-ramp payment module with three sub-modules: API contracts defining payouts and events, a Feign client for service-to-service communication, and the main Spring Boot service with database schema, integration infrastructure, security filters, and comprehensive test scaffolding. Changes
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
001db98 to
2a5738b
Compare
There was a problem hiding this comment.
Actionable comments posted: 19
🤖 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-off-ramp/fiat-off-ramp-api/src/main/java/com/stablecoin/payments/offramp/api/ApiError.java`:
- Around line 16-18: Normalize and defensively copy the errors map in
ApiError.withErrors: if the provided errors is null, replace it with an empty
map; otherwise create a new map copying each key and making an unmodifiable copy
of each List<String> value (so callers can't mutate lists after construction),
then pass that immutable/deep-copied map into the ApiError constructor (i.e.,
modify ApiError.withErrors to return new ApiError(code, status, message,
<immutable deep copy of errors>)).
In
`@fiat-off-ramp/fiat-off-ramp-api/src/main/java/com/stablecoin/payments/offramp/api/events/FiatPayoutInitiated.java`:
- Around line 7-22: The FiatPayoutInitiated record exposes schemaVersion and
eventType as constructor parameters despite having fixed constants; add a
compact constructor (or static factory) in FiatPayoutInitiated that ignores
caller-supplied schemaVersion/eventType and enforces schemaVersion =
SCHEMA_VERSION and eventType = EVENT_TYPE (validate or overwrite incoming
values), and apply the same pattern to the sibling event records so nobody can
instantiate these records with mismatched metadata.
In
`@fiat-off-ramp/fiat-off-ramp-api/src/main/java/com/stablecoin/payments/offramp/api/PayoutRequest.java`:
- Around line 14-23: PayoutRequest currently only uses `@NotBlank` for several
tightly constrained fields so invalid lengths or unsupported enum values slip
through to persistence; update the PayoutRequest DTO (fields payoutType,
stablecoin, targetCurrency, recipientAccountHash, paymentRail, offRampPartnerId,
offRampPartnerName) to enforce the same constraints as the DB schema by adding
`@Size`(max=...) with the DB column lengths and use either `@Pattern` or a custom
`@Enum` validator (or javax.validation.Constraint backed by an Enum) for fields
with limited allowed values (e.g., payoutType, stablecoin, paymentRail) so
invalid inputs fail Bean Validation and return the OF-0001 response rather than
failing at persistence.
In
`@fiat-off-ramp/fiat-off-ramp-client/src/main/java/com/stablecoin/payments/offramp/client/FiatOffRampClient.java`:
- Around line 13-20: The Feign client FiatOffRampClient is missing the service
context path so requests to initiatePayout and getPayout will target /v1/...
instead of /off-ramp/v1/.... Update the `@FeignClient` annotation (on interface
FiatOffRampClient) to include the path attribute for the service context (e.g.,
path="/off-ramp") so the client-level base path is applied and both
initiatePayout and getPayout resolve to /off-ramp/v1/payouts.
In `@fiat-off-ramp/fiat-off-ramp/build.gradle.kts`:
- Around line 25-26: The build file is incorrectly adding
sourceSets.test.get().output to higher-level classpaths (compileClasspath and
runtimeClasspath), which exposes unit-test-only helpers to integration/business
suites; remove the "+ sourceSets.test.get().output" from the compileClasspath
and runtimeClasspath assignments in the blocks that modify those classpaths (and
the similar occurrences around the 46-47 region), leaving only
sourceSets.main.get().output, and instead add explicit testFixtures dependencies
where shared test helpers are needed (use testFixtures(project) or
implementation(testFixtures(project)) on the appropriate source sets) so shared
fixtures come from the testFixtures API rather than src/test output.
- Around line 111-112: Remove the non-existent Spring Boot test starters and
correct the test dependency coordinates: delete any usages of
"spring-boot-starter-webmvc-test" and "spring-boot-starter-security-test" and
instead rely on the existing "spring-boot-starter-test" for web testing and add
the explicit security test artifact
"org.springframework.security:spring-security-test" to the testImplementation
dependencies; also confirm whether
"implementation(\"org.springframework.boot:spring-boot-starter-flyway\")" in the
build script is intentional—if you only need Flyway itself, replace it with the
canonical "org.flywaydb:flyway-core" (or keep the starter only if you
intentionally want the Spring Boot starter integration).
In
`@fiat-off-ramp/fiat-off-ramp/src/integration-test/java/com/stablecoin/payments/offramp/AbstractIntegrationTest.java`:
- Around line 38-48: In AbstractIntegrationTest, the cleanDatabase() method
currently truncates off_ramp_transactions, stablecoin_redemptions,
payout_orders, and offramp_outbox_record but omits Namastack outbox tables;
update the SQL passed to jdbcTemplate.execute in cleanDatabase() to also include
offramp_outbox_instance and offramp_outbox_partition in the TRUNCATE TABLE list
(with CASCADE) so instance heartbeats and partition assignments are cleared
between tests.
In
`@fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/application/controller/GlobalExceptionHandler.java`:
- Around line 47-58: Replace the generic JDK exception handlers in
GlobalExceptionHandler: remove or stop mapping IllegalStateException and
IllegalArgumentException in handleIllegalState and handleIllegalArgument and
instead create and handle domain-specific exceptions (e.g.,
InvalidTransitionException, InvalidParameterException) that encapsulate safe,
fixed client-facing messages; update the controller/service code to throw those
domain exceptions where appropriate and have the GlobalExceptionHandler return
ApiError.of with those fixed messages and codes (e.g., "OF-0002"/"OF-0001")
while allowing unmapped runtime exceptions to propagate to the 500 handler.
In
`@fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/application/filter/CorrelationIdFilter.java`:
- Around line 28-33: The CorrelationIdFilter currently accepts any
CORRELATION_ID_HEADER value and puts it into MDC (CORRELATION_ID_MDC_KEY) and
the response; update the filter so it attempts to parse the incoming header as a
java.util.UUID (e.g., UUID.fromString) and if parsing fails (or header is
null/blank) generate a new UUID; always store and return the normalized
UUID.toString() in MDC and response header so downstream code and logs only ever
see valid UUIDs.
In
`@fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/application/filter/IdempotencyKeyFilter.java`:
- Around line 38-48: The filter currently accepts the Idempotency-Key header
as-is in requiresIdempotencyKey(...) flow using IDEMPOTENCY_KEY_HEADER; add
format validation (e.g., call a helper isValidUuid(String) or a
validateIdempotencyKey method) after retrieving key and before proceeding: if
the key is null/blank or fails isValidUuid(key) (or exceeds acceptable
length/contains illegal chars) then log a clear info/warn including request
method/URI, set response status to HttpStatus.BAD_REQUEST, set
MediaType.APPLICATION_JSON_VALUE and write the existing error JSON (adjust
message to indicate invalid Idempotency-Key format), then return; ensure the
helper is referenced from the same class or a util and used in the same branch
that currently handles missing keys.
- Around line 44-46: The filter currently writes hard-coded JSON in
IdempotencyKeyFilter via response.getWriter().write(...), which is fragile and
inconsistent; instead inject an ObjectMapper into IdempotencyKeyFilter,
construct an ApiError instance (with code "OF-0001", status "Bad Request", the
same message and empty errors), set the response status and content-type, and
serialize the ApiError to the response using
objectMapper.writeValue(response.getWriter(), apiError) so all error responses
remain consistent with GlobalExceptionHandler.
In
`@fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/config/SecurityConfig.java`:
- Around line 18-28: The SecurityFilterChain built in
securityFilterChain(HttpSecurity) currently stops at
.anyRequest().authenticated() without wiring any authentication mechanism;
update securityFilterChain to register an authentication scheme or filter (e.g.,
call http.oauth2ResourceServer(...) or http.httpBasic(), or add a custom filter
with http.addFilterBefore(...)) and also set a custom AuthenticationEntryPoint
and AccessDeniedHandler so 401/403 responses follow your API error contract;
target the securityFilterChain method and HttpSecurity configuration where
.anyRequest().authenticated() is set and wire the chosen auth provider/filter
plus the custom entry point and access denied handler.
- Line 23: The current SecurityConfig allows unauthenticated access to all
actuator endpoints via .requestMatchers("/actuator/**").permitAll(), which
exposes sensitive metrics/prometheus data; update the security configuration in
SecurityConfig (the HttpSecurity/SecurityFilterChain setup where
.requestMatchers is called) to only permit the specific safe endpoints (e.g.,
"/actuator/health" and "/actuator/info") instead of the wildcard, or
alternatively move management endpoints to a separate management port and secure
the main application endpoints accordingly; adjust the .requestMatchers(...)
call in SecurityConfig to list only the allowed paths or implement a separate
management server security configuration.
- Around line 31-38: The application.yml currently sets app.security.enabled:
false which causes the insecure bean localSecurityFilterChain (in
SecurityConfig) to load by default; change the default in application.yml to
true so the secure securityFilterChain (with matchIfMissing = true) is used
unless overridden, and ensure only test/local environments set
APP_SECURITY_ENABLED=false via docker-compose or integration-test configs that
already do so.
In `@fiat-off-ramp/fiat-off-ramp/src/main/resources/application.yml`:
- Around line 117-119: Replace the hardcoded property app.security.enabled:
false with an environment-backed property so the default is secure and can be
overridden; change the value to reference ${APP_SECURITY_ENABLED:true} (property
name APP_SECURITY_ENABLED) in the main application.yml and add an
application-prod.yml profile that explicitly sets app.security.enabled: true to
enforce security in production. Ensure the property key app.security.enabled is
used consistently across config and any code that reads it.
In
`@fiat-off-ramp/fiat-off-ramp/src/main/resources/db/migration/V1__initial_schema.sql`:
- Around line 107-108: Rename the index idx_offramp_outbox_record_status to
follow the <table>_<columns>_idx convention by creating it as
offramp_outbox_record_status_next_retry_at_idx on table offramp_outbox_record
for columns (status, next_retry_at); update the CREATE INDEX statement
accordingly and ensure any references (drops or constraints) use the new name so
V1 uses the consistent naming convention.
- Around line 33-42: Remove the redundant index creation for payment_id: the
UNIQUE constraint payout_orders_payment_id_unique on table payout_orders already
creates an index, so delete the CREATE INDEX payout_orders_payment_id_idx ON
payout_orders (payment_id) statement to avoid duplicate index/write overhead;
keep the UNIQUE constraint as-is.
In
`@fiat-off-ramp/fiat-off-ramp/src/test/java/com/stablecoin/payments/offramp/arch/ArchitectureTest.java`:
- Around line 28-37: Update the test display name in ArchitectureTest for the
domainShouldNotDependOnSpring rule to list all three Spring exemptions; change
the `@DisplayName` on the domainShouldNotDependOnSpring test to explicitly mention
"stereotype, transaction, and beans.factory.annotation" so it matches the
resideOutsideOfPackage exemptions (org.springframework.stereotype..,
org.springframework.transaction..,
org.springframework.beans.factory.annotation..).
In
`@fiat-off-ramp/fiat-off-ramp/src/testFixtures/java/com/stablecoin/payments/offramp/fixtures/TestUtils.java`:
- Around line 24-33: The helper in TestUtils currently catches Throwable and
hides serious errors; change the catch to only catch AssertionError (the
AssertJ/JUnit comparison failure) and return false there, and let all other
Throwables/Errors propagate (i.e., rethrow or don't catch them). Locate the
matcher helper in TestUtils.java that compares original and expected (it
references original, expected, fieldsToIgnore, and uses
usingRecursiveComparison()) and replace the catch(Throwable t) block with a
catch(AssertionError ae) { return false; } so only assertion failures become
false and other errors are not swallowed.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro
Run ID: 4774566f-6379-4a54-ae9b-93769721be16
📒 Files selected for processing (25)
fiat-off-ramp/fiat-off-ramp-api/build.gradle.ktsfiat-off-ramp/fiat-off-ramp-api/src/main/java/com/stablecoin/payments/offramp/api/ApiError.javafiat-off-ramp/fiat-off-ramp-api/src/main/java/com/stablecoin/payments/offramp/api/PayoutRequest.javafiat-off-ramp/fiat-off-ramp-api/src/main/java/com/stablecoin/payments/offramp/api/PayoutResponse.javafiat-off-ramp/fiat-off-ramp-api/src/main/java/com/stablecoin/payments/offramp/api/events/FiatPayoutCompleted.javafiat-off-ramp/fiat-off-ramp-api/src/main/java/com/stablecoin/payments/offramp/api/events/FiatPayoutFailed.javafiat-off-ramp/fiat-off-ramp-api/src/main/java/com/stablecoin/payments/offramp/api/events/FiatPayoutInitiated.javafiat-off-ramp/fiat-off-ramp-api/src/main/java/com/stablecoin/payments/offramp/api/events/StablecoinRedeemed.javafiat-off-ramp/fiat-off-ramp-client/build.gradle.ktsfiat-off-ramp/fiat-off-ramp-client/src/main/java/com/stablecoin/payments/offramp/client/FiatOffRampClient.javafiat-off-ramp/fiat-off-ramp/build.gradle.ktsfiat-off-ramp/fiat-off-ramp/src/integration-test/java/com/stablecoin/payments/offramp/AbstractIntegrationTest.javafiat-off-ramp/fiat-off-ramp/src/integration-test/resources/application-integration-test.ymlfiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/FiatOffRampApplication.javafiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/application/controller/GlobalExceptionHandler.javafiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/application/filter/CorrelationIdFilter.javafiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/application/filter/IdempotencyKeyFilter.javafiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/config/FallbackAdaptersConfig.javafiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/config/SecurityConfig.javafiat-off-ramp/fiat-off-ramp/src/main/resources/application.ymlfiat-off-ramp/fiat-off-ramp/src/main/resources/db/migration/V1__initial_schema.sqlfiat-off-ramp/fiat-off-ramp/src/test/java/com/stablecoin/payments/offramp/arch/ArchitectureTest.javafiat-off-ramp/fiat-off-ramp/src/test/java/com/stablecoin/payments/offramp/config/TestKafkaConfig.javafiat-off-ramp/fiat-off-ramp/src/testFixtures/java/com/stablecoin/payments/offramp/fixtures/TestUtils.javasettings.gradle.kts
| public static ApiError withErrors(String code, String status, String message, | ||
| Map<String, List<String>> errors) { | ||
| return new ApiError(code, status, message, errors); |
There was a problem hiding this comment.
Normalize and defensively copy errors.
withErrors(...) currently preserves null and the caller's mutable collections. That can produce errors: null even though GlobalExceptionHandler and IdempotencyKeyFilter both shape empty errors as {}, and later mutations can change the serialized payload after construction.
Suggested hardening
public static ApiError withErrors(String code, String status, String message,
Map<String, List<String>> errors) {
- return new ApiError(code, status, message, errors);
+ var safeErrors = errors == null
+ ? Map.<String, List<String>>of()
+ : errors.entrySet().stream()
+ .collect(java.util.stream.Collectors.toUnmodifiableMap(
+ Map.Entry::getKey,
+ entry -> List.copyOf(entry.getValue())
+ ));
+ return new ApiError(code, status, message, safeErrors);
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public static ApiError withErrors(String code, String status, String message, | |
| Map<String, List<String>> errors) { | |
| return new ApiError(code, status, message, errors); | |
| public static ApiError withErrors(String code, String status, String message, | |
| Map<String, List<String>> errors) { | |
| var safeErrors = errors == null | |
| ? Map.<String, List<String>>of() | |
| : errors.entrySet().stream() | |
| .collect(java.util.stream.Collectors.toUnmodifiableMap( | |
| Map.Entry::getKey, | |
| entry -> List.copyOf(entry.getValue()) | |
| )); | |
| return new ApiError(code, status, message, safeErrors); | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@fiat-off-ramp/fiat-off-ramp-api/src/main/java/com/stablecoin/payments/offramp/api/ApiError.java`
around lines 16 - 18, Normalize and defensively copy the errors map in
ApiError.withErrors: if the provided errors is null, replace it with an empty
map; otherwise create a new map copying each key and making an unmodifiable copy
of each List<String> value (so callers can't mutate lists after construction),
then pass that immutable/deep-copied map into the ApiError constructor (i.e.,
modify ApiError.withErrors to return new ApiError(code, status, message,
<immutable deep copy of errors>)).
| public record FiatPayoutInitiated( | ||
| int schemaVersion, | ||
| UUID eventId, | ||
| String eventType, | ||
| UUID payoutId, | ||
| UUID paymentId, | ||
| UUID correlationId, | ||
| BigDecimal fiatAmount, | ||
| String targetCurrency, | ||
| String paymentRail, | ||
| String partner, | ||
| Instant initiatedAt | ||
| ) { | ||
| public static final String EVENT_TYPE = "fiat.payout.initiated"; | ||
| public static final int SCHEMA_VERSION = 1; | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Enforce the fixed event metadata at construction time.
This type already defines EVENT_TYPE and SCHEMA_VERSION, but callers can still instantiate it with different values. That makes it easy to publish a FiatPayoutInitiated payload with mismatched contract metadata. Add a compact constructor or a factory that pins those values, and apply the same pattern to the sibling event records.
Proposed fix
public record FiatPayoutInitiated(
int schemaVersion,
UUID eventId,
String eventType,
UUID payoutId,
UUID paymentId,
UUID correlationId,
BigDecimal fiatAmount,
String targetCurrency,
String paymentRail,
String partner,
Instant initiatedAt
) {
public static final String EVENT_TYPE = "fiat.payout.initiated";
public static final int SCHEMA_VERSION = 1;
+
+ public FiatPayoutInitiated {
+ if (schemaVersion != SCHEMA_VERSION) {
+ throw new IllegalArgumentException("Unsupported schema version: " + schemaVersion);
+ }
+ if (!EVENT_TYPE.equals(eventType)) {
+ throw new IllegalArgumentException("Unexpected event type: " + eventType);
+ }
+ }
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public record FiatPayoutInitiated( | |
| int schemaVersion, | |
| UUID eventId, | |
| String eventType, | |
| UUID payoutId, | |
| UUID paymentId, | |
| UUID correlationId, | |
| BigDecimal fiatAmount, | |
| String targetCurrency, | |
| String paymentRail, | |
| String partner, | |
| Instant initiatedAt | |
| ) { | |
| public static final String EVENT_TYPE = "fiat.payout.initiated"; | |
| public static final int SCHEMA_VERSION = 1; | |
| } | |
| public record FiatPayoutInitiated( | |
| int schemaVersion, | |
| UUID eventId, | |
| String eventType, | |
| UUID payoutId, | |
| UUID paymentId, | |
| UUID correlationId, | |
| BigDecimal fiatAmount, | |
| String targetCurrency, | |
| String paymentRail, | |
| String partner, | |
| Instant initiatedAt | |
| ) { | |
| public static final String EVENT_TYPE = "fiat.payout.initiated"; | |
| public static final int SCHEMA_VERSION = 1; | |
| public FiatPayoutInitiated { | |
| if (schemaVersion != SCHEMA_VERSION) { | |
| throw new IllegalArgumentException("Unsupported schema version: " + schemaVersion); | |
| } | |
| if (!EVENT_TYPE.equals(eventType)) { | |
| throw new IllegalArgumentException("Unexpected event type: " + eventType); | |
| } | |
| } | |
| } |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@fiat-off-ramp/fiat-off-ramp-api/src/main/java/com/stablecoin/payments/offramp/api/events/FiatPayoutInitiated.java`
around lines 7 - 22, The FiatPayoutInitiated record exposes schemaVersion and
eventType as constructor parameters despite having fixed constants; add a
compact constructor (or static factory) in FiatPayoutInitiated that ignores
caller-supplied schemaVersion/eventType and enforces schemaVersion =
SCHEMA_VERSION and eventType = EVENT_TYPE (validate or overwrite incoming
values), and apply the same pattern to the sibling event records so nobody can
instantiate these records with mismatched metadata.
| @NotBlank String payoutType, | ||
| @NotBlank String stablecoin, | ||
| @NotNull @Positive BigDecimal redeemedAmount, | ||
| @NotBlank String targetCurrency, | ||
| @NotNull @Positive BigDecimal appliedFxRate, | ||
| @NotNull UUID recipientId, | ||
| @NotBlank String recipientAccountHash, | ||
| @NotBlank String paymentRail, | ||
| @NotBlank String offRampPartnerId, | ||
| @NotBlank String offRampPartnerName |
There was a problem hiding this comment.
Validate enum and length boundaries at the API edge.
These fields only use @NotBlank, but the schema already constrains them tightly in fiat-off-ramp/fiat-off-ramp/src/main/resources/db/migration/V1__initial_schema.sql, Lines 13-28 and Lines 34-39. As written, oversized or unsupported values will pass Bean Validation and fail later at persistence time instead of returning a clean OF-0001 response.
Suggested direction
+import jakarta.validation.constraints.Pattern;
+import jakarta.validation.constraints.Size;
@@
- `@NotBlank` String payoutType,
- `@NotBlank` String stablecoin,
+ `@NotBlank`
+ `@Pattern`(regexp = "FIAT|HOLD_STABLECOIN")
+ String payoutType,
+ `@NotBlank`
+ `@Size`(max = 20)
+ String stablecoin,
`@NotNull` `@Positive` BigDecimal redeemedAmount,
- `@NotBlank` String targetCurrency,
+ `@NotBlank`
+ `@Pattern`(regexp = "^[A-Z]{3}$")
+ String targetCurrency,
`@NotNull` `@Positive` BigDecimal appliedFxRate,
`@NotNull` UUID recipientId,
- `@NotBlank` String recipientAccountHash,
- `@NotBlank` String paymentRail,
- `@NotBlank` String offRampPartnerId,
- `@NotBlank` String offRampPartnerName
+ `@NotBlank` `@Size`(max = 64) String recipientAccountHash,
+ `@NotBlank` `@Size`(max = 30) String paymentRail,
+ `@NotBlank` `@Size`(max = 50) String offRampPartnerId,
+ `@NotBlank` `@Size`(max = 100) String offRampPartnerName
) {}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| @NotBlank String payoutType, | |
| @NotBlank String stablecoin, | |
| @NotNull @Positive BigDecimal redeemedAmount, | |
| @NotBlank String targetCurrency, | |
| @NotNull @Positive BigDecimal appliedFxRate, | |
| @NotNull UUID recipientId, | |
| @NotBlank String recipientAccountHash, | |
| @NotBlank String paymentRail, | |
| @NotBlank String offRampPartnerId, | |
| @NotBlank String offRampPartnerName | |
| `@NotBlank` | |
| `@Pattern`(regexp = "FIAT|HOLD_STABLECOIN") | |
| String payoutType, | |
| `@NotBlank` | |
| `@Size`(max = 20) | |
| String stablecoin, | |
| `@NotNull` `@Positive` BigDecimal redeemedAmount, | |
| `@NotBlank` | |
| `@Pattern`(regexp = "^[A-Z]{3}$") | |
| String targetCurrency, | |
| `@NotNull` `@Positive` BigDecimal appliedFxRate, | |
| `@NotNull` UUID recipientId, | |
| `@NotBlank` `@Size`(max = 64) String recipientAccountHash, | |
| `@NotBlank` `@Size`(max = 30) String paymentRail, | |
| `@NotBlank` `@Size`(max = 50) String offRampPartnerId, | |
| `@NotBlank` `@Size`(max = 100) String offRampPartnerName | |
| ) {} |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@fiat-off-ramp/fiat-off-ramp-api/src/main/java/com/stablecoin/payments/offramp/api/PayoutRequest.java`
around lines 14 - 23, PayoutRequest currently only uses `@NotBlank` for several
tightly constrained fields so invalid lengths or unsupported enum values slip
through to persistence; update the PayoutRequest DTO (fields payoutType,
stablecoin, targetCurrency, recipientAccountHash, paymentRail, offRampPartnerId,
offRampPartnerName) to enforce the same constraints as the DB schema by adding
`@Size`(max=...) with the DB column lengths and use either `@Pattern` or a custom
`@Enum` validator (or javax.validation.Constraint backed by an Enum) for fields
with limited allowed values (e.g., payoutType, stablecoin, paymentRail) so
invalid inputs fail Bean Validation and return the OF-0001 response rather than
failing at persistence.
| @FeignClient(name = "fiat-off-ramp-service", url = "${app.services.fiat-off-ramp.url}") | ||
| public interface FiatOffRampClient { | ||
|
|
||
| @PostMapping(value = "/v1/payouts", consumes = "application/json", produces = "application/json") | ||
| PayoutResponse initiatePayout(@RequestBody PayoutRequest request); | ||
|
|
||
| @GetMapping(value = "/v1/payouts/{payoutId}", produces = "application/json") | ||
| PayoutResponse getPayout(@PathVariable("payoutId") UUID payoutId); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
# First, find the service implementation and configuration files
find . -type f -name "application*.yml" -o -name "application*.properties" | head -20Repository: Puneethkumarck/stablebridge-platform
Length of output: 1762
🏁 Script executed:
# Locate the FiatOffRamp service implementation
find . -type f -name "*FiatOffRampService*" -o -name "*FiatOffRampController*" | head -20Repository: Puneethkumarck/stablebridge-platform
Length of output: 62
🏁 Script executed:
# Check the repository structure
git ls-files | grep -E "(fiat-off-ramp|application)" | head -30Repository: Puneethkumarck/stablebridge-platform
Length of output: 3829
🏁 Script executed:
# Check the fiat-off-ramp service configuration
cat ./fiat-off-ramp/fiat-off-ramp/src/main/resources/application.ymlRepository: Puneethkumarck/stablebridge-platform
Length of output: 2888
🏁 Script executed:
# Find the controller in fiat-off-ramp service
find ./fiat-off-ramp/fiat-off-ramp -type f -name "*Controller*" -o -name "*Payout*"Repository: Puneethkumarck/stablebridge-platform
Length of output: 62
🏁 Script executed:
# Look for the PayoutResponse and PayoutRequest classes
find . -type f -name "PayoutResponse.java" -o -name "PayoutRequest.java"Repository: Puneethkumarck/stablebridge-platform
Length of output: 286
🏁 Script executed:
# Find the controller in fiat-off-ramp service with payout endpoints
find ./fiat-off-ramp/fiat-off-ramp/src -type f -name "*.java" | xargs grep -l "@PostMapping.*payouts\|@GetMapping.*payouts" | head -10Repository: Puneethkumarck/stablebridge-platform
Length of output: 62
🏁 Script executed:
# Check if there's a PayoutController
find ./fiat-off-ramp/fiat-off-ramp -type f -name "*PayoutController*" -o -name "*Controller.java" | grep -v testRepository: Puneethkumarck/stablebridge-platform
Length of output: 62
🏁 Script executed:
# Search for app.services.fiat-off-ramp.url configuration across the codebase
rg "app\.services\.fiat-off-ramp" --type yaml --type properties -A 2 -B 2Repository: Puneethkumarck/stablebridge-platform
Length of output: 114
🏁 Script executed:
# Check how the Feign client is used/configured in other services
find . -type f -name "*.yml" -o -name "*.properties" | xargs grep -l "app.services" | head -10Repository: Puneethkumarck/stablebridge-platform
Length of output: 62
🏁 Script executed:
# Search for /v1/payouts endpoint mappings in fiat-off-ramp service
rg "/v1/payouts" ./fiat-off-ramp/fiat-off-rampRepository: Puneethkumarck/stablebridge-platform
Length of output: 62
🏁 Script executed:
# Search for PostMapping or GetMapping annotations in fiat-off-ramp service
rg "@PostMapping|@GetMapping" ./fiat-off-ramp/fiat-off-ramp/src --type java -A 2 | head -50Repository: Puneethkumarck/stablebridge-platform
Length of output: 62
🏁 Script executed:
# Check where FiatOffRampClient is used
rg "FiatOffRampClient" . --type javaRepository: Puneethkumarck/stablebridge-platform
Length of output: 229
🏁 Script executed:
# Check all application.yml files for service endpoint configurations
cat ./fiat-off-ramp/fiat-off-ramp/src/integration-test/resources/application-integration-test.ymlRepository: Puneethkumarck/stablebridge-platform
Length of output: 786
🏁 Script executed:
# Search for RestController in fiat-off-ramp service
find ./fiat-off-ramp/fiat-off-ramp/src/main/java -type f -name "*.java" | xargs grep -l "@RestController"Repository: Puneethkumarck/stablebridge-platform
Length of output: 206
🏁 Script executed:
# Search for all RequestMapping patterns in fiat-off-ramp service
find ./fiat-off-ramp/fiat-off-ramp/src/main/java -type f -name "*.java" | xargs grep -l "@RequestMapping\|@PostMapping\|@GetMapping"Repository: Puneethkumarck/stablebridge-platform
Length of output: 64
🏁 Script executed:
# Look for app.services configuration across all yaml files
rg "app:" ./fiat-off-ramp -A 10 --type yamlRepository: Puneethkumarck/stablebridge-platform
Length of output: 1730
🏁 Script executed:
# Check other service's configuration to understand the pattern
rg "app:" ./merchant-onboarding -A 5 --type yaml | head -50Repository: Puneethkumarck/stablebridge-platform
Length of output: 1233
🏁 Script executed:
# List all files in fiat-off-ramp service controller directory
find ./fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/application/controller -type f -name "*.java"Repository: Puneethkumarck/stablebridge-platform
Length of output: 206
🏁 Script executed:
# Search for all Java controller files
find ./fiat-off-ramp/fiat-off-ramp/src/main -type f -name "*.java" | grep -i controllerRepository: Puneethkumarck/stablebridge-platform
Length of output: 206
🏁 Script executed:
# Look for any endpoint definitions or RequestMapping annotations
rg "@RequestMapping|@GetMapping|@PostMapping" ./fiat-off-ramp/fiat-off-ramp/src/main/java --type javaRepository: Puneethkumarck/stablebridge-platform
Length of output: 62
🏁 Script executed:
# Check the FiatOffRampClient file again to see the full implementation
cat ./fiat-off-ramp/fiat-off-ramp-client/src/main/java/com/stablecoin/payments/offramp/client/FiatOffRampClient.javaRepository: Puneethkumarck/stablebridge-platform
Length of output: 1020
🏁 Script executed:
# Search for any payout-related code in the fiat-off-ramp service
rg "payout|Payout" ./fiat-off-ramp/fiat-off-ramp/src/main/java --type java -iRepository: Puneethkumarck/stablebridge-platform
Length of output: 62
🏁 Script executed:
# List all Java classes in fiat-off-ramp service main source
find ./fiat-off-ramp/fiat-off-ramp/src/main/java -type f -name "*.java" | head -30Repository: Puneethkumarck/stablebridge-platform
Length of output: 765
🏁 Script executed:
# Check if there's a REST controller elsewhere or if this is a library service
find ./fiat-off-ramp/fiat-off-ramp/src/main -type f -name "*.java" | wc -lRepository: Puneethkumarck/stablebridge-platform
Length of output: 81
🏁 Script executed:
# Check the GlobalExceptionHandler and other files to understand the service structure
cat ./fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/application/controller/GlobalExceptionHandler.javaRepository: Puneethkumarck/stablebridge-platform
Length of output: 3113
🏁 Script executed:
# Check the main application file
cat ./fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/FiatOffRampApplication.javaRepository: Puneethkumarck/stablebridge-platform
Length of output: 701
🏁 Script executed:
# Search across entire codebase for where app.services.fiat-off-ramp.url might be defined
rg "app\.services\.fiat-off-ramp" . --type yamlRepository: Puneethkumarck/stablebridge-platform
Length of output: 62
🏁 Script executed:
# Check other services to see how they configure app.services URLs
rg "app\.services\." ./api-gateway-iam/api-gateway-iam/src/main/resources --type yaml -B 2 -A 2Repository: Puneethkumarck/stablebridge-platform
Length of output: 62
🏁 Script executed:
# Double-check by searching for any REST endpoints or payout service implementations elsewhere
find . -path ./node_modules -prune -o -type f -name "*.java" -exec grep -l "PayoutResponse initiatePayout\|PayoutResponse getPayout" {} \;Repository: Puneethkumarck/stablebridge-platform
Length of output: 192
🏁 Script executed:
# Check if there are integration tests that show expected behavior
find ./fiat-off-ramp -name "*Test*.java" -o -name "*IT.java" | xargs grep -l "payout\|Payout" -i 2>/dev/null | head -5Repository: Puneethkumarck/stablebridge-platform
Length of output: 196
🏁 Script executed:
# Check the integration test
cat ./fiat-off-ramp/fiat-off-ramp/src/integration-test/java/com/stablecoin/payments/offramp/AbstractIntegrationTest.javaRepository: Puneethkumarck/stablebridge-platform
Length of output: 2321
🏁 Script executed:
# Search for any controller or rest endpoint implementations that might handle payout requests
rg "@PostMapping|@GetMapping|@RequestMapping" ./fiat-off-ramp -B 2 -A 5 --type javaRepository: Puneethkumarck/stablebridge-platform
Length of output: 1349
🏁 Script executed:
# Check the PR or documentation for expected API structure
find ./fiat-off-ramp -name "README*" -o -name "*.md" | xargs cat 2>/dev/nullRepository: Puneethkumarck/stablebridge-platform
Length of output: 62
Add context path to the Feign client configuration.
The service runs under /off-ramp context path (configured in server.servlet.context-path), but the Feign client targets /v1/payouts directly. When service endpoints are implemented, requests will hit http://host:port/v1/payouts instead of http://host:port/off-ramp/v1/payouts, resulting in 404s. Use the path attribute to establish the correct base path at the client level.
Proposed fix
-@FeignClient(name = "fiat-off-ramp-service", url = "${app.services.fiat-off-ramp.url}")
+@FeignClient(
+ name = "fiat-off-ramp-service",
+ url = "${app.services.fiat-off-ramp.url}",
+ path = "/off-ramp"
+)
public interface FiatOffRampClient {🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@fiat-off-ramp/fiat-off-ramp-client/src/main/java/com/stablecoin/payments/offramp/client/FiatOffRampClient.java`
around lines 13 - 20, The Feign client FiatOffRampClient is missing the service
context path so requests to initiatePayout and getPayout will target /v1/...
instead of /off-ramp/v1/.... Update the `@FeignClient` annotation (on interface
FiatOffRampClient) to include the path attribute for the service context (e.g.,
path="/off-ramp") so the client-level base path is applied and both
initiatePayout and getPayout resolve to /off-ramp/v1/payouts.
| compileClasspath += sourceSets.main.get().output + sourceSets.test.get().output | ||
| runtimeClasspath += sourceSets.main.get().output + sourceSets.test.get().output |
There was a problem hiding this comment.
🧹 Nitpick | 🔵 Trivial
Keep src/test output off the higher-level test classpaths.
Both custom source sets compile against sourceSets.test.output. That lets integration/business suites depend on unit-test-only helpers and mocks instead of the fixture API, which makes the suite boundaries brittle. Shared helpers should come from testFixtures(project) only.
Proposed fix
val integrationTestSourceSet: SourceSet = sourceSets.create("integrationTest") {
java.srcDir("src/integration-test/java")
resources.srcDir("src/integration-test/resources")
- compileClasspath += sourceSets.main.get().output + sourceSets.test.get().output
- runtimeClasspath += sourceSets.main.get().output + sourceSets.test.get().output
+ compileClasspath += sourceSets.main.get().output
+ runtimeClasspath += sourceSets.main.get().output
}
@@
val businessTestSourceSet: SourceSet = sourceSets.create("businessTest") {
java.srcDir("src/business-test/java")
resources.srcDir("src/business-test/resources")
- compileClasspath += sourceSets.main.get().output + sourceSets.test.get().output + integrationTestSourceSet.output
- runtimeClasspath += sourceSets.main.get().output + sourceSets.test.get().output + integrationTestSourceSet.output
+ compileClasspath += sourceSets.main.get().output + integrationTestSourceSet.output
+ runtimeClasspath += sourceSets.main.get().output + integrationTestSourceSet.output
}Also applies to: 46-47
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@fiat-off-ramp/fiat-off-ramp/build.gradle.kts` around lines 25 - 26, The build
file is incorrectly adding sourceSets.test.get().output to higher-level
classpaths (compileClasspath and runtimeClasspath), which exposes unit-test-only
helpers to integration/business suites; remove the "+
sourceSets.test.get().output" from the compileClasspath and runtimeClasspath
assignments in the blocks that modify those classpaths (and the similar
occurrences around the 46-47 region), leaving only sourceSets.main.get().output,
and instead add explicit testFixtures dependencies where shared test helpers are
needed (use testFixtures(project) or implementation(testFixtures(project)) on
the appropriate source sets) so shared fixtures come from the testFixtures API
rather than src/test output.
| app: | ||
| security: | ||
| enabled: false |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check for production profile overrides
echo "=== Checking for production/deployment configs ==="
fd -e yml -e yaml | xargs grep -l "security" 2>/dev/null | head -10
fd -e yml -e yaml --exec grep -A2 "app:" {} \; 2>/dev/nullRepository: Puneethkumarck/stablebridge-platform
Length of output: 3357
🏁 Script executed:
#!/bin/bash
# Check fiat-off-ramp application configs specifically
echo "=== Fiat-off-ramp application configs ==="
fd "application" fiat-off-ramp -e yml -e yaml
echo ""
echo "=== Content of fiat-off-ramp application.yml (lines 110-125) ==="
sed -n '110,125p' fiat-off-ramp/fiat-off-ramp/src/main/resources/application.yml
echo ""
echo "=== Check for prod/production configs ==="
fd "application" fiat-off-ramp -e yml -e yaml | grep -i prod
echo ""
echo "=== All security-related configs in fiat-off-ramp ==="
rg "security" fiat-off-ramp/fiat-off-ramp/src/main/resources/Repository: Puneethkumarck/stablebridge-platform
Length of output: 929
🏁 Script executed:
#!/bin/bash
echo "=== Checking for fiat-off-ramp deployment configs ==="
find . -name "*fiat*off*ramp*" -o -name "fiat-off-ramp" | grep -E "(k8s|deploy|docker|helm)" | head -20
echo ""
echo "=== Check docker-compose.dev.yml for fiat-off-ramp ==="
rg "fiat-off-ramp|fiat_off_ramp" docker-compose.dev.yml -A 10 -i
echo ""
echo "=== Check for any Kubernetes deployment manifests ==="
find . -path "*/fiat-off-ramp/*" -name "*.yml" -o -name "*.yaml" | grep -v "/src/"Repository: Puneethkumarck/stablebridge-platform
Length of output: 1122
Security disabled by default — adopt environment variable pattern with safe fallback.
app.security.enabled: false is hardcoded without an environment variable override mechanism. Update to match the pattern used elsewhere in the codebase (e.g., s13):
app:
security:
enabled: ${APP_SECURITY_ENABLED:true}This ensures security is enabled by default and explicitly disabled only when configured at deployment time. Also create an application-prod.yml profile to enforce this in production environments.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@fiat-off-ramp/fiat-off-ramp/src/main/resources/application.yml` around lines
117 - 119, Replace the hardcoded property app.security.enabled: false with an
environment-backed property so the default is secure and can be overridden;
change the value to reference ${APP_SECURITY_ENABLED:true} (property name
APP_SECURITY_ENABLED) in the main application.yml and add an
application-prod.yml profile that explicitly sets app.security.enabled: true to
enforce security in production. Ensure the property key app.security.enabled is
used consistently across config and any code that reads it.
| CREATE INDEX idx_offramp_outbox_record_status | ||
| ON offramp_outbox_record (status, next_retry_at); |
There was a problem hiding this comment.
Align this index name with the migration's _idx convention.
Every other new index in this file uses the <table>_<columns>_idx pattern; idx_offramp_outbox_record_status is the outlier. Rename it before merge so V1 establishes the convention cleanly.
As per coding guidelines, **/db/migration/**: - Verify index naming follows the project convention.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@fiat-off-ramp/fiat-off-ramp/src/main/resources/db/migration/V1__initial_schema.sql`
around lines 107 - 108, Rename the index idx_offramp_outbox_record_status to
follow the <table>_<columns>_idx convention by creating it as
offramp_outbox_record_status_next_retry_at_idx on table offramp_outbox_record
for columns (status, next_retry_at); update the CREATE INDEX statement
accordingly and ensure any references (drops or constraints) use the new name so
V1 uses the consistent naming convention.
| @DisplayName("Domain should not depend on Spring (except stereotype and transaction)") | ||
| void domainShouldNotDependOnSpring() { | ||
| noClasses() | ||
| .that().resideInAPackage("..domain..") | ||
| .should().dependOnClassesThat( | ||
| resideInAPackage("org.springframework..") | ||
| .and(resideOutsideOfPackage("org.springframework.stereotype..")) | ||
| .and(resideOutsideOfPackage("org.springframework.transaction..")) | ||
| .and(resideOutsideOfPackage("org.springframework.beans.factory.annotation..")) | ||
| ) |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
find . -type f -name "ArchitectureTest.java" | head -5Repository: Puneethkumarck/stablebridge-platform
Length of output: 625
🏁 Script executed:
cat -n fiat-off-ramp/fiat-off-ramp/src/test/java/com/stablecoin/payments/offramp/arch/ArchitectureTest.java | head -60Repository: Puneethkumarck/stablebridge-platform
Length of output: 2993
🏁 Script executed:
cat -n fiat-off-ramp/fiat-off-ramp/src/test/java/com/stablecoin/payments/offramp/arch/ArchitectureTest.java | sed -n '20,50p'Repository: Puneethkumarck/stablebridge-platform
Length of output: 1640
🏁 Script executed:
find . -path "*/domain/*" -name "*.java" -type f | head -20Repository: Puneethkumarck/stablebridge-platform
Length of output: 2626
🏁 Script executed:
rg "@Autowired|@Value|@Inject" fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/domain/ --type java -B 2Repository: Puneethkumarck/stablebridge-platform
Length of output: 201
🏁 Script executed:
rg "org.springframework.beans.factory.annotation" fiat-off-ramp/fiat-off-ramp/src/main --type java -lRepository: Puneethkumarck/stablebridge-platform
Length of output: 62
🏁 Script executed:
find fiat-off-ramp/fiat-off-ramp/src/main/java -type d | head -20Repository: Puneethkumarck/stablebridge-platform
Length of output: 721
🏁 Script executed:
find fiat-off-ramp/fiat-off-ramp/src/main/java -name "*omain*" -type dRepository: Puneethkumarck/stablebridge-platform
Length of output: 62
🏁 Script executed:
rg "@Autowired|@Value|@Inject" fiat-off-ramp/ --type java -l | grep -v testRepository: Puneethkumarck/stablebridge-platform
Length of output: 62
🏁 Script executed:
find fiat-off-ramp/fiat-off-ramp/src/main/java -type f -name "*.java" | head -30Repository: Puneethkumarck/stablebridge-platform
Length of output: 753
🏁 Script executed:
ls -la fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/Repository: Puneethkumarck/stablebridge-platform
Length of output: 379
🏁 Script executed:
find fiat-off-ramp/fiat-off-ramp -type d -name "*" | grep -v ".class" | sortRepository: Puneethkumarck/stablebridge-platform
Length of output: 2371
🏁 Script executed:
cat fiat-off-ramp/fiat-off-ramp/src/test/java/com/stablecoin/payments/offramp/arch/ArchitectureTest.javaRepository: Puneethkumarck/stablebridge-platform
Length of output: 4415
🏁 Script executed:
find fiat-off-ramp/fiat-off-ramp/src/main -type d | grep -i domainRepository: Puneethkumarck/stablebridge-platform
Length of output: 62
🏁 Script executed:
git log --oneline -S "beans.factory.annotation" -- fiat-off-ramp/fiat-off-ramp/src/test/java/com/stablecoin/payments/offramp/arch/ArchitectureTest.java | head -5Repository: Puneethkumarck/stablebridge-platform
Length of output: 140
🏁 Script executed:
git show --stat $(git log --oneline -S "beans.factory.annotation" -- fiat-off-ramp/fiat-off-ramp/src/test/java/com/stablecoin/payments/offramp/arch/ArchitectureTest.java | head -1 | cut -d' ' -f1)Repository: Puneethkumarck/stablebridge-platform
Length of output: 50392
🏁 Script executed:
find fiat-off-ramp/fiat-off-ramp/src/main/java -type f -name "*.java" | xargs cat | grep -E "@Autowired|@Value|@Inject|@Bean" | head -20Repository: Puneethkumarck/stablebridge-platform
Length of output: 109
🏁 Script executed:
find fiat-off-ramp/fiat-off-ramp -type d -name "domain"Repository: Puneethkumarck/stablebridge-platform
Length of output: 62
🏁 Script executed:
ls -la fiat-off-ramp/fiat-off-ramp/src/main/java/com/stablecoin/payments/offramp/ | grep -i domainRepository: Puneethkumarck/stablebridge-platform
Length of output: 62
🏁 Script executed:
grep -r "..domain.." fiat-off-ramp/fiat-off-ramp/src --include="*.java" | head -10Repository: Puneethkumarck/stablebridge-platform
Length of output: 1725
🏁 Script executed:
find fiat-off-ramp/fiat-off-ramp/src/main/java -type f -name "*.java" | xargs grep -l "package.*domain" | head -10Repository: Puneethkumarck/stablebridge-platform
Length of output: 62
🏁 Script executed:
find . -path "*offramp*" -name "*.java" | xargs grep "package com.stablecoin.payments.offramp" | grep -v test | cut -d: -f1 | sort -uRepository: Puneethkumarck/stablebridge-platform
Length of output: 1643
🏁 Script executed:
cat -n fiat-off-ramp/fiat-off-ramp/src/test/java/com/stablecoin/payments/offramp/arch/ArchitectureTest.javaRepository: Puneethkumarck/stablebridge-platform
Length of output: 5199
🏁 Script executed:
python3 << 'EOF'
# Check if there's any domain package in fiat-off-ramp
import os
import subprocess
result = subprocess.run(
["find", "fiat-off-ramp/fiat-off-ramp/src/main/java", "-type", "d", "-name", "*domain*"],
capture_output=True,
text=True
)
print("Domain directories found:")
print(result.stdout)
print("stderr:", result.stderr)
# Also check test output for domain packages
result2 = subprocess.run(
["find", "fiat-off-ramp/fiat-off-ramp", "-type", "f", "-name", "*.java"],
capture_output=True,
text=True
)
files = result2.stdout.strip().split('\n')
domain_files = [f for f in files if 'domain' in f.lower()]
print("\nFiles with 'domain' in path:")
for f in domain_files[:20]:
print(f)
EOFRepository: Puneethkumarck/stablebridge-platform
Length of output: 145
🏁 Script executed:
find . -path "*/src/main/java/*" -type d -name "domain" | head -10Repository: Puneethkumarck/stablebridge-platform
Length of output: 832
🏁 Script executed:
cat api-gateway-iam/api-gateway-iam/src/test/java/com/stablecoin/payments/gateway/iam/ArchitectureTest.java | sed -n '25,45p'Repository: Puneethkumarck/stablebridge-platform
Length of output: 1051
🏁 Script executed:
find api-gateway-iam/api-gateway-iam/src/main/java/com/stablecoin/payments/gateway/iam/domain -name "*.java" | xargs grep -l "@Autowired\|@Value\|@Inject" | head -5Repository: Puneethkumarck/stablebridge-platform
Length of output: 200
🏁 Script executed:
grep -r "org.springframework.beans.factory.annotation" . --include="*.java" | grep -v test | head -10Repository: Puneethkumarck/stablebridge-platform
Length of output: 1241
🏁 Script executed:
cat api-gateway-iam/api-gateway-iam/src/main/java/com/stablecoin/payments/gateway/iam/domain/service/AuthCommandHandler.java | head -40Repository: Puneethkumarck/stablebridge-platform
Length of output: 2162
Update the display name to accurately document all Spring exemptions.
The domainShouldNotDependOnSpring rule permits three Spring packages but the display name only mentions stereotype and transaction. Line 36 also exempts org.springframework.beans.factory.annotation.., which is necessary since domain services across the codebase (e.g., api-gateway-iam's AuthCommandHandler) use @Value for configuration injection. Update the @DisplayName on line 28 to reflect all three exemptions:
`@DisplayName`("Domain should not depend on Spring (except stereotype, transaction, and beans.factory.annotation)")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@fiat-off-ramp/fiat-off-ramp/src/test/java/com/stablecoin/payments/offramp/arch/ArchitectureTest.java`
around lines 28 - 37, Update the test display name in ArchitectureTest for the
domainShouldNotDependOnSpring rule to list all three Spring exemptions; change
the `@DisplayName` on the domainShouldNotDependOnSpring test to explicitly mention
"stereotype, transaction, and beans.factory.annotation" so it matches the
resideOutsideOfPackage exemptions (org.springframework.stereotype..,
org.springframework.transaction..,
org.springframework.beans.factory.annotation..).
| try { | ||
| assertThat(original) | ||
| .usingRecursiveComparison() | ||
| .ignoringFieldsOfTypes(ZonedDateTime.class, LocalDateTime.class, LocalDate.class, Instant.class) | ||
| .ignoringFields(fieldsToIgnore) | ||
| .isEqualTo(expected); | ||
| return true; | ||
| } catch (Throwable t) { | ||
| return false; | ||
| } |
There was a problem hiding this comment.
Do not swallow Throwable in the matcher helper.
Only the AssertJ comparison failure needs to turn into false. Catching Throwable will also hide OutOfMemoryError, LinkageError, and similar failures as a silent matcher miss, which makes test failures much harder to diagnose.
Suggested fix
try {
assertThat(original)
.usingRecursiveComparison()
.ignoringFieldsOfTypes(ZonedDateTime.class, LocalDateTime.class, LocalDate.class, Instant.class)
.ignoringFields(fieldsToIgnore)
.isEqualTo(expected);
return true;
- } catch (Throwable t) {
+ } catch (AssertionError ignored) {
return false;
}🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@fiat-off-ramp/fiat-off-ramp/src/testFixtures/java/com/stablecoin/payments/offramp/fixtures/TestUtils.java`
around lines 24 - 33, The helper in TestUtils currently catches Throwable and
hides serious errors; change the catch to only catch AssertionError (the
AssertJ/JUnit comparison failure) and return false there, and let all other
Throwables/Errors propagate (i.e., rethrow or don't catch them). Locate the
matcher helper in TestUtils.java that compares original and expected (it
references original, expected, fieldsToIgnore, and uses
usingRecursiveComparison()) and replace the catch(Throwable t) block with a
catch(AssertionError ae) { return false; } so only assertion failures become
false and other errors are not swallowed.
Bootstrap S5 Fiat Off-Ramp with 3 Gradle modules (api, client, app), Flyway V1 (6 tables: payout_orders, stablecoin_redemptions, off_ramp_transactions, 3 Namastack outbox), SecurityConfig, IdempotencyKeyFilter, CorrelationIdFilter, GlobalExceptionHandler, 8 ArchUnit tests, AbstractIntegrationTest, and TestUtils. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2a5738b to
725e06a
Compare
- Remove duplicate payment_id index (UNIQUE constraint already creates one) - Include offramp_outbox_instance and offramp_outbox_partition in test truncation Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Redundant with CodeRabbit reviews. Removes CI noise and rate limit issues. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Summary
fiat-off-ramp-api,fiat-off-ramp-client,fiat-off-ramp)payout_orders,stablecoin_redemptions,off_ramp_transactions, 3 Namastack outbox)com.stablecoin.payments.offramp, port 8087, context path/off-rampCloses STA-146
Test plan
checktask passes (including JaCoCo)🤖 Generated with Claude Code
Summary by CodeRabbit