class TestClientServer:
    """Client-Server end-to-end test."""
    def setup_server(self):
        """Set up server connection."""
        self.server_agent_address = "server_agent_address"
        self.server_agent_identity = Identity(
            "agent running server", address=self.server_agent_address)
        self.host = get_host()
        self.port = get_unused_tcp_port()
        self.connection_id = HTTPServerConnection.connection_id
        self.protocol_id = HttpMessage.protocol_id

        self.configuration = ConnectionConfig(
            host=self.host,
            port=self.port,
            api_spec_path=None,  # do not filter on API spec
            connection_id=HTTPServerConnection.connection_id,
            restricted_to_protocols=set([self.protocol_id]),
        )
        self.server = HTTPServerConnection(
            configuration=self.configuration,
            identity=self.server_agent_identity,
        )
        self.loop = asyncio.get_event_loop()
        self.loop.run_until_complete(self.server.connect())

        # skill side dialogues
        def role_from_first_message(  # pylint: disable=unused-argument
                message: Message,
                receiver_address: Address) -> BaseDialogue.Role:
            """Infer the role of the agent from an incoming/outgoing first message

            :param message: an incoming/outgoing first message
            :param receiver_address: the address of the receiving agent
            :return: The role of the agent
            """
            return HttpDialogue.Role.SERVER

        self._server_dialogues = HttpDialogues(
            self.server_agent_address,
            role_from_first_message=role_from_first_message)

    def setup_client(self):
        """Set up client connection."""
        self.client_agent_address = "client_agent_address"
        self.client_agent_identity = Identity(
            "agent running client", address=self.client_agent_address)
        configuration = ConnectionConfig(
            host="localost",
            port="8888",  # TODO: remove host/port for client?
            connection_id=HTTPClientConnection.connection_id,
        )
        self.client = HTTPClientConnection(configuration=configuration,
                                           identity=self.client_agent_identity)
        self.loop.run_until_complete(self.client.connect())

        # skill side dialogues
        def role_from_first_message(  # pylint: disable=unused-argument
                message: Message,
                receiver_address: Address) -> BaseDialogue.Role:
            """Infer the role of the agent from an incoming/outgoing first message

            :param message: an incoming/outgoing first message
            :param receiver_address: the address of the receiving agent
            :return: The role of the agent
            """
            return HttpDialogue.Role.CLIENT

        self._client_dialogues = HttpDialogues(
            self.client_agent_address,
            role_from_first_message=role_from_first_message)

    def setup(self):
        """Set up test case."""
        self.setup_server()
        self.setup_client()

    def _make_request(self,
                      path: str,
                      method: str = "get",
                      headers: str = "",
                      body: bytes = b"") -> Envelope:
        """Make request envelope."""
        request_http_message, _ = self._client_dialogues.create(
            counterparty=str(HTTPClientConnection.connection_id),
            performative=HttpMessage.Performative.REQUEST,
            method=method,
            url=f"http://{self.host}:{self.port}{path}",
            headers="",
            version="",
            body=b"",
        )
        request_envelope = Envelope(
            to=request_http_message.to,
            sender=request_http_message.sender,
            protocol_id=request_http_message.protocol_id,
            message=request_http_message,
        )
        return request_envelope

    def _make_response(self,
                       request_envelope: Envelope,
                       status_code: int = 200,
                       status_text: str = "") -> Envelope:
        """Make response envelope."""
        incoming_message = cast(HttpMessage, request_envelope.message)
        dialogue = self._server_dialogues.update(incoming_message)
        assert dialogue is not None
        message = dialogue.reply(
            target_message=incoming_message,
            performative=HttpMessage.Performative.RESPONSE,
            version=incoming_message.version,
            headers=incoming_message.headers,
            status_code=status_code,
            status_text=status_text,
            body=incoming_message.body,
        )
        response_envelope = Envelope(
            to=message.to,
            sender=message.sender,
            protocol_id=message.protocol_id,
            context=request_envelope.context,
            message=message,
        )
        return response_envelope

    @pytest.mark.asyncio
    async def test_post_with_payload(self):
        """Test client and server with post request."""
        initial_request = self._make_request("/test",
                                             "POST",
                                             body=b"1234567890")
        await self.client.send(initial_request)
        request = await asyncio.wait_for(self.server.receive(), timeout=5)
        # this is "inside" the server agent
        initial_response = self._make_response(request)
        await self.server.send(initial_response)
        response = await asyncio.wait_for(self.client.receive(), timeout=5)
        assert (cast(HttpMessage, initial_request.message).body == cast(
            HttpMessage, response.message).body)
        assert (initial_request.message.dialogue_reference[0] ==
                response.message.dialogue_reference[0])

    @pytest.mark.asyncio
    async def test_get_with_query(self):
        """Test client and server with url query."""
        query = {"key": "value"}
        path = "/test?{}".format(urllib.parse.urlencode(query))
        initial_request = self._make_request(path, "GET")
        await self.client.send(initial_request)
        request = await asyncio.wait_for(self.server.receive(), timeout=5)
        # this is "inside" the server agent

        parsed_query = dict(
            urllib.parse.parse_qsl(
                urllib.parse.splitquery(
                    cast(HttpMessage, request.message).url)[1]))
        assert parsed_query == query
        initial_response = self._make_response(request)
        await self.server.send(initial_response)
        response = await asyncio.wait_for(self.client.receive(), timeout=5)

        assert (initial_request.message.dialogue_reference[0] ==
                response.message.dialogue_reference[0])

    def teardown(self):
        """Tear down testcase."""
        self.loop.run_until_complete(self.client.disconnect())
        self.loop.run_until_complete(self.server.disconnect())
Example #2
0
class HttpPingPongHandler(Handler):
    """Dummy handler to handle messages."""

    SUPPORTED_PROTOCOL = HttpMessage.protocol_id

    def setup(self) -> None:
        """Noop setup."""
        # pylint: disable=attribute-defined-outside-init, unused-argument
        self.count: int = 0
        self.rtt_total_time: float = 0.0
        self.rtt_count: int = 0

        self.latency_total_time: float = 0.0
        self.latency_count: int = 0

        def role(m: Message, addr: Address) -> Dialogue.Role:
            return HttpDialogue.Role.CLIENT

        self.dialogues = HttpDialogues(self.context.agent_address,
                                       role_from_first_message=role)

    def teardown(self) -> None:
        """Noop teardown."""

    def handle(self, message: Message) -> None:
        """Handle incoming message."""
        self.count += 1
        message = cast(HttpMessage, message)
        dialogue = self.dialogues.update(message)
        if not dialogue:
            raise Exception("something goes wrong")
        rtt_ts, latency_ts = struct.unpack("dd", message.body)  # type: ignore
        if message.performative == HttpMessage.Performative.REQUEST:
            self.latency_total_time += time.time() - latency_ts
            self.latency_count += 1
            self.make_response(cast(HttpDialogue, dialogue), message)
        elif message.performative == HttpMessage.Performative.RESPONSE:
            self.rtt_total_time += time.time() - rtt_ts
            self.rtt_count += 1

            # got response, make another requet to the same agent
            self.make_request(message.sender)

    def make_response(self, dialogue: HttpDialogue,
                      message: HttpMessage) -> None:
        """Construct and send a response for message received."""
        response_message = dialogue.reply(
            target_message=message,
            performative=HttpMessage.Performative.RESPONSE,
            version=message.version,
            headers="",
            status_code=200,
            status_text="Success",
            body=message.body,
        )
        self.context.outbox.put_message(response_message)

    def make_request(self, recipient_addr: str) -> None:
        """Make initial http request."""
        message, _ = self.dialogues.create(
            recipient_addr,
            performative=HttpMessage.Performative.REQUEST,
            method="get",
            url="some url",
            headers="",
            version="",
            body=struct.pack("dd", time.time(), time.time()),
        )
        self.context.outbox.put_message(message)