From 27447e533c943f0e1365bae5586a68b8803e0134 Mon Sep 17 00:00:00 2001
From: jbirddog <100367399+jbirddog@users.noreply.github.com>
Date: Tue, 6 Jun 2023 21:25:26 -0400
Subject: [PATCH] Fix issue when timer start event is greater than a day (#303)
* Fix issue when start time is greater than a day
* Add some cycle timer tests
* Add some more tests
* Getting ./bin/pyl to pass
---
.../specs/start_event.py | 8 +-
.../unit/test_workflow_service.py | 167 ++++++++++++++++++
2 files changed, 171 insertions(+), 4 deletions(-)
diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/specs/start_event.py b/spiffworkflow-backend/src/spiffworkflow_backend/specs/start_event.py
index 5cb929e4..dd8fb89a 100644
--- a/spiffworkflow-backend/src/spiffworkflow_backend/specs/start_event.py
+++ b/spiffworkflow-backend/src/spiffworkflow_backend/specs/start_event.py
@@ -1,4 +1,5 @@
from datetime import datetime
+from datetime import timedelta
from typing import Any
from SpiffWorkflow.bpmn.parser.util import full_tag # type: ignore
@@ -39,24 +40,23 @@ class StartEvent(DefaultStartEvent): # type: ignore
def configuration(self, my_task: SpiffTask, now_in_utc: datetime) -> StartConfiguration:
evaluated_expression = self.evaluated_timer_expression(my_task)
cycles = 0
- start_delay_in_seconds = 0
duration = 0
+ time_delta = timedelta(seconds=0)
if evaluated_expression is not None:
if isinstance(self.timer_definition, TimeDateEventDefinition):
parsed_duration = TimerEventDefinition.parse_time_or_duration(evaluated_expression)
time_delta = parsed_duration - now_in_utc
- start_delay_in_seconds = time_delta.seconds
elif isinstance(self.timer_definition, DurationTimerEventDefinition):
parsed_duration = TimerEventDefinition.parse_iso_duration(evaluated_expression)
time_delta = TimerEventDefinition.get_timedelta_from_start(parsed_duration, now_in_utc)
- start_delay_in_seconds = time_delta.seconds
elif isinstance(self.timer_definition, CycleTimerEventDefinition):
cycles, start, cycle_duration = TimerEventDefinition.parse_iso_recurring_interval(evaluated_expression)
time_delta = start - now_in_utc + cycle_duration
- start_delay_in_seconds = time_delta.seconds
duration = cycle_duration.seconds
+ start_delay_in_seconds = int(time_delta.total_seconds())
+
return (cycles, start_delay_in_seconds, duration)
def evaluated_timer_expression(self, my_task: SpiffTask) -> Any:
diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_workflow_service.py b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_workflow_service.py
index 191c8973..7f925090 100644
--- a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_workflow_service.py
+++ b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_workflow_service.py
@@ -46,6 +46,14 @@ def example_start_datetime_minus_5_mins_in_utc(
yield example_datetime - timedelta(minutes=5)
+@pytest.fixture()
+def example_start_datetime_minus_1_day_and_5_mins_in_utc(
+ example_start_datetime_in_utc_str: str,
+) -> Generator[datetime, None, None]:
+ example_datetime = datetime.fromisoformat(example_start_datetime_in_utc_str)
+ yield example_datetime - timedelta(days=1, minutes=5)
+
+
class CustomBpmnDmnParser(BpmnDmnParser): # type: ignore
OVERRIDE_PARSER_CLASSES = {}
OVERRIDE_PARSER_CLASSES.update(BpmnDmnParser.OVERRIDE_PARSER_CLASSES)
@@ -85,6 +93,25 @@ class TestWorkflowService(BaseTest):
_, delay, _ = WorkflowService.next_start_event_configuration(workflow, now_in_utc) # type: ignore
assert delay == 0
+ def regular_start_events_have_no_cycles(self, now_in_utc: datetime) -> None:
+ workflow = workflow_from_fragment(
+ """
+
+
+ Flow_184umot
+
+
+ Flow_184umot
+
+
+
+ """,
+ "no_tasks",
+ )
+ cycles, _, duration = WorkflowService.next_start_event_configuration(workflow, now_in_utc) # type: ignore
+ assert cycles == 0
+ assert duration == 0
+
def test_run_at_delay_is_30_for_30_second_duration_start_timer_event(self, now_in_utc: datetime) -> None:
workflow = workflow_from_fragment(
"""
@@ -106,6 +133,49 @@ class TestWorkflowService(BaseTest):
_, delay, _ = WorkflowService.next_start_event_configuration(workflow, now_in_utc) # type: ignore
assert delay == 30
+ def test_run_at_delay_is_10000_for_10000_second_duration_start_timer_event(self, now_in_utc: datetime) -> None:
+ workflow = workflow_from_fragment(
+ """
+
+
+ Flow_1x1o335
+
+ "PT10000S"
+
+
+
+
+ Flow_1x1o335
+
+
+ """,
+ "Process_aldvgey",
+ )
+ _, delay, _ = WorkflowService.next_start_event_configuration(workflow, now_in_utc) # type: ignore
+ assert delay == 10000
+
+ def test_duration_start_timer_event_have_no_cylces(self, now_in_utc: datetime) -> None:
+ workflow = workflow_from_fragment(
+ """
+
+
+ Flow_1x1o335
+
+ "PT30S"
+
+
+
+
+ Flow_1x1o335
+
+
+ """,
+ "Process_aldvgey",
+ )
+ cycles, _, duration = WorkflowService.next_start_event_configuration(workflow, now_in_utc) # type: ignore
+ assert cycles == 0
+ assert duration == 0
+
def test_run_at_delay_is_300_if_5_mins_before_date_start_timer_event(
self, example_start_datetime_in_utc_str: str, example_start_datetime_minus_5_mins_in_utc: datetime
) -> None:
@@ -130,3 +200,100 @@ class TestWorkflowService(BaseTest):
workflow, example_start_datetime_minus_5_mins_in_utc
) # type: ignore
assert delay == 300
+
+ def test_run_at_delay_is_86700_if_1_day_and_5_mins_before_date_start_timer_event(
+ self, example_start_datetime_in_utc_str: str, example_start_datetime_minus_1_day_and_5_mins_in_utc: datetime
+ ) -> None:
+ workflow = workflow_from_fragment(
+ f"""
+
+
+ Flow_1x1o335
+
+ "{example_start_datetime_in_utc_str}"
+
+
+
+
+ Flow_1x1o335
+
+
+ """,
+ "Process_aldvgey",
+ )
+ _, delay, _ = WorkflowService.next_start_event_configuration(
+ workflow, example_start_datetime_minus_1_day_and_5_mins_in_utc
+ ) # type: ignore
+ assert delay == 86700
+
+ def date_start_timer_event_has_no_cycles(
+ self, example_start_datetime_in_utc_str: str, example_start_datetime_minus_1_day_and_5_mins_in_utc: datetime
+ ) -> None:
+ workflow = workflow_from_fragment(
+ f"""
+
+
+ Flow_1x1o335
+
+ "{example_start_datetime_in_utc_str}"
+
+
+
+
+ Flow_1x1o335
+
+
+ """,
+ "Process_aldvgey",
+ )
+ cycles, _, duration = WorkflowService.next_start_event_configuration(
+ workflow, example_start_datetime_minus_1_day_and_5_mins_in_utc
+ ) # type: ignore
+ assert cycles == 0
+ assert duration == 0
+
+ def test_5_cycles_of_30_second_cycle_start_timer_event(self, now_in_utc: datetime) -> None:
+ workflow = workflow_from_fragment(
+ """
+
+
+ Flow_1x1o335
+
+ "R5/PT30S"
+
+
+
+
+ Flow_1x1o335
+
+
+ """,
+ "Process_aldvgey",
+ )
+ cycles, delay, duration = WorkflowService.next_start_event_configuration(workflow, now_in_utc) # type: ignore
+ assert cycles == 5
+ assert delay == 30
+ assert duration == 30
+
+ def test_5_cycles_of_10000_second_cycle_start_timer_event(self, now_in_utc: datetime) -> None:
+ workflow = workflow_from_fragment(
+ """
+
+
+ Flow_1x1o335
+
+ "R5/PT10000S"
+
+
+
+
+ Flow_1x1o335
+
+
+ """,
+ "Process_aldvgey",
+ )
+ cycles, delay, duration = WorkflowService.next_start_event_configuration(workflow, now_in_utc) # type: ignore
+ assert cycles == 5
+ assert delay == 10000
+ assert duration == 10000