feat: replace in-memory PrismaService fake with real PrismaClient (#475) - #536
Closed
mayborn005 wants to merge 68 commits into
Closed
feat: replace in-memory PrismaService fake with real PrismaClient (#475)#536mayborn005 wants to merge 68 commits into
mayborn005 wants to merge 68 commits into
Conversation
…ils (JSE-ORG#437) * fix(escrow): read event history from EscrowEvent table - Update findEvents to read from EscrowEvent table instead of deriving from timestamp columns - Add fromState and toState fields to EscrowEventEntry type - Update Swagger documentation for the events endpoint - Add tests for event history functionality Closes JSE-ORG#425 * fix(seed): make seeding idempotent and fail loudly - Use findFirst and create instead of upsert for idempotency - Add tests for seed script - Log summary of created records Closes JSE-ORG#424 * fix(logistics): remove unused Terminal Africa provider - Remove terminal-africa.provider.ts as it has no HTTP client and is not registered - Remove corresponding test file Closes JSE-ORG#423 * feat(vendor): implement VendorAccountDetails CRUD - Add vendorAccountDetails store to PrismaService - Add repository, service, and controller for VendorAccountDetails - Add DTOs with validation and response masking - Mask sensitive fields (bank account, tax ID) in responses - Add tests for repository operations Closes JSE-ORG#422 * style: format code with prettier
…SE-ORG#450) - Replace pattern-only validation with Keypair.fromSecret/fromPublicKey - Catch checksum-invalid keys at config load time, not runtime - Add stellarSecretKey and stellarPublicKey custom Joi validators - SYSTEM_SIGNER_SECRET, SEP10_SIGNING_SECRET now validated with checksum - ADMIN_ADDRESS now validated as public key with checksum - Error messages name the field and explain checksum failure - Set abortEarly: false to report all validation errors at once - Add comprehensive test suite: 38 tests covering valid/invalid keys - Tests verify shape-valid but checksum-invalid keys are rejected - Tests verify public keys rejected where secret keys expected - Tests verify error messages name field and say 'invalid' not 'pattern' Closes JSE-ORG#420 Co-authored-by: Alu-card19 <clintoncodes68@gmail.com>
…ts isolated (JSE-ORG#483) * fixed: all issues resolved JSE-ORG#315 JSE-ORG#316 JSE-ORG#317 JSE-ORG#318 * fix: prisma middleware and notification retry queue JSE-ORG#315 JSE-ORG#316 JSE-ORG#317 JSE-ORG#318 * fix: exponential backoff jitter, retry logging, slow query middleware, prisma timeouts JSE-ORG#315 JSE-ORG#316 JSE-ORG#317 JSE-ORG#318 * fix(config): validate AUTO_RELEASE_SOURCE_ADDRESS and keep config tests isolated --------- Co-authored-by: eric <ericjo6303@gmail.com>
…G#449) Webhook signature verification was silently skipped when STELLAR_WEBHOOK_SECRET was not configured, allowing unverified requests to be processed. This fix ensures all incoming webhook requests are either verified or rejected. Changes: - verifySignature() throws InternalServerErrorException (500) when secret is missing instead of silently accepting - Logs an explicit configuration error with distinct message (stellar.webhook.secret_missing) - Config validation requires STELLAR_WEBHOOK_SECRET in production using Joi.when (follows existing SENTRY_DSN pattern) - All unit/integration tests now use proper secrets and signatures - .env.example updated to document rejection behavior Closes JSE-ORG#421
…ORG#451) The docs/events.md document described canonical event topics as a one-element tuple (Symbol("<event_name>"),) but the escrow contract emits two-element tuples (symbol_short!("<Category>"), symbol_short!("<Action>")) for all canonical events, with an optional third Address element. Key changes: - Updated Encoding Rules to describe the two-symbol topic convention - Replaced the event index table with real topic pairs verified from the contract source at trust-link-contract@4ffc37b - Added explicit "Events Using a Different Convention" section for single-symbol exceptions (resolver_vote_recorded, contract_upgraded, storage_migrated) - Explained how indexers derive event names from topic pairs - Removed fees_withdrawn (not emitted by the contract) - Updated FeeCollectorUpdated from legacy inline to proper struct - Added the missing EscrowExpired event - Updated section headings to match PascalCase struct names - Added source verification note with commit hash and repository link Verification: Every topic was checked against emitter calls in contracts/escrow/src/events.rs. Closes JSE-ORG#412 Co-authored-by: belloaliyu11 <belloaliyu11@users.noreply.github.com> Co-authored-by: Cursor <cursoragent@cursor.com>
…time FIXED (JSE-ORG#482) Co-authored-by: Kappa16 <anagbogut@gmail.com>
…ed data FIXED (JSE-ORG#486) Co-authored-by: Kappa16 <anagbogut@gmail.com>
* implemented find Vendor Escrows loads * implemented Eight stale Dependabot pull requests and no dependency update policy * docs(api): declare response types on vendor and admin endpoints * feat(health): split liveness and readiness probes
) PATCH /admin/credentials/logistics ignored dto.key whenever a key was already set, silently re-encrypting the existing (possibly compromised) secret instead. The endpoint now always encrypts and stores the submitted key via LogisticsService.rotateApiKey(). Closes JSE-ORG#498
…ource via config, allow Idempotency-Key in CORS (JSE-ORG#527) - Remove PrismaService.reset() from the onModuleDestroy shutdown hook and guard it to refuse running outside NODE_ENV=test, so it can no longer truncate a production database on graceful shutdown. - Remove the duplicate @Cron nonce cleanup from Sep10Service; NonceCleanupService is now the single scheduled job that deletes expired nonces. - Resolve AUTO_RELEASE_SOURCE_ADDRESS through ConfigService on use in AutoReleaseWorker and AutoReleaseService instead of falling back to a fabricated Stellar address read from process.env at import time. - Add Idempotency-Key to the CORS allowedHeaders list so browser clients on an allowed origin can call POST /escrow, which requires that header. Fixes JSE-ORG#497 Fixes JSE-ORG#500 Fixes JSE-ORG#501 Fixes JSE-ORG#509
* fix(config): update ADMIN_ADDRESS and AUTO_RELEASE_SOURCE_ADDRESS validation schemas to validate Stellar public keys * style(lint): run eslint autofixes across src and test/unit directories * fix(escrow): correct escrow creation default state to CREATED and trigger notifications on webhook payments
* test(sanitization): increase contact encryption coverage above 85% (JSE-ORG#408)\n\nAdd comprehensive unit tests for contact encryption utility covering round-trip, format, IV randomization, tamper detection, and key validation.\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * test(escrow): reach >75% function coverage for EscrowService (JSE-ORG#409)\n\nAdd tests for cancelEscrow/cancelPendingEscrow auth branches, terminal-state rejections, updateBuyerContact terminal rejection, viewer flags, and privacy check for toPublicEscrow.\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * test(admin/escrow/prisma): raise coverage for ApiKeysController and BuyerDisputeService; expand PrismaService tests (JSE-ORG#410,JSE-ORG#411)\n\nAdd ApiKeysController endpoint tests for admin rejection, rotation branches, and secret leakage checks. Add comprehensive PrismaService in-memory store tests covering create/findUnique/findMany/update/updateMany/reset and security assertions.\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * test(admin/escrow): raise coverage for ApiKeysController and BuyerDisputeService (JSE-ORG#410)\n\nAdd ApiKeysController endpoint tests for admin rejection and secret leakage checks.\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * test(prisma): increase PrismaService line/function coverage above 80% (JSE-ORG#411)\n\nAdd comprehensive unit tests exercising PrismaService in-memory stores, reset(), updateMany(), and security assertions.\n\nCo-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <copilot@example.com>
…ders (JSE-ORG#534) * fix(security): stop overriding helmet headers SecurityMiddleware no longer duplicates headers already managed by helmet (X-Content-Type-Options, X-Frame-Options, Strict-Transport-Security, X-XSS-Protection, Referrer-Policy, Permissions-Policy). Only Cache-Control for authenticated requests remains. HSTS is configured through helmet so it can be turned off outside production. Closes JSE-ORG#508 * fix: add missing Optional import in PrismaService The @optional() decorator was used without importing Optional from @nestjs/common. * fix: use isProduction variable consistently and update integration test Replaced duplicate configService.isProduction() call with the isProduction variable. Updated integration test to not expect helmet-managed headers.
…E-ORG#475) - Rewrite PrismaService to extend PrismaClient with @prisma/adapter-pg - Remove all 13 Map-based in-memory stores (1532 -> 420 lines) - reset() now executes TRUNCATE TABLE ... CASCADE - Add Postgres 16 service to CI workflow - Fix BuyerDisputeService to explicitly transition escrow to DISPUTED - Fix vendor-analytics integration test (remove direct store access) - Update 4 test files for real PrismaClient API
|
Hey @mayborn005! 👋 It looks like this PR isn't linked to any issue. If this PR is for one of the issues assigned to you as part of a Wave, please link it to ensure your contribution is tracked properly. You can do this by adding a keyword to the PR description (e.g.,
|
- Add periodic sweep timer (60s interval, unref'd) that removes expired entries without requiring a get() call for that key. - Enforce a bounded maximum size (MAX_MEMORY_ENTRIES = 1_000). When the map exceeds capacity the soonest-to-expire entries are evicted first. - onModuleDestroy now clears the map and stops the sweep timer to prevent open handles in tests and graceful shutdown. - 6 new tests: sweep removes expired keys, sweep preserves valid keys, bounded capacity eviction, unbounded growth prevention, onModuleDestroy clears the map. Closes JSE-ORG#506
* ci: build Docker image on PR * refactor(tests): type global exception filter test mocks * feat(stellar): poll Soroban for events with two-symbol topic names
* refactor(admin): make the admin authorisation rule single and explicit * feat(dlq): paginate the failed transaction listing * docs(testing): document the three test suites and add test match validator * test(coverage): gate branch and function coverage alongside lines * test(dlq): update unit test mock for pagination count --------- Co-authored-by: DevNetlife <DevNetlife@users.noreply.github.com>
* fix(types): resolve 7 typecheck errors * test(common): cover sanitize.util paths * test(stellar): cover contract.service retry logic
…SorobanPollerService cursor advancement (JSE-ORG#583) Four issues (JSE-ORG#551-JSE-ORG#554). Closes JSE-ORG#551 — test/unit/prisma.service.spec.ts's plaintext-contact test The test already had `expect(...).rejects.toThrow(...)` — but `escrow.create` isn't an `async` function, so assertEncryptedContact's throw happens synchronously during evaluation of the `create(...)` expression, before it's ever handed to `expect()`. `.rejects` only catches a *rejected promise*; a synchronous throw during argument evaluation escapes expect() entirely. Fixed by wrapping each call in its own `async () => { await create(...) }`, which guarantees a promise comes out either way. Added a positive-case test that writes a properly encrypted contact via encryptContact() and reads it back with decryptContact(). Closes JSE-ORG#552 — jest.mock('axios') erasing isAxiosError A bare `jest.mock('axios')` auto-mocked the whole module, replacing `axios.isAxiosError` with a jest.fn() returning undefined, so GiglClient.fetchTracking's entire error-mapping branch was dead. Switched to spreading the real module through the mock (keeping isAxiosError, a real duck-typed predicate, genuine) while still mocking `create` per-test. Needed the same `create` jest.fn() reference at both the top level and under `default` — this codebase's esModuleInterop resolution didn't consistently pick one over the other. Added the missing 401 case (GiglUnauthorizedError) and a non-Axios-error-propagates-unchanged case. gigl.client.ts branch coverage: 71.42% (was ~21%). Closes JSE-ORG#553 — lint:check errors/warnings Ran `npx eslint --fix` for the auto-fixable prettier errors, removed the unused-variable fixtures in prisma.service.spec.ts / contract.service.spec.ts by dropping their unused `const x =` bindings (not disabling the rule), and fixed unused function params/destructured elements in tracing.interceptor.spec.ts the same way. Two `no-require-imports` errors in config.module.spec.ts are a deliberate dynamic re-require after jest.resetModules() (a static import would be cached and never re-run the module's load-time env validation) — disabled with a one-line reason per the issue's own guideline, not silenced blindly. Along the way, found and fixed the same duplicate-file-content corruption JSE-ORG#550 already covers in escrow.service.spec.ts, in two more files: tracing.interceptor.spec.ts and tracing.service.spec.ts each had a near-duplicate second copy of their whole test suite appended (with a few tests written differently) rather than a merge conflict properly resolved. Kept the more complete version in each case and folded in the one genuinely distinct test tracing.service.spec.ts's second copy had ("is disabled when OTEL_ENABLED is unset"). Remaining `npm run lint:check` errors (46) are explicitly out of scope: config.module.ts (8) and analytics.service.ts (31) are `any`/unsafe-type errors — the issue itself says "JSE-ORG#416 covers the remaining any occurrences ... keep the two from colliding by doing formatting here and typing there." dlq.service.ts (6) is a real but unrelated type bug (its list() passes skip/take/count to an in-memory Prisma stub whose type doesn't declare them) that doesn't affect this PR's use of DlqService (enqueue() only). api-keys.controller.spec.ts (1) is the parsing error JSE-ORG#547 already fixes in a separate open PR. Closes JSE-ORG#554 — SorobanPollerService cursor advancement past failed events Chose stop-at-first-failure over continue-and-track-highest-contiguous. The batch is fetched in ledger order and syncStateFromChain's switch cases assume prior transitions already landed (EscrowShipped assumes EscrowFunded already happened) — applying later events out of order after skipping a failed one risks a worse, harder-to-detect state-machine bug than the at-least-once redelivery this trades for. Full reasoning is in poll()'s doc comment. Two distinct failure classes, handled differently (see processEvent's doc comment): - Legitimately not applicable (unparseable payload, non-string topics, missing escrowId) — no retry ever fixes this, so it's dead-lettered via the existing DlqService (issue JSE-ORG#303, reused rather than inventing a second mechanism per the issue's own guidance) and treated as handled; the cursor advances past it. - syncStateFromChain throws — rethrown so poll() stops the batch and retries this exact event next cycle. A per-event-id in-memory retry counter (MAX_SYNC_RETRIES = 5) breaks the infinite-retry case: once exceeded, the event is also dead-lettered and the cursor advances. Wired DlqService into StellarModule via forwardRef (DlqModule already imports StellarModule for the replay path, so this closes a module cycle the same way WebhooksModule/EscrowModule already do in this file). New test/unit/soroban-poller.service.spec.ts covers all three required scenarios (all succeed, middle throws, first throws) plus dead-lettering for unparseable payloads, missing escrowId, and the retry-threshold path. Testing note: this sandbox has no Docker and a pre-existing (unrelated) Homebrew permissions issue blocks a local Postgres install, so `npm run test:e2e` and the e2e-spec files could not be run — none of these four issues touch e2e files, though, so this is lower-risk than it was for the previous PR. Everything here was verified via `npx tsc --noEmit -p .` (clean for every touched file), `npx eslint` (clean for every touched file), and `npx jest test/unit` (390/392 passing — the 2 failures are the exact pre-existing dev-baseline bugs JSE-ORG#547/JSE-ORG#550 already fix in the other open PR, untouched here to avoid duplicate/conflicting work across both). Co-authored-by: miraclesonly <304894442+miraclesonly@users.noreply.github.com> Co-authored-by: Omoboi-dev <bukunmiabiodun14@gmail.com>
…oped middleware, and config accessors - SentryInterceptor: pins that a successful response passes through untouched, a thrown error is reported to Sentry and rethrown (not swallowed into a 200), and that the interceptor never reads the ExecutionContext at all — so no request body or header can reach the Sentry event through it. - SanitizationPipe: covers flat/nested/array bodies, non-string values passing through unchanged, markup being stripped vs an ordinary value being left alone, and the documented in-place mutation. - SecurityMiddleware/RequestIdMiddleware/LoggerMiddleware: covers the authenticated vs unauthenticated cache-header branches (100% branch coverage on security.middleware.ts), request-id generation vs an honoured inbound header vs an array header, and that the logger middleware calls next() and writes one structured JSON line on finish. Also documents that RequestIdMiddleware accepts an inbound id verbatim with no format validation before echoing it into logs (finding only, not fixed here). - ConfigService: covers getAllowedOrigins (unset/empty/single/multiple/ whitespace), isProduction/isDevelopment/isTest across all NODE_ENV values, and getDatabaseUrl's pool-parameter accessors (default, connection_limit only, pool_timeout only, both). Tests the service directly against a small fake NestConfigService rather than a compiled Nest module, per JSE-ORG#491. Closes JSE-ORG#570 Closes JSE-ORG#571 Closes JSE-ORG#572 Closes JSE-ORG#573
…pe pipe, currency config, HorizonService (JSE-ORG#593)
The CORS origin callback in main.ts was constructing an Error for disallowed origins, which the cors middleware passed to next(err). This caused the global exception filter to return a 500 Internal Server Error instead of a clean denial. Change callback(new Error(...)) to callback(null, false), the standard cors convention. This omits the Access-Control-Allow-Origin header and lets the browser block the request naturally. Adds cors-origin.spec.ts covering: - Allowed origin receives ACAO header - Disallowed origin gets no ACAO header and no 5xx - Absent Origin header succeeds (non-browser clients) - Preflight for allowed origin still advertises Idempotency-Key - Preflight for disallowed origin omits ACAO header Co-authored-by: KingFRANKHOOD <chibuezemfrancis@gmail.com>
* test(vendor): cover the untested paths in analytics.service.ts - Add full getTransactionStats test suite: zeroed figures for vendor with no escrows, all active states (CREATED/FUNDED/SHIPPED/DELIVERED), COMPLETED + RELEASED counted as completedTransactions, exact completion/dispute rate arithmetic, channel metrics (no settings, EMAIL only, SMS only, both enabled), lastUpdated ISO timestamp - Add vendor isolation tests for both getDailyVolumeChart and getTransactionStats — vendor B's escrows never appear in vendor A's totals - Strengthen timezone boundary test: pins escrow to 00:30 UTC (always within the rolling window), asserts exact date attribution differs between UTC and America/New_York views - Add formatDateInTimezone tests for UTC-5 (NY) and UTC+9 (Tokyo) with exact expected date strings - All assertions use exact expected values (toBe), no toBeGreaterThan Coverage: analytics.service.ts 60% → 98.33% lines, 100% functions. Only uncovered line is 219 (cancelledTransactions branch) — unreachable in unit tests because the in-memory PrismaService mock silently drops CANCELLED escrows from findMany without an explicit state filter. Production Postgres path is unaffected; integration test needed to verify cancelled count end-to-end. * test(vendor): cover the untested paths in analytics.service.ts - Remove CANCELLED filter from PrismaService mock findMany; the filter was silently dropping CANCELLED escrows from getTransactionStats, making cancelledTransactions unreachable and giving vendors wrong revenue figures. Real Postgres has no such filter. - Fix admin-stats.service.spec.ts to expect 9 escrows (including CANCELLED) now that the mock matches real Postgres behaviour. - Add test: 'counts CANCELLED escrows in cancelledTransactions and totalTransactions' — covers the if (state === 'CANCELLED') branch (line 218) that was previously unreachable. - Add test: 'uses default days=30 and timezone=UTC when called with vendor address only' — covers the default-arg branches for days and timezone in getDailyVolumeChart. - Remove stale comment in mixed-states test that documented the now- fixed mock quirk. Coverage for analytics.service.ts: Lines: 98.33% → 100% Branches: 74.07% → 85.18% Functions: 100% → 100% Statements: 98.43% → 100% --------- Co-authored-by: Omoboi-dev <bukunmiabiodun14@gmail.com>
…call fails (JSE-ORG#594) * fix(workers): make delivery recording recoverable when contract call fails (JSE-ORG#507) Swapped the order of operations in TrackingPollWorker.run: contractService.recordDelivery is now called before escrowRepository.markDelivered. If the chain call fails, the escrow remains in SHIPPED state and will be retried on the next poll cycle. Added claimDelivery/clearDeliveryClaim to EscrowRepository following the same claim-and-release pattern used by AutoReleaseWorker. Closes JSE-ORG#507 * style: fix prettier formatting in tracking-poll intervals spec --------- Co-authored-by: Buffy <buffyg@freebuff.com>
…SE-ORG#446) * test(notifications): cover backoff computation and retry exhaustion * quick fix [ci skip] * chore: remove committed coverage report and restore package.json/package-lock.json from main * fix: add cross-env dep, restore test scripts flag, fix retry queue service merge error --------- Co-authored-by: OluRemiFour <lekanayoola04@gmail.com>
…ally run (JSE-ORG#491) (JSE-ORG#526) The 27 not-Keypair tests in config.module.spec.ts could never reject anything. NestConfigModule.forRoot({ validationSchema }) is invoked inside ConfigModule's @module decorator, so @nestjs/config loads the env file and runs Joi validation once when config.module.ts is first imported. Each subsequent buildConfigService(env) call returned the already-validated config from .env.test, regardless of the env the test passed. The 11 passing tests were all the Keypair SDK unit tests, which don't depend on the Nest module lifecycle. Fix: extract the schema and the two custom validators (stellarSecretKey, stellarPublicKey) into src/config/config.schema.ts so the runtime path and the spec share one definition. Call schema.validate(env, { abortEarly: false, allowUnknown: true }) directly per test, asserting on result.error (instead of a second Nest module compile). Runs Joi synchronously, so the rewrite drops the async/await on the configure- module branch. Side benefit: the original config.module.ts defined stellarPublicKey but never wired it onto ADMIN_ADDRESS, which fell through as plain Joi.string().required(). ADMIN_ADDRESS now actually validates as a Stellar public key (checksum + G prefix) at boot. Tests like 'ADMIN_ADDRESS rejects a secret key (S...)' now reject in prod, not just in this spec. Validators also override .messages({'any.invalid': '{{#label}} {{#reason}}'}) so error.message carries the full customised reason instead of Jiang's default "contains an invalid value". Acceptance criteria from JSE-ORG#491: - All 38 tests in src/config/config.module.spec.ts pass. - Each test genuinely exercises the value it supplies (result.error matches what the per-test env produced — flipping a fixture from a valid key to an invalid one flips the test result). - The checksum cases cover both SYSTEM_SIGNER_SECRET and SEP10_SIGNING_SECRET, both the public-key-where-a-secret-is-expected case, and ADMIN_ADDRESS. Pre-existing failures left untouched (verified against clean dev HEAD via git stash): - 11 unrelated test suites fail with module-resolution errors (missing .prisma/client/default, broken DTO paths). Owned by other issues. - One typecheck error in src/notifications/notification-retry-queue.service.ts(204,5). Owned by JSE-ORG#488. Closes JSE-ORG#491 Co-authored-by: MrOmale <MrOmale@users.noreply.github.com> Co-authored-by: Omoboi-dev <bukunmiabiodun14@gmail.com>
* fix(config): resolve process.env test isolation and custom Joi validation messages (JSE-ORG#481) Signed-off-by: namdamdoi68-oss <namdamdoi68@gmail.com> * fix(test): use checksum-valid Stellar pubkey for ADMIN_ADDRESS in .env.test src/config/config.module.ts's production stellarPublicKey validator (introduced when the Joi schema was tightened to use Keypair.fromPublicKey for checksum validation, era of commit 62f7137) rejects the literal "admin-address" placeholder that .env.test has carried since the repo's inception as ADMIN_ADDRESS. The result is that `npm run test:cov` aborts mid-suite with: Error: Config validation error: ADMIN_ADDRESS is an invalid Stellar public key \ — must start with G, got a value starting with 'a' Replace the placeholder with the testnet-format pubkey GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5 — the same value already declared as VALID_PUBLIC_KEY in src/config/config.module.spec.ts (line 21). It is checksum-valid, which is the only property the production validator checks beyond the G-prefix, and it is unambiguously test-only data. The lone test that depends on the OLD placeholder literal — test/unit/dispute.service.spec.ts:176 — uses it inside a Jest mock (`get: jest.fn().mockReturnValue('admin-address')`), not by reading process.env, so it is unaffected. No production code references the literal. AGENTS.md documented this as fixed earlier under "Stellar addresses" but .env.test was never re-synced with the validator change. Unblocks `npm run test:cov` end-to-end. Project total coverage now 77.14% lines (above 70% gate). * test(logistics): cover GiglClient error paths Closes JSE-ORG#404. Issue JSE-ORG#404 reports GiglClient at 46% line / 0% branch coverage with zero tests for any of its error-handling branches: every axios failure was leaking upward instead of being translated to a typed error. This commit exercises every branch of fetchTracking and adds a small shape-validation guard so a malformed 2xx body is rejected rather than passed through. ## Implementation (src/logistics/gigl/gigl.client.ts) - New: GiglInvalidResponseError — fourth typed error in the class hierarchy (alongside GiglUnauthorizedError, GiglNetworkError, GiglProviderError). Thrown when a 2xx response body fails GiglTrackingResponse shape validation. - New: isValidGiglResponse type guard — checks that response.data is a non-null object with string tracking_number, string current_status, string carrier_code, string|null estimated_delivery, and Array.isArray(events). - fetchTracking now invokes the guard inside the try block after the await. The validation error is re-thrown verbatim by the existing non-axios branch in the catch, so no new control flow is required. - @throws JSDoc updated to reflect the new failure mode. ## Tests (src/logistics/gigl/gigl.client.spec.ts) 28 new tests organised in six describe blocks. All branches exercised: - constructor — axios.create configuration (default 10s timeout, custom timeoutMs, bearer-token assembly verbatim). - fetchTracking — happy path (URL encoding, payload passthrough, null estimated_delivery). - fetchTracking — network-level failures (ECONNABORTED, ETIMEDOUT, ECONNREFUSED with axiosErr.message used as cause, ECONNRESET with null message exercising the `?? 'unknown network error'` fallback). - fetchTracking — HTTP error status (401 -> GiglUnauthorizedError, 404/500/503 -> GiglProviderError with statusCode field, plus a parallel-call test that asserts 404 and 500 are distinct shape-wise on the same client). - fetchTracking — malformed response bodies (null body, partial body, wrong-type estimated_delivery, array body, missing events, missing tracking_number, plus an affirmative that an axios network error does NOT spuriously trip GiglInvalidResponseError). - fetchTracking — non-axios errors (Error instance re-thrown verbatim by identity; non-Error primitive re-thrown verbatim). Error-class structural assertions cover name/extends/inheritance for all four typed error classes. Mock strategy: jest.mock('axios') auto-mock; a plain-object makeAxiosError helper returns `{ isAxiosError: true, code, message, response }` — satisfies real axios.isAxiosError contract and lets `message: null` exercises survive the test run (true Error instances coerce null message back to string in some engines). No real network request is made. ## Tests (src/logistics/gigl/gigl-logistics.service.spec.ts) +2 propagation tests pinning that GiglInvalidResponseError flows through getStatus() and getTrackingDetails() unchanged. The service does no transformation of fetchTracking's rejection, so this is a contract test rather than a behavioural test; it documents the dependency so a future refactor that wraps the rejection can't silently regress it. ## CHANGELOG Unreleased > Changed: documents the malformed-body rejection behaviour for callers that consume the GIGL provider strictly. ## Coverage | file | lines (before -> after) | branches (before -> after) | |-------------------------------------------------|-------------------------|----------------------------| | src/logistics/gigl/gigl.client.ts | 46.15% -> 100.00% | 0.00% -> 100.00% | | src/logistics/gigl/gigl-logistics.service.ts | unchanged | 91.66% | | project total (line-coverage gate) | 70%-floor preserved | 77.14% lines (> 70% floor) | Suites: 75 passed. Tests: 705 passed (was 642). No real network requests are made in unit runs. AGENTS.md earlier claim of "all 65 suites passing" was based on a stale coverage summary; the actual test count after this PR is 75 suites / 705 tests. ## Acceptance criteria (from issue JSE-ORG#404) - [x] Line and branch coverage above 80% (100% / 100%) - [x] axios instance configured with base URL, bearer token and timeout (3 constructor tests) - [x] Successful response returned as the typed tracking shape (3 happy-path tests) - [x] Network timeout produces the typed network error, not a raw axios error (4 network-error tests including the `?? 'unknown network error'` fallback) - [x] HTTP 404 surfaced distinctly from HTTP 500 (per-statusCode + per-message assertions; parallel-call test) - [x] Malformed response body is rejected rather than returned as-is (6 invalid-shape tests) - [x] No test makes a real network request (jest.mock('axios')) - [x] `npm run test:cov` passes and overall coverage stays at or above 70% (77.14%) --------- Signed-off-by: namdamdoi68-oss <namdamdoi68@gmail.com> Co-authored-by: namdamdoi68-oss <namdamdoi68@gmail.com> Co-authored-by: Omoboi-dev <bukunmiabiodun14@gmail.com>
* test(vendor): add comprehensive controller tests for VendorProfileController (JSE-ORG#461) * test(escrow): add comprehensive controller tests for EscrowController (JSE-ORG#459)
* test(vendor): add comprehensive controller tests for VendorProfileController (JSE-ORG#461) * test(escrow): add comprehensive controller tests for EscrowController (JSE-ORG#459) * feat(stellar): replace placeholder contract calls with real Soroban contract invocations (JSE-ORG#478) - Update StellarModule to configure a real Soroban rpc.Server using ConfigService. - Implement full Soroban transaction lifecycle (account fetch, transaction construction, simulation, preparation, signing, submission, polling) in ContractService using @stellar/stellar-sdk. - Preserve submitAutoRelease sequence retry semantics and re-fetch account on each attempt. - Add typed simulation and contract error decoding. - Expand contract.service unit tests covering full RPC lifecycle and edge cases. --------- Co-authored-by: Omoboi-dev <bukunmiabiodun14@gmail.com>
…nterval, request timeout, retention-safe start ledger (JSE-ORG#588) * fix(soroban): require an explicit RPC URL in production instead of guessing resolveRpcUrl() silently fell back to the public testnet RPC (or a third-party mainnet endpoint that needs an API key) when SOROBAN_RPC_URL was unset, so a misconfigured production service could read testnet events and apply them to production escrows. - Require SOROBAN_RPC_URL (as a URI) in production and whenever STELLAR_NETWORK is MAINNET; startup now fails fast instead of guessing. - Reject a testnet URL under STELLAR_NETWORK=MAINNET and vice versa, so the RPC endpoint and the configured network always agree. - Remove the third-party mainnet fallback entirely. - Outside production, keep the public testnet default but log clearly that the poller is running on a default. - Fix the onModuleInit guard and warning to name only CONTRACT_ID, the one condition that can actually disable the poller. - Export the Joi schema so config shapes can be tested without booting Nest, and cover the new rules with tests. * fix(config): validate SOROBAN_POLL_INTERVAL_MS as a bounded integer The poll interval was read with Number() and no schema entry, so a non-numeric value became NaN and setInterval(fn, NaN) fired as fast as the event loop allows, hammering the Soroban RPC endpoint. A value of 0 behaved the same way. - Declare SOROBAN_POLL_INTERVAL_MS in the Joi schema as an integer with a 1000ms minimum and a default of 5000, with clear startup errors. - Drop the ternary fallback in the poller constructor so the schema is the single source of truth for the default. - Mark the value as always present in the Config interface. - Document the bounds in .env.example and add tests asserting that 'abc', 0, sub-minimum, and non-integer values all fail validation and that the default applies when unset. * fix(soroban): bound the RPC request with an AbortController timeout fetchEvents called fetch with no signal, so a single request that never settled left the polling guard set forever: every later tick returned at the guard, the process stayed healthy, and event ingestion silently stopped with nothing in the logs. - Route all RPC calls through a helper that follows the checkHorizon pattern: AbortController, setTimeout to abort, clearTimeout in finally. - Make the timeout configurable via SOROBAN_RPC_TIMEOUT_MS (validated integer, default 4000ms — below the 5000ms poll interval). - Log a timeout distinctly (warn) from other poll failures; the next tick proceeds normally because the abort settles the request and the finally block always clears the polling flag. - Add tests asserting a request that never resolves is aborted, logged as a timeout, and does not block the following cycle. * fix(soroban): start from the current ledger when no cursor is stored With no cursor, getEvents was called with startLedger: 1. Soroban RPC nodes retain only a short window of events (~24h), so the node rejects the request, poll() logs the error, and the cycle repeats every tick — a fresh deployment never ingested a single event. - On first run, ask getLatestLedger for the current ledger and start a small margin behind it, keeping the request inside the retention window. Events emitted before the first deployment are intentionally never read; this is now documented rather than accidental. - Persist the chosen start ledger immediately (as a 'ledger:'-prefixed cursor value) so a restart resumes from the same point instead of skipping forward to a new "now". Stored paging tokens keep working unchanged. - Add SOROBAN_START_LEDGER so an operator can replay from a known ledger after an outage. - Distinguish a retention-window error from other RPC errors in the logs. - Add tests asserting the request body for the no-cursor, paging-token, ledger-cursor, and configured-start-ledger cases, plus the error classification. --------- Co-authored-by: distributed-nerd <267643428+distributed-nerd@users.noreply.github.com> Co-authored-by: Omoboi-dev <bukunmiabiodun14@gmail.com>
…retries (JSE-ORG#590) Adds coverage for the per-attempt failure persistence (retryCount, failedAt, lastError) that operators rely on to see the attempt count and provider error while a notification is retrying. - After three failing attempts the notification row shows retryCount: 3 and the last provider error message as lastError. - Every failing attempt writes retryCount/failedAt/lastError, not just the final one. - A rejecting DB write does not abort the retry loop (it is caught and logged) and the terminal status: 'FAILED' write still happens. closes JSE-ORG#490 Co-authored-by: Jerry_tekh <jerry.chinonso134@gmail.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com> Co-authored-by: Omoboi-dev <bukunmiabiodun14@gmail.com>
Adds a typecheck job to lint.yml so a non-compiling file can no longer reach dev through a green pull request, and adds controller-level tests for three previously untested controllers: StressTestController, VendorEscrowController, and StellarWebhookController. Closes JSE-ORG#577 Closes JSE-ORG#576 Closes JSE-ORG#575 Closes JSE-ORG#574
…SE-ORG#603) * fix: resolve bad merge in stellar.module.ts causing build errors * fix: add DATABASE_URL build arg so prisma generate runs in Docker build --------- Co-authored-by: Buffy <buffyg@freebuff.com>
…es (JSE-ORG#602) * fix: regenerate package-lock.json to resolve @emnapi version mismatches * fix: add DATABASE_URL build arg so prisma generate runs in Docker build --------- Co-authored-by: Buffy <buffyg@freebuff.com>
* test(tracing): improve coverage for tracing service and bootstrap * fix(prisma): restore optional database url injection
…ead of loading all rows (JSE-ORG#596) * fix: resolve CI conflicts for dispute pagination and admin stats aggregates * fix: regenerate package-lock.json to resolve @emnapi version mismatches * fix: resolve pre-existing test failures in escrow.service.spec.ts and dispute.repository.spec.ts * fix: repair Docker build - add DATABASE_URL arg, fix stellar.module merge, fix type casts * fix: resolve all pre-existing build errors - config.module, dlq, logistics, analytics, readiness dto * fix: suppress lint errors from as any casts, fix escrow-event-logging test state --------- Co-authored-by: Buffy <buffyg@freebuff.com>
…f in-memory (JSE-ORG#595) * fix: resolve CI conflicts for dispute pagination and admin stats aggregates * fix: regenerate package-lock.json to resolve @emnapi version mismatches * fix: resolve pre-existing test failures in escrow.service.spec.ts and dispute.repository.spec.ts * fix: repair Docker build - add DATABASE_URL arg, fix stellar.module merge, fix type casts * fix: resolve all pre-existing build errors - config.module, dlq, logistics, analytics, readiness dto * fix: suppress lint errors from as any casts, fix escrow-event-logging test state --------- Co-authored-by: Buffy <buffyg@freebuff.com>
Bring the real-PrismaClient PR up to date with base dev (~40 commits). Conflict resolutions: - src/prisma/prisma.service.ts: keep the PR's real PrismaClient (drop the in-memory fake). - test/unit/prisma.service.spec.ts, src/prisma/escrow-event-logging.spec.ts: keep the PR's real-DB-oriented tests. Upstream's added assertions targeted fake-only behaviour (sync throw on plaintext contact, auto-DISPUTED side effects, notifications without a parent escrow, no-arg effectiveDatabaseUrl undefined) and do not hold against a real PrismaClient. - .github/workflows/ci.yml: accept upstream's deletion (its coverage/typecheck work was consolidated into test.yml in JSE-ORG#525). Note: this merge is textually clean but not yet green. The fake->real swap surfaces ~151 type errors in code the base added against the fake's loose types (Decimal/JsonValue/enums/required itemRef), and @prisma/adapter-pg + pg still need to be added to package.json. Those are follow-up commits. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HzXa5DbrJ4CsnYs4Sx4qiK
Reconcile the fake->real PrismaClient swap with ~40 commits of base code
that was written against the in-memory fake's loose types.
- Declare the previously-undeclared driver-adapter deps so `npm ci` installs
them: @prisma/adapter-pg, pg (deps) and @types/pg (devDep).
- prisma.service.ts: enable query-event logging via constructor `log` option
and narrow $on('query') to Prisma.QueryEvent; drop dead input types; add
boundary mappers (toEscrowRecord/toFailedTransactionRecord/
toVendorAccountDetailsRecord/toVendorTrackingSettingsRecord) that convert
generated rows (Decimal, JsonValue) to the hand-written *Record contract
(number, plain objects) the rest of the app and its tests depend on.
- escrow / dlq / vendor repositories: convert rows through the mappers;
write JSON columns via Prisma.DbNull / InputJsonValue.
- admin-stats: Number() the Decimal _sum.amount aggregate.
- analytics: type $queryRaw result as an array.
- schema: add Notification.providerMessageId / attemptCount / lastResponseCode
(persisted by NotificationsService; previously only in the fake) + migration.
Production `tsc --noEmit` is clean. Remaining type errors are confined to
test/spec files and the benchmark script (follow-up commit).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01HzXa5DbrJ4CsnYs4Sx4qiK
…pecs Make `npm run typecheck` (tsc --noEmit) fully green after the fake->real PrismaClient migration. Type-only test changes plus repair of pre-existing bad-merge corruption inherited from base dev. - Add required `itemRef` to escrow creates; annotate `state: '...' as const` where widening broke EscrowRecord/EscrowSummaryDto assignability (escrow/dispute/admin-stats/analytics/cross-vendor/tracking-poll specs). - Add missing fields to VendorTrackingSettingsRecord mocks; cast the DLQ ABANDONED record; pass the new DlqService arg to SorobanPollerService; use Prisma.DbNull for null JSON columns. - prisma.service.spec.ts: drop the invalid `@jest/globals` Test import, remove unused e1..e4 bindings, Prisma.DbNull for ledgerFeedback. - Remove duplicated halves left by earlier bad merges in tracing.interceptor.spec, tracing.middleware.spec, and notification-retry-queue.service.spec. - config.module.spec.ts: repair the corrupted abortEarly block, the three missing sync-test `});`, the duplicate Keypair import, and restore the dropped ALL_KNOWN_KEYS definition. - benchmark script: array-typed $queryRaw, itemRef on seed escrows. No assertions or test intent changed. Note: the repo-wide `lint:check` job was already red on base dev (pre-existing prettier/require/console violations in untouched files); the files changed here are formatted clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HzXa5DbrJ4CsnYs4Sx4qiK
Contributor
Author
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
Replaces the 1532-line in-memory PrismaService fake with a real
PrismaClientusing@prisma/adapter-pg(Prisma v7 driver adapter). All Map-based stores removed — queries go directly to PostgreSQL.Changes
Core
src/prisma/prisma.service.ts— extendsPrismaClientinstead of in-memory MapsPrismaPgadapter internally, no datasources neededonModuleInit()→$connect()+ slow-query logger via$on(query)onModuleDestroy()→$disconnect()reset()→TRUNCATE TABLE ... CASCADE(skips_prisma_migrations)Behavioral Fixes
src/escrow/buyer-dispute.service.ts—openDispute()now explicitly callsescrowRepository.updateState(escrowId, DISPUTED)after creating a dispute (in-memory fake auto-transitioned escrow as side-effect; real DB does not)test/integration/vendor-analytics.integration-spec.ts— removed(prisma as any).escrows.set(...); passescreatedAtdirectly toprisma.escrow.create()Test Updates
src/prisma/prisma.service.spec.ts— updated for real PrismaClientsrc/prisma/escrow-event-logging.spec.ts— updated for real PrismaClienttest/unit/prisma.service.spec.ts— updated for real PrismaClienttest/unit/prisma-schema-parity.spec.ts— updated for real PrismaClientCI
.github/workflows/ci.yml— added Postgres 16 service (matched fromtest.yml)Known Behavioral Changes
findMany()no longer auto-filters CANCELLED recordsescrow.create()/escrow.update()no longer auto-createEscrowEventrowsdispute.create()no longer auto-transitions escrow to DISPUTEDamountfields arePrisma.Decimalat runtime — usetoEqual()instead oftoBe()for comparisons, orNumber(escrow.amount)for arithmeticTesting
Requires PostgreSQL on localhost:5432 with trustlink_test database. See AGENTS.md for setup.