Repository: microsoft/botbuilder-python

Issue:
Increase streaming unit tests
reach parity with C# unit tests


Discussion:

Resolving patch:
diff --git a/libraries/botframework-streaming/botframework/streaming/receive_request.py b/libraries/botframework-streaming/botframework/streaming/receive_request.py
--- a/libraries/botframework-streaming/botframework/streaming/receive_request.py
+++ b/libraries/botframework-streaming/botframework/streaming/receive_request.py
@@ -8,7 +8,7 @@
 
 class ReceiveRequest:
     def __init__(
-        self, *, verb: str = None, path: str = None, streams: List[ContentStream]
+        self, *, verb: str = None, path: str = None, streams: List[ContentStream] = None
     ):
         self.verb = verb
         self.path = path
diff --git a/libraries/botframework-streaming/botframework/streaming/receive_response.py b/libraries/botframework-streaming/botframework/streaming/receive_response.py
--- a/libraries/botframework-streaming/botframework/streaming/receive_response.py
+++ b/libraries/botframework-streaming/botframework/streaming/receive_response.py
@@ -9,9 +9,9 @@
 
 
 class ReceiveResponse:
-    def __init__(self, status_code: int = None, streams: List[ContentStream] = None):
+    def __init__(self, status_code: int = 0, streams: List[ContentStream] = None):
         self.status_code = status_code
-        self.streams = streams
+        self.streams = streams or []
 
     def read_body_as_json(
         self, cls: Union[Type[Model], Type[Serializable]]
diff --git a/libraries/botframework-streaming/botframework/streaming/streaming_response.py b/libraries/botframework-streaming/botframework/streaming/streaming_response.py
--- a/libraries/botframework-streaming/botframework/streaming/streaming_response.py
+++ b/libraries/botframework-streaming/botframework/streaming/streaming_response.py
@@ -2,6 +2,7 @@
 # Licensed under the MIT License.
 
 import json
+from http import HTTPStatus
 from uuid import UUID, uuid4
 from typing import List, Union
 
@@ -12,7 +13,7 @@
 
 class StreamingResponse:
     def __init__(
-        self, *, status_code: int = None, streams: List[ResponseMessageStream] = None
+        self, *, status_code: int = 0, streams: List[ResponseMessageStream] = None
     ):
         self.status_code = status_code
         self.streams = streams
@@ -48,3 +49,20 @@ def create_response(status_code: int, body: object) -> "StreamingResponse":
             response.add_stream(body)
 
         return response
+
+    @staticmethod
+    def not_found(body: object = None) -> "StreamingResponse":
+        return StreamingResponse.create_response(HTTPStatus.NOT_FOUND, body)
+
+    @staticmethod
+    def forbidden(body: object = None) -> "StreamingResponse":
+        return StreamingResponse.create_response(HTTPStatus.FORBIDDEN, body)
+
+    # pylint: disable=invalid-name
+    @staticmethod
+    def ok(body: object = None) -> "StreamingResponse":
+        return StreamingResponse.create_response(HTTPStatus.OK, body)
+
+    @staticmethod
+    def internal_server_error(body: object = None) -> "StreamingResponse":
+        return StreamingResponse.create_response(HTTPStatus.INTERNAL_SERVER_ERROR, body)


Test patch:
diff --git a/libraries/botframework-streaming/tests/test_content_stream.py b/libraries/botframework-streaming/tests/test_content_stream.py
new file mode 100644
--- /dev/null
+++ b/libraries/botframework-streaming/tests/test_content_stream.py
@@ -0,0 +1,34 @@
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License.
+
+from uuid import uuid4
+
+import aiounittest
+
+from botframework.streaming.payloads import ContentStream
+from botframework.streaming.payloads.assemblers import PayloadStreamAssembler
+
+
+class TestResponses(aiounittest.AsyncTestCase):
+    async def test_content_stream_ctor_none_assembler_throws(self):
+        with self.assertRaises(TypeError):
+            ContentStream(uuid4(), None)
+
+    async def test_content_stream_id(self):
+        test_id = uuid4()
+        test_assembler = PayloadStreamAssembler(None, test_id)
+        sut = ContentStream(test_id, test_assembler)
+
+        self.assertEqual(test_id, sut.identifier)
+
+    async def test_content_stream_type(self):
+        test_id = uuid4()
+        test_assembler = PayloadStreamAssembler(None, test_id)
+        sut = ContentStream(test_id, test_assembler)
+        test_type = "foo/bar"
+
+        sut.content_type = test_type
+
+        self.assertEqual(test_type, sut.content_type)
+
+        sut.cancel()
diff --git a/libraries/botframework-streaming/tests/test_requests.py b/libraries/botframework-streaming/tests/test_requests.py
new file mode 100644
--- /dev/null
+++ b/libraries/botframework-streaming/tests/test_requests.py
@@ -0,0 +1,129 @@
+import json
+
+import aiounittest
+
+from botbuilder.schema import Activity
+from botframework.streaming import ReceiveRequest, StreamingRequest
+from botframework.streaming.payloads import ResponseMessageStream
+
+
+class TestRequests(aiounittest.AsyncTestCase):
+    async def test_receive_request_empty_streams(self):
+        sut = ReceiveRequest()
+
+        self.assertIsNotNone(sut.streams)
+        self.assertEqual(0, len(sut.streams))
+
+    async def test_receive_request_null_properties(self):
+        sut = ReceiveRequest()
+
+        self.assertIsNone(sut.verb)
+        self.assertIsNone(sut.path)
+
+    async def test_streaming_request_null_properties(self):
+        sut = StreamingRequest()
+
+        self.assertIsNone(sut.verb)
+        self.assertIsNone(sut.path)
+
+    async def test_streaming_request_add_stream_null_throws(self):
+        sut = StreamingRequest()
+
+        with self.assertRaises(TypeError):
+            sut.add_stream(None)
+
+    async def test_streaming_request_add_stream_success(self):
+        sut = StreamingRequest()
+        content = "hi"
+
+        sut.add_stream(content)
+
+        self.assertIsNotNone(sut.streams)
+        self.assertEqual(1, len(sut.streams))
+        self.assertEqual(content, sut.streams[0].content)
+
+    async def test_streaming_request_add_stream_existing_list_success(self):
+        sut = StreamingRequest()
+        content = "hi"
+        content_2 = "hello"
+
+        sut.streams = [ResponseMessageStream(content=content_2)]
+
+        sut.add_stream(content)
+
+        self.assertIsNotNone(sut.streams)
+        self.assertEqual(2, len(sut.streams))
+        self.assertEqual(content_2, sut.streams[0].content)
+        self.assertEqual(content, sut.streams[1].content)
+
+    async def test_streaming_request_create_get_success(self):
+        sut = StreamingRequest.create_get()
+
+        self.assertEqual(StreamingRequest.GET, sut.verb)
+        self.assertIsNone(sut.path)
+        self.assertIsNone(sut.streams)
+
+    async def test_streaming_request_create_post_success(self):
+        sut = StreamingRequest.create_post()
+
+        self.assertEqual(StreamingRequest.POST, sut.verb)
+        self.assertIsNone(sut.path)
+        self.assertIsNone(sut.streams)
+
+    async def test_streaming_request_create_delete_success(self):
+        sut = StreamingRequest.create_delete()
+
+        self.assertEqual(StreamingRequest.DELETE, sut.verb)
+        self.assertIsNone(sut.path)
+        self.assertIsNone(sut.streams)
+
+    async def test_streaming_request_create_put_success(self):
+        sut = StreamingRequest.create_put()
+
+        self.assertEqual(StreamingRequest.PUT, sut.verb)
+        self.assertIsNone(sut.path)
+        self.assertIsNone(sut.streams)
+
+    async def test_streaming_request_create_with_body_success(self):
+        content = "hi"
+        sut = StreamingRequest.create_request(StreamingRequest.POST, "123", content)
+
+        self.assertEqual(StreamingRequest.POST, sut.verb)
+        self.assertEqual("123", sut.path)
+        self.assertIsNotNone(sut.streams)
+        self.assertEqual(1, len(sut.streams))
+        self.assertEqual(content, sut.streams[0].content)
+
+    async def test_streaming_request_set_body_string_success(self):
+        sut = StreamingRequest()
+
+        sut.set_body("123")
+
+        self.assertIsNotNone(sut.streams)
+        self.assertEqual(1, len(sut.streams))
+        self.assertIsInstance(sut.streams[0].content, list)
+        self.assertIsInstance(sut.streams[0].content[0], int)
+        self.assertEqual("123", bytes(sut.streams[0].content).decode("utf-8-sig"))
+
+    async def test_streaming_request_set_body_none_does_not_throw(self):
+        sut = StreamingRequest()
+
+        sut.set_body(None)
+
+    async def test_streaming_request_set_body_success(self):
+        sut = StreamingRequest()
+        activity = Activity(text="hi", type="message")
+
+        sut.set_body(activity)
+
+        self.assertIsNotNone(sut.streams)
+        self.assertEqual(1, len(sut.streams))
+        self.assertIsInstance(sut.streams[0].content, list)
+        self.assertIsInstance(sut.streams[0].content[0], int)
+
+        assert_activity = Activity.deserialize(
+            json.loads(bytes(sut.streams[0].content).decode("utf-8-sig"))
+        )
+
+        self.assertEqual(activity.text, assert_activity.text)
+        self.assertEqual(activity.type, assert_activity.type)
diff --git a/libraries/botframework-streaming/tests/test_responses.py b/libraries/botframework-streaming/tests/test_responses.py
new file mode 100644
--- /dev/null
+++ b/libraries/botframework-streaming/tests/test_responses.py
@@ -0,0 +1,132 @@
+import json
+from http import HTTPStatus
+
+import aiounittest
+
+from botbuilder.schema import Activity
+from botframework.streaming import ReceiveResponse, StreamingResponse
+from botframework.streaming.payloads import ResponseMessageStream
+
+
+class TestResponses(aiounittest.AsyncTestCase):
+    async def test_receive_response_empty_streams(self):
+        sut = ReceiveResponse()
+
+        self.assertIsNotNone(sut.streams)
+        self.assertEqual(0, len(sut.streams))
+
+    async def test_receive_response_none_properties(self):
+        sut = ReceiveResponse()
+
+        self.assertEqual(0, sut.status_code)
+
+    async def test_streaming_response_null_properties(self):
+        sut = StreamingResponse()
+
+        self.assertEqual(0, sut.status_code)
+        self.assertIsNone(sut.streams)
+
+    async def test_streaming_response_add_stream_none_throws(self):
+        sut = StreamingResponse()
+
+        with self.assertRaises(TypeError):
+            sut.add_stream(None)
+
+    async def test_streaming_response_add_stream_success(self):
+        sut = StreamingResponse()
+        content = "hi"
+
+        sut.add_stream(content)
+
+        self.assertIsNotNone(sut.streams)
+        self.assertEqual(1, len(sut.streams))
+        self.assertEqual(content, sut.streams[0].content)
+
+    async def test_streaming_response_add_stream_existing_list_success(self):
+        sut = StreamingResponse()
+        content = "hi"
+        content_2 = "hello"
+
+        sut.streams = [ResponseMessageStream(content=content_2)]
+
+        sut.add_stream(content)
+
+        self.assertIsNotNone(sut.streams)
+        self.assertEqual(2, len(sut.streams))
+        self.assertEqual(content_2, sut.streams[0].content)
+        self.assertEqual(content, sut.streams[1].content)
+
+    async def test_streaming_response_not_found_success(self):
+        sut = StreamingResponse.not_found()
+
+        self.assertEqual(HTTPStatus.NOT_FOUND, sut.status_code)
+        self.assertIsNone(sut.streams)
+
+    async def test_streaming_response_forbidden_success(self):
+        sut = StreamingResponse.forbidden()
+
+        self.assertEqual(HTTPStatus.FORBIDDEN, sut.status_code)
+        self.assertIsNone(sut.streams)
+
+    async def test_streaming_response_ok_success(self):
+        sut = StreamingResponse.ok()
+
+        self.assertEqual(HTTPStatus.OK, sut.status_code)
+        self.assertIsNone(sut.streams)
+
+    async def test_streaming_response_internal_server_error_success(self):
+        sut = StreamingResponse.internal_server_error()
+
+        self.assertEqual(HTTPStatus.INTERNAL_SERVER_ERROR, sut.status_code)
+        self.assertIsNone(sut.streams)
+
+    async def test_streaming_response_create_with_body_success(self):
+        content = "hi"
+        sut = StreamingResponse.create_response(HTTPStatus.OK, content)
+
+        self.assertEqual(HTTPStatus.OK, sut.status_code)
+        self.assertIsNotNone(sut.streams)
+        self.assertEqual(1, len(sut.streams))
+        self.assertEqual(content, sut.streams[0].content)
+
+    async def test_streaming_response_set_body_string_success(self):
+        sut = StreamingResponse()
+
+        sut.set_body("123")
+
+        self.assertIsNotNone(sut.streams)
+        self.assertEqual(1, len(sut.streams))
+        self.assertIsInstance(sut.streams[0].content, list)
+        self.assertIsInstance(sut.streams[0].content[0], int)
+        self.assertEqual("123", bytes(sut.streams[0].content).decode("utf-8-sig"))
+
+    async def test_streaming_response_set_body_none_does_not_throw(self):
+        sut = StreamingResponse()
+
+        sut.set_body(None)
+
+    async def test_streaming_response_set_body_success(self):
+        sut = StreamingResponse()
+        activity = Activity(text="hi", type="message")
+
+        sut.set_body(activity)
+
+        self.assertIsNotNone(sut.streams)
+        self.assertEqual(1, len(sut.streams))
+        self.assertIsInstance(sut.streams[0].content, list)
+        self.assertIsInstance(sut.streams[0].content[0], int)
+
+        assert_activity = Activity.deserialize(
+            json.loads(bytes(sut.streams[0].content).decode("utf-8-sig"))
+        )
+
+        self.assertEqual(activity.text, assert_activity.text)
+        self.assertEqual(activity.type, assert_activity.type)
+
+    async def test_receive_base_read_body_as_string_no_content_empty_string(self):
+        sut = ReceiveResponse()
+        sut.streams = []
+
+        result = sut.read_body_as_str()
+
+        self.assertEqual("", result)
diff --git a/libraries/botframework-streaming/tests/test_send_operations.py b/libraries/botframework-streaming/tests/test_send_operations.py
new file mode 100644
--- /dev/null
+++ b/libraries/botframework-streaming/tests/test_send_operations.py
@@ -0,0 +1,55 @@
+# Copyright (c) Microsoft Corporation. All rights reserved.
+# Licensed under the MIT License.
+
+from typing import List
+from uuid import uuid4
+
+import aiounittest
+
+from botframework.streaming import PayloadStream, StreamingRequest
+from botframework.streaming.payloads import SendOperations
+from botframework.streaming.payloads.assemblers import PayloadStreamAssembler
+from botframework.streaming.payload_transport import PayloadSender
+from botframework.streaming.transport import TransportSenderBase
+
+
+class MockTransportSender(TransportSenderBase):
+    def __init__(self):
+        super().__init__()
+        self.is_connected = True
+        self.buffers = []
+
+    async def send(self, buffer: List[int], offset: int, count: int) -> int:
+        self.buffers.append(buffer.copy())
+
+        return count
+
+
+class TestSendOperations(aiounittest.AsyncTestCase):
+    async def test_request_dissasembler_with_variable_stream_send(self):
+        sender = PayloadSender()
+        transport = MockTransportSender()
+        sender.connect(transport)
+
+        sut = SendOperations(sender)
+
+        request = StreamingRequest.create_post("/a/b")
+        stream = PayloadStream(PayloadStreamAssembler(None, uuid4(), "blah", 100))
+        stream.write([0] * 100, 0, 100)
+        request.add_stream(await stream.read_until_end())
+
+        await sut.send_request(uuid4(), request)
+        self.assertEqual(4, len(transport.buffers))
+
+    async def test_request_dissasembler_with_json_stream_send(self):
+        sender = PayloadSender()
+        transport = MockTransportSender()
+        sender.connect(transport)
+
+        sut = SendOperations(sender)
+
+        request = StreamingRequest.create_post("/a/b")
+        request.add_stream(bytes("abc", "ascii"))
+
+        await sut.send_request(uuid4(), request)
+        self.assertEqual(4, len(transport.buffers))