def test_read_multiple_messages(self): # Setup: # ... Create an input stream with two messages test_bytes = b'Content-Length: 30\r\n\r\n{"method":"test", "params":{}}' input_stream = io.BytesIO(test_bytes + test_bytes) output_stream = io.BytesIO() # ... Create a server that uses the input and output streams server = JSONRPCServer(input_stream, output_stream, logger=utils.get_mock_logger()) # ... Patch the server to not dispatch a message dispatch_mock = mock.MagicMock() server._dispatch_message = dispatch_mock # If: I start the server, run it for a bit, and stop it server.start() time.sleep(1) server.stop() server.wait_for_exit() # Then: The dispatch method should have been called twice expected_output = JSONRPCMessage.from_dictionary({"method": "test", "params": {}}) self.assertEqual(len(dispatch_mock.mock_calls), 2) self.assertDictEqual(dispatch_mock.mock_calls[0][1][0].dictionary, expected_output.dictionary) self.assertDictEqual(dispatch_mock.mock_calls[1][1][0].dictionary, expected_output.dictionary) # Teardown: All background threads should be shut down. self.assertFalse(server._input_consumer.isAlive()) self.assertFalse(server._output_consumer.isAlive())
def test_send_message(self): with io.BytesIO(b'') as stream: # If: # ... I create a JSON RPC writer writer = JSONRPCWriter(stream, logger=utils.get_mock_logger()) # ... and I send a message message = JSONRPCMessage.create_request('123', 'test/test', {}) writer.send_message(message) # Then: # ... The content-length header should be present stream.seek(0) header = stream.readline().decode('ascii') self.assertRegex( header, re.compile('^Content-Length: [0-9]+\r\n$', re.IGNORECASE)) # ... There should be a blank line to signify the end of the headers blank_line = stream.readline().decode('ascii') self.assertEqual(blank_line, '\r\n') # ... The JSON message as a dictionary should match the dictionary of the message message_str = str.join( os.linesep, [x.decode('UTF-8') for x in stream.readlines()]) message_dict = json.loads(message_str) self.assertDictEqual(message_dict, message.dictionary)
def send_response(self, params): """ Sends a successful response to this request :param params: Data to send back with the response """ message = JSONRPCMessage.create_response(self._message.message_id, params) self._queue.put(message)
def send_notification(self, method, params): """ Sends a notification, independent to this request :param method: String name of the method for the notification :param params: Data to send with the notification """ message = JSONRPCMessage.create_notification(method, params) self._queue.put(message)
def send_notification(self, method, params): """ Sends a new notification over the JSON RPC channel :param method: String name of the method of the notification being send :param params: Any data to send along with the notification """ message = JSONRPCMessage.create_notification(method, params) self._queue.put(message)
def test_request_context_init_test(self): # If: I create a request context queue = Queue() message = JSONRPCMessage.from_dictionary({'id': '123', 'method': 'test/text/', 'params': {}}) rc = RequestContext(message, queue) # Then: The internal state should be set up correctly self.assertIs(rc._message, message) self.assertIs(rc._queue, queue)
def _received_error_callback(self, message: str, data: any = None, code: int = 0): error = ReceivedError(code, message, data) rpc_message = JSONRPCMessage.create_error(0, code, message, data) received_message = ReceivedMessage(JSONRPCMessageType.ResponseError, None, error, type(data), rpc_message) self._received_messages.append(received_message)
def test_initialization(self, mockdataeditorsession): queue = Queue() message = JSONRPCMessage.from_dictionary({ 'id': '123', 'method': 'edit/initialize', 'params': {} }) request_context = RequestContext(message, queue) self._service_under_test._edit_initialize( request_context, self._initialize_edit_request) mockdataeditorsession.assert_called()
def send_error(self, message, data=None, code=0): """ Sends a failure response to this request :param message: Concise 1-sentence message explanation of the error :param data: Optional data to send back with the error :param code: Optional error code to identify the error """ message = JSONRPCMessage.create_error(self._message.message_id, code, message, data) self._queue.put(message)
def send_notification(self, method, params): """ Sends a notification, independent of any request :param method: String name of the method for the notification :param params: Data to send with the notification """ # Create the message message = JSONRPCMessage.create_notification(method, params) # TODO: Add support for handlers for the responses # Add the message to the output queue self._output_queue.put(message)
def test_dispatch_notification_no_handler(): # If: I dispatch a message that has no handler logger = utils.get_mock_logger() message = JSONRPCMessage.create_notification('non_existent', {}) server = JSONRPCServer(None, None, logger=logger) server._dispatch_message(message) # Then: # ... Nothing should have happened # TODO: Capture that an error was sent # ... A warning should have been logged logger.warn.assert_called_once()
def send_request(self, method, params): """ Add a new request to the output queue :param method: Method string of the request :param params: Data to send along with the request """ message_id = str(uuid.uuid4()) # Create the message message = JSONRPCMessage.create_request(message_id, method, params) # TODO: Add support for handlers for the responses # Add the message to the output queue self._output_queue.put(message)
def read_message(self): """ Read JSON RPC message from buffer :raises ValueError: if the body-content cannot be serialized to a JSON object :return: JsonRpcMessage that was received """ # Using a mutable list to hold the value since an immutable string passed by reference won't # change the value content = [''] try: while not self._needs_more_data or self._read_next_chunk(): # We should have all the data we need to form a message in the buffer. If we need # more data to form the next message, this flag will be reset by an attempt to form # a header or content self._needs_more_data = False # If we can't read a header, read the next chunk if self._read_state is self.ReadState.Header and not self._try_read_headers( ): self._needs_more_data = True continue # If we read the header, try the content. If that fails, read the next chunk if self._read_state is self.ReadState.Content and not self._try_read_content( content): self._needs_more_data = True continue # We have the content break # Uncomment for verbose logging # if self._logger is not None: # self._logger.debug(f'{content[0]}') return JSONRPCMessage.from_dictionary(json.loads(content[0])) except ValueError as ve: # Response has invalid json object if self._logger is not None: self._logger.warn( 'JSON RPC reader on read_message() encountered exception: {}' .format(ve)) raise finally: # Remove the bytes that have been read self._trim_buffer_and_resize(self._read_offset)
def test_request_context_send_response(self): # Setup: Create a request context queue = Queue() in_message = JSONRPCMessage.from_dictionary({'id': '123', 'method': 'test/text/', 'params': {}}) rc = RequestContext(in_message, queue) # If: I send a response via the response handler params = {} rc.send_response(params) # Then: # ... There should be a message in the outbound queue self.assertTrue(queue.not_empty) out_message = queue.get_nowait() self.assertIsInstance(out_message, JSONRPCMessage) # .. The message must be a response with the proper id self.assertEqual(out_message.message_type, JSONRPCMessageType.ResponseSuccess) self.assertEqual(out_message.message_id, '123') self.assertEqual(out_message.message_result, params)
def test_dispatch_notification_normal(self): # Setup: Create a server with a single handler that has none for the deserialization class config = IncomingMessageConfiguration('test/test', _TestParams) handler = mock.MagicMock() server = JSONRPCServer(None, None, logger=utils.get_mock_logger()) server.set_notification_handler(config, handler) # If: I dispatch a message that has none set for the deserialization class params = {} message = JSONRPCMessage.create_notification('test/test', params) server._dispatch_message(message) # Then: # ... The handler should have been called handler.assert_called_once() # ... The parameters to the handler should have been a request context and params self.assertIsInstance(handler.mock_calls[0][1][0], NotificationContext) self.assertIs(handler.mock_calls[0][1][0]._queue, server._output_queue) self.assertIsInstance(handler.mock_calls[0][1][1], _TestParams)
def test_request_context_send_notification(self): # Setup: Create a request context queue = Queue() in_message = JSONRPCMessage.from_dictionary({'id': '123', 'method': 'test/text/', 'params': {}}) rc = RequestContext(in_message, queue) # If: I send a notification params = {} method = 'test/test' rc.send_notification(method, params) # Then: # ... There should be a message in the outbound queue self.assertTrue(queue.not_empty) out_message = queue.get_nowait() self.assertIsInstance(out_message, JSONRPCMessage) # .. The message must be a response with the proper id self.assertEqual(out_message.message_type, JSONRPCMessageType.Notification) self.assertIsNone(out_message.message_id) self.assertEqual(out_message.message_params, params)
def _received_response_callback(self, params: any): rpc_message = JSONRPCMessage.create_response(0, params) received_message = ReceivedMessage(JSONRPCMessageType.ResponseSuccess, None, params, type(params), rpc_message) self._received_messages.append(received_message)
def _received_notification_callback(self, method: str, params: any): rpc_message = JSONRPCMessage.create_notification(method, params) received_message = ReceivedMessage(JSONRPCMessageType.Notification, method, params, type(params), rpc_message) self._received_messages.append(received_message)
def test_dispatch_response_success(): # TODO: Replace with robust logic once response routing is implemented # If: I dispatch a response message message = JSONRPCMessage.create_response('123', {}) server = JSONRPCServer(None, None, logger=utils.get_mock_logger()) server._dispatch_message(message)
def test_dispatch_response_error(): # TODO: Replace with robust logic once error routing is implemented # If: I dispatch an error message message = JSONRPCMessage.create_error('123', 0, message='', data={}) server = JSONRPCServer(None, None, logger=utils.get_mock_logger()) server._dispatch_message(message)
def test_dispatch_invalid(): # If: I dispatch an invalid message message = JSONRPCMessage('invalidType') server = JSONRPCServer(None, None, logger=utils.get_mock_logger()) server._dispatch_message(message)