@@ -8,12 +8,14 @@ import {
88 WebSocketTransportFactory ,
99 WsWebSocketTransport ,
1010} from "../src/core/Transport.js" ;
11- import * as ws from "ws" ;
1211import type {
1312 RosbridgeMessage ,
1413 RosbridgePngMessage ,
1514} from "../src/types/protocol.js" ;
15+ import CBOR from "cbor-js" ;
1616import pngparse from "pngparse" ;
17+ import * as bson from "bson" ;
18+ import * as ws from "ws" ;
1719
1820vi . 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