Add integration tests for reconciliation mismatch, funding idempotency, IPFS retry exhaustion, and invoice search - #238
Merged
Chucks1093 merged 4 commits intoAug 25, 2026
Conversation
…tch handling (StellarState#221) Covers amount mismatch, destination/escrow mismatch, and asset issuer mismatch scenarios in the reconciliation worker + VerifyPaymentService. Asserts mismatched payments are never marked verified, the failure surfaces as a machine-readable invalid_payment error, and a later valid payment for a different investment still reconciles successfully.
…tency (StellarState#219) Covers resubmitting the same Stellar payment confirmation for an investment: exactly one commitment/transaction record results, a conflicting confirmation with a different tx hash is rejected, and an already-confirmed investment short-circuits without re-hitting Horizon. Scope note: InvestmentService.createInvestment has no idempotency-key parameter, so duplicate-submission dedup is exercised at the payment verification layer instead. Full details in the PR description.
…rState#218) Drives InvoiceService.uploadDocument through repeated failed attempts and asserts the IPFS provider is called exactly the configured maximum number of times, the invoice never retains a false successful-pin status, the final error is a stable credential-free ServiceError, and retries stop immediately once an attempt succeeds. Scope note: IPFSService.uploadFile has no internal retry loop (one HTTP call per invocation, caller-driven attempts), matching the existing ipfs-upload.test.ts convention. Full details in the PR description.
…tellarState#205) Exercises the marketplace search endpoint's real customer_name LIKE filter end-to-end against Postgres: case-insensitive matching, partial substring matching, empty-array on no match, and non-matching of unrelated fields. Scope note: Invoice has no title/description columns and no relevance scoring exists anywhere in the codebase (results are ordered only by the caller's sort field, never by match quality), so the title-vs- description ranking scenario from the issue cannot be tested against real behavior. Two tests are explicitly skipped with this gap documented inline. Full details in the PR description.
|
@Martha-code-dev Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds four integration test files covering the assigned Stellar Wave issues. Each was implemented after reading the actual codebase mechanisms it targets (VerifyPaymentService, ReconcilePendingStellarStateWorker, IPFSService, InvoiceService, MarketplaceService), reusing the existing DB-backed and fake-repository test conventions already in
tests/integration/. Two of the four issues describe behavior that does not currently exist in the codebase (no idempotency-key mechanism on investment creation, no title/description schema or relevance ranking on invoice search) — those gaps are disclosed honestly below and in the test files themselves rather than papered over.closes #221
closes #219
closes #218
closes #205
Changes
Add integration test for Horizon reconciliation mismatch handling #221 — Horizon reconciliation mismatch handling:
tests/integration/reconcile-horizon-mismatch.integration.test.ts. Extends the existingreconcile-horizon-payment.test.tsworker/VerifyPaymentService harness with amount-mismatch, escrow-destination-mismatch, and asset-issuer-mismatch scenarios (a fake/counterfeit USDC issuer). Verifies mismatched payments are never markedCONFIRMED, noTransactionrow is created for them, the failure surfaces as a machine-readableinvalid_paymentServiceError(422), and a later valid payment for a different investment still reconciles successfully in a subsequent tick without being poisoned by the earlier failure. 5 tests, all passing against real Postgres.Add integration test for duplicate investment idempotency #219 — duplicate investment idempotency:
tests/integration/duplicate-investment-idempotency.integration.test.ts. Scope note:InvestmentService.createInvestment(src/services/investment.service.ts) has no idempotency-key or request-identity parameter at all — two calls with identical inputs create two independentInvestmentrows today; there is nothing to test at that layer. The dedup mechanism that does exist end-to-end isVerifyPaymentService.verifyPayment, keyed on the Stellar transaction hash used to confirm a commitment's on-chain funding (used by both the manual verify route and the reconciliation worker). This suite verifies, against real Postgres, that resubmitting the same tx hash for the same investment produces exactly one commitment and oneTransactionrow, that a conflicting confirmation with a different tx hash is correctly rejected (409) rather than silently accepted, and that an already-confirmed investment short-circuits without an extra Horizon call. 3 tests, all passing.Add integration test for IPFS upload retry exhaustion #218 — IPFS upload retry exhaustion:
tests/integration/ipfs-upload-retry-exhaustion.integration.test.ts. Scope note:IPFSService.uploadFilehas no internal retry loop — it makes exactly one HTTP call per invocation, andInvoiceService.uploadDocumentcalls it exactly once with no retry wrapper at all (confirmed by reading both files). This matches the existing convention intests/integration/ipfs-upload.test.ts, which already drivesuploadFilemanually across multiple calls to simulate a retrying caller. This suite builds on that pattern: it drivesuploadDocumentthrough a fixed max-attempts loop (representing a retrying caller such as a queue consumer) and asserts the provider is called exactly the configured number of times, the invoice never ends up with a false-positiveipfsHash, the final error is a stableipfs_upload_failedServiceErrorwith the Pinata JWT never appearing in the serialized error, and retries stop immediately on the first success. 4 tests, all passing.Add integration test for the invoice search endpoint returning results ranked by relevance when query matches both title and description #205 — invoice search relevance ranking:
tests/integration/invoice-search-relevance.integration.test.ts. Scope note: theInvoiceentity has notitleordescriptioncolumns, and grepping the codebase turns up no relevance-scoring logic anywhere —MarketplaceService/TypeORMMarketplaceRepositorydoes a single case-insensitiveLIKEfilter againstcustomer_nameonly, ordered strictly by the caller's chosensortfield, never by match quality. The title-vs-description ranking test literally described in the issue cannot be written against real behavior without first shipping a schema migration and a ranking algorithm, which is feature work out of scope for a test-only PR. This suite instead exercises the one real text-matching field end-to-end against Postgres (case-insensitivity, partial substring match, empty-array on no match, and confirming unrelated fields likeinvoiceNumberare not searched), and explicitly documents the two ranking scenarios asit.skip(...)with inline comments explaining exactly what schema/algorithm work would be needed to make them real — rather than asserting against fabricated behavior. 4 passing, 2 honestly skipped.Test plan
All four suites were run for real against a local Postgres 15 database (
ss_backend_test), matching theDATABASE_URL-gated pattern the existing integration suite already uses in CI.19assertions total across18test cases,16passing,2intentionally skipped (documented gaps in Add integration test for the invoice search endpoint returning results ranked by relevance when query matches both title and description #205).76/76suites,559/561tests passing (2skipped) when run with--runInBand.tsc --noEmitandeslint "src/**/*.ts"(the repo'slint/type-checkscripts, also run automatically by the Husky commit-msg/pre-commit hooks on every commit in this PR) both pass clean.prettier --checkpasses on all 4 new files.Caveat on parallel test runs: when the full suite is run with Jest's default parallel workers, several DB-backed integration suites (including the pre-existing
reconcile-horizon-payment.test.ts, unmodified by this PR) intermittently fail with Postgres deadlocks/timeouts, because multiple suites independently runsynchronize: true+dropSchema: trueagainst the same sharedDATABASE_URLconcurrently. This is a pre-existing characteristic of the test architecture, not something introduced here — I verified the same two pre-existing files deadlock-race under enough parallel workers on a cleandevcheckout. Adding three more DB-touching integration files in this PR does make the collision more likely to show up by default; running with--runInBand(or capping--maxWorkersfor thetests/integrationproject) avoids it entirely, which is how I validated everything above.Two of the four issues (#219, #205) describe functionality that isn't implemented in this codebase yet (no idempotency key on investment creation; no title/description search/ranking). I chose to write real, passing tests against the closest existing mechanism rather than either fabricating behavior or leaving the issue untouched — full reasoning is inline in each test file's top comment. Happy to adjust the interpretation if a maintainer had a different mechanism in mind.