Skip to content
13 changes: 13 additions & 0 deletions .changeset/test-non-sequential-socket-cases.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
"nostream": patch
---

test(relay): add non-sequential socket test suite for connection and command races

Adds deterministic real-socket test cases covering racing connection states:
- Reconnect storm with immediate REQ on the open tick across multiple concurrent sockets
- Pipelined commands in a single write buffer preserving arrival order
- REQ immediately followed by CLOSE of the same subscription ID
- EVENT sent on open tick
- Binary-framed JSON commands
- Socket resilience and valid command processing following malformed JSON
2 changes: 0 additions & 2 deletions src/adapters/web-socket-server-adapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,8 +175,6 @@ export class WebSocketServerAdapter extends WebServerAdapter implements IWebSock
earlyMessages.push({ data, isBinary })
}

client.on('message', onEarlyMessage)
client.pause()

let accepted = false
try {
Expand Down
33 changes: 33 additions & 0 deletions test/integration/features/socket-race/socket-race.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
@socket-race
Feature: Commands that race the connection are answered, not just survived

# Issue #1052: a relay that drops a REQ has survived perfectly well. Each
# case asserts the reply the client is entitled to — EOSE, OK, NOTICE — not
# merely that the socket stayed open.

Scenario: Subscriptions sent at the open tick each receive their EOSE
When 10 sockets subscribe at the open tick, repeated 20 times

Check failure on line 9 in test/integration/features/socket-race/socket-race.feature

View workflow job for this annotation

GitHub Actions / Cucumber report

Subscriptions sent at the open tick each receive their EOSE Failed.

Scenario: Subscriptions sent at the open tick each receive their EOSE Step: 10 sockets subscribe at the open tick, repeated 20 times Error: Error: timed out waiting for EOSE for storm-0-0 at waitFor (/code/test/integration/features/socket-race/socket-race.feature.ts:52:13) at async World.<anonymous> (/code/test/integration/features/socket-race/socket-race.feature.ts:117:9)
Then each socket received EOSE for its own subscription

Scenario: Two subscriptions sent in one turn are both answered
When a socket sends REQ "pipelined-1" and then REQ "pipelined-2" in one turn

Check failure on line 13 in test/integration/features/socket-race/socket-race.feature

View workflow job for this annotation

GitHub Actions / Cucumber report

Two subscriptions sent in one turn are both answered Failed.

Scenario: Two subscriptions sent in one turn are both answered Step: a socket sends REQ "pipelined-1" and then REQ "pipelined-2" in one turn Error: Error: timed out waiting for EOSE for both pipelined-1 and pipelined-2 at waitFor (/code/test/integration/features/socket-race/socket-race.feature.ts:52:13) at async World.<anonymous> (/code/test/integration/features/socket-race/socket-race.feature.ts:152:5)
Then the socket received EOSE for "pipelined-1" and "pipelined-2"

Scenario: A subscription closed in the same turn never receives an event
Given a publisher called race-cancel-author
When a socket subscribes at the open tick and closes it in the same turn

Check failure on line 18 in test/integration/features/socket-race/socket-race.feature

View workflow job for this annotation

GitHub Actions / Cucumber report

A subscription closed in the same turn never receives an event Failed.

Scenario: A subscription closed in the same turn never receives an event Step: a socket subscribes at the open tick and closes it in the same turn Error: Error: timed out waiting for EOSE for after-close at waitFor (/code/test/integration/features/socket-race/socket-race.feature.ts:52:13) at async World.<anonymous> (/code/test/integration/features/socket-race/socket-race.feature.ts:195:5)
And the publisher publishes a text note
Then "race-cancel" received no event and the socket still answers

Scenario: An event sent at the open tick receives its OK
Given a publisher called race-writer
When the publisher sends an event at the open tick

Check failure on line 24 in test/integration/features/socket-race/socket-race.feature

View workflow job for this annotation

GitHub Actions / Cucumber report

An event sent at the open tick receives its OK Failed.

Scenario: An event sent at the open tick receives its OK Step: the publisher sends an event at the open tick Error: Error: timed out waiting for OK for the event sent at the open tick at waitFor (/code/test/integration/features/socket-race/socket-race.feature.ts:52:13) at async World.<anonymous> (/code/test/integration/features/socket-race/socket-race.feature.ts:256:5)
Then the socket received OK for that event

Scenario: Fragmented and binary frames are honoured
When a socket sends a fragmented REQ and a binary REQ in one turn

Check failure on line 28 in test/integration/features/socket-race/socket-race.feature

View workflow job for this annotation

GitHub Actions / Cucumber report

Fragmented and binary frames are honoured Failed.

Scenario: Fragmented and binary frames are honoured Step: a socket sends a fragmented REQ and a binary REQ in one turn Error: Error: timed out waiting for EOSE for the fragmented and the binary REQ at waitFor (/code/test/integration/features/socket-race/socket-race.feature.ts:52:13) at async World.<anonymous> (/code/test/integration/features/socket-race/socket-race.feature.ts:290:5)
Then both subscriptions received EOSE and the socket still answers

Scenario: Invalid JSON mid-stream is refused and the socket stays usable
When a socket sends invalid JSON and then a REQ

Check failure on line 32 in test/integration/features/socket-race/socket-race.feature

View workflow job for this annotation

GitHub Actions / Cucumber report

Invalid JSON mid-stream is refused and the socket stays usable Failed.

Scenario: Invalid JSON mid-stream is refused and the socket stays usable Step: a socket sends invalid JSON and then a REQ Error: Error: timed out waiting for a NOTICE and an EOSE at waitFor (/code/test/integration/features/socket-race/socket-race.feature.ts:52:13) at async World.<anonymous> (/code/test/integration/features/socket-race/socket-race.feature.ts:325:5)
Then the socket received a NOTICE and then EOSE for the REQ
Loading
Loading