Skip to content

Commit b8a966a

Browse files
fix(infra): align WireMock stubs with real external API payloads (STA-195) (#181)
* fix(infra): align WireMock stubs with real external API payloads (STA-195) - Onfido: fix camelCase to snake_case (applicant_id, report_ids) - Chainalysis: fix rating case (lowRisk to LOW), remove cluster field - WorldCheck: replace matchedLists with sources, status to screeningState - Modulr: add payout initiation adapter and WireMock stub - Stripe: enrich stub with metadata and payment_method_types Closes STA-195, STA-196, STA-197, STA-198, STA-199, STA-200 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(s5): pass appliedFxRate to RedemptionRequest for correct fallback fiat calculation (STA-195) FallbackAdaptersConfig returned amount * 1.0 instead of amount * fxRate, causing PayoutOrder tolerance check to reject the fiat amount in ITs. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(s5): apply spotless formatting to ModulrPayoutAdapterTest (STA-195) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * chore(infra): add make check excluding E2E + make e2e commands (STA-195) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(infra): address CodeRabbit review — field mapping, cache, scheme routing (STA-195) - Modulr: use partnerName for destination.name (not bankCode/BIC) - Modulr: resolve permittedScheme by PaymentRail (null for Faster Payments) - Modulr: remove hardcoded default credentials, use empty defaults - Onfido: fix cache verification bug (SENDER_CACHE_KEY used as both key and value) - Modulr test: fix display name, use BIC for bankCode field Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(s2): remove unused never import from OnfidoKycAdapterTest (STA-195) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent b5d45fd commit b8a966a

27 files changed

Lines changed: 721 additions & 93 deletions

File tree

Makefile

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
infra-up infra-down infra-destroy infra-status infra-logs infra-logs-% \
44
run-% db-reset db-psql topics \
55
deps outdated \
6-
assemble sonar fresh ci
6+
assemble sonar fresh ci \
7+
e2e-up e2e-down e2e-destroy e2e-status e2e-build e2e-test e2e
78

89
# ─────────────────────────────────────────────
910
# Variables
@@ -78,8 +79,8 @@ format: ## Apply Spotless formatting
7879
lint: ## Check Spotless formatting (CI)
7980
$(GRADLE) spotlessCheck
8081

81-
check: ## Full CI check — build + format check + all tests
82-
$(GRADLE) spotlessCheck build
82+
check: ## Full CI check — build + format check + all tests (excludes E2E)
83+
$(GRADLE) check -x :phase2-integration-tests:test -x :phase3-integration-tests:test --build-cache --parallel
8384

8485
sonar: ## Run SonarCloud analysis
8586
$(GRADLE) sonar
@@ -144,3 +145,28 @@ outdated: ## Check for outdated dependencies
144145
fresh: clean build ## Clean + build
145146

146147
ci: lint test ## Full CI pipeline (format check + all tests)
148+
149+
# ─────────────────────────────────────────────
150+
# E2E Tests (require Docker Compose stack)
151+
# ─────────────────────────────────────────────
152+
E2E_COMPOSE := docker compose -f docker-compose.phase3-test.yml
153+
154+
e2e-up: ## Start Phase 3 E2E Docker Compose stack
155+
$(E2E_COMPOSE) up -d
156+
157+
e2e-down: ## Stop Phase 3 E2E stack
158+
$(E2E_COMPOSE) down
159+
160+
e2e-destroy: ## Stop E2E stack and remove volumes
161+
$(E2E_COMPOSE) down -v
162+
163+
e2e-status: ## Show E2E stack container status
164+
$(E2E_COMPOSE) ps
165+
166+
e2e-build: ## Build all service Docker images for E2E
167+
$(GRADLE) $(foreach s,$(SERVICES),:$(s):$(s):jibDockerBuild) --parallel
168+
169+
e2e-test: ## Run Phase 3 E2E tests (stack must be running)
170+
PHASE3_TESTS_ENABLED=true $(GRADLE) :phase3-integration-tests:cleanTest :phase3-integration-tests:test
171+
172+
e2e: e2e-build e2e-up e2e-test ## Build images, start stack, run E2E tests

compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/infrastructure/provider/chainalysis/ChainalysisAmlAdapter.java

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
@EnableConfigurationProperties(ChainalysisProperties.class)
2828
public class ChainalysisAmlAdapter implements AmlProvider {
2929

30+
private static final String HIGH_RATING = "HIGH";
31+
private static final String SEVERE_RATING = "SEVERE";
3032
private static final String HIGH_ALERT = "HIGH";
3133
private static final String SEVERE_ALERT = "SEVERE";
3234

@@ -110,11 +112,23 @@ private List<String> collectFlagReasons(ChainalysisTransferResponse senderResp,
110112
ChainalysisTransferResponse recipientResp,
111113
UUID senderId, UUID recipientId) {
112114
List<String> reasons = new ArrayList<>();
115+
addRatingReasons(reasons, senderResp, "sender", senderId);
116+
addRatingReasons(reasons, recipientResp, "recipient", recipientId);
113117
addAlertReasons(reasons, senderResp, "sender", senderId);
114118
addAlertReasons(reasons, recipientResp, "recipient", recipientId);
115119
return List.copyOf(reasons);
116120
}
117121

122+
private void addRatingReasons(List<String> reasons, ChainalysisTransferResponse response,
123+
String party, UUID partyId) {
124+
if (response == null || response.rating() == null) {
125+
return;
126+
}
127+
if (HIGH_RATING.equals(response.rating()) || SEVERE_RATING.equals(response.rating())) {
128+
reasons.add("%s(%s):rating/%s".formatted(party, partyId, response.rating()));
129+
}
130+
}
131+
118132
private void addAlertReasons(List<String> reasons, ChainalysisTransferResponse response,
119133
String party, UUID partyId) {
120134
if (response == null || response.alerts() == null) {
@@ -131,8 +145,6 @@ private String buildChainAnalysis(ChainalysisTransferResponse senderResp,
131145
var sb = new StringBuilder("{");
132146
sb.append("\"senderRating\":\"%s\"".formatted(ratingOf(senderResp)));
133147
sb.append(",\"recipientRating\":\"%s\"".formatted(ratingOf(recipientResp)));
134-
sb.append(",\"senderCluster\":\"%s\"".formatted(clusterOf(senderResp)));
135-
sb.append(",\"recipientCluster\":\"%s\"".formatted(clusterOf(recipientResp)));
136148
sb.append("}");
137149
return sb.toString();
138150
}
@@ -141,10 +153,6 @@ private String ratingOf(ChainalysisTransferResponse response) {
141153
return response != null && response.rating() != null ? response.rating() : "unknown";
142154
}
143155

144-
private String clusterOf(ChainalysisTransferResponse response) {
145-
return response != null && response.cluster() != null ? response.cluster() : "unknown";
146-
}
147-
148156
private String buildProviderRef(UUID senderId, UUID recipientId) {
149157
return "chainalysis:%s/%s".formatted(senderId, recipientId);
150158
}

compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/infrastructure/provider/chainalysis/ChainalysisTransferResponse.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
record ChainalysisTransferResponse(
66
String updatedAt,
77
String asset,
8-
String cluster,
98
String rating,
109
List<Alert> alerts
1110
) {
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.stablecoin.payments.compliance.infrastructure.provider.onfido;
22

3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
35
import java.util.List;
46

57
record OnfidoCheckListResponse(List<OnfidoCheck> checks) {
@@ -8,7 +10,7 @@ record OnfidoCheck(
810
String id,
911
String status,
1012
String result,
11-
String applicantId,
12-
List<String> reportIds
13+
@JsonProperty("applicant_id") String applicantId,
14+
@JsonProperty("report_ids") List<String> reportIds
1315
) {}
1416
}

compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/infrastructure/provider/worldcheck/WorldCheckSanctionsAdapter.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,7 @@ private String buildHitDetails(WorldCheckScreeningResponse senderResp,
119119
var matches = senderResp.results().stream()
120120
.filter(r -> "STRONG".equals(r.matchStrength()) || "EXACT".equals(r.matchStrength()))
121121
.map(r -> "{\"matchStrength\":\"%s\",\"lists\":%s,\"categories\":%s}"
122-
.formatted(r.matchStrength(), toJsonArray(r.matchedLists()), toJsonArray(r.categories())))
122+
.formatted(r.matchStrength(), toJsonArray(r.sources()), toJsonArray(r.categories())))
123123
.toList();
124124
details.append(String.join(",", matches));
125125
details.append("]");
@@ -132,7 +132,7 @@ private String buildHitDetails(WorldCheckScreeningResponse senderResp,
132132
var matches = recipientResp.results().stream()
133133
.filter(r -> "STRONG".equals(r.matchStrength()) || "EXACT".equals(r.matchStrength()))
134134
.map(r -> "{\"matchStrength\":\"%s\",\"lists\":%s,\"categories\":%s}"
135-
.formatted(r.matchStrength(), toJsonArray(r.matchedLists()), toJsonArray(r.categories())))
135+
.formatted(r.matchStrength(), toJsonArray(r.sources()), toJsonArray(r.categories())))
136136
.toList();
137137
details.append(String.join(",", matches));
138138
details.append("]");

compliance-travel-rule/compliance-travel-rule/src/main/java/com/stablecoin/payments/compliance/infrastructure/provider/worldcheck/WorldCheckScreeningResponse.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
record WorldCheckScreeningResponse(
66
String caseId,
77
String caseSystemId,
8-
String status,
8+
String screeningState,
99
List<MatchResult> results
1010
) {
1111
record MatchResult(
@@ -14,7 +14,7 @@ record MatchResult(
1414
String matchedTerm,
1515
String matchedNameType,
1616
String submittedTerm,
17-
List<String> matchedLists,
17+
List<String> sources,
1818
List<String> categories
1919
) {}
2020
}

compliance-travel-rule/compliance-travel-rule/src/test/java/com/stablecoin/payments/compliance/infrastructure/provider/chainalysis/ChainalysisAmlAdapterTest.java

Lines changed: 16 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,7 @@ private void stubGetTransferClean() {
6666
{
6767
"updatedAt": "2026-03-08T00:00:00Z",
6868
"asset": "USDC",
69-
"cluster": "known-exchange",
70-
"rating": "lowRisk",
69+
"rating": "LOW",
7170
"alerts": []
7271
}
7372
""")));
@@ -82,8 +81,7 @@ private void stubGetTransferFlagged() {
8281
{
8382
"updatedAt": "2026-03-08T00:00:00Z",
8483
"asset": "USDC",
85-
"cluster": "darknet-market",
86-
"rating": "highRisk",
84+
"rating": "HIGH",
8785
"alerts": [
8886
{
8987
"alertLevel": "SEVERE",
@@ -127,7 +125,7 @@ void cleanResult() {
127125
.usingRecursiveComparison()
128126
.ignoringFields("amlResultId", "checkId", "screenedAt", "chainAnalysis")
129127
.isEqualTo(expected);
130-
assertThat(result.chainAnalysis()).contains("lowRisk").contains("known-exchange");
128+
assertThat(result.chainAnalysis()).contains("LOW");
131129
assertThat(result.screenedAt()).isNotNull();
132130

133131
wireMock.verify(2, postRequestedFor(urlPathMatching("/v2/users/.+/transfers")));
@@ -149,13 +147,21 @@ void flaggedResult() {
149147
var recipientId = UUID.randomUUID();
150148
var result = adapter.analyze(senderId, recipientId);
151149

152-
assertThat(result.flagged()).isTrue();
153-
assertThat(result.flagReasons()).hasSize(4);
150+
var expected = AmlResult.builder()
151+
.flagged(true)
152+
.provider("chainalysis")
153+
.providerRef("chainalysis:%s/%s".formatted(senderId, recipientId))
154+
.build();
155+
assertThat(result)
156+
.usingRecursiveComparison()
157+
.ignoringFields("amlResultId", "checkId", "screenedAt", "chainAnalysis", "flagReasons")
158+
.isEqualTo(expected);
159+
assertThat(result.flagReasons()).hasSize(6);
154160
assertThat(result.flagReasons())
161+
.anyMatch(r -> r.contains("rating/HIGH"))
155162
.anyMatch(r -> r.contains("SEVERE") && r.contains("darknet market"))
156163
.anyMatch(r -> r.contains("HIGH") && r.contains("sanctions"));
157-
assertThat(result.provider()).isEqualTo("chainalysis");
158-
assertThat(result.chainAnalysis()).contains("highRisk").contains("darknet-market");
164+
assertThat(result.chainAnalysis()).contains("HIGH");
159165
}
160166

161167
@Test
@@ -170,8 +176,7 @@ void lowAlertsIgnored() {
170176
{
171177
"updatedAt": "2026-03-08T00:00:00Z",
172178
"asset": "USDC",
173-
"cluster": "known-exchange",
174-
"rating": "lowRisk",
179+
"rating": "LOW",
175180
"alerts": [
176181
{
177182
"alertLevel": "LOW",

0 commit comments

Comments
 (0)