def test_envelope_context_raises_with_public_id_specified_twice(): """Test the EnvelopeContext constructor, negative""" with pytest.raises( ValueError, match="Cannot define connection_id explicitly and in URI."): EnvelopeContext( uri=URI("connection/author/connection_name/0.1.0"), connection_id=PublicId("author", "connection_name", "0.1.0"), ) with pytest.raises(ValueError, match="Cannot define skill_id explicitly and in URI."): EnvelopeContext( uri=URI("skill/author/skill_name/0.1.0"), skill_id=PublicId("author", "skill_name", "0.1.0"), )
def _search_services(self) -> None: """ Search on OEF Service Directory. In particular, search - for sellers and their supply, or - for buyers and their demand, or - for both. :return: None """ strategy = cast(Strategy, self.context.strategy) oef_search_dialogues = cast(OefSearchDialogues, self.context.oef_search_dialogues) query = strategy.get_location_and_service_query() for (is_seller_search, searching_for) in strategy.searching_for_types: oef_search_msg, oef_search_dialogue = oef_search_dialogues.create( counterparty=self.context.search_service_address, performative=OefSearchMessage.Performative.SEARCH_SERVICES, query=query, ) oef_search_dialogue = cast(OefSearchDialogue, oef_search_dialogue) oef_search_dialogue.is_seller_search = is_seller_search envelope_context = EnvelopeContext(skill_id=self.context.skill_id) self.context.outbox.put_message(message=oef_search_msg, context=envelope_context) self.context.logger.info("searching for {}, search_id={}.".format( searching_for, oef_search_msg.dialogue_reference))
def _handle_get(self, http_msg: HttpMessage, http_dialogue: HttpDialogue) -> None: """ Handle a Http request of verb GET. :param http_msg: the http message :param http_dialogue: the http dialogue :return: None """ http_response = http_dialogue.reply( performative=HttpMessage.Performative.RESPONSE, target_message=http_msg, version=http_msg.version, status_code=200, status_text="Success", headers=http_msg.headers, body=json.dumps(self.context.shared_state.get("oracle_data", "")).encode("utf-8"), ) self.context.logger.info("responding with: {}".format(http_response)) envelope_context = EnvelopeContext(connection_id=self._http_server_id) self.context.outbox.put_message(message=http_response, context=envelope_context) if self.context.prometheus_dialogues.enabled: metric_name = "num_requests" self.context.behaviours.coin_price_behaviour.update_prometheus_metric( metric_name, "inc", 1.0, {})
async def to_envelope(self, request: web.Request) -> Envelope: """ Convert a webhook request object into an Envelope containing an HttpMessage `from the 'http' Protocol`. :param request: the webhook request :return: The envelop representing the webhook request """ payload_bytes = await request.read() version = str(request.version[0]) + "." + str(request.version[1]) context = EnvelopeContext( connection_id=WebhookConnection.connection_id) http_message, _ = self._dialogues.create( counterparty=self.agent_address, performative=HttpMessage.Performative.REQUEST, method=request.method, url=str(request.url), version=version, headers=json.dumps(dict(request.headers)), body=payload_bytes if payload_bytes is not None else b"", ) envelope = Envelope( to=http_message.to, sender=http_message.sender, protocol_id=http_message.protocol_id, context=context, message=http_message, ) return envelope
def send_http_request_message(self, method: str, url: str, content: Dict = None) -> None: """ Send an http request message. :param method: the http request method (i.e. 'GET' or 'POST'). :param url: the url to send the message to. :param content: the payload. :return: None """ # context http_dialogues = cast(HttpDialogues, self.context.http_dialogues) # http request message request_http_message, _ = http_dialogues.create( counterparty=str(HTTP_CLIENT_ID), performative=HttpMessage.Performative.REQUEST, method=method, url=url, headers="", version="", body=b"" if content is None else json.dumps(content).encode("utf-8"), ) # send message envelope_context = EnvelopeContext(skill_id=self.context.skill_id, connection_id=HTTP_CLIENT_ID) self.context.outbox.put_message(message=request_http_message, context=envelope_context)
def _send_default_message(self, content: Dict) -> None: """ Send a default message to Alice. :param content: the content of the message. :return: None """ # context strategy = cast(FaberStrategy, self.context.strategy) default_dialogues = cast(DefaultDialogues, self.context.default_dialogues) # default message message = DefaultMessage( dialogue_reference=default_dialogues.new_self_initiated_dialogue_reference(), performative=DefaultMessage.Performative.BYTES, content=json.dumps(content).encode("utf-8"), ) message.counterparty = strategy.alice_aea_address # default dialogue default_dialogue = default_dialogues.update(message) assert ( default_dialogue is not None ), "faber -> http_handler -> _send_default_message(): something went wrong when sending a default message." # send context = EnvelopeContext(connection_id=P2P_CONNECTION_PUBLIC_ID) self.context.outbox.put_message(message=message, context=context)
async def test_default_route_applied(caplog): """Test default route is selected automatically.""" logger = logging.getLogger("aea.multiplexer") with caplog.at_level(logging.DEBUG, logger="aea.multiplexer"): connection_1 = _make_dummy_connection() connections = [connection_1] multiplexer = AsyncMultiplexer(connections, loop=asyncio.get_event_loop()) multiplexer.logger = logger envelope = Envelope( to="", sender="", protocol_id=DefaultMessage.protocol_id, message=b"", context=EnvelopeContext(), ) multiplexer.default_routing = { DefaultMessage.protocol_id: connection_1.connection_id } try: await multiplexer.connect() inbox = InBox(multiplexer) outbox = InBox(multiplexer) assert inbox.empty() assert outbox.empty() multiplexer.put(envelope) await outbox.async_get() finally: await multiplexer.disconnect() assert "Using default routing:" in caplog.text
def to_envelope(self, connection_id: PublicId, agent_address: str) -> Envelope: """ Process incoming API request by packaging into Envelope and sending it in-queue. The Envelope's message body contains the "performative", "path", "params", and "payload". :param http_method: the http method :param url: the url :param param: the parameter :param body: the body """ uri = URI(self.full_url_pattern) context = EnvelopeContext(connection_id=connection_id, uri=uri) http_message = HttpMessage( dialogue_reference=("", ""), target=0, message_id=1, performative=HttpMessage.Performative.REQUEST, method=self.method, url=self.full_url_pattern, headers=self.parameters.header.as_string(), bodyy=self.body.encode() if self.body is not None else b"", version="", ) envelope = Envelope( to=agent_address, sender=self.id, protocol_id=PublicId.from_str("fetchai/http:0.1.0"), context=context, message=HttpSerializer().encode(http_message), ) return envelope
def _register_service(self) -> None: """ Register to the OEF Service Directory. In particular, register - as a seller, listing the goods supplied, or - as a buyer, listing the goods demanded, or - as both. :return: None """ strategy = cast(Strategy, self.context.strategy) oef_search_dialogues = cast(OefSearchDialogues, self.context.oef_search_dialogues) self.context.logger.debug("updating service directory as {}.".format( strategy.registering_as)) description = strategy.get_register_service_description() oef_search_msg, _ = oef_search_dialogues.create( counterparty=self.context.search_service_address, performative=OefSearchMessage.Performative.REGISTER_SERVICE, service_description=description, ) envelope_context = EnvelopeContext(skill_id=self.context.skill_id) self.context.outbox.put_message(message=oef_search_msg, context=envelope_context)
def _unregister_service(self) -> None: """ Unregister service from OEF Service Directory. :return: None """ strategy = cast(Strategy, self.context.strategy) oef_search_dialogues = cast( OefSearchDialogues, self.context.oef_search_dialogues ) self.context.logger.debug( "unregistering from service directory as {}.".format( strategy.registering_as ) ) description = strategy.get_unregister_service_description() oef_search_msg, _ = oef_search_dialogues.create( counterparty=self.context.search_service_address, performative=OefSearchMessage.Performative.UNREGISTER_SERVICE, service_description=description, ) envelope_context = EnvelopeContext(skill_id=self.context.skill_id) self.context.outbox.put_message( message=oef_search_msg, context=envelope_context )
async def to_envelope(self, request: web.Request) -> Envelope: """ Convert a webhook request object into an Envelope containing an HttpMessage `from the 'http' Protocol`. :param request: the webhook request :return: The envelop representing the webhook request """ payload_bytes = await request.read() version = str(request.version[0]) + "." + str(request.version[1]) context = EnvelopeContext(uri=URI("aea/mail/base.py")) http_message = HttpMessage( performative=HttpMessage.Performative.REQUEST, method=request.method, url=str(request.url), version=version, headers=json.dumps(dict(request.headers)), bodyy=payload_bytes if payload_bytes is not None else b"", dialogue_reference=self._dialogues. new_self_initiated_dialogue_reference(), ) http_message.counterparty = self.agent_address http_dialogue = self._dialogues.update(http_message) assert http_dialogue is not None, "Could not create dialogue." envelope = Envelope( to=http_message.counterparty, sender=http_message.sender, protocol_id=http_message.protocol_id, context=context, message=http_message, ) return envelope
async def to_envelope(self, request: web.Request) -> Envelope: """ Convert a webhook request object into an Envelope containing an HttpMessage `from the 'http' Protocol`. :param request: the webhook request :return: The envelop representing the webhook request """ payload_bytes = await request.read() version = str(request.version[0]) + "." + str(request.version[1]) context = EnvelopeContext(uri=URI("aea/mail/base.py")) http_message = HttpMessage( performative=HttpMessage.Performative.REQUEST, method=request.method, url=str(request.url), version=version, headers=json.dumps(dict(request.headers)), bodyy=payload_bytes if payload_bytes is not None else b"", ) envelope = Envelope( to=self.agent_address, sender=request.remote, protocol_id=PublicId.from_str("fetchai/http:0.3.0"), context=context, message=http_message, ) return envelope
def _request_grant_role_transaction(self) -> None: """ Request transaction that grants oracle role in a Fetch oracle contract :return: None """ strategy = cast(Strategy, self.context.strategy) strategy.is_behaviour_active = False contract_api_dialogues = cast(ContractApiDialogues, self.context.contract_api_dialogues) contract_api_msg, contract_api_dialogue = contract_api_dialogues.create( counterparty=str(LEDGER_API_ADDRESS), performative=ContractApiMessage.Performative.GET_RAW_TRANSACTION, ledger_id=strategy.ledger_id, contract_id=str(CONTRACT_PUBLIC_ID), contract_address=strategy.contract_address, callable="get_grant_role_transaction", kwargs=ContractApiMessage.Kwargs({ "oracle_address": self.context.agent_address, "gas": strategy.default_gas_grant_role, }), ) contract_api_dialogue = cast(ContractApiDialogue, contract_api_dialogue) contract_api_dialogue.terms = strategy.get_grant_role_terms() envelope_context = EnvelopeContext(skill_id=self.context.skill_id, connection_id=LEDGER_API_ADDRESS) self.context.outbox.put_message(message=contract_api_msg, context=envelope_context) self.context.logger.info("requesting grant role transaction...")
def to_envelope( self, connection_id: PublicId, http_request_message: HttpMessage, http_response: requests.models.Response, ) -> Envelope: """ Convert an HTTP response object (from the 'requests' library) into an Envelope containing an HttpMessage (from the 'http' Protocol). :param connection_id: the connection id :param http_request_message: the message of the http request envelop :param http_response: the http response object """ context = EnvelopeContext(connection_id=connection_id) http_message = HttpMessage( dialogue_reference=http_request_message.dialogue_reference, target=http_request_message.target, message_id=http_request_message.message_id, performative=HttpMessage.Performative.RESPONSE, status_code=http_response.status_code, headers=json.dumps(dict(http_response.headers)), status_text=http_response.reason, bodyy=http_response.content if http_response.content is not None else b"", version="", ) envelope = Envelope( to=self.agent_address, sender="HTTP Server", protocol_id=PublicId.from_str("fetchai/http:0.1.0"), context=context, message=HttpSerializer().encode(http_message), ) return envelope
def _send_message(self, content: Dict) -> None: # message & envelope message = DefaultMessage( performative=DefaultMessage.Performative.BYTES, content=json.dumps(content).encode("utf-8"), ) message.counterparty = self.alice_id context = EnvelopeContext(connection_id=OEF_CONNECTION_PUBLIC_ID) self.context.outbox.put_message(message=message, context=context)
async def test_outbox_negative(): """Test InBox OutBox objects.""" connection_1 = _make_dummy_connection() connections = [connection_1] multiplexer = AsyncMultiplexer(connections, loop=asyncio.get_event_loop()) msg = DefaultMessage( performative=DefaultMessage.Performative.BYTES, content=b"", ) context = EnvelopeContext(connection_id=connection_1.connection_id) envelope = Envelope( to="to", sender="sender", protocol_specification_id=msg.protocol_specification_id, message=b"", context=context, ) try: await multiplexer.connect() outbox = OutBox(multiplexer) assert outbox.empty() with pytest.raises(ValueError) as execinfo: outbox.put(envelope) assert ( str(execinfo.value) == "Only Message type allowed in envelope message field when putting into outbox." ) assert outbox.empty() with pytest.raises(ValueError) as execinfo: outbox.put_message("") assert str(execinfo.value) == "Provided message not of type Message." assert outbox.empty() with pytest.raises(ValueError) as execinfo: outbox.put_message(msg) assert str( execinfo.value) == "Provided message has message.to not set." assert outbox.empty() msg.to = "to" with pytest.raises(ValueError) as execinfo: outbox.put_message(msg) assert str( execinfo.value) == "Provided message has message.sender not set." finally: await multiplexer.disconnect()
def test_envelope_skill_id(): """Test the property Envelope.skill_id.""" envelope_context = EnvelopeContext(uri=URI("author/skill_name/0.1.0")) envelope = Envelope( to="to", sender="sender", protocol_id=PublicId("author", "name", "0.1.0"), message=b"message", context=envelope_context, ) assert envelope.skill_id == PublicId("author", "skill_name", "0.1.0")
async def test_send_envelope_with_non_registered_connection(): """Test that sending an envelope with an unregistered connection raises an exception.""" connection = DummyConnection(connection_id="dummy") multiplexer = Multiplexer([connection]) multiplexer.connect() envelope = Envelope(to="", sender="", protocol_id="default", message=b"", context=EnvelopeContext(connection_id="this_is_an_unexisting_connection_id")) with pytest.raises(AEAConnectionError, match="No connection registered with id:.*"): await multiplexer._send(envelope) multiplexer.disconnect()
def test_multiple_connection(): """Test that we can send a message with two different connections.""" with LocalNode() as node: public_key_1 = "public_key_1" public_key_2 = "public_key_2" connection_1_id = "local_1" connection_2_id = "local_2" connection_1 = OEFLocalConnection(public_key_1, node, connection_id=connection_1_id) connection_2 = OEFLocalConnection(public_key_2, node, connection_id=connection_2_id) multiplexer = Multiplexer([connection_1, connection_2]) assert not connection_1.connection_status.is_connected assert not connection_2.connection_status.is_connected multiplexer.connect() assert connection_1.connection_status.is_connected assert connection_2.connection_status.is_connected message = DefaultMessage(type=DefaultMessage.Type.BYTES, content=b"hello") envelope_from_1_to_2 = Envelope(to=public_key_2, sender=public_key_1, protocol_id=DefaultMessage.protocol_id, message=DefaultSerializer().encode(message), context=EnvelopeContext(connection_id=connection_1_id)) multiplexer.put(envelope_from_1_to_2) actual_envelope = multiplexer.get(block=True, timeout=2.0) assert envelope_from_1_to_2 == actual_envelope envelope_from_2_to_1 = Envelope(to=public_key_1, sender=public_key_2, protocol_id=DefaultMessage.protocol_id, message=DefaultSerializer().encode(message), context=EnvelopeContext(connection_id=connection_2_id)) multiplexer.put(envelope_from_2_to_1) actual_envelope = multiplexer.get(block=True, timeout=2.0) assert envelope_from_2_to_1 == actual_envelope multiplexer.disconnect()
def test_envelope_connection_id(): """Test the property Envelope.connection_id.""" envelope_context = EnvelopeContext( uri=URI("connection/author/connection_name/0.1.0")) envelope = Envelope( to="to", sender="sender", protocol_specification_id=PublicId("author", "name", "0.1.0"), message=b"message", context=envelope_context, ) assert envelope.connection_id == PublicId("author", "connection_name", "0.1.0")
def test_send_envelope_error_is_logged_by_send_loop(): """Test that the AEAConnectionError in the '_send' method is logged by the '_send_loop'.""" connection = DummyConnection(connection_id="dummy") multiplexer = Multiplexer([connection]) multiplexer.connect() envelope = Envelope(to="", sender="", protocol_id="default", message=b"", context=EnvelopeContext(connection_id="this_is_an_unexisting_connection_id")) with unittest.mock.patch.object(aea.mail.base.logger, "error") as mock_logger_error: multiplexer.put(envelope) time.sleep(0.1) mock_logger_error.assert_called_with("No connection registered with id: this_is_an_unexisting_connection_id.") multiplexer.disconnect()
def test_protobuf_envelope_serializer(): """Test Protobuf envelope serializer.""" serializer = ProtobufEnvelopeSerializer() # connection id is None because it is not included in the encoded envelope envelope_context = EnvelopeContext(connection_id=None, uri=URI("/uri")) expected_envelope = Envelope( to="to", sender="sender", protocol_id=PublicId("author", "name", "0.1.0"), message=b"message", context=envelope_context, ) encoded_envelope = serializer.encode(expected_envelope) actual_envelope = serializer.decode(encoded_envelope) assert actual_envelope == expected_envelope
def setup(self): """Set up test case.""" self.connection = _make_dummy_connection() self.multiplexer = Multiplexer([self.connection], protocols=[DefaultProtocolMock]) self.multiplexer.connect() self.envelope = Envelope( to="", sender="", protocol_specification_id=DefaultMessage.protocol_specification_id, message=b"", context=EnvelopeContext( connection_id=self.connection.connection_id), ) self.exception = ValueError("expected")
def _admin_post(self, path: str, content: Dict = None): # Request message & envelope request_http_message = HttpMessage( performative=HttpMessage.Performative.REQUEST, method="POST", url=self.admin_url + path, headers="", version="", bodyy=b"" if content is None else json.dumps(content).encode("utf-8"), ) request_http_message.counterparty = self.admin_url self.context.outbox.put_message( message=request_http_message, context=EnvelopeContext( connection_id=HTTP_CLIENT_CONNECTION_PUBLIC_ID), )
def test_envelope_skill_id_raises_value_error(caplog): """Test the property Envelope.skill_id raises ValueError if the URI is not a public id..""" with caplog.at_level(logging.DEBUG, logger="aea.mail.base"): bad_uri = "author/skill_name/bad_version" envelope_context = EnvelopeContext(uri=URI(bad_uri)) envelope = Envelope( to="to", sender="sender", protocol_id=PublicId("author", "name", "0.1.0"), message=b"message", context=envelope_context, ) assert envelope.skill_id is None assert ( f"URI - {bad_uri} - not a valid skill id." in caplog.text ), f"Cannot find message in output: {caplog.text}"
async def test_send_envelope_with_non_registered_connection(): """Test that sending an envelope with an unregistered connection raises an exception.""" connection = _make_dummy_connection() multiplexer = Multiplexer([connection]) multiplexer.connect() envelope = Envelope( to="", sender="", protocol_id=DefaultMessage.protocol_id, message=b"", context=EnvelopeContext(connection_id=UNKNOWN_CONNECTION_PUBLIC_ID), ) with pytest.raises(AEAConnectionError, match="No connection registered with id:.*"): await multiplexer._send(envelope) multiplexer.disconnect()
def test_envelope_skill_id_raises_value_error(): """Test the property Envelope.skill_id raises ValueError if the URI is not a package id..""" with unittest.mock.patch.object(aea.mail.base._default_logger, "debug") as mock_logger_method: bad_uri = "skill/author/skill_name/bad_version" envelope_context = EnvelopeContext(uri=URI(bad_uri)) envelope = Envelope( to="to", sender="sender", protocol_specification_id=PublicId("author", "name", "0.1.0"), message=b"message", context=envelope_context, ) assert envelope.skill_id is None mock_logger_method.assert_called_with( f"URI - {bad_uri} - not a valid package_id id. Error: Input '{bad_uri}' is not well formatted." )
def test_envelope_skill_id_raises_value_error_wrong_package_type(): """Test the property Envelope.skill_id raises ValueError if the URI is not a valid package type.""" with unittest.mock.patch.object(aea.mail.base._default_logger, "debug") as mock_logger_method: invalid_uri = "protocol/author/skill_name/0.1.0" envelope_context = EnvelopeContext(uri=URI(invalid_uri)) envelope = Envelope( to="to", sender="sender", protocol_specification_id=PublicId("author", "name", "0.1.0"), message=b"message", context=envelope_context, ) assert envelope.skill_id is None mock_logger_method.assert_called_with( f"URI - {invalid_uri} - not a valid package_id id. Error: Invalid package type protocol in uri for envelope context." )
def act(self) -> None: """ Implement the act. :return: None """ strategy = cast(Strategy, self.context.strategy) oef_search_dialogues = cast(OefSearchDialogues, self.context.oef_search_dialogues) search_request, _ = oef_search_dialogues.create( counterparty=self.context.search_service_address, performative=OefSearchMessage.Performative.SEARCH_SERVICES, query=strategy.get_query(), ) self.context.logger.info("sending search request to OEF search node") context = EnvelopeContext(skill_id=self.context.skill_id) self.context.outbox.put_message(message=search_request, context=context)
def to_envelope( self, connection_id: PublicId, http_request_message: HttpMessage, status_code: int, headers: dict, status_text: Optional[Any], bodyy: bytes, dialogue: HttpDialogue, ) -> Envelope: """ Convert an HTTP response object (from the 'requests' library) into an Envelope containing an HttpMessage (from the 'http' Protocol). :param connection_id: the connection id :param http_request_message: the message of the http request envelop :param status_code: the http status code, int :param headers: dict of http response headers :param status_text: the http status_text, str :param bodyy: bytes of http response content :return: Envelope with http response data. """ context = EnvelopeContext(connection_id=connection_id) http_message = HttpMessage( performative=HttpMessage.Performative.RESPONSE, status_code=status_code, headers=json.dumps(dict(headers.items())), status_text=status_text, bodyy=bodyy, version="", dialogue_reference=dialogue.dialogue_label.dialogue_reference, target=http_request_message.message_id, message_id=http_request_message.message_id + 1, ) http_message.counterparty = http_request_message.counterparty assert dialogue.update(http_message) envelope = Envelope( to=self.agent_address, sender="HTTP Server", protocol_id=PublicId.from_str("fetchai/http:0.4.0"), context=context, message=http_message, ) return envelope