Skip to content

Commit a5f27c7

Browse files
committed
Add Transport tests
1 parent a695fbb commit a5f27c7

File tree

1 file changed

+337
-0
lines changed

1 file changed

+337
-0
lines changed

test/transport.test.ts

Lines changed: 337 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,337 @@
1+
/* eslint-disable @typescript-eslint/unbound-method -- to expect spy methods */
2+
3+
import type { MockedObject } from "vitest";
4+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
5+
import {
6+
NativeWebSocketTransport,
7+
WebSocketTransportFactory,
8+
WsWebSocketTransport,
9+
} from "../src/core/Transport.js";
10+
import * as ws from "ws";
11+
import type { RosbridgeMessage } from "../src/types/protocol.js";
12+
13+
describe("Transport", () => {
14+
afterEach(() => {
15+
vi.clearAllMocks();
16+
vi.unstubAllGlobals();
17+
});
18+
19+
describe("AbstractTransport", () => {
20+
it.todo("todo");
21+
});
22+
23+
describe("NativeWebSocketTransport", () => {
24+
let mockSocket: MockedObject<WebSocket>;
25+
26+
beforeEach(() => {
27+
mockSocket = {
28+
send: vi.fn(),
29+
close: vi.fn(),
30+
readyState: WebSocket.OPEN,
31+
onopen: null,
32+
onclose: null,
33+
onerror: null,
34+
onmessage: null,
35+
} as unknown as MockedObject<WebSocket>;
36+
});
37+
38+
it("should send messages as JSON", () => {
39+
const transport = new NativeWebSocketTransport(mockSocket);
40+
41+
transport.send({ op: "test" });
42+
43+
expect(mockSocket.send).toHaveBeenCalledWith(
44+
JSON.stringify({ op: "test" }),
45+
);
46+
});
47+
48+
it("should close the socket", () => {
49+
const transport = new NativeWebSocketTransport(mockSocket);
50+
51+
transport.close();
52+
53+
expect(mockSocket.close).toHaveBeenCalled();
54+
});
55+
56+
it("should reflect the socket's ready state", () => {
57+
const transport = new NativeWebSocketTransport(mockSocket);
58+
59+
// -- CONNECTING --
60+
61+
// @ts-expect-error -- mocking readonly property
62+
mockSocket.readyState = WebSocket.CONNECTING;
63+
64+
expect(transport.isConnecting()).toBe(true);
65+
expect(transport.isOpen()).toBe(false);
66+
expect(transport.isClosing()).toBe(false);
67+
expect(transport.isClosed()).toBe(false);
68+
69+
// -- OPEN --
70+
71+
// @ts-expect-error -- mocking readonly property
72+
mockSocket.readyState = WebSocket.OPEN;
73+
74+
expect(transport.isConnecting()).toBe(false);
75+
expect(transport.isOpen()).toBe(true);
76+
expect(transport.isClosing()).toBe(false);
77+
expect(transport.isClosed()).toBe(false);
78+
79+
// -- CLOSING --
80+
81+
// @ts-expect-error -- mocking readonly property
82+
mockSocket.readyState = WebSocket.CLOSING;
83+
84+
expect(transport.isConnecting()).toBe(false);
85+
expect(transport.isOpen()).toBe(false);
86+
expect(transport.isClosing()).toBe(true);
87+
expect(transport.isClosed()).toBe(false);
88+
89+
// -- CLOSED --
90+
91+
// @ts-expect-error -- mocking readonly property
92+
mockSocket.readyState = WebSocket.CLOSED;
93+
94+
expect(transport.isConnecting()).toBe(false);
95+
expect(transport.isOpen()).toBe(false);
96+
expect(transport.isClosing()).toBe(false);
97+
expect(transport.isClosed()).toBe(true);
98+
});
99+
100+
it("should emit events when the socket is opened, closed, or errors", () => {
101+
const transport = new NativeWebSocketTransport(mockSocket);
102+
103+
const openListener = vi.fn();
104+
const closeListener = vi.fn();
105+
const errorListener = vi.fn();
106+
107+
transport.on("open", openListener);
108+
transport.on("close", closeListener);
109+
transport.on("error", errorListener);
110+
111+
// -- OPEN -- //
112+
113+
const openEvent: Partial<Event> = { type: "open" };
114+
115+
mockSocket.onopen?.(openEvent as Event);
116+
117+
expect(openListener).toHaveBeenCalledWith(openEvent);
118+
expect(closeListener).not.toHaveBeenCalled();
119+
expect(errorListener).not.toHaveBeenCalled();
120+
121+
vi.clearAllMocks();
122+
123+
// -- CLOSE -- //
124+
125+
const closeEvent: Partial<CloseEvent> = { type: "close" };
126+
127+
mockSocket.onclose?.(closeEvent as CloseEvent);
128+
129+
expect(openListener).not.toHaveBeenCalled();
130+
expect(closeListener).toHaveBeenCalledWith(closeEvent);
131+
expect(errorListener).not.toHaveBeenCalled();
132+
133+
vi.clearAllMocks();
134+
135+
// -- ERROR -- //
136+
137+
const errorEvent: Partial<ErrorEvent> = { type: "error" };
138+
139+
mockSocket.onerror?.(errorEvent as ErrorEvent);
140+
141+
expect(openListener).not.toHaveBeenCalled();
142+
expect(closeListener).not.toHaveBeenCalled();
143+
expect(errorListener).toHaveBeenCalledWith(errorEvent);
144+
});
145+
146+
it("should emit messages as RosbridgeMessage objects", () => {
147+
const transport = new NativeWebSocketTransport(mockSocket);
148+
149+
const messageListener = vi.fn();
150+
151+
transport.on("message", messageListener);
152+
153+
const message: RosbridgeMessage = {
154+
op: "test",
155+
};
156+
157+
const messageEvent: Partial<MessageEvent> = {
158+
type: "message",
159+
data: JSON.stringify(message),
160+
};
161+
162+
mockSocket.onmessage?.(messageEvent as MessageEvent);
163+
164+
expect(messageListener).toHaveBeenCalledWith(message);
165+
});
166+
});
167+
168+
describe("WsWebSocketTransport", () => {
169+
let mockSocket: MockedObject<ws.WebSocket>;
170+
171+
beforeEach(() => {
172+
mockSocket = {
173+
send: vi.fn(),
174+
close: vi.fn(),
175+
readyState: ws.WebSocket.OPEN,
176+
onopen: null,
177+
onclose: null,
178+
onerror: null,
179+
onmessage: null,
180+
} as unknown as MockedObject<ws.WebSocket>;
181+
});
182+
183+
it("should send messages as JSON", () => {
184+
const transport = new WsWebSocketTransport(mockSocket);
185+
186+
transport.send({ op: "test" });
187+
188+
expect(mockSocket.send).toHaveBeenCalledWith(
189+
JSON.stringify({ op: "test" }),
190+
);
191+
});
192+
193+
it("should close the socket", () => {
194+
const transport = new WsWebSocketTransport(mockSocket);
195+
196+
transport.close();
197+
198+
expect(mockSocket.close).toHaveBeenCalled();
199+
});
200+
201+
it("should reflect the socket's ready state", () => {
202+
const transport = new WsWebSocketTransport(mockSocket);
203+
204+
// -- CONNECTING --
205+
206+
// @ts-expect-error -- mocking readonly property
207+
mockSocket.readyState = ws.WebSocket.CONNECTING;
208+
209+
expect(transport.isConnecting()).toBe(true);
210+
expect(transport.isOpen()).toBe(false);
211+
expect(transport.isClosing()).toBe(false);
212+
expect(transport.isClosed()).toBe(false);
213+
214+
// -- OPEN --
215+
216+
// @ts-expect-error -- mocking readonly property
217+
mockSocket.readyState = ws.WebSocket.OPEN;
218+
219+
expect(transport.isConnecting()).toBe(false);
220+
expect(transport.isOpen()).toBe(true);
221+
expect(transport.isClosing()).toBe(false);
222+
expect(transport.isClosed()).toBe(false);
223+
224+
// -- CLOSING --
225+
226+
// @ts-expect-error -- mocking readonly property
227+
mockSocket.readyState = ws.WebSocket.CLOSING;
228+
229+
expect(transport.isConnecting()).toBe(false);
230+
expect(transport.isOpen()).toBe(false);
231+
expect(transport.isClosing()).toBe(true);
232+
expect(transport.isClosed()).toBe(false);
233+
234+
// -- CLOSED --
235+
236+
// @ts-expect-error -- mocking readonly property
237+
mockSocket.readyState = ws.WebSocket.CLOSED;
238+
239+
expect(transport.isConnecting()).toBe(false);
240+
expect(transport.isOpen()).toBe(false);
241+
expect(transport.isClosing()).toBe(false);
242+
expect(transport.isClosed()).toBe(true);
243+
});
244+
245+
it("should emit events when the socket is opened, closed, or errors", () => {
246+
const transport = new WsWebSocketTransport(mockSocket);
247+
248+
const openListener = vi.fn();
249+
const closeListener = vi.fn();
250+
const errorListener = vi.fn();
251+
252+
transport.on("open", openListener);
253+
transport.on("close", closeListener);
254+
transport.on("error", errorListener);
255+
256+
// -- OPEN -- //
257+
258+
const openEvent: Partial<ws.Event> = { type: "open" };
259+
260+
mockSocket.onopen?.(openEvent as ws.Event);
261+
262+
expect(openListener).toHaveBeenCalledWith(openEvent);
263+
expect(closeListener).not.toHaveBeenCalled();
264+
expect(errorListener).not.toHaveBeenCalled();
265+
266+
vi.clearAllMocks();
267+
268+
// -- CLOSE -- //
269+
270+
const closeEvent: Partial<ws.CloseEvent> = { type: "close" };
271+
272+
mockSocket.onclose?.(closeEvent as ws.CloseEvent);
273+
274+
expect(openListener).not.toHaveBeenCalled();
275+
expect(closeListener).toHaveBeenCalledWith(closeEvent);
276+
expect(errorListener).not.toHaveBeenCalled();
277+
278+
vi.clearAllMocks();
279+
280+
// -- ERROR -- //
281+
282+
const errorEvent: Partial<ws.ErrorEvent> = { type: "error" };
283+
284+
mockSocket.onerror?.(errorEvent as ws.ErrorEvent);
285+
286+
expect(openListener).not.toHaveBeenCalled();
287+
expect(closeListener).not.toHaveBeenCalled();
288+
expect(errorListener).toHaveBeenCalledWith(errorEvent);
289+
});
290+
291+
it("should emit messages as RosbridgeMessage objects", () => {
292+
const transport = new WsWebSocketTransport(mockSocket);
293+
294+
const messageListener = vi.fn();
295+
296+
transport.on("message", messageListener);
297+
298+
const message: RosbridgeMessage = {
299+
op: "test",
300+
};
301+
302+
const messageEvent: ws.MessageEvent = {
303+
type: "message",
304+
target: mockSocket,
305+
data: JSON.stringify(message),
306+
};
307+
308+
mockSocket.onmessage?.(messageEvent);
309+
310+
expect(messageListener).toHaveBeenCalledWith(message);
311+
});
312+
});
313+
314+
describe("WebSocketTransportFactory", () => {
315+
it("uses native WebSocket when available", async () => {
316+
vi.stubGlobal("WebSocket", WebSocket);
317+
expect(typeof WebSocket).toBe("function");
318+
319+
const factory = new WebSocketTransportFactory();
320+
321+
const transport = await factory.createTransport("ws://localhost:9090");
322+
323+
expect(transport).toBeInstanceOf(NativeWebSocketTransport);
324+
});
325+
326+
it("uses ws package WebSocket when native WebSocket is not available", async () => {
327+
vi.stubGlobal("WebSocket", undefined);
328+
expect(typeof WebSocket).toBe("undefined");
329+
330+
const factory = new WebSocketTransportFactory();
331+
332+
const transport = await factory.createTransport("ws://localhost:9090");
333+
334+
expect(transport).toBeInstanceOf(WsWebSocketTransport);
335+
});
336+
});
337+
});

0 commit comments

Comments
 (0)