Skip to content

Commit

Permalink
Merge pull request #534 from sklump/error-audit-no-endpoint
Browse files Browse the repository at this point in the history
include root cause chain in error roll-up; integrate with deserializa…
  • Loading branch information
andrewwhitehead authored May 29, 2020
2 parents 00df08a + 7d5f566 commit d95c9de
Show file tree
Hide file tree
Showing 7 changed files with 97 additions and 58 deletions.
22 changes: 17 additions & 5 deletions aries_cloudagent/core/error.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ def __init__(self, *args, error_code: str = None, **kwargs):
@property
def message(self) -> str:
"""Accessor for the error message."""
return self.args[0].strip() if self.args else ""
return str(self.args[0]).strip() if self.args else ""

@property
def roll_up(self) -> str:
Expand All @@ -23,10 +23,22 @@ def roll_up(self) -> str:
For display: aiohttp.web errors truncate after newline.
"""
line = "{}{}".format(
"({}) ".format(self.error_code) if self.error_code else "",
re.sub(r"\n\s*", ". ", self.args[0]) if self.args else "",
)

def flatten(exc: Exception):
ret = ".".join(
(
re.sub(r"\n\s*", ". ", str(exc.args[0]).strip()).strip()
if exc.args
else ""
).rsplit(".", 1)
)
return ret

line = flatten(self)
err = self
while err.__cause__:
err = err.__cause__
line += ". {}".format(flatten(err))
return line.strip()


Expand Down
11 changes: 10 additions & 1 deletion aries_cloudagent/core/tests/test_error.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,13 @@ async def test_base_error(self):
err = BaseError(MESSAGE, error_code=CODE)
assert err.error_code == CODE
assert err.message == MESSAGE.strip()
assert err.roll_up == "(-1) Not enough space. Clear 10MB."
assert err.roll_up == "Not enough space. Clear 10MB"

iox = IOError("hello")
keyx = KeyError("world")
osx = OSError("oh\nno")
osx.__cause__ = keyx
iox.__cause__ = osx
err.__cause__ = iox

assert err.roll_up == "Not enough space. Clear 10MB. hello. oh. no. world"
5 changes: 3 additions & 2 deletions aries_cloudagent/protocols/actionmenu/v1_0/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
from marshmallow import fields, Schema

from aries_cloudagent.connections.models.connection_record import ConnectionRecord
from aries_cloudagent.messaging.models.base import BaseModelError
from aries_cloudagent.messaging.valid import UUIDFour
from aries_cloudagent.storage.error import StorageNotFoundError

Expand Down Expand Up @@ -185,8 +186,8 @@ async def actionmenu_send(request: web.BaseRequest):
LOGGER.debug("Received send-menu request: %s %s", connection_id, menu_json)
try:
msg = Menu.deserialize(menu_json["menu"])
except Exception:
LOGGER.exception("Exception deserializing menu")
except BaseModelError as err:
LOGGER.exception("Exception deserializing menu: %s", err.roll_up)
raise

try:
Expand Down
7 changes: 6 additions & 1 deletion aries_cloudagent/protocols/connections/v1_0/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
ConnectionRecord,
ConnectionRecordSchema,
)
from aries_cloudagent.messaging.models.base import BaseModelError
from aries_cloudagent.messaging.valid import (
ENDPOINT,
INDY_DID,
Expand Down Expand Up @@ -334,7 +335,11 @@ async def connections_receive_invitation(request: web.BaseRequest):
raise web.HTTPForbidden()
connection_mgr = ConnectionManager(context)
invitation_json = await request.json()
invitation = ConnectionInvitation.deserialize(invitation_json)
try:
invitation = ConnectionInvitation.deserialize(invitation_json)
except BaseModelError as x:
raise web.HTTPBadRequest(reason=x.roll_up)

auto_accept = json.loads(request.query.get("auto_accept", "null"))
alias = request.query.get("alias")

Expand Down
25 changes: 25 additions & 0 deletions aries_cloudagent/protocols/connections/v1_0/tests/test_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -200,6 +200,31 @@ async def test_connections_receive_invitation(self):
await test_module.connections_receive_invitation(mock_req)
mock_response.assert_called_once_with(mock_conn_rec.serialize.return_value)

async def test_connections_receive_invitation_bad(self):
context = RequestContext(base_context=InjectionContext(enforce_typing=False))
mock_req = async_mock.MagicMock()
mock_req.app = {
"request_context": context,
}
mock_req.json = async_mock.CoroutineMock()
mock_req.query = {
"auto_accept": "true",
"alias": "alias",
}

mock_conn_rec = async_mock.MagicMock()
mock_conn_rec.serialize = async_mock.MagicMock()

with async_mock.patch.object(
test_module.ConnectionInvitation, "deserialize", autospec=True
) as mock_inv_deser, async_mock.patch.object(
test_module, "ConnectionManager", autospec=True
) as mock_conn_mgr:
mock_inv_deser.side_effect = test_module.BaseModelError()

with self.assertRaises(test_module.web.HTTPBadRequest):
await test_module.connections_receive_invitation(mock_req)

async def test_connections_receive_invitation_forbidden(self):
context = RequestContext(base_context=InjectionContext(enforce_typing=False))
context.update_settings({"admin.no_receive_invites": True})
Expand Down
59 changes: 30 additions & 29 deletions aries_cloudagent/protocols/present_proof/v1_0/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from ....connections.models.connection_record import ConnectionRecord
from ....holder.base import BaseHolder
from ....messaging.decorators.attach_decorator import AttachDecorator
from ....messaging.models.base import BaseModelError
from ....messaging.valid import (
INDY_CRED_DEF_ID,
INDY_DID,
Expand Down Expand Up @@ -374,8 +375,8 @@ async def presentation_exchange_retrieve(request: web.BaseRequest):
record = await V10PresentationExchange.retrieve_by_id(
context, presentation_exchange_id
)
except StorageNotFoundError:
raise web.HTTPNotFound()
except StorageNotFoundError as err:
raise web.HTTPNotFound(reason=err.roll_up)
return web.json_response(record.serialize())


Expand Down Expand Up @@ -408,8 +409,8 @@ async def presentation_exchange_credentials_list(request: web.BaseRequest):
presentation_exchange_record = await V10PresentationExchange.retrieve_by_id(
context, presentation_exchange_id
)
except StorageNotFoundError:
raise web.HTTPNotFound()
except StorageNotFoundError as err:
raise web.HTTPNotFound(reason=err.roll_up)

start = request.query.get("start")
count = request.query.get("count")
Expand Down Expand Up @@ -464,25 +465,25 @@ async def presentation_exchange_send_proposal(request: web.BaseRequest):
outbound_handler = request.app["outbound_message_router"]

body = await request.json()

comment = body.get("comment")
connection_id = body.get("connection_id")

# Aries#0037 calls it a proposal in the proposal struct but it's of type preview
presentation_preview = body.get("presentation_proposal")
try:
connection_record = await ConnectionRecord.retrieve_by_id(
context, connection_id
)
except StorageNotFoundError:
raise web.HTTPBadRequest()
presentation_proposal_message = PresentationProposal(
comment=comment,
presentation_proposal=PresentationPreview.deserialize(presentation_preview),
)
except (BaseModelError, StorageNotFoundError) as err:
raise web.HTTPBadRequest(reason=err.roll_up)

if not connection_record.is_ready:
raise web.HTTPForbidden()
raise web.HTTPForbidden(reason=f"Connection {connection_id} not ready")

comment = body.get("comment")
# Aries#0037 calls it a proposal in the proposal struct but it's of type preview
presentation_preview = body.get("presentation_proposal")
presentation_proposal_message = PresentationProposal(
comment=comment,
presentation_proposal=PresentationPreview.deserialize(presentation_preview),
)
trace_msg = body.get("trace")
presentation_proposal_message.assign_trace_decorator(
context.settings, trace_msg,
Expand Down Expand Up @@ -610,11 +611,11 @@ async def presentation_exchange_send_free_request(request: web.BaseRequest):
connection_record = await ConnectionRecord.retrieve_by_id(
context, connection_id
)
except StorageNotFoundError:
raise web.HTTPBadRequest()
except StorageNotFoundError as err:
raise web.HTTPBadRequest(reason=err.roll_up)

if not connection_record.is_ready:
raise web.HTTPForbidden()
raise web.HTTPForbidden(reason=f"Connection {connection_id} not ready")

comment = body.get("comment")
indy_proof_request = body.get("proof_request")
Expand Down Expand Up @@ -693,11 +694,11 @@ async def presentation_exchange_send_bound_request(request: web.BaseRequest):
connection_record = await ConnectionRecord.retrieve_by_id(
context, connection_id
)
except StorageNotFoundError:
raise web.HTTPBadRequest()
except StorageNotFoundError as err:
raise web.HTTPBadRequest(reason=err.roll_up)

if not connection_record.is_ready:
raise web.HTTPForbidden()
raise web.HTTPForbidden(reason=f"Connection {connection_id} not ready")

presentation_manager = PresentationManager(context)

Expand Down Expand Up @@ -753,11 +754,11 @@ async def presentation_exchange_send_presentation(request: web.BaseRequest):
connection_record = await ConnectionRecord.retrieve_by_id(
context, connection_id
)
except StorageNotFoundError:
raise web.HTTPBadRequest()
except StorageNotFoundError as err:
raise web.HTTPBadRequest(reason=err.roll_up)

if not connection_record.is_ready:
raise web.HTTPForbidden()
raise web.HTTPForbidden(reason=f"Connection {connection_id} not ready")

assert (
presentation_exchange_record.state
Expand Down Expand Up @@ -822,11 +823,11 @@ async def presentation_exchange_verify_presentation(request: web.BaseRequest):
connection_record = await ConnectionRecord.retrieve_by_id(
context, connection_id
)
except StorageNotFoundError:
raise web.HTTPBadRequest()
except StorageNotFoundError as err:
raise web.HTTPBadRequest(reason=err.roll_up)

if not connection_record.is_ready:
raise web.HTTPForbidden()
raise web.HTTPForbidden(reason=f"Connection {connection_id} not ready")

assert (
presentation_exchange_record.state
Expand Down Expand Up @@ -864,8 +865,8 @@ async def presentation_exchange_remove(request: web.BaseRequest):
presentation_exchange_record = await V10PresentationExchange.retrieve_by_id(
context, presentation_exchange_id
)
except StorageNotFoundError:
raise web.HTTPNotFound()
except StorageNotFoundError as err:
raise web.HTTPNotFound(reason=err.roll_up)

await presentation_exchange_record.delete_record(context)
return web.json_response({})
Expand Down
26 changes: 6 additions & 20 deletions aries_cloudagent/protocols/present_proof/v1_0/tests/test_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -216,22 +216,13 @@ async def test_presentation_exchange_send_proposal_no_conn_record(self):

with async_mock.patch.object(
test_module, "ConnectionRecord", autospec=True
) as mock_connection_record, async_mock.patch.object(
test_module, "PresentationManager", autospec=True
) as mock_presentation_manager:
) as mock_connection_record:

# Emulate storage not found (bad connection id)
mock_connection_record.retrieve_by_id = async_mock.CoroutineMock(
side_effect=StorageNotFoundError
)

mock_presentation_manager.return_value.create_exchange_for_proposal = (
async_mock.CoroutineMock()
)
mock_presentation_manager.return_value.create_exchange_for_proposal.return_value = (
async_mock.MagicMock()
)

with self.assertRaises(test_module.web.HTTPBadRequest):
await test_module.presentation_exchange_send_proposal(mock)

Expand All @@ -247,20 +238,15 @@ async def test_presentation_exchange_send_proposal_not_ready(self):
with async_mock.patch.object(
test_module, "ConnectionRecord", autospec=True
) as mock_connection_record, async_mock.patch.object(
test_module, "PresentationManager", autospec=True
) as mock_presentation_manager:
test_module, "PresentationPreview", autospec=True
) as mock_preview, async_mock.patch.object(
test_module, "PresentationProposal", autospec=True
) as mock_proposal:
mock_preview.deserialize = async_mock.CoroutineMock()

# Emulate connection not ready
mock_connection_record.retrieve_by_id = async_mock.CoroutineMock()
mock_connection_record.retrieve_by_id.return_value.is_ready = False

mock_presentation_manager.return_value.create_exchange_for_proposal = (
async_mock.CoroutineMock()
)
mock_presentation_manager.return_value.create_exchange_for_proposal.return_value = (
async_mock.MagicMock()
)

with self.assertRaises(test_module.web.HTTPForbidden):
await test_module.presentation_exchange_send_proposal(mock)

Expand Down

0 comments on commit d95c9de

Please sign in to comment.