Skip to content

Commit bc5116b

Browse files
committed
Test transport handling of messages
1 parent 4e84e56 commit bc5116b

File tree

1 file changed

+212
-18
lines changed

1 file changed

+212
-18
lines changed

test/transport.test.ts

Lines changed: 212 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@ import {
88
WebSocketTransportFactory,
99
WsWebSocketTransport,
1010
} from "../src/core/Transport.js";
11-
import * as ws from "ws";
1211
import type {
1312
RosbridgeMessage,
1413
RosbridgePngMessage,
1514
} from "../src/types/protocol.js";
15+
import CBOR from "cbor-js";
1616
import pngparse from "pngparse";
17+
import * as bson from "bson";
18+
import * as ws from "ws";
1719

1820
vi.mock("pngparse");
1921

@@ -24,8 +26,12 @@ describe("Transport", () => {
2426
});
2527

2628
describe("AbstractTransport", () => {
29+
const messageListener = vi.fn();
30+
const errorListener = vi.fn();
31+
2732
let mockPngParseModule: MockedObject<typeof pngparse>;
2833
let mockSocket: MockedObject<WebSocket>;
34+
2935
let transport: AbstractTransport;
3036

3137
beforeEach(() => {
@@ -45,13 +51,12 @@ describe("Transport", () => {
4551
} as unknown as MockedObject<WebSocket>;
4652

4753
transport = new NativeWebSocketTransport(mockSocket);
48-
});
49-
50-
it("should handle RosbridgeMessage", () => {
51-
const messageListener = vi.fn();
5254

5355
transport.on("message", messageListener);
56+
transport.on("error", errorListener);
57+
});
5458

59+
it("should handle RosbridgeMessage", () => {
5560
const message: RosbridgeMessage = {
5661
op: "test",
5762
};
@@ -66,8 +71,109 @@ describe("Transport", () => {
6671
expect(messageListener).toHaveBeenCalledWith(message);
6772
});
6873

69-
it("should handle RosbridgeFragmentMessage", () => {
70-
// TODO
74+
describe("should handle RosbridgeFragmentMessage", () => {
75+
const sendFragment = (
76+
id: string,
77+
total: number,
78+
fragments: unknown[],
79+
) => {
80+
for (let i = 0; i < fragments.length; i++) {
81+
mockSocket.onmessage?.({
82+
type: "message",
83+
data: JSON.stringify({
84+
op: "fragment",
85+
id,
86+
data: fragments[i],
87+
num: i,
88+
total,
89+
}),
90+
} as MessageEvent);
91+
}
92+
};
93+
94+
it("reassembles fragments and emits message", () => {
95+
const id = "test1";
96+
const total = 3;
97+
const msg = { op: "publish", topic: "foo", msg: { data: 42 } };
98+
const json = JSON.stringify(msg);
99+
const fragments = [
100+
json.slice(0, 10),
101+
json.slice(10, 20),
102+
json.slice(20),
103+
];
104+
sendFragment(id, total, fragments);
105+
expect(messageListener).toHaveBeenCalledWith(msg);
106+
expect(messageListener).toHaveBeenCalledTimes(1);
107+
expect(errorListener).toHaveBeenCalledTimes(0);
108+
});
109+
110+
it("handles float total by using integer part", () => {
111+
const id = "test2";
112+
const total = 2.9;
113+
const msg = { op: "publish", topic: "bar", msg: { data: 99 } };
114+
const json = JSON.stringify(msg);
115+
const fragments = [json.slice(0, 10), json.slice(10)];
116+
sendFragment(id, total, fragments);
117+
expect(messageListener).toHaveBeenCalledWith(msg);
118+
expect(messageListener).toHaveBeenCalledTimes(1);
119+
expect(errorListener).toHaveBeenCalledTimes(0);
120+
});
121+
122+
it("handles extra fragments beyond integer total", () => {
123+
const id = "test3";
124+
const total = 2.1;
125+
const msg = { op: "publish", topic: "baz", msg: { data: 7 } };
126+
const json = JSON.stringify(msg);
127+
const fragments = [json.slice(0, 10), json.slice(10), "extra"];
128+
sendFragment(id, total, fragments);
129+
expect(messageListener).toHaveBeenCalledWith(msg);
130+
expect(messageListener).toHaveBeenCalledTimes(1);
131+
expect(errorListener).toHaveBeenCalledTimes(0);
132+
});
133+
134+
it("does not emit if fragments are missing", () => {
135+
const id = "test4";
136+
const total = 2;
137+
const msg = { op: "publish", topic: "qux", msg: { data: 123 } };
138+
const json = JSON.stringify(msg);
139+
const fragments = [json.slice(0, 10)]; // missing one fragment
140+
sendFragment(id, total, fragments);
141+
expect(messageListener).toHaveBeenCalledTimes(0);
142+
expect(errorListener).toHaveBeenCalledTimes(0);
143+
});
144+
145+
it("ignores malformed fragments", () => {
146+
mockSocket.onmessage?.({
147+
type: "message",
148+
data: JSON.stringify({ op: "fragment", id: "bad" }),
149+
} as MessageEvent);
150+
expect(messageListener).toHaveBeenCalledTimes(0);
151+
expect(errorListener).toHaveBeenCalledTimes(0);
152+
});
153+
154+
it("emits error when reassembled message is invalid", () => {
155+
const id = "test5";
156+
const total = 1;
157+
const fragments = ['{ "foo": "bar" }'];
158+
sendFragment(id, total, fragments);
159+
expect(messageListener).toHaveBeenCalledTimes(0);
160+
expect(errorListener).toHaveBeenCalledTimes(1);
161+
expect(errorListener).toHaveBeenCalledWith(
162+
new Error("Received invalid rosbridge message!"),
163+
);
164+
});
165+
166+
it("emits error when reassembled message is invalid JSON", () => {
167+
const id = "test6";
168+
const total = 1;
169+
const fragments = ["{ not valid json }"];
170+
sendFragment(id, total, fragments);
171+
expect(messageListener).toHaveBeenCalledTimes(0);
172+
expect(errorListener).toHaveBeenCalledTimes(1);
173+
expect(errorListener).toHaveBeenCalledWith(
174+
new Error("Fragments did not form a valid JSON message!"),
175+
);
176+
});
71177
});
72178

73179
it("should handle RosbridgePngMessage", async () => {
@@ -96,12 +202,6 @@ describe("Transport", () => {
96202
},
97203
);
98204

99-
const messageListener = vi.fn();
100-
const errorListener = vi.fn();
101-
102-
transport.on("message", messageListener);
103-
transport.on("error", errorListener);
104-
105205
// Obviously these are not real PNG encoded messages.
106206
// But they're good enough for mocking responses in our tests.
107207
const successMessage: RosbridgePngMessage = {
@@ -147,16 +247,110 @@ describe("Transport", () => {
147247
}, 500);
148248
});
149249

150-
it("should handle BSON message", () => {
151-
// TODO
250+
it("should handle BSON message", async () => {
251+
const goodBsonData = bson.serialize({ op: "test" });
252+
const successMessage = new Blob([Buffer.from(goodBsonData)]);
253+
254+
const badBsonData = bson.serialize({ foo: "bar" });
255+
const failureMessage = new Blob([Buffer.from(badBsonData)]);
256+
257+
// -- SUCCESS -- //
258+
259+
mockSocket.onmessage?.({
260+
type: "message",
261+
data: successMessage,
262+
} as MessageEvent);
263+
264+
// Wait for the message to be processed.
265+
// BSON decompression occurs asynchronously.
266+
await vi.waitFor(() => {
267+
expect(errorListener).not.toHaveBeenCalled();
268+
expect(messageListener).toHaveBeenCalledWith({ op: "test" });
269+
}, 500);
270+
271+
vi.clearAllMocks();
272+
273+
// -- FAILURE -- //
274+
275+
mockSocket.onmessage?.({
276+
type: "message",
277+
data: failureMessage,
278+
} as MessageEvent);
279+
280+
// Wait for the message to be processed.
281+
// BSON decompression occurs asynchronously.
282+
await vi.waitFor(() => {
283+
expect(errorListener).toHaveBeenCalledWith(
284+
new Error("Decoded BSON data was invalid!"),
285+
);
286+
expect(messageListener).not.toHaveBeenCalled();
287+
}, 500);
152288
});
153289

154-
it("should handle CBOR message", () => {
155-
// TODO
290+
it("should handle CBOR message", async () => {
291+
const successMessage = CBOR.encode({ op: "test" });
292+
const failureMessage = CBOR.encode({ foo: "bar" });
293+
294+
// -- SUCCESS -- //
295+
296+
mockSocket.onmessage?.({
297+
type: "message",
298+
data: successMessage,
299+
} as MessageEvent);
300+
301+
// Wait for the message to be processed.
302+
// CBOR decompression occurs asynchronously.
303+
await vi.waitFor(() => {
304+
expect(errorListener).not.toHaveBeenCalled();
305+
expect(messageListener).toHaveBeenCalledWith({ op: "test" });
306+
}, 500);
307+
308+
vi.clearAllMocks();
309+
310+
// -- FAILURE -- //
311+
312+
mockSocket.onmessage?.({
313+
type: "message",
314+
data: failureMessage,
315+
} as MessageEvent);
316+
317+
// Wait for the message to be processed.
318+
// CBOR decompression occurs asynchronously.
319+
await vi.waitFor(() => {
320+
expect(errorListener).toHaveBeenCalledWith(
321+
new Error("Decoded CBOR data was invalid!"),
322+
);
323+
expect(messageListener).not.toHaveBeenCalled();
324+
}, 500);
156325
});
157326

158327
it("should handle JSON message", () => {
159-
// TODO
328+
const successMessage = JSON.stringify({ op: "test" });
329+
const failureMessage = JSON.stringify({ foo: "bar" });
330+
331+
// -- SUCCESS -- //
332+
333+
mockSocket.onmessage?.({
334+
type: "message",
335+
data: successMessage,
336+
} as MessageEvent);
337+
338+
expect(errorListener).not.toHaveBeenCalled();
339+
expect(messageListener).toHaveBeenCalledWith({ op: "test" });
340+
341+
vi.clearAllMocks();
342+
343+
// -- FAILURE -- //
344+
345+
mockSocket.onmessage?.({
346+
type: "message",
347+
data: failureMessage,
348+
} as MessageEvent);
349+
350+
expect(errorListener).toHaveBeenCalledWith(
351+
new Error("Received invalid rosbridge message!"),
352+
);
353+
expect(messageListener).not.toHaveBeenCalled();
160354
});
161355
});
162356

0 commit comments

Comments
 (0)