async def test_can_handle_connected_frame_without_heartbeat( self, heartbeater_klass_mock): frame = Frame("CONNECTED", {}, "{}") stomp = StompReader(None, self.loop) await stomp._handle_connect(frame) heartbeater_klass_mock.assert_not_called()
async def test_can_handle_exception(self, logger_mock): frame = Frame('SOMETHING', {'message': 'Invalid error, blah, blah, blah'}, 'Detail Error: blah, blahh-line-a') stomp = StompReader(None, self.loop) await stomp._handle_exception(frame) logger_mock.warn.assert_called_with('Unhandled frame: SOMETHING')
def test_cannot_ack_an_unsubscribed_frame(self): self.stomp._protocol.ack = Mock() self.assertEqual(len(self.stomp._subscriptions), 0) frame = Frame("MESSAGE", {"subscription": "1"}, "data") with self.assertLogs() as cm: self.stomp.ack(frame) self.assertIn("WARNING:aiostomp:Subscription 1 not found", cm.output) self.stomp._protocol.ack.assert_not_called()
def test_cannot_ack_an_unsubscribed_frame(self): self.stomp._protocol.ack = Mock() self.assertEqual(len(self.stomp._subscriptions), 0) frame = Frame('MESSAGE', {'subscription': '1'}, 'data') with self.assertLogs() as cm: self.stomp.ack(frame) self.assertIn('WARNING:aiostomp:Subscription 1 not found', cm.output) self.stomp._protocol.ack.assert_not_called()
def process_command(self) -> None: body: Optional[bytes] = bytes(self.current_command) if body == b"": body = None frame = Frame(self.action or "", self.headers, body) self._frames_ready.append(frame) self.processed_headers = False self.awaiting_command = True self.content_length = -1 self.current_command.clear()
def process_command(self): body = b''.join(self.current_command) if body == b'': body = None frame = Frame(self.action, self.headers, body) self._frames_ready.append(frame) self.processed_headers = False self.awaiting_command = True self.content_length = -1 self.current_command.clear()
def test_can_nack_a_frame(self): self.stomp._protocol.subscribe = Mock() self.stomp._protocol.nack = Mock() self.stomp.subscribe('/queue/test', auto_ack=False) self.assertEqual(len(self.stomp._subscriptions), 1) frame = Frame('MESSAGE', {'subscription': '1'}, 'data') self.stomp.nack(frame) self.stomp._protocol.nack.assert_called_with(frame)
def test_can_nack_a_frame(self): self.stomp._protocol.subscribe = Mock() self.stomp._protocol.nack = Mock() self.stomp.subscribe("/queue/test", auto_ack=False) self.assertEqual(len(self.stomp._subscriptions), 1) frame = Frame("MESSAGE", {"subscription": "1"}, "data") self.stomp.nack(frame) self.stomp._protocol.nack.assert_called_with(frame)
async def test_can_handle_exception(self): frame = Frame( "SOMETHING", {"message": "Invalid error, blah, blah, blah"}, "Detail Error: blah, blahh-line-a", ) stomp = StompReader(None, self.loop) with self.assertLogs("aiostomp", level="WARNING") as cm: await stomp._handle_exception(frame) self.assertEqual(cm.output, ["WARNING:aiostomp:Unhandled frame: SOMETHING"])
async def test_can_handle_exception(self): frame = Frame( 'SOMETHING', {'message': 'Invalid error, blah, blah, blah'}, 'Detail Error: blah, blahh-line-a', ) stomp = StompReader(None, self.loop) with self.assertLogs('aiostomp', level="WARNING") as cm: await stomp._handle_exception(frame) self.assertEqual(cm.output, ["WARNING:aiostomp:Unhandled frame: SOMETHING"])
async def test_can_handle_message_with_no_subscription(self): frame = Frame("MESSAGE", { "subscription": "123", "message-id": "321" }, "blah") handler = CoroutineMock() frame_handler = Mock() frame_handler.get.return_value = None stomp = StompReader(frame_handler, self.loop) await stomp._handle_message(frame) handler.assert_not_called()
async def test_can_handle_message_with_no_subscription(self): frame = Frame('MESSAGE', { 'subscription': '123', 'message-id': '321' }, 'blah') handler = CoroutineMock() frame_handler = Mock() frame_handler.get.return_value = None stomp = StompReader(frame_handler, self.loop) await stomp._handle_message(frame) handler.assert_not_called()
def test_cannot_ack_an_auto_ack_frame(self): self.stomp._protocol.subscribe = Mock() self.stomp._protocol.ack = Mock() self.stomp.subscribe("/queue/test", auto_ack=True) self.assertEqual(len(self.stomp._subscriptions), 1) frame = Frame("MESSAGE", {"subscription": "1"}, "data") with self.assertLogs() as cm: self.stomp.ack(frame) self.assertIn( "WARNING:aiostomp:Auto ack/nack is enabled. Ignoring call.", cm.output) self.stomp._protocol.ack.assert_not_called()
async def test_can_handle_message(self): frame = Frame("MESSAGE", { "subscription": "123", "message-id": "321" }, "blah") handler = CoroutineMock() subscription = Subscription("123", 1, "auto", {}, handler) frame_handler = Mock() frame_handler.get.return_value = subscription stomp = StompReader(frame_handler, self.loop) await stomp._handle_message(frame) handler.assert_called_with(frame, frame.body)
def test_cannot_ack_an_auto_ack_frame(self): self.stomp._protocol.subscribe = Mock() self.stomp._protocol.ack = Mock() self.stomp.subscribe('/queue/test', auto_ack=True) self.assertEqual(len(self.stomp._subscriptions), 1) frame = Frame('MESSAGE', {'subscription': '1'}, 'data') with self.assertLogs() as cm: self.stomp.ack(frame) self.assertIn( 'WARNING:aiostomp:Auto ack/nack is enabled. Ignoring call.', cm.output) self.stomp._protocol.ack.assert_not_called()
async def test_can_handle_message(self): frame = Frame('MESSAGE', { 'subscription': '123', 'message-id': '321' }, 'blah') handler = CoroutineMock() subscription = Subscription('123', 1, 'auto', {}, handler) frame_handler = Mock() frame_handler.get.return_value = subscription stomp = StompReader(frame_handler, self.loop) await stomp._handle_message(frame) handler.assert_called_with(frame, frame.body)
async def test_can_handle_connected_frame_with_heartbeat_disabled( self, heartbeater_klass_mock): frame = Frame("CONNECTED", {"heart-beat": "1000,1000"}, "{}") heartbeater_mock = heartbeater_klass_mock.return_value heartbeater_mock.start = CoroutineMock() stomp = StompReader(None, self.loop, heartbeat={ "enabled": False, "cx": 0, "cy": 0 }) stomp._transport = Mock() await stomp._handle_connect(frame) heartbeater_klass_mock.assert_not_called
def _process_frame(self, data): command, remaining = data.split(b'\n', 1) command = self._decode(command) raw_headers, remaining = remaining.split(b'\n\n', 1) raw_headers = self._decode(raw_headers) headers = dict([l.split(':', 1) for l in raw_headers.split('\n')]) body = None # Only SEND, MESSAGE and ERROR frames can have body if remaining and command in ('SEND', 'MESSAGE', 'ERROR'): if 'content-length' in headers: body = remaining[:int(headers['content-length'])] else: body = remaining self._frames_ready.append(Frame(command, headers=headers, body=body))
async def test_can_handle_connected_frame_with_heartbeat_disabled( self, heartbeater_klass_mock): frame = Frame('CONNECTED', {'heart-beat': '1000,1000'}, '{}') heartbeater_mock = heartbeater_klass_mock.return_value heartbeater_mock.start = CoroutineMock() stomp = StompReader(None, self.loop, heartbeat={ 'enabled': False, 'cx': 0, 'cy': 0 }) stomp._transport = Mock() await stomp._handle_connect(frame) heartbeater_klass_mock.assert_not_called
async def test_can_handle_error_frame(self, logger_mock): frame = Frame('ERROR', {'message': 'Invalid error, blah, blah, blah'}, 'Detail Error: blah, blahh-line-a') frame_handler = Mock() frame_handler._on_error = CoroutineMock() stomp = StompReader(frame_handler, self.loop) await stomp._handle_error(frame) frame_handler._on_error.assert_called_once() self.assertTrue( isinstance(frame_handler._on_error.call_args[0][0], StompError)) logger_mock.error.assert_called_with( 'Received error: Invalid error, blah, blah, blah') logger_mock.debug.assert_called_with( 'Error details: Detail Error: blah, blahh-line-a')
async def test_can_handle_message_can_nack(self, send_frame_mock): frame = Frame('MESSAGE', { 'subscription': '123', 'message-id': '321' }, 'blah') handler = CoroutineMock() handler.return_value = False subscription = Subscription('123', 1, 'client-individual', {}, handler) frame_handler = Mock() frame_handler.get.return_value = subscription stomp = StompReader(frame_handler, self.loop) await stomp._handle_message(frame) handler.assert_called_with(frame, frame.body) send_frame_mock.assert_called_with('NACK', { 'subscription': '123', 'message-id': '321' })
async def test_can_handle_message_can_nack(self, send_frame_mock): frame = Frame("MESSAGE", { "subscription": "123", "message-id": "321" }, "blah") handler = CoroutineMock() handler.return_value = False subscription = Subscription("123", 1, "client-individual", {}, handler) frame_handler = Mock() frame_handler.get.return_value = subscription stomp = StompReader(frame_handler, self.loop) await stomp._handle_message(frame) handler.assert_called_with(frame, frame.body) send_frame_mock.assert_called_with("NACK", { "subscription": "123", "message-id": "321" })
def _feed_data(self, data): if data is None: return None if not self._pending_parts and data.startswith(self.HEART_BEAT): self._frames_ready.append(Frame('HEARTBEAT', headers={}, body='')) data = data[1:] if data: return data before_eof, sep, after_eof = data.partition(self.EOF) if before_eof: self._pending_parts.append(before_eof) if sep: frame_data = b''.join(self._pending_parts) self._pending_parts = [] self._process_frame(frame_data) if after_eof: return after_eof
def _feed_data(self, data): if data.startswith(self.HEART_BEAT) and not self._pending_parts: self._frames_ready.append(Frame('HEARTBEAT', headers={}, body='')) data = data[1:] if not data: return self._pending_parts.append(data) extra_data, partial_data = self._parse_data(b''.join( self._pending_parts)) if partial_data: self._pending_parts = [partial_data] else: self._pending_parts = [] # Check if the frame is incomplete or has # additional data if extra_data: self._pending_parts = [] return extra_data
def feed_data(self, inp: bytes) -> None: read_size = len(inp) data: Deque[int] = deque(inp) i = 0 while i < read_size: i += 1 b = bytes([data.popleft()]) if (not self.processed_headers and self.previous_byte == self.EOF and b == self.EOF): continue if not self.processed_headers: if self.awaiting_command and b == b"\n": self._frames_ready.append( Frame("HEARTBEAT", headers={}, body=None)) continue else: self.awaiting_command = False self.current_command.append(b[0]) if b == b"\n" and (self.previous_byte == b"\n" or ends_with_crlf(self.current_command)): try: self.action = self._parse_action(self.current_command) self.headers = self._parse_headers( self.current_command) logger.debug("Parsed action %s", self.action) if (self.action in ("SEND", "MESSAGE", "ERROR") and "content-length" in self.headers): self.content_length = int( self.headers["content-length"]) else: self.content_length = -1 except Exception: self.current_command.clear() return self.processed_headers = True self.current_command.clear() else: if self.content_length == -1: if b == self.EOF: self.process_command() else: self.current_command.append(b[0]) if len(self.current_command) > self.MAX_DATA_LENGTH: # error return else: if self.read_length == self.content_length: self.process_command() self.read_length = 0 else: self.read_length += 1 self.current_command.append(b[0]) self.previous_byte = b
def _parse_data(self, data): if not self._intermediate_frame: command, data = data.split(b'\n', 1) command = self._decode(command) self._intermediate_frame = {'command': command} if 'headers' not in self._intermediate_frame: headers_body = data.split(b'\n\n', 1) if len(headers_body) < 2: return None, data raw_headers, data = headers_body raw_headers = self._decode(raw_headers) headers = dict([l.split(':', 1) for l in raw_headers.split('\n')]) self._intermediate_frame['headers'] = headers # After parsing the headers if any, there must be EOF to signal # end of frame, failing which return all data if not data: return None, None if self._intermediate_frame['command'] not in\ ('SEND', 'MESSAGE', 'ERROR'): self._intermediate_frame['body'] = None self._frames_ready.append(Frame(**self._intermediate_frame)) self._intermediate_frame = None # For commands not allowed to have body, discard all data # between headers and the end of frame _, extra = data.split(self.EOF, 1) return extra, None # Only SEND, MESSAGE and ERROR frames can have body headers = self._intermediate_frame['headers'] if 'content-length' in headers: content_length = int(headers['content-length']) if 'body' not in self._intermediate_frame: self._intermediate_frame['body'] = b'' existing_length = len(self._intermediate_frame['body']) missing_length = content_length - existing_length # Wait till the entire body is received if len(data) <= missing_length: self._intermediate_frame['body'] += data return None, None self._intermediate_frame['body'] += data[:missing_length] self._frames_ready.append(Frame(**self._intermediate_frame)) self._intermediate_frame = None # Split at the end of the frame which is at the end of the body # and return the rest for further processing _, extra = data[missing_length:].split(self.EOF, 1) return extra, None else: if 'body' not in self._intermediate_frame: self._intermediate_frame['body'] = b'' # Try to find the end of the frame body, sep, extra = data.partition(self.EOF) # If end of frame is not found, return the entire data # as partial data to be processed when the rest of the frame # arrives if not sep: self._intermediate_frame['body'] += data return None, None self._intermediate_frame['body'] += body self._frames_ready.append(Frame(**self._intermediate_frame)) self._intermediate_frame = None return extra, None
def feed_data(self, data): read_size = len(data) data = deque(data) i = 0 while i < read_size: i += 1 b = bytes([data.popleft()]) if (not self.processed_headers and self.previous_byte == self.EOF and b == self.EOF): continue if not self.processed_headers: if self.awaiting_command and b == b'\n': self._frames_ready.append( Frame('HEARTBEAT', headers={}, body='')) continue else: self.awaiting_command = False self.current_command.append(b) if b == b'\n' and (self.previous_byte == b'\n' or self.ends_with_crlf(self.current_command)): try: self.action = self._parse_action(self.current_command) self.headers = self._parse_headers( self.current_command) if (self.action in ('SEND', 'MESSAGE', 'ERROR') and 'content-length' in self.headers): self.content_length = int( self.headers['content-length']) else: self.content_length = -1 except Exception: self.current_command.clear() return self.processed_headers = True self.current_command.clear() else: if self.content_length == -1: if b == self.EOF: self.process_command() else: self.current_command.append(b) if len(self.current_command) > self.MAX_DATA_LENGTH: # error return else: if self.read_length == self.content_length: self.process_command() self.read_length = 0 else: self.read_length += 1 self.current_command.append(b) self.previous_byte = b