Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
242 changes: 242 additions & 0 deletions docker-compose.phase2-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,242 @@
# Phase 2 Integration Test — Cross-Service Saga (S1 + S2 + S6)
#
# Usage:
# ./scripts/run-phase2-tests.sh
#
# Or manually:
# ./gradlew :payment-orchestrator:payment-orchestrator:jibDockerBuild \
# :compliance-travel-rule:compliance-travel-rule:jibDockerBuild \
# :fx-liquidity-engine:fx-liquidity-engine:jibDockerBuild
# docker compose -f docker-compose.phase2-test.yml up -d
# ./gradlew :phase2-integration-tests:test
# docker compose -f docker-compose.phase2-test.yml down -v

services:

# ── Infrastructure ─────────────────────────────────────────────

postgres:
image: postgres:18-alpine
container_name: p2t-postgres
ports:
- "5432:5432"
environment:
POSTGRES_USER: dev
POSTGRES_PASSWORD: dev
POSTGRES_DB: postgres
volumes:
- ./infra/local/postgres/init.sql:/docker-entrypoint-initdb.d/init.sql
healthcheck:
test: ["CMD-SHELL", "pg_isready -U dev"]
interval: 5s
timeout: 5s
retries: 10

timescaledb:
image: timescale/timescaledb:latest-pg17
container_name: p2t-timescaledb
ports:
- "5433:5432"
environment:
POSTGRES_USER: dev
POSTGRES_PASSWORD: dev
POSTGRES_DB: fx_rates
healthcheck:
test: ["CMD-SHELL", "pg_isready -U dev"]
interval: 5s
timeout: 5s
retries: 10

redis:
image: redis:8-alpine
container_name: p2t-redis
ports:
- "6379:6379"
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 5s
retries: 10

redpanda:
image: docker.redpanda.com/redpandadata/redpanda:latest
Comment on lines +61 to +62

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 pinning image versions for reproducibility.

Using :latest for redpanda, temporal, and wiremock images can cause flaky tests when upstream releases break compatibility. Pin to specific versions for deterministic builds.

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

In `@docker-compose.phase2-test.yml` around lines 61 - 62, The compose file pins
the Redpanda image to :latest which risks non-reproducible tests; update the
redpanda service (image: docker.redpanda.com/redpandadata/redpanda:latest) to a
specific, tested tag (e.g., docker.redpanda.com/redpandadata/redpanda:<version>)
and do the same for the temporal and wiremock services by replacing their
:latest image tags with explicit versioned tags or variables so builds/tests are
deterministic.

container_name: p2t-redpanda
command:
- redpanda
- start
- --smp=1
- --memory=512M
- --overprovisioned
- --kafka-addr=internal://0.0.0.0:29092,external://0.0.0.0:9092
- --advertise-kafka-addr=internal://redpanda:29092,external://localhost:9092
ports:
- "9092:9092"
healthcheck:
test: ["CMD-SHELL", "rpk cluster health | grep -q 'Healthy:.*true'"]
interval: 5s
timeout: 5s
retries: 20
start_period: 15s

kafka-init:
image: docker.redpanda.com/redpandadata/redpanda:latest
container_name: p2t-kafka-init
depends_on:
redpanda:
condition: service_healthy
entrypoint: ["/bin/bash", "-c"]
command: |
"
rpk --brokers redpanda:29092 topic create payment.initiated --partitions 3 --replicas 1 &&
rpk --brokers redpanda:29092 topic create compliance.result --partitions 3 --replicas 1 &&
rpk --brokers redpanda:29092 topic create fx.rate.locked --partitions 3 --replicas 1 &&
rpk --brokers redpanda:29092 topic create payment.completed --partitions 3 --replicas 1 &&
rpk --brokers redpanda:29092 topic create payment.failed --partitions 3 --replicas 1 &&
rpk --brokers redpanda:29092 topic create audit.event --partitions 3 --replicas 1 &&
rpk --brokers redpanda:29092 topic create merchant.activated --partitions 3 --replicas 1 &&
echo 'All topics created'
"
restart: on-failure

temporal:
image: temporalio/auto-setup:latest
container_name: p2t-temporal
ports:
- "7233:7233"
environment:
DB: postgres12
DB_PORT: 5432
POSTGRES_USER: dev
POSTGRES_PWD: dev
POSTGRES_SEEDS: postgres
depends_on:
postgres:
condition: service_healthy
healthcheck:
test: ["CMD-SHELL", "temporal operator cluster health 2>/dev/null | grep -q 'OK' || exit 0"]
interval: 15s
timeout: 10s
retries: 15
Comment on lines +115 to +119

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

Temporal healthcheck always succeeds.

The || exit 0 at the end means this healthcheck never fails, defeating its purpose. Remove it so Docker can detect when Temporal is actually unhealthy.

🐛 Proposed fix
     healthcheck:
-      test: ["CMD-SHELL", "temporal operator cluster health 2>/dev/null | grep -q 'OK' || exit 0"]
+      test: ["CMD-SHELL", "temporal operator cluster health 2>&1 | grep -q 'SERVING'"]
       interval: 15s
       timeout: 10s
       retries: 15
+      start_period: 30s
📝 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.

Suggested change
healthcheck:
test: ["CMD-SHELL", "temporal operator cluster health 2>/dev/null | grep -q 'OK' || exit 0"]
interval: 15s
timeout: 10s
retries: 15
healthcheck:
test: ["CMD-SHELL", "temporal operator cluster health 2>&1 | grep -q 'SERVING'"]
interval: 15s
timeout: 10s
retries: 15
start_period: 30s
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@docker-compose.phase2-test.yml` around lines 115 - 119, The healthcheck
currently always succeeds because the test command in the healthcheck block (the
line starting with test: ["CMD-SHELL", "temporal operator cluster health
2>/dev/null | grep -q 'OK' || exit 0"]) appends "|| exit 0", so remove the "||
exit 0" suffix from that test string so the command returns a non‑zero exit code
when Temporal is unhealthy; leave the rest of the healthcheck
(interval/timeout/retries) unchanged.


temporal-ui:
image: temporalio/ui:latest
container_name: p2t-temporal-ui
ports:
- "8233:8080"
environment:
TEMPORAL_ADDRESS: temporal:7233
depends_on:
- temporal

wiremock:
image: wiremock/wiremock:latest
container_name: p2t-wiremock
ports:
- "4444:8080"
volumes:
- ./infra/local/wiremock/mappings:/home/wiremock/mappings
- ./infra/local/wiremock/__files:/home/wiremock/__files
command: --verbose --global-response-templating

# ── Application Services ───────────────────────────────────────

compliance-travel-rule:
image: stablebridge/compliance-travel-rule:latest
container_name: p2t-compliance
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
redpanda:
condition: service_healthy
wiremock:
condition: service_started
environment:
JAVA_TOOL_OPTIONS: "-Xms256m -Xmx512m"
DB_URL: "jdbc:postgresql://postgres:5432/s2_compliance"
DB_USER: dev
DB_PASS: dev
KAFKA_BROKERS: "redpanda:29092"
SPRING_DATA_REDIS_HOST: redis
SPRING_DATA_REDIS_PORT: "6379"
APP_SECURITY_ENABLED: "false"
# Use WorldCheck adapter → WireMock for sanctions testing
APP_SANCTIONS_PROVIDER: world-check
APP_SANCTIONS_WORLDCHECK_BASEURL: "http://wiremock:8080/v2"
APP_SANCTIONS_WORLDCHECK_APIKEY: "test-api-key"
APP_SANCTIONS_WORLDCHECK_TIMEOUTSECONDS: "30"
ports:
- "8083:8083"
healthcheck:
test: ["CMD-SHELL", "wget -q --spider http://localhost:8083/compliance/actuator/health || exit 1"]
interval: 10s
timeout: 5s
retries: 30
start_period: 30s

fx-liquidity-engine:
image: stablebridge/fx-liquidity-engine:latest
container_name: p2t-fx-engine
depends_on:
timescaledb:
condition: service_healthy
redis:
condition: service_healthy
redpanda:
condition: service_healthy
environment:
JAVA_TOOL_OPTIONS: "-Xms256m -Xmx512m"
DB_URL: "jdbc:postgresql://timescaledb:5432/fx_rates"
DB_USER: dev
DB_PASS: dev
KAFKA_BROKERS: "redpanda:29092"
REDIS_HOST: redis
REDIS_PORT: "6379"
APP_SECURITY_ENABLED: "false"
ports:
- "8084:8084"
healthcheck:
test: ["CMD-SHELL", "wget -q --spider http://localhost:8084/fx/actuator/health || exit 1"]
interval: 10s
timeout: 5s
retries: 30
start_period: 30s

payment-orchestrator:
image: stablebridge/payment-orchestrator:latest
container_name: p2t-orchestrator
depends_on:
postgres:
condition: service_healthy
redis:
condition: service_healthy
redpanda:
condition: service_healthy
temporal:
condition: service_healthy
compliance-travel-rule:
condition: service_healthy
fx-liquidity-engine:
condition: service_healthy
environment:
JAVA_TOOL_OPTIONS: "-Xms256m -Xmx512m"
DB_URL: "jdbc:postgresql://postgres:5432/s1_payment_orchestrator"
DB_USER: dev
DB_PASS: dev
KAFKA_BROKERS: "redpanda:29092"
SPRING_DATA_REDIS_HOST: redis
SPRING_DATA_REDIS_PORT: "6379"
TEMPORAL_ADDRESS: "temporal:7233"
TEMPORAL_NAMESPACE: default
COMPLIANCE_SERVICE_URL: "http://compliance-travel-rule:8083/compliance"
FX_SERVICE_URL: "http://fx-liquidity-engine:8084/fx"
APP_SECURITY_ENABLED: "false"
ports:
- "8082:8082"
healthcheck:
test: ["CMD-SHELL", "wget -q --spider http://localhost:8082/orchestrator/actuator/health || exit 1"]
interval: 10s
timeout: 5s
retries: 30
start_period: 30s
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@

import com.stablecoin.payments.fx.domain.event.FxRateExpired;
import com.stablecoin.payments.fx.domain.model.FxRateLock;
import com.stablecoin.payments.fx.domain.port.EventPublisher;
import com.stablecoin.payments.fx.domain.port.FxRateLockRepository;
import com.stablecoin.payments.fx.domain.port.LiquidityPoolRepository;
import com.stablecoin.payments.fx.domain.service.LiquidityService;
import com.stablecoin.payments.fx.domain.service.LockService;
import com.stablecoin.payments.fx.infrastructure.messaging.OutboxEventPublisher;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
Expand All @@ -26,7 +26,7 @@ public class LockExpiryJob {
private final LiquidityPoolRepository poolRepository;
private final LockService lockService;
private final LiquidityService liquidityService;
private final EventPublisher<FxRateExpired> eventPublisher;
private final OutboxEventPublisher eventPublisher;

@Scheduled(fixedDelayString = "${app.fx.lock-expiry.interval-ms:5000}")
@Transactional
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
import com.stablecoin.payments.fx.domain.event.FxRateExpired;
import com.stablecoin.payments.fx.domain.model.FxRateLock;
import com.stablecoin.payments.fx.domain.model.FxRateLockStatus;
import com.stablecoin.payments.fx.domain.port.EventPublisher;
import com.stablecoin.payments.fx.domain.port.FxRateLockRepository;
import com.stablecoin.payments.fx.domain.port.LiquidityPoolRepository;
import com.stablecoin.payments.fx.domain.service.LiquidityService;
import com.stablecoin.payments.fx.domain.service.LockService;
import com.stablecoin.payments.fx.infrastructure.messaging.OutboxEventPublisher;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
Expand Down Expand Up @@ -46,7 +46,7 @@ class LockExpiryJobTest {
private LiquidityService liquidityService;

@Mock
private EventPublisher<FxRateExpired> eventPublisher;
private OutboxEventPublisher eventPublisher;

@InjectMocks
private LockExpiryJob lockExpiryJob;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.stablecoin.payments.orchestrator.infrastructure.config;

import feign.RequestInterceptor;
import feign.RequestTemplate;
import org.springframework.stereotype.Component;

import java.util.UUID;

/**
* Feign interceptor that adds an Idempotency-Key header to all outgoing requests.
* Required by S2 Compliance Service which rejects POST requests without this header.
*/
Comment on lines +9 to +12

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

Interceptor applies to all HTTP methods, not just POST/PUT.

The comment notes S2 requires this for POST, but the interceptor injects the header on GET, DELETE, etc. as well. Low impact but adds unnecessary overhead and noise in logs/traces.

✂️ Limit to state-changing methods
 `@Override`
 public void apply(RequestTemplate template) {
+    if (template.method() == null || 
+        (!template.method().equals("POST") && !template.method().equals("PUT"))) {
+        return;
+    }
     if (!template.headers().containsKey(IDEMPOTENCY_KEY_HEADER)) {
         template.header(IDEMPOTENCY_KEY_HEADER, UUID.randomUUID().toString());
     }
 }

Also applies to: 19-22

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

In
`@payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/infrastructure/config/FeignIdempotencyInterceptor.java`
around lines 9 - 12, The FeignIdempotencyInterceptor currently adds an
Idempotency-Key to every request; update the interceptor
(FeignIdempotencyInterceptor) so its apply(RequestTemplate template) only
injects the Idempotency-Key for state-changing HTTP methods (e.g., POST, PUT,
PATCH — or the specific set required by S2) by checking template.method() (or
equivalent) before adding the header, leaving GET/HEAD and other non-mutating
methods unchanged; keep the header generation logic and header name the same but
guard it behind this method check to avoid adding the header to safe/idempotent
requests.

@Component
public class FeignIdempotencyInterceptor implements RequestInterceptor {

private static final String IDEMPOTENCY_KEY_HEADER = "Idempotency-Key";

@Override
public void apply(RequestTemplate template) {
if (!template.headers().containsKey(IDEMPOTENCY_KEY_HEADER)) {

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 | 🟡 Minor

Header lookup may be case-sensitive.

HTTP headers are case-insensitive per RFC 7230, but template.headers() returns a standard Map whose key comparison depends on Feign's internal implementation. A caller passing "idempotency-key" (lowercase) could result in a duplicate header.

🔧 Safer case-insensitive check
-        if (!template.headers().containsKey(IDEMPOTENCY_KEY_HEADER)) {
+        boolean hasKey = template.headers().keySet().stream()
+                .anyMatch(h -> h.equalsIgnoreCase(IDEMPOTENCY_KEY_HEADER));
+        if (!hasKey) {
📝 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.

Suggested change
if (!template.headers().containsKey(IDEMPOTENCY_KEY_HEADER)) {
boolean hasKey = template.headers().keySet().stream()
.anyMatch(h -> h.equalsIgnoreCase(IDEMPOTENCY_KEY_HEADER));
if (!hasKey) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/infrastructure/config/FeignIdempotencyInterceptor.java`
at line 20, The header existence check in FeignIdempotencyInterceptor uses
template.headers().containsKey(IDEMPOTENCY_KEY_HEADER) which can be
case-sensitive and allow duplicates; change the check to perform a
case-insensitive lookup over template.headers().keySet() (or normalize keys)
before adding IDEMPOTENCY_KEY_HEADER so any existing header like
"idempotency-key" or "IDEMPOTENCY-KEY" is detected and avoided; update the logic
around the add operation in FeignIdempotencyInterceptor to use this
case-insensitive check and ensure you still add the header only when no matching
key is present.

template.header(IDEMPOTENCY_KEY_HEADER, UUID.randomUUID().toString());
}
}
Comment on lines +18 to +23

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

Idempotency breaks on Feign retries.

When Feign retries a failed request (timeout, 5xx), the RequestTemplate is rebuilt and apply() is called again. If the caller didn't supply an idempotency key, each retry gets a fresh UUID—defeating the entire purpose of idempotency.

Consider propagating the idempotency key from request context (e.g., MDC, ThreadLocal, or a request-scoped bean) so retries reuse the same key.

🛠️ Sketch of context-aware approach
+import org.slf4j.MDC;
+
 `@Override`
 public void apply(RequestTemplate template) {
     if (!template.headers().containsKey(IDEMPOTENCY_KEY_HEADER)) {
-        template.header(IDEMPOTENCY_KEY_HEADER, UUID.randomUUID().toString());
+        String contextKey = MDC.get("idempotencyKey");
+        String key = (contextKey != null) ? contextKey : UUID.randomUUID().toString();
+        template.header(IDEMPOTENCY_KEY_HEADER, key);
     }
 }

Upstream controller/entry point sets MDC.put("idempotencyKey", incomingKey) at request start.

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

In
`@payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/infrastructure/config/FeignIdempotencyInterceptor.java`
around lines 18 - 23, The FeignIdempotencyInterceptor.apply currently generates
a new UUID per retry; instead, read an existing idempotency value from a
request-scoped context before creating one: check RequestTemplate.headers() for
IDEMPOTENCY_KEY_HEADER, if missing try to fetch a contextual key (e.g.,
MDC.get("idempotencyKey") or a ThreadLocal/request-scoped bean) and if found set
that header, otherwise generate a UUID, set the header and also populate the
context (e.g., MDC.put("idempotencyKey", generatedId)) so subsequent retries
calling FeignIdempotencyInterceptor.apply() reuse the same idempotency key;
update references in FeignIdempotencyInterceptor.apply and ensure the context
key name matches upstream controllers.

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.stablecoin.payments.orchestrator.infrastructure.config;

import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.common.serialization.StringSerializer;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.core.DefaultKafkaProducerFactory;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.kafka.core.ProducerFactory;
import org.springframework.kafka.support.serializer.JsonSerializer;

import java.util.Map;

@Configuration
public class KafkaProducerConfig {

@Bean
@ConditionalOnMissingBean
public ProducerFactory<String, Object> producerFactory(
@Value("${spring.kafka.bootstrap-servers}") String bootstrapServers) {
return new DefaultKafkaProducerFactory<>(Map.of(
ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers,
ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class,
ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class));
}
Comment on lines +23 to +27

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 adding producer reliability settings.

For a payment orchestration system, missing acks, retries, and enable.idempotence configurations can lead to message loss under failure scenarios. These are especially important for financial workflows.

♻️ Proposed enhancement
         return new DefaultKafkaProducerFactory<>(Map.of(
                 ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers,
                 ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class,
-                ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class));
+                ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class,
+                ProducerConfig.ACKS_CONFIG, "all",
+                ProducerConfig.RETRIES_CONFIG, 3,
+                ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, true));
📝 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.

Suggested change
return new DefaultKafkaProducerFactory<>(Map.of(
ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers,
ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class,
ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class));
}
return new DefaultKafkaProducerFactory<>(Map.of(
ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers,
ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class,
ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, JsonSerializer.class,
ProducerConfig.ACKS_CONFIG, "all",
ProducerConfig.RETRIES_CONFIG, 3,
ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, true));
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@payment-orchestrator/payment-orchestrator/src/main/java/com/stablecoin/payments/orchestrator/infrastructure/config/KafkaProducerConfig.java`
around lines 23 - 27, The producer config in KafkaProducerConfig (the method
returning DefaultKafkaProducerFactory) lacks reliability settings; update the
Map passed to new DefaultKafkaProducerFactory in that method to include
ProducerConfig.ACKS_CONFIG set to "all", ProducerConfig.RETRIES_CONFIG set to a
suitable value (e.g., Integer.MAX_VALUE or a specific retry count), and
ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG set to true, and consider adding
ProducerConfig.MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION to 1 for ordering; ensure
you add these entries to the Map.of(...) used in the producerFactory method so
the producer is configured for durable, idempotent deliveries.


@Bean
@ConditionalOnMissingBean
public KafkaTemplate<String, Object> kafkaTemplate(
ProducerFactory<String, Object> producerFactory) {
return new KafkaTemplate<>(producerFactory);
}
}
45 changes: 45 additions & 0 deletions phase2-integration-tests/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
plugins {
java
}

val temporalVersion: String by project
val wiremockVersion: String by project

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 | 🟡 Minor

Unused property declaration.

wiremockVersion is declared but never referenced in dependencies. Remove it or add the WireMock dependency if needed.

🧹 Proposed fix
 val temporalVersion: String by project
-val wiremockVersion: String by project
📝 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.

Suggested change
val wiremockVersion: String by project
val temporalVersion: String by project
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@phase2-integration-tests/build.gradle.kts` at line 6, The property
wiremockVersion is declared but unused; either remove the val wiremockVersion:
String by project declaration or use it in the dependencies block to add
WireMock (e.g., add a dependency like
"com.github.tomakehurst:wiremock-jre8:$wiremockVersion" inside the dependencies
{}), and ensure any test sourceSets or testImplementation entries reference the
variable so it is no longer unused.


dependencies {
// API modules — DTOs for request/response deserialization
testImplementation(project(":payment-orchestrator:payment-orchestrator-api"))
testImplementation(project(":compliance-travel-rule:compliance-travel-rule-api"))
testImplementation(project(":fx-liquidity-engine:fx-liquidity-engine-api"))

// Temporal SDK — query workflow results
testImplementation("io.temporal:temporal-sdk:$temporalVersion")

// Jackson 3 — JSON parsing (JSR-310 built into core in Jackson 3)
testImplementation("tools.jackson.core:jackson-databind")
Comment on lines +17 to +18

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 | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check what Jackson version is used elsewhere in the project
rg -n "jackson" --type=kotlin -g "*.gradle.kts" | head -20

# Check version catalog for Jackson
fd "libs.versions.toml" --exec cat {} 2>/dev/null | grep -i jackson

Repository: Puneethkumarck/stablebridge-platform

Length of output: 1124


🏁 Script executed:

#!/bin/bash
# Find the full context of the Jackson dependency in question
cat -n phase2-integration-tests/build.gradle.kts | sed -n '1,30p'

# Search for version catalog files in the project root
fd "versions.toml|gradle.toml|settings.gradle.kts" --max-depth 3

# Check if Jackson 3.x is used anywhere else
rg -n "tools\.jackson" --type=kotlin -g "*.gradle.kts"

# Look for any explicit version specifications for the problematic dependency
rg -n "jackson-databind" --type=kotlin

Repository: Puneethkumarck/stablebridge-platform

Length of output: 1609


🏁 Script executed:

#!/bin/bash
# Check for dependency management or constraints
cat -n phase2-integration-tests/build.gradle.kts | sed -n '1,50p'

# Check if there's a root gradle file with dependencyManagement
fd "build.gradle.kts" --max-depth 1 -x head -50 {}

# Search for any dependency constraints or exclusions
rg -n "exclude|constraint|dependencyManagement" --type=kotlin phase2-integration-tests/

Repository: Puneethkumarck/stablebridge-platform

Length of output: 3487


Remove Jackson 3.x or exclude Jackson 2.x transitively — classpath conflict detected.

The explicit tools.jackson.core:jackson-databind (Jackson 3.x) conflicts with Jackson 2.x (com.fasterxml.jackson.core) pulled transitively by the imported API modules (payment-orchestrator-api, compliance-travel-rule-api, fx-liquidity-engine-api). Spring Boot's BOM also manages Jackson 2.x, making the 3.x dependency incompatible on the same classpath. Additionally, the dependency lacks a version specification; follow the project convention and parameterize it via gradle.properties or align with Spring Boot's managed version.

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

In `@phase2-integration-tests/build.gradle.kts` around lines 17 - 18, The test
dependency declaration testImplementation("tools.jackson.core:jackson-databind")
brings Jackson 3.x and conflicts with Jackson 2.x on the classpath; remove this
explicit Jackson 3.x entry or replace it with the project-managed Jackson 2.x
artifact/version. Either delete the tools.jackson.core line and rely on Spring
Boot's BOM-managed com.fasterxml.jackson.core, or change to the
com.fasterxml.jackson.core:jackson-databind coordinate parameterized from
gradle.properties (e.g. use a jacksonVersion property) so the version is
consistent with Spring Boot; if any imported modules (payment-orchestrator-api,
compliance-travel-rule-api, fx-liquidity-engine-api) pull in Jackson 2.x
transitively and you must keep the current declaration, add an exclusion on
those module dependencies for com.fasterxml.jackson.core to avoid mixed 2.x/3.x
classes.


// Kafka — event verification
testImplementation("org.apache.kafka:kafka-clients")

// Test framework
testImplementation("org.junit.jupiter:junit-jupiter")
testImplementation("org.assertj:assertj-core")
testImplementation("org.awaitility:awaitility")

// SLF4J for logging in tests
testImplementation("org.slf4j:slf4j-api")
testRuntimeOnly("ch.qos.logback:logback-classic")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}

tasks.test {
useJUnitPlatform {
excludeTags("load")
}
}

tasks.register<Test>("loadTest") {
useJUnitPlatform {
includeTags("load")
}
maxHeapSize = "1g"
}
Loading