From 1f3ab7ae7495f1017e6fafa645b669445e40f733 Mon Sep 17 00:00:00 2001 From: Kevin Viglucci Date: Thu, 13 Jun 2024 22:42:26 -0500 Subject: [PATCH] test: (wip) websocket server duplex connectio tests Signed-off-by: Kevin Viglucci --- .../rsocket-websocket-server/jest.config.ts | 20 ++ .../WebsocketDuplexConnection.spec.ts | 321 ++++++++++++++++++ 2 files changed, 341 insertions(+) create mode 100644 packages/rsocket-websocket-server/jest.config.ts create mode 100644 packages/rsocket-websocket-server/src/__tests__/WebsocketDuplexConnection.spec.ts diff --git a/packages/rsocket-websocket-server/jest.config.ts b/packages/rsocket-websocket-server/jest.config.ts new file mode 100644 index 00000000..062c08d9 --- /dev/null +++ b/packages/rsocket-websocket-server/jest.config.ts @@ -0,0 +1,20 @@ +import type { Config } from "@jest/types"; +import { pathsToModuleNameMapper } from "ts-jest/utils"; +import { compilerOptions } from "../../tsconfig.json"; + +const config: Config.InitialOptions = { + preset: "ts-jest", + testRegex: "(\\/__tests__\\/.*|\\.(test|spec))\\.(ts)$", + moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { + // This has to match the baseUrl defined in tsconfig.json. + prefix: "/../../", + }), + modulePathIgnorePatterns: [ + "/__tests__/test-utils", + "/__tests__/*.d.ts", + ], + collectCoverage: true, + collectCoverageFrom: ["/src/**/*.ts", "!**/node_modules/**"], +}; + +export default config; diff --git a/packages/rsocket-websocket-server/src/__tests__/WebsocketDuplexConnection.spec.ts b/packages/rsocket-websocket-server/src/__tests__/WebsocketDuplexConnection.spec.ts new file mode 100644 index 00000000..89ab70d5 --- /dev/null +++ b/packages/rsocket-websocket-server/src/__tests__/WebsocketDuplexConnection.spec.ts @@ -0,0 +1,321 @@ +import { mock } from "jest-mock-extended"; +import { + Demultiplexer, + Deserializer, + Flags, + Frame, + FrameHandler, + FrameTypes, + Multiplexer, + serializeFrame, + SetupFrame, +} from "rsocket-core"; +import { WebsocketDuplexConnection } from "../WebsocketDuplexConnection"; +// import { MockSocket } from "../__mocks__/ws"; +import { Duplex } from "stream"; + +// const deserializer = mock(); + +describe("WebsocketDuplexConnection", function () { + describe("when closed", () => { + it("removes listeners from the underlying socket event emitter", () => { + // arrange + const socketStub = mock(); + const multiplexerDemultiplexer = mock< + Multiplexer & Demultiplexer & FrameHandler + >(); + const frame = mock(); + const connection = new WebsocketDuplexConnection( + socketStub, + frame, + () => multiplexerDemultiplexer + ); + + // act + connection.close(); + + // assert + expect(socketStub.removeAllListeners).toBeCalledWith(); + }); + + it("cleans up the socket resource when closed without an error", () => { + // arrange + const socketStub = mock(); + const multiplexerDemultiplexer = mock< + Multiplexer & Demultiplexer & FrameHandler + >(); + const frame = mock(); + const connection = new WebsocketDuplexConnection( + socketStub, + frame, + () => multiplexerDemultiplexer + ); + + // act + connection.close(); + + // assert + expect(socketStub.end).toBeCalledWith(); + }); + + it("cleans up the socket resource when closed with an error", () => { + // arrange + const socketStub = mock(); + const multiplexerDemultiplexer = mock< + Multiplexer & Demultiplexer & FrameHandler + >(); + const frame = mock(); + const connection = new WebsocketDuplexConnection( + socketStub, + frame, + () => multiplexerDemultiplexer + ); + + // act + const error = new Error(); + connection.close(error); + + // assert + expect(socketStub.end).toBeCalledWith(); + }); + + it("calls the onClose callback when one is registered", () => { + // arrange + const socketStub = mock(); + const multiplexerDemultiplexer = mock< + Multiplexer & Demultiplexer & FrameHandler + >(); + const frame = mock(); + const connection = new WebsocketDuplexConnection( + socketStub, + frame, + () => multiplexerDemultiplexer + ); + const onCloseCallback = jest.fn(); + + // act + connection.onClose(onCloseCallback); + connection.close(); + + // assert + expect(onCloseCallback).toBeCalledTimes(1); + expect(onCloseCallback).toBeCalledWith(); + }); + + it("when closed with an error it calls the onClose callback when one is registered", () => { + // arrange + const socketStub = mock(); + const multiplexerDemultiplexer = mock< + Multiplexer & Demultiplexer & FrameHandler + >(); + const frame = mock(); + const connection = new WebsocketDuplexConnection( + socketStub, + frame, + () => multiplexerDemultiplexer + ); + const onCloseCallback = jest.fn(); + const error = new Error(); + + // act + connection.onClose(onCloseCallback); + connection.close(error); + + // assert + expect(onCloseCallback).toBeCalledTimes(1); + expect(onCloseCallback).toBeCalledWith(error); + }); + // + // it("subsequent calls to close result in only a single invocation of onClose", () => { + // const socketStub = mock(); + // const multiplexerDemultiplexer = mock< + // Multiplexer & Demultiplexer & FrameHandler + // >(); + // const connection = new WebsocketDuplexConnection( + // socketStub, + // deserializer, + // () => multiplexerDemultiplexer + // ); + // const onCloseCallback = jest.fn(); + // const error = new Error(); + // connection.onClose(onCloseCallback); + // connection.close(error); + // connection.close(error); + // + // expect(onCloseCallback).toBeCalledTimes(1); + // expect(onCloseCallback).toBeCalledWith(error); + // }); + // + // it("the onClose callback is called with an error when the socket is closed unexpectedly", () => { + // const socket = new MockSocket() as unknown as WebSocket; + // const multiplexerDemultiplexer = mock< + // Multiplexer & Demultiplexer & FrameHandler + // >(); + // const connection = new WebsocketDuplexConnection( + // socket, + // deserializer, + // () => multiplexerDemultiplexer + // ); + // const onCloseCallback = jest.fn(); + // + // connection.onClose(onCloseCallback); + // (socket as unknown as MockSocket).mock.close({}); + // + // expect(onCloseCallback).toBeCalledTimes(1); + // expect(onCloseCallback).toHaveBeenCalledWith( + // new Error("WebsocketDuplexConnection: Socket closed unexpectedly.") + // ); + // }); + // + // it("the onClose callback is called with an error when the socket is closed with an error", () => { + // const socket = new MockSocket() as unknown as WebSocket; + // const multiplexerDemultiplexer = mock< + // Multiplexer & Demultiplexer & FrameHandler + // >(); + // const connection = new WebsocketDuplexConnection( + // socket, + // deserializer, + // () => multiplexerDemultiplexer + // ); + // const onCloseCallback = jest.fn(); + // const expectedError = new Error( + // "WebsocketDuplexConnection: Test error 1" + // ); + // + // connection.onClose(onCloseCallback); + // (socket as unknown as MockSocket).mock.error({ error: expectedError }); + // + // expect(onCloseCallback).toBeCalledTimes(1); + // expect(onCloseCallback).toHaveBeenCalledWith(expectedError); + // }); + }); + + // describe("send()", () => { + // const setupFrame = { + // type: FrameTypes.SETUP, + // dataMimeType: "application/octet-stream", + // metadataMimeType: "application/octet-stream", + // keepAlive: 60000, + // lifetime: 300000, + // metadata: Buffer.from("hello world"), + // data: Buffer.from("hello world"), + // resumeToken: null, + // streamId: 0, + // majorVersion: 1, + // minorVersion: 0, + // flags: Flags.METADATA, + // } as SetupFrame; + // + // it("serializes and writes the given frame to the underlying socket", () => { + // // arrange + // const socketStub = mock(); + // const multiplexerDemultiplexer = mock< + // Multiplexer & Demultiplexer & FrameHandler + // >(); + // const connection = new WebsocketDuplexConnection( + // socketStub, + // deserializer, + // () => multiplexerDemultiplexer + // ); + // + // // act + // connection.send(setupFrame); + // + // // assert + // expect(socketStub.send).toBeCalledWith(expect.any(Buffer)); + // }); + // + // it("does not write the given frame to the underlying socket when close was previously called", () => { + // // arrange + // const socketStub = mock(); + // const multiplexerDemultiplexer = mock< + // Multiplexer & Demultiplexer & FrameHandler + // >(); + // const connection = new WebsocketDuplexConnection( + // socketStub, + // deserializer, + // () => multiplexerDemultiplexer + // ); + // + // // act + // connection.close(); + // connection.send(setupFrame); + // + // // assert + // expect(socketStub.send).toBeCalledTimes(0); + // }); + // }); + // + // describe("when receiving data", () => { + // const setupFrame: SetupFrame = { + // type: FrameTypes.SETUP, + // dataMimeType: "application/octet-stream", + // metadataMimeType: "application/octet-stream", + // keepAlive: 60000, + // lifetime: 300000, + // metadata: Buffer.from("hello world"), + // data: Buffer.from("hello world"), + // resumeToken: null, + // streamId: 0, + // majorVersion: 1, + // minorVersion: 0, + // flags: Flags.METADATA, + // }; + // + // describe("when buffer contains a single frame", () => { + // it("deserializes received frames and calls the configured handler", () => { + // // arrange + // const multiplexerDemultiplexer = mock< + // Multiplexer & Demultiplexer & FrameHandler + // >(); + // const socketStub = new MockSocket() as unknown as WebSocket; + // const connection = new WebsocketDuplexConnection( + // socketStub, + // new Deserializer(), + // () => multiplexerDemultiplexer + // ); + // + // // act + // (socketStub as unknown as MockSocket).mock.message({ + // data: serializeFrame(setupFrame), + // }); + // + // // assert + // expect(multiplexerDemultiplexer.handle).toBeCalledTimes(1); + // + // const [call0] = multiplexerDemultiplexer.handle.mock.calls; + // const [arg0] = call0; + // expect(arg0).toMatchSnapshot(); + // }); + // }); + // + // describe("causes an error", () => { + // it("the connection is closed", () => { + // // arrange + // const multiplexerDemultiplexer = mock< + // Multiplexer & Demultiplexer & FrameHandler + // >(); + // const socketStub = new MockSocket() as unknown as WebSocket; + // const deserializerStub = mock(); + // const connection = new WebsocketDuplexConnection( + // socketStub as unknown as WebSocket, + // deserializerStub, + // () => multiplexerDemultiplexer + // ); + // deserializerStub.deserializeFrame.mockImplementation(() => { + // throw new Error("Mock error"); + // }); + // const onCloseCallback = jest.fn(); + // const data = Buffer.allocUnsafe(0).toString(); + // + // // act + // connection.onClose(onCloseCallback); + // (socketStub as unknown as MockSocket).mock.message({ data }); + // + // // assert + // expect(onCloseCallback).toBeCalledTimes(1); + // expect(onCloseCallback).toBeCalledWith(expect.any(Error)); + // }); + // }); + // }); +});