Project durable workflow handoffs as work items
This commit is contained in:
@@ -43,6 +43,7 @@ from govoplan_core.core.runtime_coordination import (
|
||||
RuntimeIdentity,
|
||||
bind_process_runtime_identity,
|
||||
)
|
||||
from govoplan_core.core.tasks import WorkItemQuery
|
||||
from govoplan_core.db.base import Base
|
||||
from govoplan_core.db.base import utcnow
|
||||
from govoplan_workflow_engine.backend.db.models import (
|
||||
@@ -84,6 +85,7 @@ from govoplan_workflow_engine.backend.service import (
|
||||
create_definition,
|
||||
)
|
||||
from govoplan_workflow_engine.backend.service_launcher import WorkflowServiceLauncher
|
||||
from govoplan_workflow_engine.backend.work_items import WorkflowWorkItemProvider
|
||||
|
||||
try:
|
||||
from test_bpmn import NATIVE_BPMN
|
||||
@@ -687,6 +689,107 @@ class WorkflowInstanceServiceTests(unittest.TestCase):
|
||||
start_origin="api",
|
||||
)
|
||||
|
||||
def test_human_handoff_projects_typed_due_work_and_disappears_on_completion(
|
||||
self,
|
||||
) -> None:
|
||||
definition = create_definition(
|
||||
self.session,
|
||||
tenant_id="tenant-1",
|
||||
actor_id="account-1",
|
||||
payload=WorkflowDefinitionCreateRequest(
|
||||
name="Guided case review",
|
||||
graph=WorkflowGraph(
|
||||
nodes=[
|
||||
WorkflowNode(
|
||||
id="start",
|
||||
type="workflow.start.manual",
|
||||
config={"input_schema_ref": ""},
|
||||
),
|
||||
WorkflowNode(
|
||||
id="activity",
|
||||
type="workflow.activity",
|
||||
config={
|
||||
"title": "Assess the application",
|
||||
"instructions": "Record the assessment evidence.",
|
||||
"assignee": "account:account-1",
|
||||
"due_after": "2h",
|
||||
},
|
||||
),
|
||||
WorkflowNode(
|
||||
id="done",
|
||||
type="workflow.end.completed",
|
||||
),
|
||||
],
|
||||
edges=[
|
||||
WorkflowEdge(
|
||||
id="start-activity", source="start", target="activity"
|
||||
),
|
||||
WorkflowEdge(
|
||||
id="activity-done", source="activity", target="done"
|
||||
),
|
||||
],
|
||||
),
|
||||
execution_mode="guided",
|
||||
),
|
||||
)
|
||||
activate_definition(
|
||||
self.session,
|
||||
tenant_id="tenant-1",
|
||||
definition_id=definition.id,
|
||||
actor_id="account-1",
|
||||
)
|
||||
before = utcnow()
|
||||
instance, _replayed = start_instance(
|
||||
self.session,
|
||||
tenant_id="tenant-1",
|
||||
definition_id=definition.id,
|
||||
actor_id="account-1",
|
||||
principal=principal(),
|
||||
registry=self.registry,
|
||||
payload=WorkflowInstanceStartRequest(idempotency_key="guided-work-1"),
|
||||
)
|
||||
step = self.session.get(WorkflowInstanceStep, instance.current_step_id)
|
||||
self.assertIsNotNone(step)
|
||||
assert step is not None
|
||||
self.assertEqual("account", step.work_assignment_kind)
|
||||
self.assertEqual("account-1", step.work_assignment_id)
|
||||
self.assertIsNotNone(step.work_due_at)
|
||||
assert step.work_due_at is not None
|
||||
due_at = (
|
||||
step.work_due_at.replace(tzinfo=UTC)
|
||||
if step.work_due_at.tzinfo is None
|
||||
else step.work_due_at
|
||||
)
|
||||
self.assertGreaterEqual(due_at, before + timedelta(hours=1, minutes=59))
|
||||
|
||||
provider = WorkflowWorkItemProvider(registry=self.registry)
|
||||
page = provider.list_items(
|
||||
self.session,
|
||||
principal(),
|
||||
query=WorkItemQuery(tenant_id="tenant-1"),
|
||||
)
|
||||
self.assertEqual(1, page.total)
|
||||
self.assertEqual("Assess the application", page.items[0].title)
|
||||
self.assertEqual("account-1", page.items[0].assignments[0].id)
|
||||
self.assertEqual(step.id, page.items[0].id)
|
||||
|
||||
resolve_step(
|
||||
self.session,
|
||||
tenant_id="tenant-1",
|
||||
instance_id=instance.id,
|
||||
step_id=step.id,
|
||||
actor_id="account-1",
|
||||
principal=principal(),
|
||||
registry=self.registry,
|
||||
payload=WorkflowStepActionRequest(action="complete"),
|
||||
)
|
||||
closed_page = provider.list_items(
|
||||
self.session,
|
||||
principal(),
|
||||
query=WorkItemQuery(tenant_id="tenant-1"),
|
||||
)
|
||||
self.assertEqual(0, closed_page.total)
|
||||
|
||||
def test_module_action_records_effects_and_completes_idempotently(
|
||||
self,
|
||||
) -> None:
|
||||
@@ -990,9 +1093,7 @@ class WorkflowInstanceServiceTests(unittest.TestCase):
|
||||
request=request,
|
||||
)
|
||||
checkpoint = session.scalar(
|
||||
select(RecoveryCheckpoint).order_by(
|
||||
RecoveryCheckpoint.sequence
|
||||
)
|
||||
select(RecoveryCheckpoint).order_by(RecoveryCheckpoint.sequence)
|
||||
)
|
||||
assert checkpoint is not None
|
||||
checkpoint.summary = "tampered provider evidence"
|
||||
|
||||
Reference in New Issue
Block a user