Skip to content
Merged
Show file tree
Hide file tree
Changes from 12 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ jobs:

- name: archive reports
if: success() || failure()
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
with:
name: reports
path: reports/**/*
Expand Down
1,856 changes: 976 additions & 880 deletions poetry.lock

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,8 @@ lint.select = [
src = ["."]
lint.ignore = [
"PT004",
"UP007"
"UP007",
"UP045"
]
exclude = [
".git",
Expand Down
2 changes: 2 additions & 0 deletions src/mesh_sandbox/common/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ class Headers:
Mex_AddressType = "mex-AddressType"


MESSAGE_IN_INBOX_EXPIRY_IN_DAYS = 5

# Error codes
ERROR_INVALID_FROM_ADDRESS: Final = "Invalid From Address"
ERROR_MISSING_TO_ADDRESS: Final = "TO_DTS missing"
Expand Down
38 changes: 38 additions & 0 deletions src/mesh_sandbox/common/exceptions.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from typing import Any, Optional, Union

from mesh_sandbox.views.admin import CreateReportRequest

from ..models.message import MessageEvent, MessageMetadata, MessageStatus
from . import constants


Expand Down Expand Up @@ -44,3 +47,38 @@ def try_parse_error(detail: Union[str, dict, None] = None, message_id: Optional[
if isinstance(detail, dict):
return detail
return {"errorDescription": str(detail)}


def get_ndr_error() -> dict:
expiry_period = constants.MESSAGE_IN_INBOX_EXPIRY_IN_DAYS
error_description = parse_error(
detail=constants.ERROR_UNDELIVERED_MESSAGE,
format_params=(expiry_period,),
)

return error_description


def create_ndr_event(request: CreateReportRequest) -> MessageEvent:
error_description = get_ndr_error()

error_code = error_description.get("errorCode")
error_event = error_description.get("errorEvent")
error_message = error_description.get("errorDescription")

return MessageEvent(
status=MessageStatus.ERROR,
code=error_code,
event=error_event,
description=error_message,
linked_message_id=request.linked_message_id,
)


def create_ndr_metadata(request: CreateReportRequest) -> MessageMetadata:
subject = "NDR" if not request.subject else f"NDR: {request.subject}"

return MessageMetadata(
subject=subject,
local_id=request.local_id,
)
33 changes: 21 additions & 12 deletions src/mesh_sandbox/handlers/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@

from fastapi import BackgroundTasks, Depends, HTTPException, status

from mesh_sandbox.common.exceptions import create_ndr_event, create_ndr_metadata

from ..common.messaging import Messaging
from ..dependencies import get_messaging
from ..models.mailbox import Mailbox
Expand Down Expand Up @@ -50,16 +52,27 @@ async def create_report(self, request: CreateReportRequest, background_tasks: Ba

assert request.status in (MessageStatus.UNDELIVERABLE, MessageStatus.ERROR)

if request.status == MessageStatus.UNDELIVERABLE:
message_event = create_ndr_event(request)
message_metadata = create_ndr_metadata(request)
else:
message_event = MessageEvent(
status=request.status,
event="TRANSFER",
code=request.code,
description=request.description,
linked_message_id=request.linked_message_id,
)
message_metadata = MessageMetadata(
subject=request.subject,
local_id=request.local_id,
file_name=request.file_name,
)

message = Message(
events=[
MessageEvent(status=MessageStatus.ACCEPTED),
MessageEvent(
status=request.status,
event="TRANSFER",
code=request.code,
description=request.description,
linked_message_id=request.linked_message_id,
),
message_event,
],
message_id=uuid4().hex.upper(),
sender=MessageParty(
Expand All @@ -81,11 +94,7 @@ async def create_report(self, request: CreateReportRequest, background_tasks: Ba
total_chunks=0,
message_type=MessageType.REPORT,
workflow_id=request.workflow_id,
metadata=MessageMetadata(
subject=request.subject,
local_id=request.local_id,
file_name=request.file_name,
),
metadata=message_metadata,
)

await self.messaging.send_message(message=message, body=b"", background_tasks=background_tasks)
Expand Down
2 changes: 1 addition & 1 deletion src/mesh_sandbox/store/serialisation.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ def _deserialise_value(field_type, value):
return value

if is_dataclass(field_type):
return deserialise_model(value, field_type)
return deserialise_model(value, field_type) # type: ignore

if field_type in (int, float):
return field_type(value)
Expand Down
36 changes: 36 additions & 0 deletions src/mesh_sandbox/tests/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -447,3 +447,39 @@ def test_get_message_report_type(app: TestClient, root_path: str):
def test_get_message_not_found(app: TestClient, root_path: str):
res = app.get(f"{root_path}/notfound")
assert res.status_code == status.HTTP_404_NOT_FOUND


@pytest.mark.parametrize("root_path", ["/messageexchange/admin/message"])
def test_generate_ndr(app: TestClient, root_path: str):

expected_response = {
"status": MessageStatus.UNDELIVERABLE,
"workflow_id": uuid4().hex,
"code": "21",
"description": "non delivery reason",
"subject": f"my subject {uuid4().hex}",
"local_id": f"my local id {uuid4().hex}",
"file_name": f"my filename {uuid4().hex}",
"linked_message_id": uuid4().hex,
}

request = CreateReportRequest(
mailbox_id=_CANNED_MAILBOX1,
code=expected_response["code"],
description=expected_response["description"],
workflow_id=expected_response["workflow_id"],
subject=expected_response["subject"],
local_id=expected_response["local_id"],
status=expected_response["status"],
file_name=expected_response["file_name"],
linked_message_id=expected_response["linked_message_id"],
)

res = app.post("/messageexchange/admin/report", json=request.model_dump())
assert res.status_code == status.HTTP_200_OK
msg_id = res.json()["message_id"]

res = app.get(f"{root_path}/{msg_id}")
non_delivery_report = res.json()
assert non_delivery_report["status"] == "Accepted"
assert non_delivery_report["status_description"] == "Message not collected by recipient after 5 days"
1 change: 1 addition & 0 deletions src/mesh_sandbox/tests/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ def ensure_client_installed(java_path: str, base_dir: str, version: str): # pyl

with httpx.Client() as client:
res = client.get(installer_uri)
res.raise_for_status()
with open(installer_rar, "wb+") as f:
f.write(res.read())

Expand Down
4 changes: 2 additions & 2 deletions src/mesh_sandbox/views/outbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ class Config:


class UploadChunkV1(BaseModel):
messageID: str = Field(default=None, description="message identifier, as supplied in the request url")
blockID: int = Field(default=None, description="chunk number, as supplied in the request url")
messageID: Union[str, None] = Field(default=None, description="message identifier, as supplied in the request url") # type: ignore
blockID: Union[int, None] = Field(default=None, description="chunk number, as supplied in the request url") # type: ignore

class Config:
title = "upload_chunk"
Expand Down