|
@@ -42,6 +42,18 @@ class TransactionalProjector:
|
|
|
raise RuntimeError("failed after business data write")
|
|
raise RuntimeError("failed after business data write")
|
|
|
self.cursor = event.event_id
|
|
self.cursor = event.event_id
|
|
|
|
|
|
|
|
|
|
+ async def advance_cursor(
|
|
|
|
|
+ self,
|
|
|
|
|
+ application_ref,
|
|
|
|
|
+ root_trace_id,
|
|
|
|
|
+ event_id,
|
|
|
|
|
+ expected_cursor,
|
|
|
|
|
+ ):
|
|
|
|
|
+ del application_ref, root_trace_id
|
|
|
|
|
+ if expected_cursor != self.cursor:
|
|
|
|
|
+ raise ValueError("stale projector cursor")
|
|
|
|
|
+ self.cursor = event_id
|
|
|
|
|
+
|
|
|
|
|
|
|
|
class ApplicationEventsTest(unittest.IsolatedAsyncioTestCase):
|
|
class ApplicationEventsTest(unittest.IsolatedAsyncioTestCase):
|
|
|
async def asyncSetUp(self):
|
|
async def asyncSetUp(self):
|
|
@@ -98,7 +110,7 @@ class ApplicationEventsTest(unittest.IsolatedAsyncioTestCase):
|
|
|
service = self.runner.event_service
|
|
service = self.runner.event_service
|
|
|
await service.emit(
|
|
await service.emit(
|
|
|
source_trace_id=self.trace.trace_id,
|
|
source_trace_id=self.trace.trace_id,
|
|
|
- event_type="test.first",
|
|
|
|
|
|
|
+ event_type="task.progress.revised",
|
|
|
event_key="first",
|
|
event_key="first",
|
|
|
effective_at_sequence=1,
|
|
effective_at_sequence=1,
|
|
|
payload={"value": 1},
|
|
payload={"value": 1},
|
|
@@ -106,7 +118,7 @@ class ApplicationEventsTest(unittest.IsolatedAsyncioTestCase):
|
|
|
self.projector.fail_key = "second"
|
|
self.projector.fail_key = "second"
|
|
|
await service.emit(
|
|
await service.emit(
|
|
|
source_trace_id=self.trace.trace_id,
|
|
source_trace_id=self.trace.trace_id,
|
|
|
- event_type="test.second",
|
|
|
|
|
|
|
+ event_type="candidate.version_registered",
|
|
|
event_key="second",
|
|
event_key="second",
|
|
|
effective_at_sequence=2,
|
|
effective_at_sequence=2,
|
|
|
payload={"value": 2},
|
|
payload={"value": 2},
|
|
@@ -115,7 +127,7 @@ class ApplicationEventsTest(unittest.IsolatedAsyncioTestCase):
|
|
|
self.projector.failed_once = False
|
|
self.projector.failed_once = False
|
|
|
await service.emit(
|
|
await service.emit(
|
|
|
source_trace_id=self.trace.trace_id,
|
|
source_trace_id=self.trace.trace_id,
|
|
|
- event_type="test.third",
|
|
|
|
|
|
|
+ event_type="validation.completed",
|
|
|
event_key="third",
|
|
event_key="third",
|
|
|
effective_at_sequence=3,
|
|
effective_at_sequence=3,
|
|
|
payload={"value": 3},
|
|
payload={"value": 3},
|
|
@@ -129,7 +141,7 @@ class ApplicationEventsTest(unittest.IsolatedAsyncioTestCase):
|
|
|
self.assertEqual(1, list(self.projector.view).count("second"))
|
|
self.assertEqual(1, list(self.projector.view).count("second"))
|
|
|
self.assertEqual(3, self.projector.calls.count("second"))
|
|
self.assertEqual(3, self.projector.calls.count("second"))
|
|
|
|
|
|
|
|
- async def test_unknown_schema_and_event_id_gap_are_safely_projected(self):
|
|
|
|
|
|
|
+ async def test_unknown_schema_and_event_id_gap_advance_without_projection(self):
|
|
|
await self.store.append_event(
|
|
await self.store.append_event(
|
|
|
self.trace.trace_id,
|
|
self.trace.trace_id,
|
|
|
"ordinary",
|
|
"ordinary",
|
|
@@ -153,7 +165,28 @@ class ApplicationEventsTest(unittest.IsolatedAsyncioTestCase):
|
|
|
)
|
|
)
|
|
|
await self.runner.event_service.pump.pump(self.trace.trace_id)
|
|
await self.runner.event_service.pump.pump(self.trace.trace_id)
|
|
|
self.assertEqual(event_id, self.projector.cursor)
|
|
self.assertEqual(event_id, self.projector.cursor)
|
|
|
- self.assertEqual({"future": True}, self.projector.view["future"])
|
|
|
|
|
|
|
+ self.assertNotIn("future", self.projector.view)
|
|
|
|
|
+ self.assertNotIn({"future": True}, self.projector.view.values())
|
|
|
|
|
+
|
|
|
|
|
+ unknown_type_id = await self.store.append_event(
|
|
|
|
|
+ self.trace.trace_id,
|
|
|
|
|
+ "run_event",
|
|
|
|
|
+ {
|
|
|
|
|
+ "schema_version": 1,
|
|
|
|
|
+ "event_type": "unknown.current-schema-event",
|
|
|
|
|
+ "event_key": "unknown-type",
|
|
|
|
|
+ "application_ref": self.binding.application_ref.model_dump(
|
|
|
|
|
+ mode="json"
|
|
|
|
|
+ ),
|
|
|
|
|
+ "root_trace_id": self.trace.trace_id,
|
|
|
|
|
+ "source_trace_id": self.trace.trace_id,
|
|
|
|
|
+ "effective_at_sequence": 2,
|
|
|
|
|
+ "payload": {"must_not_reach_view": True},
|
|
|
|
|
+ },
|
|
|
|
|
+ )
|
|
|
|
|
+ await self.runner.event_service.pump.pump(self.trace.trace_id)
|
|
|
|
|
+ self.assertEqual(unknown_type_id, self.projector.cursor)
|
|
|
|
|
+ self.assertNotIn("unknown-type", self.projector.view)
|
|
|
|
|
|
|
|
async def test_event_append_failure_preserves_state_and_reconcile_repairs_it(self):
|
|
async def test_event_append_failure_preserves_state_and_reconcile_repairs_it(self):
|
|
|
service = self.runner.event_service
|
|
service = self.runner.event_service
|