Skip to content

test(events): cover SSE streamEvents success paths - #1963

Merged
K1NGD4VID merged 9 commits into
LabsCrypt:mainfrom
Jubilee-001:feat/SSE-streamEvents
Oct 10, 2026
Merged

K1NGD4VID merged 9 commits into
LabsCrypt:mainfrom
Jubilee-001:feat/SSE-streamEvents

Conversation

@Jubilee-001

Copy link
Copy Markdown
Contributor

Summary

Adds success-path coverage for the SSE endpoints (GET /api/events/stream), complementing the reject-path tests in eventStream.test.ts (issue #1854).

Because a never-ending SSE response cannot be consumed by a plain supertest request, the tests drive a real http.createServer(app) on an ephemeral port and speak raw HTTP with client sockets.

Coverage (all acceptance criteria)

  • Borrower connect — asserts SSE headers (text/event-stream, no-cache, keep-alive, X-Accel-Buffering: no), the event: init payload ({"type":"init","replayed":0}), the replay query params ([borrower, null, 100]), service registration, and unregistration when the client socket closes
  • Last-Event-ID replay — two mocked contract_events rows are streamed as camelCase loan-event frames in order (eventId/eventType/loanId/amount/ledger/txHash), with no init frame when replay rows exist, and query params [borrower, 'evt-prev', 100]
  • Admin (subscribeAll branch) — an admin request without borrower receives the subscribeAll init payload; a second admin connection's init carries the counts of the first ({"borrower":0,"admin":1,"total":1}), pinning the write-before-register behavior
  • 429 through the route — after 3 live SSE connections for one user, a fourth actual HTTP request is rejected with Maximum of 3 SSE connections allowed per user before any SSE headers are written

Test infrastructure notes

  • authService is imported dynamically after the cacheService mock registration. A static import instantiates it pre-mock; the auth middleware then resolves that real Redis-backed instance and its never-resolving connect() keeps the Jest worker alive after the run (exit 124). The suite now exits cleanly without --forceExit.
  • The rateLimiter module is mocked with pass-through middleware so express-rate-limit MemoryStore timers are never created at import time.
  • DB, cache, notification, and Soroban services are mocked (same pattern as remittanceRoutes.test.ts); teardown destroys all client sockets before server.close().

Verification

node --experimental-vm-modules node_modules/jest/bin/jest.js src/__tests__/eventStream.success.test.ts
→ Tests: 4 passed, 4 total — clean exit, no --forceExit
npx eslint src/__tests__/eventStream.success.test.ts → clean

Closes #1854

Jubilee-001 and others added 9 commits September 30, 2026 00:36
Add eventStream.success.test.ts driving GET /api/events/stream through a
real HTTP server with raw client sockets (an open SSE response cannot be
consumed by supertest):

- borrower connect: SSE headers, init payload, replay query params, and
  service registration/unregistration on socket close
- Last-Event-ID replay: contract_events rows mapped to camelCase
  loan-event frames in order, no init frame when replay rows exist
- admin (subscribeAll): init payload carrying connection counts
- 429 via an actual HTTP request once the per-user limit is exhausted

Import authService dynamically after the cacheService mock registration;
a static import instantiates it pre-mock, the auth middleware then binds
the real Redis-backed cacheService, and the never-resolving connect keeps
the Jest worker alive after the run. Mock the rateLimiter module with
pass-through middleware to avoid MemoryStore timers at import time.

Closes LabsCrypt#1854
Line-wrapping only; no behavioral change.
The middleware builds idemp:<wallet|anon>:<key> since the LabsCrypt#1809
namespacing, so pin the get/delete/set assertions to the real
namespaced format instead of the bare idemp:<key> form.
…bsCrypt#1809)

A mock returning the same entry for every key cannot model per-user
isolation. Back the cacheService spies with a real Map keyed by the
full namespaced cache key, seed Bob's entry/lock under his namespace,
and assert the replay check against the original res.json mock — on
the handler path the middleware wraps res.json and delegates to the
original, so the wrapper is not a jest mock.
…rypt#1808)

- pageQuery() now inspects the most recent query call (getAuditLogs
  emits the page SELECT after the COUNT, so the first match could be
  a stale earlier call)
- pass an actual cursor into the keyset-predicate test so the
  (created_at, id) < ($n, $n) comparison has real values to assert
- split nextCursor on the last ':' — ISO timestamps contain colons
- compare the COUNT SQL trimmed; the template-built query carries a
  trailing space after the table name
- pin AuditLogFilters to its real field count of 7 (actor, action,
  from, to, cursor, limit, withTotal), not 8
// targets the original res.json mock captured beforehand: the wrapper
// delegates to it, making it the recorder of every body the response
// actually carried.
const originalJson = res.json as jest.Mock;
@K1NGD4VID
K1NGD4VID merged commit e5bdd1a into LabsCrypt:main Oct 10, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[test] SSE streamEvents success paths are untested (only reject paths covered)

3 participants