mesh-sandbox 0.1.33__py3-none-any.whl → 1.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,175 +1,49 @@
1
- import asyncio
2
- import importlib
3
- import inspect
4
1
  import logging
5
- import pkgutil
6
- from abc import ABC, abstractmethod
7
2
  from collections import defaultdict
8
- from types import ModuleType
9
- from typing import Literal, Optional, cast
3
+ from typing import Optional
10
4
 
11
- from fastapi import BackgroundTasks
12
-
13
- from .. import plugins as plugins_ns
14
5
  from ..common import EnvConfig
15
- from ..models.message import Message, MessageEvent, MessageStatus
6
+ from ..models.message import Message
16
7
  from .canned_store import CannedStore
17
8
 
18
9
 
19
- class _SandboxPlugin(ABC):
20
-
21
- triggers: list[Literal["message_accepted", "message_acknowledged"]] = []
22
-
23
- @abstractmethod
24
- async def on_event(self, event: str, message: Message):
25
- pass
26
-
27
-
28
10
  class MemoryStore(CannedStore):
29
11
  """
30
12
  in memory store, good for 'in-process' testing or small messages
31
13
  """
32
14
 
33
- supports_reset = True
15
+ readonly = False
34
16
  load_messages = False
35
17
 
36
- def __init__(self, config: EnvConfig, logger: logging.Logger, plugins_module: ModuleType = plugins_ns):
18
+ def __init__(self, config: EnvConfig, logger: logging.Logger):
37
19
  super().__init__(config, logger, filter_expired=True)
38
20
 
39
- self._plugin_registry = self._find_plugins(plugins_module)
40
- self._plugin_instances: dict[str, list[_SandboxPlugin]] = {}
41
-
42
- def _find_plugins(self, package: ModuleType) -> dict[str, list[type[_SandboxPlugin]]]:
43
- plugin_classes = defaultdict(list)
44
- for _, name, _ in pkgutil.iter_modules(package.__path__, package.__name__ + "."):
45
- module = importlib.import_module(name)
46
- for _, class_type in inspect.getmembers(module):
47
-
48
- if not inspect.isclass(class_type) or not class_type.__name__.endswith("Plugin"):
49
- continue
50
-
51
- self.logger.info(f"potential plugin: {class_type.__name__} found")
52
- if not hasattr(class_type, "triggers"):
53
- self.logger.warning(f"plugin: {class_type.__name__} has no class attr triggers .. not loading")
54
- continue
55
-
56
- if not hasattr(class_type, "on_event"):
57
- self.logger.warning(f"plugin: {class_type.__name__} has no class attr on_event .. not loading")
58
- continue
59
-
60
- if not inspect.iscoroutinefunction(class_type.on_event):
61
- self.logger.warning(f"plugin: {class_type.__name__} on_event os not a coroutine.. not loading")
62
- continue
63
-
64
- on_event_args = cast(list[str], inspect.getfullargspec(class_type.on_event)[0])
65
-
66
- if not on_event_args:
67
- self.logger.warning(
68
- f"plugin: {class_type.__name__} on_event expected args event, message .. not loading"
69
- )
70
- continue
71
-
72
- if not isinstance(inspect.getattr_static(class_type, "on_event"), staticmethod):
73
- on_event_args.pop(0)
74
-
75
- if not len(on_event_args) == 2:
76
- self.logger.warning(
77
- f"plugin: {class_type.__name__} on_event expected args event, message .. not loading"
78
- )
79
- continue
80
-
81
- for trigger in class_type.triggers:
82
- plugin_classes[trigger].append(class_type)
83
-
84
- return cast(dict[str, list[type[_SandboxPlugin]]], plugin_classes)
85
-
86
- @staticmethod
87
- async def _construct(plugin_type: type[_SandboxPlugin]) -> _SandboxPlugin:
88
- created = plugin_type()
89
- return created
90
-
91
- async def on_event(self, event: str, message: Message):
92
-
93
- instances = self._plugin_instances.get(event, [])
94
- if not instances:
95
- registered = self._plugin_registry.get(event, [])
96
- if not registered:
97
- return
98
-
99
- instances = await asyncio.gather(*[self._construct(plugin_type) for plugin_type in registered])
100
- self._plugin_instances[event] = instances
101
-
102
- await asyncio.gather(*[plugin.on_event(event, message) for plugin in instances])
103
-
104
21
  async def reset(self):
105
- async with self.lock:
106
- super().initialise()
22
+ super().initialise()
107
23
 
108
24
  async def reset_mailbox(self, mailbox_id: str):
25
+ self.inboxes[mailbox_id] = []
26
+ self.outboxes[mailbox_id] = []
27
+ self.local_ids[mailbox_id] = defaultdict(list)
28
+ self.mailboxes[mailbox_id].inbox_count = 0
109
29
 
110
- async with self.lock:
111
- self.inboxes[mailbox_id] = []
112
- self.outboxes[mailbox_id] = []
113
- self.local_ids[mailbox_id] = defaultdict(list)
114
- self.mailboxes[mailbox_id].inbox_count = 0
115
-
116
- async def save_message(self, message: Message):
117
- self.messages[message.message_id] = message
118
-
119
- async def send_message(self, message: Message, body: bytes, background_tasks: BackgroundTasks) -> Message:
120
-
121
- async with self.lock:
122
- parts = cast(list[Optional[bytes]], [None for _ in range(message.total_chunks)])
123
-
124
- self.chunks[message.message_id] = parts
30
+ async def add_to_outbox(self, message: Message):
31
+ if not message.sender.mailbox_id:
32
+ return
125
33
 
126
- if message.total_chunks > 0:
127
- await self.receive_chunk(message, 1, body, background_tasks)
34
+ self.outboxes[message.sender.mailbox_id].insert(0, message)
35
+ if not message.metadata.local_id:
36
+ return
128
37
 
129
- if message.sender.mailbox_id:
130
- self.outboxes[message.sender.mailbox_id].insert(0, message)
131
- if message.metadata.local_id:
132
- self.local_ids[message.sender.mailbox_id][message.metadata.local_id].insert(0, message)
38
+ self.local_ids[message.sender.mailbox_id][message.metadata.local_id].insert(0, message)
133
39
 
134
- if message.status != MessageStatus.ACCEPTED:
135
- await self.save_message(message)
136
- else:
137
- await self._accept_message_no_lock(message, background_tasks)
40
+ async def add_to_inbox(self, message: Message):
41
+ self.inboxes[message.recipient.mailbox_id].append(message)
138
42
 
139
- background_tasks.add_task(self.on_event, "post_send_message", message)
140
- return message
43
+ async def save_message(self, message: Message):
44
+ self.messages[message.message_id] = message
141
45
 
142
46
  async def save_chunk(self, message: Message, chunk_number: int, chunk: Optional[bytes]):
47
+ if message.message_id not in self.chunks:
48
+ self.chunks[message.message_id] = [None for _ in range(message.total_chunks)]
143
49
  self.chunks[message.message_id][chunk_number - 1] = chunk
144
-
145
- async def receive_chunk(
146
- self, message: Message, chunk_number: int, chunk: Optional[bytes], background_tasks: BackgroundTasks
147
- ):
148
- await self.save_chunk(message, chunk_number, chunk)
149
- background_tasks.add_task(self.on_event, "chunk_received", message)
150
-
151
- async def _get_file_size(self, message: Message) -> int:
152
- return sum(len(chunk or b"") for chunk in self.chunks.get(message.message_id, []))
153
-
154
- async def _accept_message_no_lock(self, message: Message, background_tasks: BackgroundTasks) -> Message:
155
-
156
- if message.status != MessageStatus.ACCEPTED:
157
- message.events.insert(0, MessageEvent(status=MessageStatus.ACCEPTED))
158
- message.file_size = await self._get_file_size(message)
159
- self.inboxes[message.recipient.mailbox_id].append(message)
160
- await self.save_message(message)
161
-
162
- background_tasks.add_task(self.on_event, "message_accepted", message)
163
- return message
164
-
165
- async def accept_message(self, message: Message, background_tasks: BackgroundTasks) -> Message:
166
- async with self.lock:
167
- return await self._accept_message_no_lock(message, background_tasks)
168
-
169
- async def acknowledge_message(self, message: Message, background_tasks: BackgroundTasks) -> Message:
170
- async with self.lock:
171
- message.events.insert(0, MessageEvent(status=MessageStatus.ACKNOWLEDGED))
172
- await self.save_message(message)
173
-
174
- background_tasks.add_task(self.on_event, "message_acknowledged", message)
175
- return message
@@ -1,5 +1,6 @@
1
1
  import logging
2
2
  import os.path
3
+ from typing import Any, Optional
3
4
 
4
5
 
5
6
  class OnMessageAcceptedPlugin:
@@ -8,9 +9,18 @@ class OnMessageAcceptedPlugin:
8
9
  with open(os.path.join(os.path.dirname(__file__), "example_plugin.txt"), encoding="utf-8", mode="r") as f:
9
10
  self.config_message = f.read().strip()
10
11
 
11
- triggers = ["message_accepted"]
12
+ triggers = ["before_accept_message"]
12
13
 
13
- async def on_event(self, event: str, message): # pylint: disable=unused-argument
14
+ async def on_event(self, event: str, kwargs: dict[str, Any], err: Optional[Exception] = None):
15
+
16
+ if err:
17
+ print(f"plugin received error {event}\n{kwargs}\n{err}")
18
+ return
19
+
20
+ print(f"plugin received event {event}")
21
+
22
+ message = kwargs.get("message")
23
+ assert message
14
24
  self.logger.info(f"message: {message.message_id} accepted - {self.config_message}")
15
25
 
16
26
  if not message.metadata.subject:
@@ -17,7 +17,7 @@ from mesh_sandbox.tests.mesh_api_helpers import (
17
17
 
18
18
  from ..common.constants import Headers
19
19
  from ..models.message import MessageStatus, MessageType
20
- from ..views.admin import PutReportRequest
20
+ from ..views.admin import AddMessageEventRequest, CreateReportRequest
21
21
  from .helpers import generate_auth_token, temp_env_vars
22
22
 
23
23
 
@@ -241,7 +241,7 @@ def test_put_report_in_inbox(app: TestClient, tmp_path: str):
241
241
 
242
242
  with temp_env_vars(STORE_MODE="file", MAILBOXES_DATA_DIR=tmp_path):
243
243
 
244
- request = PutReportRequest(
244
+ request = CreateReportRequest(
245
245
  mailbox_id=recipient,
246
246
  status=MessageStatus.ERROR,
247
247
  workflow_id=uuid4().hex,
@@ -298,3 +298,64 @@ def test_put_report_in_inbox(app: TestClient, tmp_path: str):
298
298
  )
299
299
  messages = res.json().get("messages", [])
300
300
  assert not messages
301
+
302
+
303
+ def test_add_message_event(app: TestClient, tmp_path: str):
304
+
305
+ recipient = _CANNED_MAILBOX1
306
+
307
+ with temp_env_vars(STORE_MODE="file", MAILBOXES_DATA_DIR=tmp_path):
308
+
309
+ create_report_request = CreateReportRequest(
310
+ mailbox_id=recipient,
311
+ status=MessageStatus.ERROR,
312
+ workflow_id=uuid4().hex,
313
+ code="21",
314
+ description="my error",
315
+ subject=f"my subject {uuid4().hex}",
316
+ local_id=f"my local id {uuid4().hex}",
317
+ file_name=f"my filename {uuid4().hex}",
318
+ linked_message_id=uuid4().hex,
319
+ )
320
+
321
+ res = app.post("/messageexchange/report", json=create_report_request.dict())
322
+ assert res.status_code == status.HTTP_200_OK
323
+
324
+ result = res.json()
325
+ assert result
326
+ message_id = result["message_id"]
327
+ assert message_id
328
+
329
+ res = app.get(
330
+ f"/messageexchange/{recipient}/inbox",
331
+ headers={Headers.Authorization: generate_auth_token(recipient)},
332
+ )
333
+ messages = res.json().get("messages", [])
334
+ assert messages
335
+ assert messages[0] == message_id
336
+
337
+ res = app.put(
338
+ f"/messageexchange/{recipient}/inbox/{message_id}/status/acknowledged",
339
+ headers={Headers.Authorization: generate_auth_token(recipient)},
340
+ )
341
+ assert res.status_code == status.HTTP_200_OK
342
+
343
+ res = app.get(
344
+ f"/messageexchange/{recipient}/inbox",
345
+ headers={Headers.Authorization: generate_auth_token(recipient)},
346
+ )
347
+ messages = res.json().get("messages", [])
348
+ assert not messages
349
+
350
+ # move the message to accepted again
351
+ add_event_request = AddMessageEventRequest(status=MessageStatus.ACCEPTED)
352
+
353
+ res = app.post(f"/messageexchange/message/{message_id}/event", json=add_event_request.dict())
354
+ assert res.status_code == status.HTTP_200_OK
355
+
356
+ res = app.get(
357
+ f"/messageexchange/{recipient}/inbox",
358
+ headers={Headers.Authorization: generate_auth_token(recipient)},
359
+ )
360
+ messages = res.json().get("messages", [])
361
+ assert messages == [message_id]
@@ -0,0 +1,165 @@
1
+ from typing import Any, Optional
2
+ from uuid import uuid4
3
+
4
+ import pytest
5
+ from fastapi import BackgroundTasks
6
+
7
+ from .. import tests as tests_module
8
+ from ..common.messaging import Messaging
9
+ from ..dependencies import get_messaging, get_store
10
+ from ..models.message import (
11
+ Message,
12
+ MessageEvent,
13
+ MessageMetadata,
14
+ MessageParty,
15
+ MessageStatus,
16
+ )
17
+ from .helpers import temp_env_vars
18
+
19
+
20
+ @pytest.fixture(name="message", scope="function")
21
+ def _create_message() -> Message:
22
+ message = Message(
23
+ message_id=uuid4().hex.upper(),
24
+ workflow_id=uuid4().hex.upper(),
25
+ sender=MessageParty(mailbox_id=uuid4().hex.upper()),
26
+ recipient=MessageParty(mailbox_id=uuid4().hex.upper()),
27
+ metadata=MessageMetadata(),
28
+ events=[MessageEvent(status=MessageStatus.ACCEPTED)],
29
+ )
30
+ return message
31
+
32
+
33
+ @pytest.fixture(name="background_tasks", scope="function")
34
+ def _background_tasks() -> BackgroundTasks:
35
+ return BackgroundTasks()
36
+
37
+
38
+ async def test_canned_store_raises_for_save_message(message: Message):
39
+
40
+ with pytest.raises(NotImplementedError):
41
+ with temp_env_vars(STORE_MODE="canned"):
42
+ store = get_store()
43
+ await store.save_message(message)
44
+
45
+
46
+ async def test_canned_messaging_does_not_raise_no_bgt(message: Message):
47
+
48
+ calls = []
49
+
50
+ class TestPlugin:
51
+
52
+ triggers = ["before_save_message", "after_save_message", "save_message_error"]
53
+
54
+ async def on_event(self, event: str, args: dict[str, Any], exception: Optional[Exception] = None):
55
+ calls.append((event, args, exception))
56
+
57
+ with temp_env_vars(STORE_MODE="canned"):
58
+ messaging = get_messaging()
59
+ messaging.register_plugin(TestPlugin)
60
+ await messaging.save_message(message=message)
61
+
62
+ assert len(calls) == 1
63
+ assert calls[0][0] == "before_save_message"
64
+
65
+
66
+ async def test_canned_messaging_does_not_raise_with_bgt(message: Message, background_tasks: BackgroundTasks):
67
+
68
+ calls = []
69
+
70
+ class Test2Plugin:
71
+
72
+ triggers = ["before_save_message", "after_save_message", "save_message_error"]
73
+
74
+ async def on_event(self, event: str, args: dict[str, Any], exception: Optional[Exception] = None):
75
+ calls.append((event, args, exception))
76
+
77
+ with temp_env_vars(STORE_MODE="canned"):
78
+ messaging = get_messaging()
79
+ messaging.register_plugin(Test2Plugin)
80
+ await messaging.save_message(message=message, background_tasks=background_tasks)
81
+
82
+ await background_tasks()
83
+
84
+ assert len(calls) == 2
85
+ assert calls[0][0] == "before_save_message"
86
+ assert calls[1][0] == "after_save_message"
87
+
88
+
89
+ async def test_messaging_does_not_raise_with_bgt(message: Message, background_tasks: BackgroundTasks):
90
+
91
+ calls = []
92
+
93
+ class Test2Plugin:
94
+
95
+ triggers = ["before_save_message", "after_save_message", "save_message_error"]
96
+
97
+ async def on_event(self, event: str, args: dict[str, Any], exception: Optional[Exception] = None):
98
+ calls.append((event, args, exception))
99
+
100
+ with temp_env_vars(STORE_MODE="memory"):
101
+ messaging = get_messaging()
102
+ messaging.register_plugin(Test2Plugin)
103
+ await messaging.save_message(message=message, background_tasks=background_tasks)
104
+
105
+ await background_tasks()
106
+
107
+ assert len(calls) == 2
108
+ assert calls[0][0] == "before_save_message"
109
+ assert calls[1][0] == "after_save_message"
110
+
111
+
112
+ async def test_error_events_raised_with_bgt(message: Message, background_tasks: BackgroundTasks):
113
+
114
+ calls = []
115
+
116
+ class Test2Plugin:
117
+
118
+ triggers = ["before_save_message", "after_save_message", "save_message_error"]
119
+
120
+ async def on_event(self, event: str, args: dict[str, Any], exception: Optional[Exception] = None):
121
+ calls.append((event, args, exception))
122
+
123
+ with temp_env_vars(STORE_MODE="memory"):
124
+ messaging = get_messaging()
125
+ messaging.register_plugin(Test2Plugin)
126
+ await messaging.save_message(message=message, background_tasks=background_tasks)
127
+
128
+ await background_tasks()
129
+
130
+ assert len(calls) == 2
131
+ assert calls[0][0] == "before_save_message"
132
+ assert calls[1][0] == "after_save_message"
133
+
134
+
135
+ class TestDiscoveredPlugin:
136
+
137
+ triggers = ["before_accept_message"]
138
+
139
+ async def on_event(self, event: str, args: dict[str, Any], err: Optional[Exception] = None):
140
+ pass
141
+
142
+
143
+ async def test_plugin_discovery(monkeypatch):
144
+
145
+ messaging = Messaging(store=get_store(), plugins_module=tests_module)
146
+
147
+ calls = []
148
+
149
+ async def on_event(_, event: str, args: dict[str, Any], err: Optional[Exception] = None):
150
+ calls.append((event, args, err))
151
+
152
+ monkeypatch.setattr(TestDiscoveredPlugin, "on_event", on_event)
153
+
154
+ assert messaging._plugin_registry # pylint: disable=protected-access
155
+
156
+ message = Message(message_id=uuid4().hex)
157
+ args = {"message": message}
158
+
159
+ await messaging.on_event("before_accept_message", args)
160
+
161
+ assert len(calls) == 1
162
+
163
+ assert calls[0][0] == "before_accept_message"
164
+ assert calls[0][1] == args
165
+ assert calls[0][2] is None
@@ -5,7 +5,7 @@ from pydantic import BaseModel, Field # pylint: disable=no-name-in-module
5
5
  from mesh_sandbox.models.message import MessageStatus
6
6
 
7
7
 
8
- class PutReportRequest(BaseModel):
8
+ class CreateReportRequest(BaseModel):
9
9
  mailbox_id: str = Field(description="mailbox id to put the report in")
10
10
  code: str = Field(description="error code")
11
11
  description: str = Field(description="error description")
@@ -15,3 +15,11 @@ class PutReportRequest(BaseModel):
15
15
  status: str = Field(description="report status (error/undeliverable)", default=MessageStatus.UNDELIVERABLE)
16
16
  file_name: Optional[str] = Field(description="file name", default=None)
17
17
  linked_message_id: Optional[str] = Field(description="linked message id", default=None)
18
+
19
+
20
+ class AddMessageEventRequest(BaseModel):
21
+ status: str = Field(description="new message status")
22
+ code: str = Field(description="error code", default=None)
23
+ event: str = Field(description="error event (SEND/TRANSFER) etc)", default=None)
24
+ description: str = Field(description="error description", default=None)
25
+ linked_message_id: Optional[str] = Field(description="linked message id", default=None)
@@ -127,7 +127,7 @@ def create_tracking_response(message: Message, model_version: int = 1) -> Union[
127
127
  return TrackingV1(
128
128
  checksum=message.metadata.checksum or _EMPTY,
129
129
  chunkCount=message.total_chunks,
130
- compressFlag="Y" if message.metadata.is_compressed else _EMPTY,
130
+ compressFlag="Y" if message.metadata.compressed else _EMPTY,
131
131
  contentEncoding=message.metadata.content_encoding,
132
132
  downloadTimestamp=_format_timestamp(message.status_timestamp(MessageStatus.ACKNOWLEDGED)),
133
133
  dtsId=message.message_id,
@@ -137,7 +137,7 @@ def create_tracking_response(message: Message, model_version: int = 1) -> Union[
137
137
  failureDiagnostic=failure_description,
138
138
  fileName=message.metadata.file_name or f"{message.message_id}.dat",
139
139
  fileSize=message.file_size,
140
- isCompressed="Y" if message.metadata.is_compressed else _EMPTY,
140
+ isCompressed="Y" if message.metadata.compressed else _EMPTY,
141
141
  linkedMsgId=error_event.linked_message_id if error_event else None,
142
142
  localId=message.metadata.local_id,
143
143
  meshRecipientOdsCode=message.recipient.ods_code,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: mesh-sandbox
3
- Version: 0.1.33
3
+ Version: 1.0.1
4
4
  Summary: NHSDigital mesh sandbox, a locally testable version of the MESH api
5
5
  License: MIT
6
6
  Author: spinecore
@@ -1,27 +1,28 @@
1
- mesh_sandbox/__init__.py,sha256=gzg6nU6x2Uud0fXG6Kts9v4UFjYEjLGQu5DaW7kU0qc,23
1
+ mesh_sandbox/__init__.py,sha256=d4QHYmS_30j0hPN8NmNPnQ_Z0TphDRbu4MtQj9cT9e8,22
2
2
  mesh_sandbox/api.py,sha256=F2KUKENAsSe6NAGG0wzHA2jebGF6mWFgta1q55oqacU,3925
3
3
  mesh_sandbox/common/__init__.py,sha256=MwKS4FixADUGgN49MQJI7vW0Wx6mnf9SBOPbgE4RddM,3403
4
4
  mesh_sandbox/common/constants.py,sha256=_hnaHDkAQGHWLF7n_WfC5ZHIY5D-fUbOdpSqLusUMNY,6504
5
5
  mesh_sandbox/common/exceptions.py,sha256=YQII8w6DQQoKuW0cukEr6PIE9j0rwrqDpCn5lapgmkQ,1481
6
6
  mesh_sandbox/common/fernet.py,sha256=f8yygkVnK1cmQLBgcPifoB4PwGpgiOrG8Yk-6OMDy8o,551
7
7
  mesh_sandbox/common/handler_helpers.py,sha256=Eg00Tide2mL87EoMFw83fDuxiTL5DgIwxHs2RaJasgE,392
8
- mesh_sandbox/common/mex_headers.py,sha256=4hX80kxx3bbpZu9_mwDKwikL6589bN0pOntx0_TDung,6091
9
- mesh_sandbox/conftest.py,sha256=Bd-soyJTgtNAhC_LOH0vFNuE5bK9mPW1uYPglxL2Ox4,2186
10
- mesh_sandbox/dependencies.py,sha256=ityaCPppg6tLa-bm-uARIDt334ULDjooDDWZvHFp93E,3945
8
+ mesh_sandbox/common/messaging.py,sha256=xZbNpWQLhfjI4wiBG3PpIkEy2WbvwxqaWSR90sSb4hk,14664
9
+ mesh_sandbox/common/mex_headers.py,sha256=Eu-ixdVml2obROXggYyUyiS4QZHUA1JUgF7gO4YYkyg,6088
10
+ mesh_sandbox/conftest.py,sha256=IXrb8Y8Hi4-0ZxDNfTDFI0L1uG1Iz9FBQdNBZtPMsEw,2233
11
+ mesh_sandbox/dependencies.py,sha256=Crl7yEUED-XYl_odMenpvMJoYeIVDROWfvcrfvV85fc,4089
11
12
  mesh_sandbox/handlers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
12
- mesh_sandbox/handlers/admin.py,sha256=G1MagezVqk9jJP8h1l5AlybVdUSxGCDUQZklq3eJe3M,3143
13
+ mesh_sandbox/handlers/admin.py,sha256=JwPqeN3LHpau0pk7QOEFVrKZ9DV_896j0L_eSZxHIX4,3783
13
14
  mesh_sandbox/handlers/handshake.py,sha256=p3_NveSscNBgCdIIJhBza34b0WreqgBxZwI2sw7ZMTk,605
14
- mesh_sandbox/handlers/inbox.py,sha256=JgjBAPEgU9nUL8oTElcUXSAzmp2mxvl7XMfe1waLBuA,15504
15
- mesh_sandbox/handlers/lookup.py,sha256=hqZBjp-Bwg4Jrx71BfKE3ET2AO6x8XXsbl81tLYoshM,1471
16
- mesh_sandbox/handlers/outbox.py,sha256=Au-7xzTNXULHoUcavLHHgY1m_vH_ZKZC3c4EbFUTBdo,9872
17
- mesh_sandbox/handlers/tracking.py,sha256=yZdb30g8vHRXv5o6AjgNcX6lY_sxlFxzc0_TxYmP_jU,2407
15
+ mesh_sandbox/handlers/inbox.py,sha256=J20JUX1dHlS2We2UIDQa1MMoVN_QsRoP0tC-3zKAlD8,15450
16
+ mesh_sandbox/handlers/lookup.py,sha256=p_xmnCJi6BoNqRaQjRAGeQthL5EohQfZ_LA1h0QU36Q,1392
17
+ mesh_sandbox/handlers/outbox.py,sha256=XrLOGhw0QqMTfMFevrh-_Z-EyFSEAQLO1BcmbL1bR0Y,10045
18
+ mesh_sandbox/handlers/tracking.py,sha256=nmn5JexFhAvzFmj9C5l0w4sq8sNz3x-mMd3CsGVrEM4,2356
18
19
  mesh_sandbox/models/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
19
20
  mesh_sandbox/models/mailbox.py,sha256=W1b1R1XjhyiCp30LvTLtQVlxagfxXH8eb0XnNj3D4Dw,993
20
- mesh_sandbox/models/message.py,sha256=iiiMb66U9bk1RHIBBaimlxZLfkBB3OPc23fr0Td5hRI,4929
21
+ mesh_sandbox/models/message.py,sha256=ze-IPbCg6XlKZtOL1RWQvMnu04AzrGppwdk2mTyVAfU,4926
21
22
  mesh_sandbox/models/workflow.py,sha256=T8A1Q729TOUaz1MOa1Ly8oZs_G4769xMZpTYGF0TlO8,518
22
23
  mesh_sandbox/plugins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
23
24
  mesh_sandbox/routers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
24
- mesh_sandbox/routers/admin.py,sha256=dNnW77DUc5IWNehkpuKgbOywEmG8Jg_uVpK-pMTI-ZE,2990
25
+ mesh_sandbox/routers/admin.py,sha256=pJ9rPF-JDWs1dbVbQVqW8uKQrX3yh4oUxswBOnO5zR8,3852
25
26
  mesh_sandbox/routers/handshake.py,sha256=5TVyQ5OrHIe6W3UVpbap-hc5ia669Of6w6F1vgcYjm4,2693
26
27
  mesh_sandbox/routers/inbox.py,sha256=EkOmL9pxE6pYPBTzGU1CZJLu80ZR2EnKg9fe7DDMLLQ,8898
27
28
  mesh_sandbox/routers/inbox_count.py,sha256=hY88h7dBa6asnvYEP9mmx6CEBw5ZyRO7gz6lHg4Yj3c,1521
@@ -31,8 +32,8 @@ mesh_sandbox/routers/request_logging.py,sha256=gZaOJ_Mp3QPsmrpxXQ0pOzVGNPKd_RAEj
31
32
  mesh_sandbox/routers/tracking.py,sha256=UVIRkMBGD5pI7vgp8l6cnP5VX8OFRDPNb2JHs9q5w58,2210
32
33
  mesh_sandbox/routers/update.py,sha256=a9ttmk3levdDcu-ZF7a5EniR35zRMhty8pG2EyzB1po,409
33
34
  mesh_sandbox/store/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
34
- mesh_sandbox/store/base.py,sha256=UDNqXBWTsJTyEL4XcuEIhW6xD7R7exp0pwVuVegRmLM,6283
35
- mesh_sandbox/store/canned_store.py,sha256=cdO0uXi0TMRTxHF6X7pn1hdQSbFqBHdNMqnoLkEZ3u4,9929
35
+ mesh_sandbox/store/base.py,sha256=FV93oTeg40ikOaIqcRmDGnigbOEpYexYNqcLyvCaLnA,1983
36
+ mesh_sandbox/store/canned_store.py,sha256=DG5v7Aqy29mCCF9nupqoLIW4m2puLl6iGs2JiNtYnyw,9862
36
37
  mesh_sandbox/store/data/mailboxes/X26ABC1/in/SIMPLE_MESSAGE/1,sha256=aqaFLD9HL0hqn0sLIsv-qQqMVyaBmnHx17fu-lgVR9A,662
37
38
  mesh_sandbox/store/data/mailboxes/X26ABC1/in/SIMPLE_MESSAGE.json,sha256=lb_vaAXr9hJefOdQ1WXvrBXlIRtzPKzUXt3NT_-b_qA,714
38
39
  mesh_sandbox/store/data/mailboxes/X26ABC1/in/UNDELIVERED_MESSAGE/1,sha256=aqaFLD9HL0hqn0sLIsv-qQqMVyaBmnHx17fu-lgVR9A,662
@@ -42,14 +43,14 @@ mesh_sandbox/store/data/mailboxes/X26ABC2/in/CHUNKED_MESSAGE_GZ/2,sha256=uelYjD2
42
43
  mesh_sandbox/store/data/mailboxes/X26ABC2/in/CHUNKED_MESSAGE_GZ.json,sha256=N9T-V-nTrwnJeSefL5aIjXLLlBkjdO86iUmKyxjee0k,817
43
44
  mesh_sandbox/store/data/mailboxes.jsonl,sha256=ADXpImNo9UH6rY6T9bqgI-BvnbziFaGwcf_XeJW1bW4,432
44
45
  mesh_sandbox/store/data/workflows.jsonl,sha256=RFvycuqVmEkImeXlFD2wjuJFjt6dw581D_raPWstxpU,292
45
- mesh_sandbox/store/file_store.py,sha256=CHKJ5h_JRgMPEUS3-GvxoD0ZzjR6hlh2jNkqK0BB6oY,2383
46
- mesh_sandbox/store/memory_store.py,sha256=tGwhygUawf-VG05xxkgkE5nA6AYW1SXJ4XtCPRw9GdI,7036
46
+ mesh_sandbox/store/file_store.py,sha256=a4GAhDLjEj2S-zvQxpbMkyDKhC69POCFZln0abH6sUw,2400
47
+ mesh_sandbox/store/memory_store.py,sha256=VJTsPAnwvPAVFTEo-mO2A__lcc7jMnyqiq0JerYmrKY,1643
47
48
  mesh_sandbox/store/serialisation.py,sha256=pMYZ704GP74aCDRX9Nu1r4B1XA4BPJXYY-RKLFJJfUg,3615
48
49
  mesh_sandbox/test_plugin/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
49
- mesh_sandbox/test_plugin/example_plugin.py,sha256=K0yFDl64bH4QRtx1XFw2HzaSutNCYi3LGszwkcdTcnI,698
50
+ mesh_sandbox/test_plugin/example_plugin.py,sha256=MnNpyyWKziqvOr89LeIxOwqskwHcrSsh_te67SeNJr4,967
50
51
  mesh_sandbox/test_plugin/example_plugin.txt,sha256=aUePU6UUVhpxeP37Pdz5Zxyn_nxI6HCY3qtQBo1iUEI,24
51
52
  mesh_sandbox/tests/__init__.py,sha256=wPjH0Ka1334a0OR9VMUfeYxsr03JqWjHTBnAH_1SB7I,106
52
- mesh_sandbox/tests/admin.py,sha256=3DPIZXNmItwnoAEmf4Bn601l2QmDy7iPuFFfnV3jH7c,12362
53
+ mesh_sandbox/tests/admin.py,sha256=Y_nzBhqSGPCTjG3EczSHClA-mVALGNBd-iOShoYh8WU,14549
53
54
  mesh_sandbox/tests/docker_tests.py,sha256=SJuuquoQA7_fP49--89mrIZmY_4KRO7fiFkOcZeR4AU,1584
54
55
  mesh_sandbox/tests/exceptions.py,sha256=j_jKskVzLAYpyu4qNAEY5ahkbk8Uh2Nc_CGpnpdXjnI,740
55
56
  mesh_sandbox/tests/handshake.py,sha256=X-fCbwaEAc8cAvvnniCvt4W4DAV9ItPq7tpIpZkfj6M,6259
@@ -59,17 +60,17 @@ mesh_sandbox/tests/java_client_tests.py,sha256=NYvNH1LxJc0SmHQ3q6mSvEd6Wuv2r_xMq
59
60
  mesh_sandbox/tests/lookup.py,sha256=wY8x0F415y2um48Lw5kyD4ugMo1vBGKB7cqPdHEaVO4,2768
60
61
  mesh_sandbox/tests/mesh_api_helpers.py,sha256=ZedDWMHO88j4Zxw3hsK5vmvivy3UhdzbzHle0g9GJXA,3901
61
62
  mesh_sandbox/tests/mesh_client_tests.py,sha256=WL7ht-tT4QLMw73pIbcFMZg7Ehpn5Mi4jcZg5qgEgyw,3947
63
+ mesh_sandbox/tests/messaging_tests.py,sha256=Bup6aJsiJ0xdtg0Ab37ME0VHI6HFJA-0ATmfS88giBI,5006
62
64
  mesh_sandbox/tests/outbox.py,sha256=AbQR8vgRy8KAG0RMCU0jYFUXSCPQMrLRJMJTU3PGQhI,20061
63
- mesh_sandbox/tests/plugin_manager_tests.py,sha256=wTmyDtg1Up2jzqZgiRIVjkD35USZw-tTEoS4LtdFwCo,1070
64
65
  mesh_sandbox/tests/serialisation.py,sha256=kRSMZLDHlX6oRK6c2OCZL0XQoUCKQxqcYXrJWbc9VkA,663
65
66
  mesh_sandbox/views/__init__.py,sha256=nZkb6_1S8jz8Xl_AayfwjgEZG0JD2dfulfGxjJ5W9Ec,1237
66
- mesh_sandbox/views/admin.py,sha256=l4WfErLNgc14qObt0iwK_Q3sl_IbMImybcyLgv3pW0Y,896
67
+ mesh_sandbox/views/admin.py,sha256=2YGslfDyC0QKoq3WTGXBicqrOMR9UmQYocP3R0qTbAY,1315
67
68
  mesh_sandbox/views/error.py,sha256=9lnUr3P93Vm-nOrBTEuAD6nrSBUvpI6-XqXzILWjgGk,3885
68
69
  mesh_sandbox/views/inbox.py,sha256=gnaD9Csx5BqilVRefQQ_tXmeq80lwcLJfepW005GrkU,5662
69
70
  mesh_sandbox/views/lookup.py,sha256=HHUqZ-Iy22ysC3qaO8Bl5GBQqf_7IiBbe5acyxqS78M,2775
70
71
  mesh_sandbox/views/outbox.py,sha256=jxYiHylEdpljZ6Wl45Ke3aaH5rEKb-kzUuCNEKDS3So,4932
71
- mesh_sandbox/views/tracking.py,sha256=FRjJgBI3WbTuuOw4Y6uWok0KIeOcjGl289eyyuTcwfo,8924
72
- mesh_sandbox-0.1.33.dist-info/WHEEL,sha256=vVCvjcmxuUltf8cYhJ0sJMRDLr1XsPuxEId8YDzbyCY,88
73
- mesh_sandbox-0.1.33.dist-info/LICENSE,sha256=usgzIvDUpVX5pYZepJTRXQJqIaz0mdd32GuS5a3PFlY,1051
74
- mesh_sandbox-0.1.33.dist-info/METADATA,sha256=u1_Wpq7O3xnTZEcrLADbSu6HsUsdDgDFkHhMlWqHgao,2433
75
- mesh_sandbox-0.1.33.dist-info/RECORD,,
72
+ mesh_sandbox/views/tracking.py,sha256=1H7Ghcvqkmx__KS1Y-lm105EVx_Z1eJo3oMDh8pzRMQ,8918
73
+ mesh_sandbox-1.0.1.dist-info/WHEEL,sha256=vVCvjcmxuUltf8cYhJ0sJMRDLr1XsPuxEId8YDzbyCY,88
74
+ mesh_sandbox-1.0.1.dist-info/LICENSE,sha256=usgzIvDUpVX5pYZepJTRXQJqIaz0mdd32GuS5a3PFlY,1051
75
+ mesh_sandbox-1.0.1.dist-info/METADATA,sha256=UUnhdGz3llub6OOLmTV7d2zGBkawfaUyyUzhSR1zMuE,2432
76
+ mesh_sandbox-1.0.1.dist-info/RECORD,,
@@ -1,41 +0,0 @@
1
- import logging
2
- from uuid import uuid4
3
-
4
- from .. import tests as tests_module
5
- from ..common import EnvConfig
6
- from ..models.message import Message
7
- from ..store.memory_store import MemoryStore
8
-
9
-
10
- class TestPlugin:
11
-
12
- triggers = ["message_accepted"]
13
-
14
- def real_handler(self, event: str, message: Message):
15
- pass
16
-
17
- async def on_event(self, event: str, message: Message):
18
- self.real_handler(event, message)
19
-
20
-
21
- async def test_plugin_provider():
22
-
23
- manager = MemoryStore(config=EnvConfig(), logger=logging.getLogger("mesh-sandbox"), plugins_module=tests_module)
24
-
25
- calls = []
26
-
27
- def test_handler(_: TestPlugin, event: str, message: Message):
28
- calls.append((event, message))
29
-
30
- TestPlugin.real_handler = test_handler # type: ignore[method-assign, assignment]
31
-
32
- assert manager._plugin_registry # pylint: disable=protected-access
33
-
34
- message = Message(message_id=uuid4().hex)
35
-
36
- await manager.on_event("message_accepted", message)
37
-
38
- assert len(calls) == 1
39
-
40
- assert calls[0][0] == "message_accepted"
41
- assert calls[0][1] == message