diff --git a/libraries/botframework-streaming/tests/test_header_serializer.py b/libraries/botframework-streaming/tests/test_header_serializer.py new file mode 100644 index 000000000..ac0be0c6b --- /dev/null +++ b/libraries/botframework-streaming/tests/test_header_serializer.py @@ -0,0 +1,136 @@ +from typing import List +from unittest import TestCase +from uuid import uuid4, UUID + +import pytest +from botframework.streaming.payloads import HeaderSerializer +from botframework.streaming.payloads.models import Header, PayloadTypes +from botframework.streaming.transport import TransportConstants + + +class TestHeaderSerializer(TestCase): + def test_can_round_trip(self): + header = Header() + header.type = PayloadTypes.REQUEST + header.payload_length = 168 + header.id = uuid4() + header.end = True + + buffer: List[int] = [None] * TransportConstants.MAX_PAYLOAD_LENGTH + offset: int = 0 + + length = HeaderSerializer.serialize(header, buffer, offset) + result = HeaderSerializer.deserialize(buffer, 0, length) + + self.assertEqual(header.type, result.type) + self.assertEqual(header.payload_length, result.payload_length) + self.assertEqual(header.id, result.id) + self.assertEqual(header.end, result.end) + + def test_serializes_to_ascii(self): + header = Header() + header.type = PayloadTypes.REQUEST + header.payload_length = 168 + header.id = uuid4() + header.end = True + + buffer: List[int] = [None] * TransportConstants.MAX_PAYLOAD_LENGTH + offset: int = 0 + + length = HeaderSerializer.serialize(header, buffer, offset) + decoded = bytes(buffer[offset:length]).decode("ascii") + + self.assertEqual(f"A.000168.{str(header.id)}.1\n", decoded) + + def test_deserializes_from_ascii(self): + header_id: UUID = uuid4() + header: str = f"A.000168.{str(header_id)}.1\n" + buffer: List[int] = list(bytes(header, "ascii")) + + result = HeaderSerializer.deserialize(buffer, 0, len(buffer)) + + self.assertEqual("A", result.type) + self.assertEqual(168, result.payload_length) + self.assertEqual(header_id, result.id) + self.assertTrue(result.end) + + def test_deserialize_unknown_type(self): + header_id: UUID = uuid4() + header: str = f"Z.000168.{str(header_id)}.1\n" + buffer: List[int] = list(bytes(header, "ascii")) + + result = HeaderSerializer.deserialize(buffer, 0, len(buffer)) + + self.assertEqual("Z", result.type) + self.assertEqual(168, result.payload_length) + + def test_deserialize_length_too_short_throws(self): + header_id: UUID = uuid4() + header: str = f"A.000168.{str(header_id)}.1\n" + buffer: List[int] = list(bytes(header, "ascii")) + + with pytest.raises(ValueError): + HeaderSerializer.deserialize(buffer, 0, 5) + + def test_deserialize_length_too_long_throws(self): + header_id: UUID = uuid4() + header: str = f"A.000168.{str(header_id)}.1\n" + buffer: List[int] = list(bytes(header, "ascii")) + + with pytest.raises(ValueError): + HeaderSerializer.deserialize(buffer, 0, 55) + + def test_deserialize_bad_type_delimiter_throws(self): + header_id: UUID = uuid4() + header: str = f"Ax000168.{str(header_id)}.1\n" + buffer: List[int] = list(bytes(header, "ascii")) + + with pytest.raises(ValueError): + HeaderSerializer.deserialize(buffer, 0, len(buffer)) + + def test_deserialize_bad_length_delimiter_throws(self): + header_id: UUID = uuid4() + header: str = f"A.000168x{str(header_id)}.1\n" + buffer: List[int] = list(bytes(header, "ascii")) + + with pytest.raises(ValueError): + HeaderSerializer.deserialize(buffer, 0, len(buffer)) + + def test_deserialize_bad_id_delimiter_throws(self): + header_id: UUID = uuid4() + header: str = f"A.000168.{str(header_id)}x1\n" + buffer: List[int] = list(bytes(header, "ascii")) + + with pytest.raises(ValueError): + HeaderSerializer.deserialize(buffer, 0, len(buffer)) + + def test_deserialize_bad_terminator_throws(self): + header_id: UUID = uuid4() + header: str = f"A.000168.{str(header_id)}.1c" + buffer: List[int] = list(bytes(header, "ascii")) + + with pytest.raises(ValueError): + HeaderSerializer.deserialize(buffer, 0, len(buffer)) + + def test_deserialize_bad_length_throws(self): + header_id: UUID = uuid4() + header: str = f"A.00p168.{str(header_id)}.1\n" + buffer: List[int] = list(bytes(header, "ascii")) + + with pytest.raises(ValueError): + HeaderSerializer.deserialize(buffer, 0, len(buffer)) + + def test_deserialize_bad_id_throws(self): + header: str = "A.000168.68e9p9ca-a651-40f4-ad8f-3aaf781862b4.1\n" + buffer: List[int] = list(bytes(header, "ascii")) + + with pytest.raises(ValueError): + HeaderSerializer.deserialize(buffer, 0, len(buffer)) + + def test_deserialize_bad_end_throws(self): + header_id: UUID = uuid4() + header: str = f"A.000168.{str(header_id)}.z\n" + buffer: List[int] = list(bytes(header, "ascii")) + + with pytest.raises(ValueError): + HeaderSerializer.deserialize(buffer, 0, len(buffer)) diff --git a/libraries/botframework-streaming/tests/test_payload_assembler.py b/libraries/botframework-streaming/tests/test_payload_assembler.py new file mode 100644 index 000000000..a8cea4580 --- /dev/null +++ b/libraries/botframework-streaming/tests/test_payload_assembler.py @@ -0,0 +1,57 @@ +from unittest import TestCase +from uuid import UUID, uuid4 + +from botframework.streaming.payloads import StreamManager +from botframework.streaming.payloads.assemblers import PayloadStreamAssembler +from botframework.streaming.payloads.models import Header + + +class TestPayloadAssembler(TestCase): + def test_ctor_id(self): + identifier: UUID = uuid4() + stream_manager = StreamManager() + assembler = PayloadStreamAssembler(stream_manager, identifier) + self.assertEqual(identifier, assembler.identifier) + + def test_ctor_end_false(self): + identifier: UUID = uuid4() + stream_manager = StreamManager() + assembler = PayloadStreamAssembler(stream_manager, identifier) + self.assertFalse(assembler.end) + + def test_get_stream(self): + identifier: UUID = uuid4() + stream_manager = StreamManager() + assembler = PayloadStreamAssembler(stream_manager, identifier) + stream = assembler.get_payload_as_stream() + self.assertIsNotNone(stream) + + def test_get_stream_does_not_make_new_each_time(self): + identifier: UUID = uuid4() + stream_manager = StreamManager() + assembler = PayloadStreamAssembler(stream_manager, identifier) + stream1 = assembler.get_payload_as_stream() + stream2 = assembler.get_payload_as_stream() + self.assertEqual(stream1, stream2) + + def test_on_receive_sets_end(self): + identifier: UUID = uuid4() + stream_manager = StreamManager() + assembler = PayloadStreamAssembler(stream_manager, identifier) + + header = Header() + header.end = True + + assembler.get_payload_as_stream() + assembler.on_receive(header, [], 100) + + self.assertTrue(assembler.end) + + def test_close_does_not_set_end(self): + identifier: UUID = uuid4() + stream_manager = StreamManager() + assembler = PayloadStreamAssembler(stream_manager, identifier) + + assembler.close() + + self.assertFalse(assembler.end) diff --git a/libraries/botframework-streaming/tests/test_request_manager.py b/libraries/botframework-streaming/tests/test_request_manager.py new file mode 100644 index 000000000..20358d3c5 --- /dev/null +++ b/libraries/botframework-streaming/tests/test_request_manager.py @@ -0,0 +1,119 @@ +import asyncio +from asyncio import Future, ensure_future +from typing import Dict +from uuid import UUID, uuid4 + +import aiounittest +from botframework.streaming import ReceiveResponse +from botframework.streaming.payloads import RequestManager + + +class TestRequestManager(aiounittest.AsyncTestCase): + def test_ctor_empty_dictionary(self): + pending_requests: Dict[UUID, Future[ReceiveResponse]] = {} + _ = RequestManager(pending_requests=pending_requests) + + self.assertEqual(0, len(pending_requests)) + + async def test_signal_response_returns_false_when_no_uuid(self): + pending_requests: Dict[UUID, Future[ReceiveResponse]] = {} + manager = RequestManager(pending_requests=pending_requests) + request_id: UUID = uuid4() + response = ReceiveResponse() + signal = await manager.signal_response(request_id=request_id, response=response) + + self.assertFalse(signal) + + async def test_signal_response_returns_true_when_uuid(self): + pending_requests: Dict[UUID, Future[ReceiveResponse]] = {} + request_id: UUID = uuid4() + pending_requests[request_id] = Future() + + manager = RequestManager(pending_requests=pending_requests) + + response = ReceiveResponse() + signal = await manager.signal_response(request_id=request_id, response=response) + + self.assertTrue(signal) + + async def test_signal_response_null_response_is_ok(self): + pending_requests: Dict[UUID, Future[ReceiveResponse]] = {} + request_id: UUID = uuid4() + pending_requests[request_id] = Future() + + manager = RequestManager(pending_requests=pending_requests) + + # noinspection PyTypeChecker + _ = await manager.signal_response(request_id=request_id, response=None) + + self.assertIsNone(pending_requests[request_id].result()) + + async def test_signal_response_response(self): + pending_requests: Dict[UUID, Future[ReceiveResponse]] = {} + request_id: UUID = uuid4() + pending_requests[request_id] = Future() + + manager = RequestManager(pending_requests=pending_requests) + response = ReceiveResponse() + + _ = await manager.signal_response(request_id=request_id, response=response) + + self.assertEqual(response, pending_requests[request_id].result()) + + async def test_get_response_returns_null_on_duplicate_call(self): + pending_requests: Dict[UUID, Future[ReceiveResponse]] = {} + request_id: UUID = uuid4() + pending_requests[request_id] = Future() + + manager = RequestManager(pending_requests=pending_requests) + + response = await manager.get_response(request_id) + + self.assertIsNone(response) + + async def test_get_response_returns_response(self): + pending_requests: Dict[UUID, Future[ReceiveResponse]] = {} + request_id: UUID = uuid4() + + manager = RequestManager(pending_requests=pending_requests) + test_response = ReceiveResponse() + + async def set_response(): + nonlocal manager + nonlocal request_id + nonlocal test_response + + while True: + signal = await manager.signal_response( + request_id, response=test_response + ) + if signal: + break + await asyncio.sleep(2) + + ensure_future(set_response()) + response = await manager.get_response(request_id) + + self.assertEqual(test_response, response) + + async def test_get_response_returns_null_response(self): + pending_requests: Dict[UUID, Future[ReceiveResponse]] = {} + request_id: UUID = uuid4() + + manager = RequestManager(pending_requests=pending_requests) + + async def set_response(): + nonlocal manager + nonlocal request_id + + while True: + # noinspection PyTypeChecker + signal = await manager.signal_response(request_id, response=None) + if signal: + break + await asyncio.sleep(2) + + ensure_future(set_response()) + response = await manager.get_response(request_id) + + self.assertIsNone(response) diff --git a/libraries/botframework-streaming/tests/test_stream_manager.py b/libraries/botframework-streaming/tests/test_stream_manager.py new file mode 100644 index 000000000..622a4a7a2 --- /dev/null +++ b/libraries/botframework-streaming/tests/test_stream_manager.py @@ -0,0 +1,112 @@ +from unittest import TestCase +from uuid import UUID, uuid4 + +from botframework.streaming.payloads import StreamManager +from botframework.streaming.payloads.assemblers import PayloadStreamAssembler +from botframework.streaming.payloads.models import Header + + +class TestStreamManager(TestCase): + def test_ctor_null_cancel_ok(self): + manager = StreamManager(None) + self.assertIsNotNone(manager) + + def test_get_payload_assembler_not_exists_ok(self): + manager = StreamManager(None) + identifier: UUID = uuid4() + + assembler = manager.get_payload_assembler(identifier) + + self.assertIsNotNone(assembler) + self.assertEqual(identifier, assembler.identifier) + + def test_get_payload_assembler_exists_ok(self): + manager = StreamManager(None) + identifier: UUID = uuid4() + + assembler1 = manager.get_payload_assembler(identifier) + assembler2 = manager.get_payload_assembler(identifier) + + self.assertEqual(assembler1, assembler2) + + def test_get_payload_stream_not_exists_ok(self): + manager = StreamManager(None) + identifier: UUID = uuid4() + + stream = manager.get_payload_stream(Header(id=identifier)) + + self.assertIsNotNone(stream) + + def test_get_payload_stream_exists_ok(self): + manager = StreamManager(None) + identifier: UUID = uuid4() + + stream1 = manager.get_payload_stream(Header(id=identifier)) + stream2 = manager.get_payload_stream(Header(id=identifier)) + + self.assertEqual(stream1, stream2) + + def test_get_payload_stream_streams_match(self): + manager = StreamManager(None) + identifier: UUID = uuid4() + + assembler = manager.get_payload_assembler(identifier) + stream = manager.get_payload_stream(Header(id=identifier)) + + self.assertEqual(assembler.get_payload_as_stream(), stream) + + def test_on_receive_not_exists_no_op(self): + manager = StreamManager(None) + identifier: UUID = uuid4() + + manager.on_receive(Header(id=identifier), [], 100) + + def test_on_receive_exists(self): + manager = StreamManager(None) + identifier: UUID = uuid4() + + assembler = manager.get_payload_assembler(identifier) + assembler.get_payload_as_stream() + + manager.on_receive(Header(id=identifier, end=True), [], 100) + + self.assertTrue(assembler.end) + + def test_close_stream_not_exists_no_op(self): + manager = StreamManager(None) + identifier: UUID = uuid4() + + manager.close_stream(identifier) + + def test_close_stream_not_end_closed(self): + closed = False + + def mock_cancel_stream(_: PayloadStreamAssembler): + nonlocal closed + closed = True + + manager = StreamManager(on_cancel_stream=mock_cancel_stream) + identifier: UUID = uuid4() + assembler = manager.get_payload_assembler(identifier) + assembler.get_payload_as_stream() + + manager.close_stream(identifier) + + self.assertTrue(closed) + + def test_close_stream_end_no_op(self): + closed = False + + def mock_cancel_stream(_: PayloadStreamAssembler): + nonlocal closed + closed = True + + manager = StreamManager(on_cancel_stream=mock_cancel_stream) + identifier: UUID = uuid4() + assembler = manager.get_payload_assembler(identifier) + assembler.get_payload_as_stream() + assembler.on_receive(Header(end=True), [], 1) # Set it as ended + + manager.close_stream(identifier) + + self.assertFalse(closed)