SOL-38: add end-to-end integration tests for both stream adapters#104
SOL-38: add end-to-end integration tests for both stream adapters#104Puneethkumarck merged 3 commits intomainfrom
Conversation
Introduce a canonical logical block fixture that encodes the same three transactions (large transfer, failed tx, memo tx) as both protobuf SubscribeUpdate messages and a WebSocket blockNotification JSON frame, along with the expected domain projections. AdapterParityTest drives both TransactionParser and BlockNotificationParser against this fixture and proves they produce identical SolanaTransaction and Account outputs. Full-pipeline integration tests boot IndexerApplication against a shared Testcontainers Postgres and verify stream -> batch -> DB -> HTTP round-trips: - GrpcEndToEndIntegrationTest uses an in-process Geyser service feeding YellowstoneTransactionStream. - WebSocketEndToEndIntegrationTest injects a scripted WebSocketFactory that drives the canonical JSON frame through WebSocketTransactionStream. Both assert that successful transactions, failed transactions, memos, large transfers, and fee payer accounts land in their respective tables and are queryable via the REST API. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
Warning Rate limit exceeded
Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 2 minutes and 2 seconds. ⌛ 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: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (6)
📝 WalkthroughWalkthroughAdds end-to-end integration tests (gRPC and WebSocket), adapter-parity tests, shared test fixtures/utilities, and a small build change (Jackson added to testFixtures). Tests exercise full pipeline: stream → indexer → Postgres → REST API and include deterministic fixtures and test startup helpers. Changes
Sequence Diagram(s)sequenceDiagram
actor Test as TestRunner
participant Stream as (gRPC / WebSocket) Mock
participant Indexer as IndexerApplication
participant DB as Postgres
participant API as Indexer REST API
Test->>Stream: send fixture updates
Stream->>Indexer: deliver transaction updates
Indexer->>DB: write transactions / failed_transactions / memos / large_transfers / accounts
Indexer->>API: start HTTP server (ephemeral port)
Test->>API: HTTP GET requests
API->>DB: query persisted data
DB-->>API: return rows
API-->>Test: return JSON responses
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@prism/src/integrationTest/java/com/stablebridge/prism/e2e/GrpcEndToEndIntegrationTest.java`:
- Around line 69-73: The test invokes truncateTables against the shared Postgres
fixture in startPipelineAndDispatchBlock (calling
SharedPostgresContainer.writePool()/readPool()), which can race with other
suites using the same DB; change this by either isolating this suite to its own
schema/database in SharedPostgresContainer (so startPipelineAndDispatchBlock
truncates only its schema) or by applying a cross-class lock/non-parallel
execution annotation (e.g., add a common `@ResourceLock` or disable
parallelization) to this class and the WebSocket suite so truncateTables cannot
run concurrently; update references in startPipelineAndDispatchBlock and any
other tests that call truncateTables to use the chosen approach.
In
`@prism/src/integrationTest/java/com/stablebridge/prism/infrastructure/websocket/WebSocketEndToEndIntegrationTest.java`:
- Around line 121-405: The tests duplicate assertion logic that also exists in
GrpcEndToEndIntegrationTest; extract the shared assertion matrix into reusable
helpers on testFixtures and call them from WebSocketEndToEndIntegrationTest.
Create helper methods such as
assertTransactionListPage(Page<TransactionResponse> actual),
assertTransactionBySignature(TransactionResponse actual, String
expectedSignature), assertSlotTransactions(List<TransactionResponse> actual,
long slot), assertTransfersPage(Page<TransferResponse> actual),
assertMemosPage(Page<MemoResponse> actual),
assertAccountResponse(AccountResponse actual, String pubkey, long lamports), and
assertFailedTransactionRows(List<FailedTransactionRow> actual) that encapsulate
the recursiveComparison settings (ignoring Instant.class, id regexes, collection
order where used) and expected builders used in methods like
shouldExposeSuccessfulTransactionsViaListEndpoint,
shouldReturnLargeTransferTransactionBySignature,
shouldReturnBothSuccessfulTransactionsForSlot,
shouldReturnLargeTransferViaTransfersEndpoint, shouldReturnMemoViaMemosEndpoint,
shouldReturnLargeTransferFeePayerAccount and shouldPersistFailedTransactionRow;
then replace the inline assertions in those test methods with calls to the new
testFixtures helpers so both WebSocketEndToEndIntegrationTest and
GrpcEndToEndIntegrationTest reuse the same contract checks.
In
`@prism/src/testFixtures/java/com/stablebridge/prism/fixtures/E2eBlockFixture.java`:
- Around line 31-54: The public mutable byte[] fixtures
(LARGE_TRANSFER_SIGNATURE_BYTES, FAILED_TX_SIGNATURE_BYTES,
MEMO_TX_SIGNATURE_BYTES, LARGE_TRANSFER_FEE_PAYER_BYTES,
FAILED_TX_FEE_PAYER_BYTES, MEMO_TX_FEE_PAYER_BYTES,
FAILED_TX_OTHER_PUBKEY_BYTES, SENDER_PUBKEY_BYTES, RECEIVER_PUBKEY_BYTES) must
be made private static final and replaced with public accessor methods that
return clones (e.g., getLargeTransferSignatureBytes() returns
LARGE_TRANSFER_SIGNATURE_BYTES.clone()); keep the existing Base58 constants
computed from the private arrays (or compute them once from the private arrays)
so the Base58 values remain stable, and update any callers (like grpcUpdates())
to use the new getters to avoid exposing internal mutable arrays.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: c275e489-fd60-498b-bf03-14755658090e
📒 Files selected for processing (6)
prism/build.gradle.ktsprism/src/integrationTest/java/com/stablebridge/prism/e2e/GrpcEndToEndIntegrationTest.javaprism/src/integrationTest/java/com/stablebridge/prism/infrastructure/websocket/WebSocketEndToEndIntegrationTest.javaprism/src/test/java/com/stablebridge/prism/e2e/AdapterParityTest.javaprism/src/testFixtures/java/com/stablebridge/prism/application/TestIndexerApplication.javaprism/src/testFixtures/java/com/stablebridge/prism/fixtures/E2eBlockFixture.java
prism/src/integrationTest/java/com/stablebridge/prism/e2e/GrpcEndToEndIntegrationTest.java
Outdated
Show resolved
Hide resolved
...t/java/com/stablebridge/prism/infrastructure/websocket/WebSocketEndToEndIntegrationTest.java
Outdated
Show resolved
Hide resolved
| public static final byte[] LARGE_TRANSFER_SIGNATURE_BYTES = sigBytes((byte) 0xA1); | ||
| public static final byte[] FAILED_TX_SIGNATURE_BYTES = sigBytes((byte) 0xB2); | ||
| public static final byte[] MEMO_TX_SIGNATURE_BYTES = sigBytes((byte) 0xC3); | ||
|
|
||
| public static final String LARGE_TRANSFER_SIGNATURE_BASE58 = | ||
| Base58.encode(LARGE_TRANSFER_SIGNATURE_BYTES); | ||
| public static final String FAILED_TX_SIGNATURE_BASE58 = Base58.encode(FAILED_TX_SIGNATURE_BYTES); | ||
| public static final String MEMO_TX_SIGNATURE_BASE58 = Base58.encode(MEMO_TX_SIGNATURE_BYTES); | ||
|
|
||
| public static final byte[] LARGE_TRANSFER_FEE_PAYER_BYTES = pubkeyBytes((byte) 0x41); | ||
| public static final byte[] FAILED_TX_FEE_PAYER_BYTES = pubkeyBytes((byte) 0x52); | ||
| public static final byte[] MEMO_TX_FEE_PAYER_BYTES = pubkeyBytes((byte) 0x63); | ||
| public static final byte[] FAILED_TX_OTHER_PUBKEY_BYTES = pubkeyBytes((byte) 0x74); | ||
| public static final byte[] SENDER_PUBKEY_BYTES = pubkeyBytes((byte) 0x85); | ||
| public static final byte[] RECEIVER_PUBKEY_BYTES = pubkeyBytes((byte) 0x96); | ||
|
|
||
| public static final String LARGE_TRANSFER_FEE_PAYER_BASE58 = | ||
| Base58.encode(LARGE_TRANSFER_FEE_PAYER_BYTES); | ||
| public static final String FAILED_TX_FEE_PAYER_BASE58 = Base58.encode(FAILED_TX_FEE_PAYER_BYTES); | ||
| public static final String MEMO_TX_FEE_PAYER_BASE58 = Base58.encode(MEMO_TX_FEE_PAYER_BYTES); | ||
| public static final String FAILED_TX_OTHER_PUBKEY_BASE58 = | ||
| Base58.encode(FAILED_TX_OTHER_PUBKEY_BYTES); | ||
| public static final String SENDER_PUBKEY_BASE58 = Base58.encode(SENDER_PUBKEY_BYTES); | ||
| public static final String RECEIVER_PUBKEY_BASE58 = Base58.encode(RECEIVER_PUBKEY_BYTES); |
There was a problem hiding this comment.
Make the canonical byte fixtures immutable.
These public static final byte[] fields are still mutable. Any test that writes to one of them can make grpcUpdates() emit signatures/pubkeys that no longer match the precomputed *_BASE58 constants, which is a subtle source of cross-test flakiness. Prefer private arrays plus clone-returning accessors if raw bytes need to stay externally visible.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@prism/src/testFixtures/java/com/stablebridge/prism/fixtures/E2eBlockFixture.java`
around lines 31 - 54, The public mutable byte[] fixtures
(LARGE_TRANSFER_SIGNATURE_BYTES, FAILED_TX_SIGNATURE_BYTES,
MEMO_TX_SIGNATURE_BYTES, LARGE_TRANSFER_FEE_PAYER_BYTES,
FAILED_TX_FEE_PAYER_BYTES, MEMO_TX_FEE_PAYER_BYTES,
FAILED_TX_OTHER_PUBKEY_BYTES, SENDER_PUBKEY_BYTES, RECEIVER_PUBKEY_BYTES) must
be made private static final and replaced with public accessor methods that
return clones (e.g., getLargeTransferSignatureBytes() returns
LARGE_TRANSFER_SIGNATURE_BYTES.clone()); keep the existing Base58 constants
computed from the private arrays (or compute them once from the private arrays)
so the Base58 values remain stable, and update any callers (like grpcUpdates())
to use the new getters to avoid exposing internal mutable arrays.
- Serialize both E2E suites on SharedPostgresContainer via @ResourceLock so future parallel execution cannot race their truncateTables calls. - Make E2eBlockFixture byte[] fixtures private to prevent mutation drift from the precomputed Base58 constants. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@prism/src/integrationTest/java/com/stablebridge/prism/e2e/GrpcEndToEndIntegrationTest.java`:
- Around line 101-103: The test currently only awaits
geyserService.responseObserver() and then injects updates, so it never verifies
the adapter's subscribe-handshake; modify the test to capture and await the
initial SubscribeRequest from the client before sending updates: after awaiting
geyserService.responseObserver(), wait for the TestGeyserService to record a
client request (the SubscribeRequest) — e.g., poll or await a
geyserService.receivedRequests()/nextReceivedRequest() and assert it is a
SubscribeRequest — only then call E2eBlockFixture.grpcUpdates() and
geyserService.send(...); ensure this pattern is applied to the other occurrences
referenced (around lines 437-468) so the test verifies
YellowstoneTransactionStream actually calls requestObserver.onNext(...) during
the handshake.
In
`@prism/src/integrationTest/java/com/stablebridge/prism/infrastructure/websocket/WebSocketEndToEndIntegrationTest.java`:
- Around line 73-80: The test currently bypasses the real send by directly
invoking listener.onText, so add an explicit assertion that the adapter actually
calls stubSocket.sendText(BLOCK_SUBSCRIBE_PAYLOAD, true) (use Mockito.verify or
equivalent) before you trigger the incoming text; locate the mock WebSocket
stubSocket and the BLOCK_SUBSCRIBE_PAYLOAD constant used in the WebSocketFactory
lambda and assert sendText was invoked (or verify invocation count) to ensure
WebSocketTransactionStream actually performs the subscribe handshake rather than
merely handling onText.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: b49d3309-279f-49fb-855e-afacdd4df87a
📒 Files selected for processing (4)
prism/src/integrationTest/java/com/stablebridge/prism/e2e/GrpcEndToEndIntegrationTest.javaprism/src/integrationTest/java/com/stablebridge/prism/infrastructure/websocket/WebSocketEndToEndIntegrationTest.javaprism/src/testFixtures/java/com/stablebridge/prism/fixtures/E2eBlockFixture.javaprism/src/testFixtures/java/com/stablebridge/prism/testutil/SharedPostgresContainer.java
| await().atMost(10, SECONDS).until(() -> geyserService.responseObserver() != null); | ||
| E2eBlockFixture.grpcUpdates().forEach(update -> | ||
| geyserService.send(SubscribeUpdate.newBuilder().setTransaction(update).build())); |
There was a problem hiding this comment.
Capture and await the initial SubscribeRequest.
Right now the test only waits for responseObserver != null, and TestGeyserService discards every client request. The suite therefore still passes if YellowstoneTransactionStream stops calling requestObserver.onNext(...), because updates are injected directly into the response observer. That misses the adapter's subscribe-handshake path.
Minimal fix
- await().atMost(10, SECONDS).until(() -> geyserService.responseObserver() != null);
+ await().atMost(10, SECONDS).until(() -> geyserService.firstRequest() != null);
E2eBlockFixture.grpcUpdates().forEach(update ->
geyserService.send(SubscribeUpdate.newBuilder().setTransaction(update).build()));
...
private static final class TestGeyserService extends GeyserGrpc.GeyserImplBase {
private volatile StreamObserver<SubscribeUpdate> responseObserver;
+ private volatile SubscribeRequest firstRequest;
`@Override`
public StreamObserver<SubscribeRequest> subscribe(StreamObserver<SubscribeUpdate> observer) {
this.responseObserver = observer;
return new StreamObserver<>() {
`@Override`
- public void onNext(SubscribeRequest value) {}
+ public void onNext(SubscribeRequest value) {
+ firstRequest = value;
+ }
`@Override`
public void onError(Throwable t) {
responseObserver = null;
}
@@
StreamObserver<SubscribeUpdate> responseObserver() {
return responseObserver;
}
+
+ SubscribeRequest firstRequest() {
+ return firstRequest;
+ }Also applies to: 437-468
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@prism/src/integrationTest/java/com/stablebridge/prism/e2e/GrpcEndToEndIntegrationTest.java`
around lines 101 - 103, The test currently only awaits
geyserService.responseObserver() and then injects updates, so it never verifies
the adapter's subscribe-handshake; modify the test to capture and await the
initial SubscribeRequest from the client before sending updates: after awaiting
geyserService.responseObserver(), wait for the TestGeyserService to record a
client request (the SubscribeRequest) — e.g., poll or await a
geyserService.receivedRequests()/nextReceivedRequest() and assert it is a
SubscribeRequest — only then call E2eBlockFixture.grpcUpdates() and
geyserService.send(...); ensure this pattern is applied to the other occurrences
referenced (around lines 437-468) so the test verifies
YellowstoneTransactionStream actually calls requestObserver.onNext(...) during
the handshake.
| stubSocket = mock(WebSocket.class); | ||
| given(stubSocket.sendText(BLOCK_SUBSCRIBE_PAYLOAD, true)) | ||
| .willReturn(CompletableFuture.completedFuture(stubSocket)); | ||
| capturedListeners = new CopyOnWriteArrayList<>(); | ||
| WebSocketFactory factory = (uri, listener) -> { | ||
| capturedListeners.add(listener); | ||
| return CompletableFuture.completedFuture(stubSocket); | ||
| }; |
There was a problem hiding this comment.
Assert the adapter actually sends the block-subscribe frame.
This setup pushes the fixture straight into listener.onText(...), so the suite still passes if WebSocketTransactionStream regresses and never calls sendText(BLOCK_SUBSCRIBE_PAYLOAD, true). That misses a production-breaking subscribe-handshake bug in the adapter.
Minimal fix
await().atMost(10, SECONDS).until(() -> !capturedListeners.isEmpty());
+ await().atMost(10, SECONDS).untilAsserted(
+ () -> org.mockito.BDDMockito.then(stubSocket)
+ .should()
+ .sendText(BLOCK_SUBSCRIBE_PAYLOAD, true));
var listener = capturedListeners.get(0);
listener.onText(stubSocket, E2eBlockFixture.webSocketFrame(), true);Also applies to: 100-103
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In
`@prism/src/integrationTest/java/com/stablebridge/prism/infrastructure/websocket/WebSocketEndToEndIntegrationTest.java`
around lines 73 - 80, The test currently bypasses the real send by directly
invoking listener.onText, so add an explicit assertion that the adapter actually
calls stubSocket.sendText(BLOCK_SUBSCRIBE_PAYLOAD, true) (use Mockito.verify or
equivalent) before you trigger the incoming text; locate the mock WebSocket
stubSocket and the BLOCK_SUBSCRIBE_PAYLOAD constant used in the WebSocketFactory
lambda and assert sendText was invoked (or verify invocation count) to ensure
WebSocketTransactionStream actually performs the subscribe handshake rather than
merely handling onText.
Extract an E2ePipelineAssertions helper in testFixtures so both adapter pipelines delegate to a single set of HTTP + JDBC verifications. Route-contract changes now live in one place. Cache a static ObjectMapper in E2eBlockFixture so the WebSocket frame builder avoids allocating a new mapper per call. Expose LAMPORTS_PER_SOL to replace the magic 1_000_000_000d divisor. Rename fixture constants to the SOME_* prefix convention used by the rest of the fixture suite. Consolidate the classification parity assertion into a single recursive-comparison check on a ClassificationCounts record so each test has exactly one when-action. Make the WebSocketTransactionStream reference a local variable in the @BeforeAll bootstrap since it is no longer read after setup. Narrow the JDBC helper's catch clause from Exception to SQLException. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
SOL Issue
Closes #40
Summary
Phase 7 end-to-end coverage: the indexer pipeline is now exercised for both stream adapters against a real Testcontainers Postgres via the real Helidon
WebServer, with a shared canonical block fixture and an explicit parser-parity proof. No production code changes — the entire deliverable is test code plus onetestFixturesbuild dependency and twotestFixturesbridge classes.Context
The Phase 6 lifecycle wiring (SOL-36/SOL-37, #101) stopped short of driving real protobuf / JSON through the pipeline —
IndexerApplicationIntegrationTestintentionally uses aRecordingTransactionStreamstub. SOL-38 is the missing piece: prove that bothYellowstoneTransactionStream(gRPC) andWebSocketTransactionStream(WebSocket) take the same logical transaction all the way from raw wire format to a REST response, producing identical domain objects along the way.Approach
One canonical 3-transaction logical block is encoded twice by
E2eBlockFixture:accountKeys=[feePayer, sender, receiver])transactions,large_transfers,accountsmeta.errsetfailed_transactions,accountstransactions,memos,accountsThe fixture exposes the block as both:
List<SubscribeUpdateTransaction>(protobuf) for the gRPC adapter, andblockNotificationJSON-RPC frame for the WebSocket adapter (built via JacksonObjectNode).It also exposes
expectedDomainTransactions()/expectedDomainFeePayers()so parser-parity and pipeline assertions build against the same source of truth. Balance math was engineered soSolanaBalanceMath.computepicks the intended sender/receiver indices in each scenario, and fee payer pubkeys are distinct across all three transactions soaccountsstores three rows.Changes
prism/src/testFixtures/.../fixtures/E2eBlockFixture.java— canonical block fixture. Public methods:grpcUpdates(),webSocketFrame(),webSocketBlockValue(),expectedDomainTransactions(),expectedDomainFeePayers(). Public constants for every signature / pubkey / lamport balance used in assertions.prism/src/testFixtures/.../application/TestIndexerApplication.java— tiny bridge class in theapplicationpackage exposing a publicstart(config, writePool, readPool, stream)that delegates to the package-privateIndexerApplication.start. Lets integration tests live in any package without opening production API visibility.prism/src/test/.../e2e/AdapterParityTest.java— unit-level proof (no DB, no server) thatTransactionParserandBlockNotificationParserproduce identicalSolanaTransactionandAccountoutputs for the canonical block. Four tests: transactions parity, fee payers parity, success/failure classification parity, distinct fee payers. Satisfies the "both adapters produce identical domain objects" acceptance criterion explicitly.prism/src/integrationTest/.../e2e/GrpcEndToEndIntegrationTest.java— full pipeline viaInProcessServerBuilder+ an inlineTestGeyserServicethat captures theStreamObserverand forwardsSubscribeUpdatemessages.@BeforeAllstartsSharedPostgresContainer, bootsTestIndexerApplication.start()against the realYellowstoneTransactionStream, waits for the in-process stream to bind, pushes the three canonical updates, and waits via Awaitility until each table has the expected row count. Ten@Testmethods then verify via a realjava.net.http.HttpClient:/api/transactions?limit=50— paginated list of two successful txs/api/transactions/{sig}for the large transfer/api/transactions/{sig}for the memo tx/api/slots/{slot}— both successful txs at the shared slot/api/transfers?min_amount=0— the one large transfer/api/memos— the one memo with the expected text/api/accounts/{pubkey}for each of the three fee payersfailed_transactions(no REST route) asserted via a local record +usingRecursiveComparison().prism/src/integrationTest/.../infrastructure/websocket/WebSocketEndToEndIntegrationTest.java— same assertion matrix, same canonical block, sameTestIndexerApplication.start(). Lives in theinfrastructure.websocketpackage to reach the package-privateWebSocketFactoryinterface and the 6-argWebSocketTransactionStreamconstructor (same trick the existingWebSocketTransactionStreamTestuses). The scripted factory captures theWebSocket.Listener,listener.onText(...)pushesE2eBlockFixture.webSocketFrame()into the real stream's virtual-thread loop, and downstream verification is identical to the gRPC test.prism/build.gradle.kts— addsjackson-bom+jackson-databindtotestFixturesImplementationso the fixture can build the WebSocket JSON frame viaObjectMapper. Jackson is already a production runtime dependency; this only opens the test-fixture compile classpath.Architecture notes
IndexerApplication.startstays package-private;WebSocketFactoryand the 6-argWebSocketTransactionStreamconstructor stay package-private. ThetestFixturesTestIndexerApplicationbridge keeps the production API surface unchanged while letting test classes in any package boot the full pipeline.AdapterParityTestat the unit level (fast, no DB), and once implicitly in each pipeline test by asserting against the sameexpectedDomainTransactions()/expectedDomainFeePayers()source of truth. Divergence in either adapter fails both the parity test and at least one pipeline assertion.SolanaBalanceMath.computepicks specificsenderIndex/receiverIndexvalues: the large transfer has the sender at index 1 with the max decrease; the failed tx has the fee payer at index 0 as the only decrease; the memo tx has the fee payer at index 0 as both sender and max decrease. See the constants inE2eBlockFixturefor the exact pre/post balances.Pubkey's 44-char limit. Base58 signatures are 64 bytes and stay within theSignature's 88-char limit.await().atMost(15, SECONDS).untilAsserted(...)— noThread.sleep, no hardcoded delays.@BeforeAllbefore pushing its block, so running both tests in the same integrationTest JVM is safe. State loaded by@BeforeAllis consumed read-only by the@Testmethods, so test ordering doesn't matter.Testing conventions followed
usingRecursiveComparison()once. Auto-incrementidfields are excluded viaignoringFieldsMatchingRegexes(".*\\.id")and DB-populatedcreatedAtviaignoringFieldsOfTypes(Instant.class). Thefailed_transactionscheck uses a localFailedTransactionRowrecord so even that direct JDBC read goes through recursive comparison.given(stubSocket.sendText(BLOCK_SUBSCRIBE_PAYLOAD, true)).willReturn(...)with the exact expected payload — noany()/anyString().// given/// when/// thenmarkers on every test,varfor every local, no comments or Javadoc.testFixtures/; no test-helper logic leaks intosrc/test/orsrc/integrationTest/.Checklist
./gradlew buildpasses (compile + Spotless + unit + integration + ArchUnit)AdapterParityTest(parser parity, success/failure classification, distinct fee payers)GrpcEndToEndIntegrationTest,WebSocketEndToEndIntegrationTesttestFixturesbridge classes onlyusingRecursiveComparison()used for every object assertionKnown follow-ups (out of scope)
Phase 7: E2E & Docsmilestone does not yet exist; this PR is therefore not attached to a milestone. Creating that milestone is a project-bookkeeping task, not part of this story.testFixturesTestIndexerApplicationbridge — if/when more tests need to boot the full indexer outside theapplicationpackage, this bridge becomes the canonical entry point. Alternatively,IndexerApplication.startcould be promoted topublicin a future cleanup.Summary by CodeRabbit
Tests
Chores