eventsourcing 9.3.3__py3-none-any.whl → 9.3.4__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.

Potentially problematic release.


This version of eventsourcing might be problematic. Click here for more details.

Files changed (127) hide show
  1. {eventsourcing-9.3.3.dist-info → eventsourcing-9.3.4.dist-info}/METADATA +1 -1
  2. eventsourcing-9.3.4.dist-info/RECORD +24 -0
  3. eventsourcing/examples/__init__.py +0 -0
  4. eventsourcing/examples/aggregate1/__init__.py +0 -0
  5. eventsourcing/examples/aggregate1/application.py +0 -27
  6. eventsourcing/examples/aggregate1/domainmodel.py +0 -16
  7. eventsourcing/examples/aggregate1/test_application.py +0 -37
  8. eventsourcing/examples/aggregate2/__init__.py +0 -0
  9. eventsourcing/examples/aggregate2/application.py +0 -27
  10. eventsourcing/examples/aggregate2/domainmodel.py +0 -22
  11. eventsourcing/examples/aggregate2/test_application.py +0 -37
  12. eventsourcing/examples/aggregate3/__init__.py +0 -0
  13. eventsourcing/examples/aggregate3/application.py +0 -27
  14. eventsourcing/examples/aggregate3/domainmodel.py +0 -38
  15. eventsourcing/examples/aggregate3/test_application.py +0 -37
  16. eventsourcing/examples/aggregate4/__init__.py +0 -0
  17. eventsourcing/examples/aggregate4/application.py +0 -27
  18. eventsourcing/examples/aggregate4/domainmodel.py +0 -114
  19. eventsourcing/examples/aggregate4/test_application.py +0 -38
  20. eventsourcing/examples/aggregate5/__init__.py +0 -0
  21. eventsourcing/examples/aggregate5/application.py +0 -27
  22. eventsourcing/examples/aggregate5/domainmodel.py +0 -131
  23. eventsourcing/examples/aggregate5/test_application.py +0 -38
  24. eventsourcing/examples/aggregate6/__init__.py +0 -0
  25. eventsourcing/examples/aggregate6/application.py +0 -30
  26. eventsourcing/examples/aggregate6/domainmodel.py +0 -123
  27. eventsourcing/examples/aggregate6/test_application.py +0 -38
  28. eventsourcing/examples/aggregate6a/__init__.py +0 -0
  29. eventsourcing/examples/aggregate6a/application.py +0 -40
  30. eventsourcing/examples/aggregate6a/domainmodel.py +0 -149
  31. eventsourcing/examples/aggregate6a/test_application.py +0 -45
  32. eventsourcing/examples/aggregate7/__init__.py +0 -0
  33. eventsourcing/examples/aggregate7/application.py +0 -53
  34. eventsourcing/examples/aggregate7/domainmodel.py +0 -142
  35. eventsourcing/examples/aggregate7/persistence.py +0 -57
  36. eventsourcing/examples/aggregate7/test_application.py +0 -45
  37. eventsourcing/examples/aggregate7/test_compression_and_encryption.py +0 -45
  38. eventsourcing/examples/aggregate7/test_snapshotting_intervals.py +0 -67
  39. eventsourcing/examples/aggregate7a/__init__.py +0 -0
  40. eventsourcing/examples/aggregate7a/application.py +0 -56
  41. eventsourcing/examples/aggregate7a/domainmodel.py +0 -168
  42. eventsourcing/examples/aggregate7a/test_application.py +0 -46
  43. eventsourcing/examples/aggregate7a/test_compression_and_encryption.py +0 -45
  44. eventsourcing/examples/aggregate8/__init__.py +0 -0
  45. eventsourcing/examples/aggregate8/application.py +0 -47
  46. eventsourcing/examples/aggregate8/domainmodel.py +0 -71
  47. eventsourcing/examples/aggregate8/persistence.py +0 -57
  48. eventsourcing/examples/aggregate8/test_application.py +0 -44
  49. eventsourcing/examples/aggregate8/test_compression_and_encryption.py +0 -44
  50. eventsourcing/examples/aggregate8/test_snapshotting_intervals.py +0 -38
  51. eventsourcing/examples/bankaccounts/__init__.py +0 -0
  52. eventsourcing/examples/bankaccounts/application.py +0 -70
  53. eventsourcing/examples/bankaccounts/domainmodel.py +0 -56
  54. eventsourcing/examples/bankaccounts/test.py +0 -173
  55. eventsourcing/examples/cargoshipping/__init__.py +0 -0
  56. eventsourcing/examples/cargoshipping/application.py +0 -126
  57. eventsourcing/examples/cargoshipping/domainmodel.py +0 -330
  58. eventsourcing/examples/cargoshipping/interface.py +0 -143
  59. eventsourcing/examples/cargoshipping/test.py +0 -231
  60. eventsourcing/examples/contentmanagement/__init__.py +0 -0
  61. eventsourcing/examples/contentmanagement/application.py +0 -118
  62. eventsourcing/examples/contentmanagement/domainmodel.py +0 -69
  63. eventsourcing/examples/contentmanagement/test.py +0 -180
  64. eventsourcing/examples/contentmanagement/utils.py +0 -26
  65. eventsourcing/examples/contentmanagementsystem/__init__.py +0 -0
  66. eventsourcing/examples/contentmanagementsystem/application.py +0 -54
  67. eventsourcing/examples/contentmanagementsystem/postgres.py +0 -17
  68. eventsourcing/examples/contentmanagementsystem/sqlite.py +0 -17
  69. eventsourcing/examples/contentmanagementsystem/system.py +0 -14
  70. eventsourcing/examples/contentmanagementsystem/test_system.py +0 -180
  71. eventsourcing/examples/searchablecontent/__init__.py +0 -0
  72. eventsourcing/examples/searchablecontent/application.py +0 -45
  73. eventsourcing/examples/searchablecontent/persistence.py +0 -23
  74. eventsourcing/examples/searchablecontent/postgres.py +0 -118
  75. eventsourcing/examples/searchablecontent/sqlite.py +0 -136
  76. eventsourcing/examples/searchablecontent/test_application.py +0 -110
  77. eventsourcing/examples/searchablecontent/test_recorder.py +0 -68
  78. eventsourcing/examples/searchabletimestamps/__init__.py +0 -0
  79. eventsourcing/examples/searchabletimestamps/application.py +0 -32
  80. eventsourcing/examples/searchabletimestamps/persistence.py +0 -20
  81. eventsourcing/examples/searchabletimestamps/postgres.py +0 -110
  82. eventsourcing/examples/searchabletimestamps/sqlite.py +0 -99
  83. eventsourcing/examples/searchabletimestamps/test_searchabletimestamps.py +0 -94
  84. eventsourcing/examples/test_invoice.py +0 -176
  85. eventsourcing/examples/test_parking_lot.py +0 -206
  86. eventsourcing/tests/application_tests/__init__.py +0 -0
  87. eventsourcing/tests/application_tests/test_application_with_automatic_snapshotting.py +0 -55
  88. eventsourcing/tests/application_tests/test_application_with_popo.py +0 -22
  89. eventsourcing/tests/application_tests/test_application_with_postgres.py +0 -75
  90. eventsourcing/tests/application_tests/test_application_with_sqlite.py +0 -72
  91. eventsourcing/tests/application_tests/test_cache.py +0 -134
  92. eventsourcing/tests/application_tests/test_event_sourced_log.py +0 -162
  93. eventsourcing/tests/application_tests/test_notificationlog.py +0 -232
  94. eventsourcing/tests/application_tests/test_notificationlogreader.py +0 -126
  95. eventsourcing/tests/application_tests/test_processapplication.py +0 -110
  96. eventsourcing/tests/application_tests/test_processingpolicy.py +0 -109
  97. eventsourcing/tests/application_tests/test_repository.py +0 -504
  98. eventsourcing/tests/application_tests/test_snapshotting.py +0 -68
  99. eventsourcing/tests/application_tests/test_upcasting.py +0 -459
  100. eventsourcing/tests/docs_tests/__init__.py +0 -0
  101. eventsourcing/tests/docs_tests/test_docs.py +0 -293
  102. eventsourcing/tests/domain_tests/__init__.py +0 -0
  103. eventsourcing/tests/domain_tests/test_aggregate.py +0 -1200
  104. eventsourcing/tests/domain_tests/test_aggregate_decorators.py +0 -1604
  105. eventsourcing/tests/domain_tests/test_domainevent.py +0 -80
  106. eventsourcing/tests/interface_tests/__init__.py +0 -0
  107. eventsourcing/tests/interface_tests/test_remotenotificationlog.py +0 -258
  108. eventsourcing/tests/persistence_tests/__init__.py +0 -0
  109. eventsourcing/tests/persistence_tests/test_aes.py +0 -93
  110. eventsourcing/tests/persistence_tests/test_connection_pool.py +0 -722
  111. eventsourcing/tests/persistence_tests/test_eventstore.py +0 -72
  112. eventsourcing/tests/persistence_tests/test_infrastructure_factory.py +0 -21
  113. eventsourcing/tests/persistence_tests/test_mapper.py +0 -113
  114. eventsourcing/tests/persistence_tests/test_noninterleaving_notification_ids.py +0 -69
  115. eventsourcing/tests/persistence_tests/test_popo.py +0 -124
  116. eventsourcing/tests/persistence_tests/test_postgres.py +0 -1120
  117. eventsourcing/tests/persistence_tests/test_sqlite.py +0 -348
  118. eventsourcing/tests/persistence_tests/test_transcoder.py +0 -44
  119. eventsourcing/tests/system_tests/__init__.py +0 -0
  120. eventsourcing/tests/system_tests/test_runner.py +0 -935
  121. eventsourcing/tests/system_tests/test_system.py +0 -284
  122. eventsourcing/tests/utils_tests/__init__.py +0 -0
  123. eventsourcing/tests/utils_tests/test_utils.py +0 -226
  124. eventsourcing-9.3.3.dist-info/RECORD +0 -145
  125. {eventsourcing-9.3.3.dist-info → eventsourcing-9.3.4.dist-info}/AUTHORS +0 -0
  126. {eventsourcing-9.3.3.dist-info → eventsourcing-9.3.4.dist-info}/LICENSE +0 -0
  127. {eventsourcing-9.3.3.dist-info → eventsourcing-9.3.4.dist-info}/WHEEL +0 -0
@@ -1,330 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from datetime import datetime, timedelta
4
- from enum import Enum
5
- from typing import Dict, List, Optional, Tuple, Union, cast
6
- from uuid import UUID, uuid4
7
-
8
- from eventsourcing.dispatch import singledispatchmethod
9
- from eventsourcing.domain import Aggregate
10
-
11
-
12
- class Location(Enum):
13
- """
14
- Locations in the world.
15
- """
16
-
17
- HAMBURG = "HAMBURG"
18
- HONGKONG = "HONGKONG"
19
- NEWYORK = "NEWYORK"
20
- STOCKHOLM = "STOCKHOLM"
21
- TOKYO = "TOKYO"
22
-
23
- NLRTM = "NLRTM"
24
- USDAL = "USDAL"
25
- AUMEL = "AUMEL"
26
-
27
-
28
- class Leg:
29
- """
30
- Leg of an itinerary.
31
- """
32
-
33
- def __init__(
34
- self,
35
- origin: str,
36
- destination: str,
37
- voyage_number: str,
38
- ):
39
- self.origin: str = origin
40
- self.destination: str = destination
41
- self.voyage_number: str = voyage_number
42
-
43
-
44
- class Itinerary:
45
- """
46
- An itinerary along which cargo is shipped.
47
- """
48
-
49
- def __init__(
50
- self,
51
- origin: str,
52
- destination: str,
53
- legs: Tuple[Leg, ...],
54
- ):
55
- self.origin = origin
56
- self.destination = destination
57
- self.legs = legs
58
-
59
-
60
- class HandlingActivity(Enum):
61
- RECEIVE = "RECEIVE"
62
- LOAD = "LOAD"
63
- UNLOAD = "UNLOAD"
64
- CLAIM = "CLAIM"
65
-
66
-
67
- # Custom static types.
68
- LegDetails = Dict[str, str]
69
-
70
- ItineraryDetails = Dict[str, Union[str, List[LegDetails]]]
71
-
72
- NextExpectedActivity = Optional[Tuple[HandlingActivity, Location, str]]
73
-
74
-
75
- # Some routes from one location to another.
76
- REGISTERED_ROUTES = {
77
- ("HONGKONG", "STOCKHOLM"): [
78
- Itinerary(
79
- origin="HONGKONG",
80
- destination="STOCKHOLM",
81
- legs=(
82
- Leg(
83
- origin="HONGKONG",
84
- destination="NEWYORK",
85
- voyage_number="V1",
86
- ),
87
- Leg(
88
- origin="NEWYORK",
89
- destination="STOCKHOLM",
90
- voyage_number="V2",
91
- ),
92
- ),
93
- )
94
- ],
95
- ("TOKYO", "STOCKHOLM"): [
96
- Itinerary(
97
- origin="TOKYO",
98
- destination="STOCKHOLM",
99
- legs=(
100
- Leg(
101
- origin="TOKYO",
102
- destination="HAMBURG",
103
- voyage_number="V3",
104
- ),
105
- Leg(
106
- origin="HAMBURG",
107
- destination="STOCKHOLM",
108
- voyage_number="V4",
109
- ),
110
- ),
111
- )
112
- ],
113
- }
114
-
115
-
116
- class Cargo(Aggregate):
117
- """
118
- The Cargo aggregate is an event-sourced domain model aggregate that
119
- specifies the routing from origin to destination, and can track what
120
- happens to the cargo after it has been booked.
121
- """
122
-
123
- def __init__(
124
- self,
125
- origin: Location,
126
- destination: Location,
127
- arrival_deadline: datetime,
128
- ):
129
- self._origin: Location = origin
130
- self._destination: Location = destination
131
- self._arrival_deadline: datetime = arrival_deadline
132
- self._transport_status: str = "NOT_RECEIVED"
133
- self._routing_status: str = "NOT_ROUTED"
134
- self._is_misdirected: bool = False
135
- self._estimated_time_of_arrival: datetime | None = None
136
- self._next_expected_activity: NextExpectedActivity = None
137
- self._route: Itinerary | None = None
138
- self._last_known_location: Location | None = None
139
- self._current_voyage_number: str | None = None
140
-
141
- @property
142
- def origin(self) -> Location:
143
- return self._origin
144
-
145
- @property
146
- def destination(self) -> Location:
147
- return self._destination
148
-
149
- @property
150
- def arrival_deadline(self) -> datetime:
151
- return self._arrival_deadline
152
-
153
- @property
154
- def transport_status(self) -> str:
155
- return self._transport_status
156
-
157
- @property
158
- def routing_status(self) -> str:
159
- return self._routing_status
160
-
161
- @property
162
- def is_misdirected(self) -> bool:
163
- return self._is_misdirected
164
-
165
- @property
166
- def estimated_time_of_arrival(
167
- self,
168
- ) -> datetime | None:
169
- return self._estimated_time_of_arrival
170
-
171
- @property
172
- def next_expected_activity(self) -> NextExpectedActivity:
173
- return self._next_expected_activity
174
-
175
- @property
176
- def route(self) -> Itinerary | None:
177
- return self._route
178
-
179
- @property
180
- def last_known_location(self) -> Location | None:
181
- return self._last_known_location
182
-
183
- @property
184
- def current_voyage_number(self) -> str | None:
185
- return self._current_voyage_number
186
-
187
- @classmethod
188
- def new_booking(
189
- cls,
190
- origin: Location,
191
- destination: Location,
192
- arrival_deadline: datetime,
193
- ) -> Cargo:
194
- return cls._create(
195
- event_class=cls.BookingStarted,
196
- id=uuid4(),
197
- origin=origin,
198
- destination=destination,
199
- arrival_deadline=arrival_deadline,
200
- )
201
-
202
- class BookingStarted(Aggregate.Created):
203
- origin: Location
204
- destination: Location
205
- arrival_deadline: datetime
206
-
207
- class Event(Aggregate.Event):
208
- def apply(self, aggregate: Aggregate) -> None:
209
- cast(Cargo, aggregate).when(self)
210
-
211
- @singledispatchmethod
212
- def when(self, event: Event) -> None:
213
- """
214
- Default method to apply an aggregate event to the aggregate object.
215
- """
216
-
217
- def change_destination(self, destination: Location) -> None:
218
- self.trigger_event(
219
- self.DestinationChanged,
220
- destination=destination,
221
- )
222
-
223
- class DestinationChanged(Event):
224
- destination: Location
225
-
226
- @when.register
227
- def _(self, event: Cargo.DestinationChanged) -> None:
228
- self._destination = event.destination
229
-
230
- def assign_route(self, itinerary: Itinerary) -> None:
231
- self.trigger_event(self.RouteAssigned, route=itinerary)
232
-
233
- class RouteAssigned(Event):
234
- route: Itinerary
235
-
236
- @when.register
237
- def _(self, event: Cargo.RouteAssigned) -> None:
238
- self._route = event.route
239
- self._routing_status = "ROUTED"
240
- self._estimated_time_of_arrival = Cargo.Event.create_timestamp() + timedelta(
241
- weeks=1
242
- )
243
- self._next_expected_activity = (HandlingActivity.RECEIVE, self.origin, "")
244
- self._is_misdirected = False
245
-
246
- def register_handling_event(
247
- self,
248
- tracking_id: UUID,
249
- voyage_number: str | None,
250
- location: Location,
251
- handling_activity: HandlingActivity,
252
- ) -> None:
253
- self.trigger_event(
254
- self.HandlingEventRegistered,
255
- tracking_id=tracking_id,
256
- voyage_number=voyage_number,
257
- location=location,
258
- handling_activity=handling_activity,
259
- )
260
-
261
- class HandlingEventRegistered(Event):
262
- tracking_id: UUID
263
- voyage_number: str
264
- location: Location
265
- handling_activity: str
266
-
267
- @when.register
268
- def _(self, event: Cargo.HandlingEventRegistered) -> None:
269
- assert self.route is not None
270
- if event.handling_activity == HandlingActivity.RECEIVE:
271
- self._transport_status = "IN_PORT"
272
- self._last_known_location = event.location
273
- self._next_expected_activity = (
274
- HandlingActivity.LOAD,
275
- event.location,
276
- self.route.legs[0].voyage_number,
277
- )
278
- elif event.handling_activity == HandlingActivity.LOAD:
279
- self._transport_status = "ONBOARD_CARRIER"
280
- self._current_voyage_number = event.voyage_number
281
- for leg in self.route.legs:
282
- if (
283
- leg.origin == event.location.value
284
- and leg.voyage_number == event.voyage_number
285
- ):
286
- self._next_expected_activity = (
287
- HandlingActivity.UNLOAD,
288
- Location[leg.destination],
289
- event.voyage_number,
290
- )
291
- break
292
- else:
293
- msg = "Can't find leg with origin={} and voyage_number={}".format(
294
- event.location,
295
- event.voyage_number,
296
- )
297
- raise Exception(msg)
298
-
299
- elif event.handling_activity == HandlingActivity.UNLOAD:
300
- self._current_voyage_number = None
301
- self._last_known_location = event.location
302
- self._transport_status = "IN_PORT"
303
- if event.location == self.destination:
304
- self._next_expected_activity = (
305
- HandlingActivity.CLAIM,
306
- event.location,
307
- "",
308
- )
309
- elif event.location.value in [leg.destination for leg in self.route.legs]:
310
- for i, leg in enumerate(self.route.legs):
311
- if leg.voyage_number == event.voyage_number:
312
- next_leg: Leg = self.route.legs[i + 1]
313
- assert Location[next_leg.origin] == event.location
314
- self._next_expected_activity = (
315
- HandlingActivity.LOAD,
316
- event.location,
317
- next_leg.voyage_number,
318
- )
319
- break
320
- else:
321
- self._is_misdirected = True
322
- self._next_expected_activity = None
323
-
324
- elif event.handling_activity == HandlingActivity.CLAIM:
325
- self._next_expected_activity = None
326
- self._transport_status = "CLAIMED"
327
-
328
- else:
329
- msg = f"Unsupported handling event: {event.handling_activity}"
330
- raise Exception(msg)
@@ -1,143 +0,0 @@
1
- from __future__ import annotations
2
-
3
- from datetime import datetime
4
- from typing import TYPE_CHECKING, Dict, List, Optional, Tuple, Union
5
- from uuid import UUID
6
-
7
- from eventsourcing.examples.cargoshipping.domainmodel import (
8
- HandlingActivity,
9
- Itinerary,
10
- ItineraryDetails,
11
- LegDetails,
12
- Location,
13
- )
14
-
15
- if TYPE_CHECKING: # pragma: nocover
16
- from eventsourcing.examples.cargoshipping.application import BookingApplication
17
-
18
- NextExpectedActivityDetails = Optional[Tuple[str, ...]]
19
- CargoDetails = Dict[
20
- str, Optional[Union[str, bool, datetime, NextExpectedActivityDetails]]
21
- ]
22
-
23
-
24
- class BookingService:
25
- """
26
- Presents an application interface that uses
27
- simple types of object (str, bool, datetime).
28
- """
29
-
30
- def __init__(self, app: BookingApplication):
31
- self.app = app
32
-
33
- def book_new_cargo(
34
- self,
35
- origin: str,
36
- destination: str,
37
- arrival_deadline: datetime,
38
- ) -> str:
39
- tracking_id = self.app.book_new_cargo(
40
- Location[origin],
41
- Location[destination],
42
- arrival_deadline,
43
- )
44
- return str(tracking_id)
45
-
46
- def get_cargo_details(self, tracking_id: str) -> CargoDetails:
47
- cargo = self.app.get_cargo(UUID(tracking_id))
48
-
49
- # Present 'next_expected_activity'.
50
- next_expected_activity: NextExpectedActivityDetails
51
- if cargo.next_expected_activity is None:
52
- next_expected_activity = None
53
- elif len(cargo.next_expected_activity) == 2:
54
- next_expected_activity = (
55
- cargo.next_expected_activity[0].value,
56
- cargo.next_expected_activity[1].value,
57
- )
58
- elif len(cargo.next_expected_activity) == 3:
59
- next_expected_activity = (
60
- cargo.next_expected_activity[0].value,
61
- cargo.next_expected_activity[1].value,
62
- cargo.next_expected_activity[2],
63
- )
64
- else:
65
- msg = f"Invalid next expected activity: {cargo.next_expected_activity}"
66
- raise Exception(msg)
67
-
68
- # Present 'last_known_location'.
69
- if cargo.last_known_location is None:
70
- last_known_location = None
71
- else:
72
- last_known_location = cargo.last_known_location.value
73
-
74
- # Present the cargo details.
75
- return {
76
- "id": str(cargo.id),
77
- "origin": cargo.origin.value,
78
- "destination": cargo.destination.value,
79
- "arrival_deadline": cargo.arrival_deadline,
80
- "transport_status": cargo.transport_status,
81
- "routing_status": cargo.routing_status,
82
- "is_misdirected": cargo.is_misdirected,
83
- "estimated_time_of_arrival": cargo.estimated_time_of_arrival,
84
- "next_expected_activity": next_expected_activity,
85
- "last_known_location": last_known_location,
86
- "current_voyage_number": cargo.current_voyage_number,
87
- }
88
-
89
- def change_destination(self, tracking_id: str, destination: str) -> None:
90
- self.app.change_destination(UUID(tracking_id), Location[destination])
91
-
92
- def request_possible_routes_for_cargo(
93
- self, tracking_id: str
94
- ) -> List[ItineraryDetails]:
95
- routes = self.app.request_possible_routes_for_cargo(UUID(tracking_id))
96
- return [self.dict_from_itinerary(route) for route in routes]
97
-
98
- def dict_from_itinerary(self, itinerary: Itinerary) -> ItineraryDetails:
99
- legs_details = []
100
- for leg in itinerary.legs:
101
- leg_details: LegDetails = {
102
- "origin": leg.origin,
103
- "destination": leg.destination,
104
- "voyage_number": leg.voyage_number,
105
- }
106
- legs_details.append(leg_details)
107
- route_details: ItineraryDetails = {
108
- "origin": itinerary.origin,
109
- "destination": itinerary.destination,
110
- "legs": legs_details,
111
- }
112
- return route_details
113
-
114
- def assign_route(
115
- self,
116
- tracking_id: str,
117
- route_details: ItineraryDetails,
118
- ) -> None:
119
- routes = self.app.request_possible_routes_for_cargo(UUID(tracking_id))
120
- for route in routes:
121
- if route_details == self.dict_from_itinerary(route):
122
- self.app.assign_route(UUID(tracking_id), route)
123
-
124
- def register_handling_event(
125
- self,
126
- tracking_id: str,
127
- voyage_number: str | None,
128
- location: str,
129
- handling_activity: str,
130
- ) -> None:
131
- self.app.register_handling_event(
132
- UUID(tracking_id),
133
- voyage_number,
134
- Location[location],
135
- HandlingActivity[handling_activity],
136
- )
137
-
138
-
139
- # Stub function that picks an itinerary from a list of possible itineraries.
140
- def select_preferred_itinerary(
141
- itineraries: List[ItineraryDetails],
142
- ) -> ItineraryDetails:
143
- return itineraries[0]
@@ -1,231 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import unittest
4
- from datetime import timedelta
5
-
6
- from eventsourcing.examples.cargoshipping.application import BookingApplication
7
- from eventsourcing.examples.cargoshipping.domainmodel import Cargo
8
- from eventsourcing.examples.cargoshipping.interface import (
9
- BookingService,
10
- select_preferred_itinerary,
11
- )
12
-
13
-
14
- class TestBookingService(unittest.TestCase):
15
- def setUp(self) -> None:
16
- self.service = BookingService(BookingApplication())
17
-
18
- def test_admin_can_book_new_cargo(self) -> None:
19
- arrival_deadline = Cargo.Event.create_timestamp() + timedelta(weeks=3)
20
-
21
- cargo_id = self.service.book_new_cargo(
22
- origin="NLRTM",
23
- destination="USDAL",
24
- arrival_deadline=arrival_deadline,
25
- )
26
-
27
- cargo_details = self.service.get_cargo_details(cargo_id)
28
- self.assertTrue(cargo_details["id"])
29
- self.assertEqual(cargo_details["origin"], "NLRTM")
30
- self.assertEqual(cargo_details["destination"], "USDAL")
31
-
32
- self.service.change_destination(cargo_id, destination="AUMEL")
33
- cargo_details = self.service.get_cargo_details(cargo_id)
34
- self.assertEqual(cargo_details["destination"], "AUMEL")
35
- self.assertEqual(
36
- cargo_details["arrival_deadline"],
37
- arrival_deadline,
38
- )
39
-
40
- def test_scenario_cargo_from_hongkong_to_stockholm(
41
- self,
42
- ) -> None:
43
- # Test setup: A cargo should be shipped from
44
- # Hongkong to Stockholm, and it should arrive
45
- # in no more than two weeks.
46
- origin = "HONGKONG"
47
- destination = "STOCKHOLM"
48
- arrival_deadline = Cargo.Event.create_timestamp() + timedelta(weeks=2)
49
-
50
- # Use case 1: booking.
51
-
52
- # A new cargo is booked, and the unique tracking
53
- # id is assigned to the cargo.
54
- tracking_id = self.service.book_new_cargo(origin, destination, arrival_deadline)
55
-
56
- # The tracking id can be used to lookup the cargo
57
- # in the repository.
58
- # Important: The cargo, and thus the domain model,
59
- # is responsible for determining the status of the
60
- # cargo, whether it is on the right track or not
61
- # and so on. This is core domain logic. Tracking
62
- # the cargo basically amounts to presenting
63
- # information extracted from the cargo aggregate
64
- # in a suitable way.
65
- cargo_details = self.service.get_cargo_details(tracking_id)
66
- self.assertEqual(
67
- cargo_details["transport_status"],
68
- "NOT_RECEIVED",
69
- )
70
- self.assertEqual(cargo_details["routing_status"], "NOT_ROUTED")
71
- self.assertEqual(cargo_details["is_misdirected"], False)
72
- self.assertEqual(
73
- cargo_details["estimated_time_of_arrival"],
74
- None,
75
- )
76
- self.assertEqual(cargo_details["next_expected_activity"], None)
77
-
78
- # Use case 2: routing.
79
- #
80
- # A number of possible routes for this cargo is
81
- # requested and may be presented to the customer
82
- # in some way for him/her to choose from.
83
- # Selection could be affected by things like price
84
- # and time of delivery, but this test simply uses
85
- # an arbitrary selection to mimic that process.
86
- itineraries = self.service.request_possible_routes_for_cargo(tracking_id)
87
- route_details = select_preferred_itinerary(itineraries)
88
-
89
- # The cargo is then assigned to the selected
90
- # route, described by an itinerary.
91
- self.service.assign_route(tracking_id, route_details)
92
-
93
- cargo_details = self.service.get_cargo_details(tracking_id)
94
- self.assertEqual(
95
- cargo_details["transport_status"],
96
- "NOT_RECEIVED",
97
- )
98
- self.assertEqual(cargo_details["routing_status"], "ROUTED")
99
- self.assertEqual(cargo_details["is_misdirected"], False)
100
- self.assertTrue(cargo_details["estimated_time_of_arrival"])
101
- self.assertEqual(
102
- cargo_details["next_expected_activity"],
103
- ("RECEIVE", "HONGKONG", ""),
104
- )
105
-
106
- # Use case 3: handling
107
-
108
- # A handling event registration attempt will be
109
- # formed from parsing the data coming in as a
110
- # handling report either via the web service
111
- # interface or as an uploaded CSV file. The
112
- # handling event factory tries to create a
113
- # HandlingEvent from the attempt, and if the
114
- # factory decides that this is a plausible
115
- # handling event, it is stored. If the attempt
116
- # is invalid, for example if no cargo exists for
117
- # the specified tracking id, the attempt is
118
- # rejected.
119
- #
120
- # Handling begins: cargo is received in Hongkong.
121
- self.service.register_handling_event(tracking_id, None, "HONGKONG", "RECEIVE")
122
- cargo_details = self.service.get_cargo_details(tracking_id)
123
- self.assertEqual(cargo_details["transport_status"], "IN_PORT")
124
- self.assertEqual(
125
- cargo_details["last_known_location"],
126
- "HONGKONG",
127
- )
128
- self.assertEqual(
129
- cargo_details["next_expected_activity"],
130
- ("LOAD", "HONGKONG", "V1"),
131
- )
132
-
133
- # Load onto voyage V1.
134
- self.service.register_handling_event(tracking_id, "V1", "HONGKONG", "LOAD")
135
- cargo_details = self.service.get_cargo_details(tracking_id)
136
- self.assertEqual(cargo_details["current_voyage_number"], "V1")
137
- self.assertEqual(
138
- cargo_details["last_known_location"],
139
- "HONGKONG",
140
- )
141
- self.assertEqual(
142
- cargo_details["transport_status"],
143
- "ONBOARD_CARRIER",
144
- )
145
- self.assertEqual(
146
- cargo_details["next_expected_activity"],
147
- ("UNLOAD", "NEWYORK", "V1"),
148
- )
149
-
150
- # Incorrectly unload in Tokyo.
151
- self.service.register_handling_event(tracking_id, "V1", "TOKYO", "UNLOAD")
152
- cargo_details = self.service.get_cargo_details(tracking_id)
153
- self.assertEqual(cargo_details["current_voyage_number"], None)
154
- self.assertEqual(cargo_details["last_known_location"], "TOKYO")
155
- self.assertEqual(cargo_details["transport_status"], "IN_PORT")
156
- self.assertEqual(cargo_details["is_misdirected"], True)
157
- self.assertEqual(cargo_details["next_expected_activity"], None)
158
-
159
- # Reroute.
160
- itineraries = self.service.request_possible_routes_for_cargo(tracking_id)
161
- route_details = select_preferred_itinerary(itineraries)
162
- self.service.assign_route(tracking_id, route_details)
163
-
164
- # Load in Tokyo.
165
- self.service.register_handling_event(tracking_id, "V3", "TOKYO", "LOAD")
166
- cargo_details = self.service.get_cargo_details(tracking_id)
167
- self.assertEqual(cargo_details["current_voyage_number"], "V3")
168
- self.assertEqual(cargo_details["last_known_location"], "TOKYO")
169
- self.assertEqual(
170
- cargo_details["transport_status"],
171
- "ONBOARD_CARRIER",
172
- )
173
- self.assertEqual(cargo_details["is_misdirected"], False)
174
- self.assertEqual(
175
- cargo_details["next_expected_activity"],
176
- ("UNLOAD", "HAMBURG", "V3"),
177
- )
178
-
179
- # Unload in Hamburg.
180
- self.service.register_handling_event(tracking_id, "V3", "HAMBURG", "UNLOAD")
181
- cargo_details = self.service.get_cargo_details(tracking_id)
182
- self.assertEqual(cargo_details["current_voyage_number"], None)
183
- self.assertEqual(cargo_details["last_known_location"], "HAMBURG")
184
- self.assertEqual(cargo_details["transport_status"], "IN_PORT")
185
- self.assertEqual(cargo_details["is_misdirected"], False)
186
- self.assertEqual(
187
- cargo_details["next_expected_activity"],
188
- ("LOAD", "HAMBURG", "V4"),
189
- )
190
-
191
- # Load in Hamburg
192
- self.service.register_handling_event(tracking_id, "V4", "HAMBURG", "LOAD")
193
- cargo_details = self.service.get_cargo_details(tracking_id)
194
- self.assertEqual(cargo_details["current_voyage_number"], "V4")
195
- self.assertEqual(cargo_details["last_known_location"], "HAMBURG")
196
- self.assertEqual(
197
- cargo_details["transport_status"],
198
- "ONBOARD_CARRIER",
199
- )
200
- self.assertEqual(cargo_details["is_misdirected"], False)
201
- self.assertEqual(
202
- cargo_details["next_expected_activity"],
203
- ("UNLOAD", "STOCKHOLM", "V4"),
204
- )
205
-
206
- # Unload in Stockholm
207
- self.service.register_handling_event(tracking_id, "V4", "STOCKHOLM", "UNLOAD")
208
- cargo_details = self.service.get_cargo_details(tracking_id)
209
- self.assertEqual(cargo_details["current_voyage_number"], None)
210
- self.assertEqual(
211
- cargo_details["last_known_location"],
212
- "STOCKHOLM",
213
- )
214
- self.assertEqual(cargo_details["transport_status"], "IN_PORT")
215
- self.assertEqual(cargo_details["is_misdirected"], False)
216
- self.assertEqual(
217
- cargo_details["next_expected_activity"],
218
- ("CLAIM", "STOCKHOLM", ""),
219
- )
220
-
221
- # Finally, cargo is claimed in Stockholm.
222
- self.service.register_handling_event(tracking_id, None, "STOCKHOLM", "CLAIM")
223
- cargo_details = self.service.get_cargo_details(tracking_id)
224
- self.assertEqual(cargo_details["current_voyage_number"], None)
225
- self.assertEqual(
226
- cargo_details["last_known_location"],
227
- "STOCKHOLM",
228
- )
229
- self.assertEqual(cargo_details["transport_status"], "CLAIMED")
230
- self.assertEqual(cargo_details["is_misdirected"], False)
231
- self.assertEqual(cargo_details["next_expected_activity"], None)
File without changes