1// Copyright 2022 The Pigweed Authors 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); you may not 4// use this file except in compliance with the License. You may obtain a copy of 5// the License at 6// 7// https://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12// License for the specific language governing permissions and limitations under 13// the License. 14 15/* eslint-env browser */ 16import { SerialMock } from '../transport/serial_mock'; 17import { Device } from './'; 18import { ProtoCollection } from 'pigweedjs/protos/collection'; 19import { WebSerialTransport } from '../transport/web_serial_transport'; 20import { Serial } from 'pigweedjs/types/serial'; 21import { Message } from 'google-protobuf'; 22import { 23 RpcPacket, 24 PacketType, 25} from 'pigweedjs/protos/pw_rpc/internal/packet_pb'; 26import { 27 Method, 28 ServerStreamingMethodStub, 29 UnaryMethodStub, 30} from 'pigweedjs/pw_rpc'; 31import { Status } from 'pigweedjs/pw_status'; 32import { Response } from 'pigweedjs/protos/pw_rpc/ts/test_pb'; 33 34import { EchoMessage } from 'pigweedjs/protos/pw_rpc/echo_pb'; 35 36describe('WebSerialTransport', () => { 37 let device: Device; 38 let serialMock: SerialMock; 39 40 function newResponse(payload = '._.'): Message { 41 const response = new Response(); 42 response.setPayload(payload); 43 return response; 44 } 45 46 function generateResponsePacket( 47 channelId: number, 48 method: Method, 49 status: Status, 50 callId: number, 51 response?: Message, 52 ) { 53 const packet = new RpcPacket(); 54 packet.setType(PacketType.RESPONSE); 55 packet.setChannelId(channelId); 56 packet.setServiceId(method.service.id); 57 packet.setMethodId(method.id); 58 packet.setCallId(callId); 59 packet.setStatus(status); 60 if (response === undefined) { 61 packet.setPayload(new Uint8Array(0)); 62 } else { 63 packet.setPayload(response.serializeBinary()); 64 } 65 return packet.serializeBinary(); 66 } 67 68 function generateStreamingPacket( 69 channelId: number, 70 method: Method, 71 response: Message, 72 callId: number, 73 status: Status = Status.OK, 74 ) { 75 const packet = new RpcPacket(); 76 packet.setType(PacketType.SERVER_STREAM); 77 packet.setChannelId(channelId); 78 packet.setServiceId(method.service.id); 79 packet.setMethodId(method.id); 80 packet.setCallId(callId); 81 packet.setPayload(response.serializeBinary()); 82 packet.setStatus(status); 83 return packet.serializeBinary(); 84 } 85 86 beforeEach(() => { 87 serialMock = new SerialMock(); 88 device = new Device( 89 new ProtoCollection(), 90 new WebSerialTransport(serialMock as Serial), 91 ); 92 }); 93 94 it('has rpcs defined', () => { 95 expect(device.rpcs).toBeDefined(); 96 expect(device.rpcs.pw.rpc.EchoService.Echo).toBeDefined(); 97 }); 98 99 it('has method arguments data', () => { 100 expect(device.getMethodArguments('pw.rpc.EchoService.Echo')).toStrictEqual([ 101 'msg', 102 ]); 103 expect(device.getMethodArguments('pw.test2.Alpha.Unary')).toStrictEqual([ 104 'magic_number', 105 ]); 106 }); 107 108 it('unary rpc sends request to serial', async () => { 109 const methodStub = device.client 110 .channel()! 111 .methodStub('pw.rpc.EchoService.Echo')! as UnaryMethodStub; 112 const responseMsg = new EchoMessage(); 113 responseMsg.setMsg('hello'); 114 await device.connect(); 115 const nextCallId = methodStub.rpcs.nextCallId; 116 setTimeout(() => { 117 device.client.processPacket( 118 generateResponsePacket( 119 1, 120 methodStub.method, 121 Status.OK, 122 nextCallId, 123 responseMsg, 124 ), 125 ); 126 }, 10); 127 const [status, response] = 128 await device.rpcs.pw.rpc.EchoService.Echo('hello'); 129 expect(response.getMsg()).toBe('hello'); 130 expect(status).toBe(0); 131 }); 132 133 it('server streaming rpc sends response', async () => { 134 await device.connect(); 135 const response1 = newResponse('!!!'); 136 const response2 = newResponse('?'); 137 const serverStreaming = device.client 138 .channel() 139 ?.methodStub( 140 'pw.rpc.test1.TheTestService.SomeServerStreaming', 141 ) as ServerStreamingMethodStub; 142 const nextCallId = serverStreaming.rpcs.nextCallId; 143 const onNext = jest.fn(); 144 const onCompleted = jest.fn(); 145 const onError = jest.fn(); 146 147 device.rpcs.pw.rpc.test1.TheTestService.SomeServerStreaming( 148 4, 149 onNext, 150 onCompleted, 151 onError, 152 ); 153 device.client.processPacket( 154 generateStreamingPacket(1, serverStreaming.method, response1, nextCallId), 155 ); 156 device.client.processPacket( 157 generateStreamingPacket(1, serverStreaming.method, response2, nextCallId), 158 ); 159 device.client.processPacket( 160 generateResponsePacket( 161 1, 162 serverStreaming.method, 163 Status.ABORTED, 164 nextCallId, 165 ), 166 ); 167 168 expect(onNext).toBeCalledWith(response1); 169 expect(onNext).toBeCalledWith(response2); 170 expect(onCompleted).toBeCalledWith(Status.ABORTED); 171 }); 172}); 173