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())
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)