|
@@ -723,6 +723,18 @@ class CandidateServiceTest(unittest.IsolatedAsyncioTestCase):
|
|
|
await self.store.get_candidate_ledger("root-a")
|
|
await self.store.get_candidate_ledger("root-a")
|
|
|
).current_state(peer)
|
|
).current_state(peer)
|
|
|
))
|
|
))
|
|
|
|
|
+ with self.assertRaisesRegex(
|
|
|
|
|
+ CandidateStateError,
|
|
|
|
|
+ "review batch payload changed",
|
|
|
|
|
+ ):
|
|
|
|
|
+ await self.service.apply_review_actions(
|
|
|
|
|
+ "root-a",
|
|
|
|
|
+ "child-a",
|
|
|
|
|
+ report_refs=[candidate, peer],
|
|
|
|
|
+ actions=[],
|
|
|
|
|
+ effective_at_sequence=1_002,
|
|
|
|
|
+ command_id="adoption-review-call",
|
|
|
|
|
+ )
|
|
|
|
|
|
|
|
async def test_review_batch_replay_rejects_deleted_and_reordered_actions(self):
|
|
async def test_review_batch_replay_rejects_deleted_and_reordered_actions(self):
|
|
|
await self._create_child("child-a")
|
|
await self._create_child("child-a")
|
|
@@ -768,6 +780,48 @@ class CandidateServiceTest(unittest.IsolatedAsyncioTestCase):
|
|
|
command_id="two-action-review",
|
|
command_id="two-action-review",
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
|
|
+ async def test_empty_review_batch_cannot_gain_an_action_during_replay(self):
|
|
|
|
|
+ await self._create_child("child-a")
|
|
|
|
|
+ candidate = await self.service.manage(
|
|
|
|
|
+ "child-a",
|
|
|
|
|
+ operation="create",
|
|
|
|
|
+ content={"text": "A"},
|
|
|
|
|
+ parent_refs=[],
|
|
|
|
|
+ effective_at_sequence=4,
|
|
|
|
|
+ )
|
|
|
|
|
+ await self._record_validation("child-a", candidate)
|
|
|
|
|
+ self.assertEqual([], await self.service.apply_review_actions(
|
|
|
|
|
+ "root-a",
|
|
|
|
|
+ "child-a",
|
|
|
|
|
+ report_refs=[candidate],
|
|
|
|
|
+ actions=[],
|
|
|
|
|
+ effective_at_sequence=10,
|
|
|
|
|
+ command_id="empty-review-batch",
|
|
|
|
|
+ ))
|
|
|
|
|
+
|
|
|
|
|
+ with self.assertRaisesRegex(
|
|
|
|
|
+ CandidateStateError,
|
|
|
|
|
+ "review batch payload changed",
|
|
|
|
|
+ ):
|
|
|
|
|
+ await self.service.apply_review_actions(
|
|
|
|
|
+ "root-a",
|
|
|
|
|
+ "child-a",
|
|
|
|
|
+ report_refs=[candidate],
|
|
|
|
|
+ actions=[CandidateReviewAction(
|
|
|
|
|
+ action="discard",
|
|
|
|
|
+ candidate_ref=candidate,
|
|
|
|
|
+ reason="injected after empty batch",
|
|
|
|
|
+ )],
|
|
|
|
|
+ effective_at_sequence=999,
|
|
|
|
|
+ command_id="empty-review-batch",
|
|
|
|
|
+ )
|
|
|
|
|
+ self.assertEqual(
|
|
|
|
|
+ "proposed",
|
|
|
|
|
+ CandidateLedger.model_validate(
|
|
|
|
|
+ await self.store.get_candidate_ledger("root-a")
|
|
|
|
|
+ ).current_state(candidate),
|
|
|
|
|
+ )
|
|
|
|
|
+
|
|
|
async def test_descendant_adoption_blocks_ancestor_rewind_without_mapping(self):
|
|
async def test_descendant_adoption_blocks_ancestor_rewind_without_mapping(self):
|
|
|
await self._create_child("child-a")
|
|
await self._create_child("child-a")
|
|
|
await self.store.create_trace(Trace(
|
|
await self.store.create_trace(Trace(
|