diff --git a/spiffworkflow-backend/poetry.lock b/spiffworkflow-backend/poetry.lock
index d23f8fe61..0ddb437e6 100644
--- a/spiffworkflow-backend/poetry.lock
+++ b/spiffworkflow-backend/poetry.lock
@@ -1839,8 +1839,6 @@ files = [
{file = "psycopg2-2.9.9-cp310-cp310-win_amd64.whl", hash = "sha256:426f9f29bde126913a20a96ff8ce7d73fd8a216cfb323b1f04da402d452853c3"},
{file = "psycopg2-2.9.9-cp311-cp311-win32.whl", hash = "sha256:ade01303ccf7ae12c356a5e10911c9e1c51136003a9a1d92f7aa9d010fb98372"},
{file = "psycopg2-2.9.9-cp311-cp311-win_amd64.whl", hash = "sha256:121081ea2e76729acfb0673ff33755e8703d45e926e416cb59bae3a86c6a4981"},
- {file = "psycopg2-2.9.9-cp312-cp312-win32.whl", hash = "sha256:d735786acc7dd25815e89cc4ad529a43af779db2e25aa7c626de864127e5a024"},
- {file = "psycopg2-2.9.9-cp312-cp312-win_amd64.whl", hash = "sha256:a7653d00b732afb6fc597e29c50ad28087dcb4fbfb28e86092277a559ae4e693"},
{file = "psycopg2-2.9.9-cp37-cp37m-win32.whl", hash = "sha256:5e0d98cade4f0e0304d7d6f25bbfbc5bd186e07b38eac65379309c4ca3193efa"},
{file = "psycopg2-2.9.9-cp37-cp37m-win_amd64.whl", hash = "sha256:7e2dacf8b009a1c1e843b5213a87f7c544b2b042476ed7755be813eaf4e8347a"},
{file = "psycopg2-2.9.9-cp38-cp38-win32.whl", hash = "sha256:ff432630e510709564c01dafdbe996cb552e0b9f3f065eb89bdce5bd31fabf4c"},
@@ -2165,7 +2163,6 @@ files = [
{file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"},
{file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"},
{file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"},
- {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"},
{file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"},
{file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"},
{file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"},
@@ -2542,51 +2539,37 @@ python-versions = ">=3.6"
files = [
{file = "ruamel.yaml.clib-0.2.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:b42169467c42b692c19cf539c38d4602069d8c1505e97b86387fcf7afb766e1d"},
{file = "ruamel.yaml.clib-0.2.8-cp310-cp310-macosx_13_0_arm64.whl", hash = "sha256:07238db9cbdf8fc1e9de2489a4f68474e70dffcb32232db7c08fa61ca0c7c462"},
+ {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux2014_aarch64.whl", hash = "sha256:d92f81886165cb14d7b067ef37e142256f1c6a90a65cd156b063a43da1708cfd"},
{file = "ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:fff3573c2db359f091e1589c3d7c5fc2f86f5bdb6f24252c2d8e539d4e45f412"},
- {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-manylinux_2_24_aarch64.whl", hash = "sha256:aa2267c6a303eb483de8d02db2871afb5c5fc15618d894300b88958f729ad74f"},
- {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:840f0c7f194986a63d2c2465ca63af8ccbbc90ab1c6001b1978f05119b5e7334"},
- {file = "ruamel.yaml.clib-0.2.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:024cfe1fc7c7f4e1aff4a81e718109e13409767e4f871443cbff3dba3578203d"},
{file = "ruamel.yaml.clib-0.2.8-cp310-cp310-win32.whl", hash = "sha256:c69212f63169ec1cfc9bb44723bf2917cbbd8f6191a00ef3410f5a7fe300722d"},
{file = "ruamel.yaml.clib-0.2.8-cp310-cp310-win_amd64.whl", hash = "sha256:cabddb8d8ead485e255fe80429f833172b4cadf99274db39abc080e068cbcc31"},
{file = "ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:bef08cd86169d9eafb3ccb0a39edb11d8e25f3dae2b28f5c52fd997521133069"},
{file = "ruamel.yaml.clib-0.2.8-cp311-cp311-macosx_13_0_arm64.whl", hash = "sha256:b16420e621d26fdfa949a8b4b47ade8810c56002f5389970db4ddda51dbff248"},
+ {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux2014_aarch64.whl", hash = "sha256:b5edda50e5e9e15e54a6a8a0070302b00c518a9d32accc2346ad6c984aacd279"},
{file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_24_x86_64.whl", hash = "sha256:25c515e350e5b739842fc3228d662413ef28f295791af5e5110b543cf0b57d9b"},
- {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-manylinux_2_24_aarch64.whl", hash = "sha256:1707814f0d9791df063f8c19bb51b0d1278b8e9a2353abbb676c2f685dee6afe"},
- {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:46d378daaac94f454b3a0e3d8d78cafd78a026b1d71443f4966c696b48a6d899"},
- {file = "ruamel.yaml.clib-0.2.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:09b055c05697b38ecacb7ac50bdab2240bfca1a0c4872b0fd309bb07dc9aa3a9"},
{file = "ruamel.yaml.clib-0.2.8-cp311-cp311-win32.whl", hash = "sha256:53a300ed9cea38cf5a2a9b069058137c2ca1ce658a874b79baceb8f892f915a7"},
{file = "ruamel.yaml.clib-0.2.8-cp311-cp311-win_amd64.whl", hash = "sha256:c2a72e9109ea74e511e29032f3b670835f8a59bbdc9ce692c5b4ed91ccf1eedb"},
{file = "ruamel.yaml.clib-0.2.8-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:ebc06178e8821efc9692ea7544aa5644217358490145629914d8020042c24aa1"},
{file = "ruamel.yaml.clib-0.2.8-cp312-cp312-macosx_13_0_arm64.whl", hash = "sha256:edaef1c1200c4b4cb914583150dcaa3bc30e592e907c01117c08b13a07255ec2"},
+ {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux2014_aarch64.whl", hash = "sha256:7048c338b6c86627afb27faecf418768acb6331fc24cfa56c93e8c9780f815fa"},
{file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:d176b57452ab5b7028ac47e7b3cf644bcfdc8cacfecf7e71759f7f51a59e5c92"},
- {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-manylinux_2_24_aarch64.whl", hash = "sha256:1dc67314e7e1086c9fdf2680b7b6c2be1c0d8e3a8279f2e993ca2a7545fecf62"},
- {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:3213ece08ea033eb159ac52ae052a4899b56ecc124bb80020d9bbceeb50258e9"},
- {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:aab7fd643f71d7946f2ee58cc88c9b7bfc97debd71dcc93e03e2d174628e7e2d"},
- {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-win32.whl", hash = "sha256:5c365d91c88390c8d0a8545df0b5857172824b1c604e867161e6b3d59a827eaa"},
- {file = "ruamel.yaml.clib-0.2.8-cp312-cp312-win_amd64.whl", hash = "sha256:1758ce7d8e1a29d23de54a16ae867abd370f01b5a69e1a3ba75223eaa3ca1a1b"},
{file = "ruamel.yaml.clib-0.2.8-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:a5aa27bad2bb83670b71683aae140a1f52b0857a2deff56ad3f6c13a017a26ed"},
{file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c58ecd827313af6864893e7af0a3bb85fd529f862b6adbefe14643947cfe2942"},
{file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-macosx_12_0_arm64.whl", hash = "sha256:f481f16baec5290e45aebdc2a5168ebc6d35189ae6fea7a58787613a25f6e875"},
- {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-manylinux_2_24_aarch64.whl", hash = "sha256:77159f5d5b5c14f7c34073862a6b7d34944075d9f93e681638f6d753606c6ce6"},
+ {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:3fcc54cb0c8b811ff66082de1680b4b14cf8a81dce0d4fbf665c2265a81e07a1"},
{file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:7f67a1ee819dc4562d444bbafb135832b0b909f81cc90f7aa00260968c9ca1b3"},
- {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:4ecbf9c3e19f9562c7fdd462e8d18dd902a47ca046a2e64dba80699f0b6c09b7"},
- {file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:87ea5ff66d8064301a154b3933ae406b0863402a799b16e4a1d24d9fbbcbe0d3"},
{file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-win32.whl", hash = "sha256:75e1ed13e1f9de23c5607fe6bd1aeaae21e523b32d83bb33918245361e9cc51b"},
{file = "ruamel.yaml.clib-0.2.8-cp37-cp37m-win_amd64.whl", hash = "sha256:3f215c5daf6a9d7bbed4a0a4f760f3113b10e82ff4c5c44bec20a68c8014f675"},
{file = "ruamel.yaml.clib-0.2.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1b617618914cb00bf5c34d4357c37aa15183fa229b24767259657746c9077615"},
{file = "ruamel.yaml.clib-0.2.8-cp38-cp38-macosx_12_0_arm64.whl", hash = "sha256:a6a9ffd280b71ad062eae53ac1659ad86a17f59a0fdc7699fd9be40525153337"},
- {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux_2_24_aarch64.whl", hash = "sha256:305889baa4043a09e5b76f8e2a51d4ffba44259f6b4c72dec8ca56207d9c6fe1"},
+ {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:665f58bfd29b167039f714c6998178d27ccd83984084c286110ef26b230f259f"},
{file = "ruamel.yaml.clib-0.2.8-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:700e4ebb569e59e16a976857c8798aee258dceac7c7d6b50cab63e080058df91"},
- {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:e2b4c44b60eadec492926a7270abb100ef9f72798e18743939bdbf037aab8c28"},
- {file = "ruamel.yaml.clib-0.2.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:e79e5db08739731b0ce4850bed599235d601701d5694c36570a99a0c5ca41a9d"},
{file = "ruamel.yaml.clib-0.2.8-cp38-cp38-win32.whl", hash = "sha256:955eae71ac26c1ab35924203fda6220f84dce57d6d7884f189743e2abe3a9fbe"},
{file = "ruamel.yaml.clib-0.2.8-cp38-cp38-win_amd64.whl", hash = "sha256:56f4252222c067b4ce51ae12cbac231bce32aee1d33fbfc9d17e5b8d6966c312"},
{file = "ruamel.yaml.clib-0.2.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:03d1162b6d1df1caa3a4bd27aa51ce17c9afc2046c31b0ad60a0a96ec22f8001"},
{file = "ruamel.yaml.clib-0.2.8-cp39-cp39-macosx_12_0_arm64.whl", hash = "sha256:bba64af9fa9cebe325a62fa398760f5c7206b215201b0ec825005f1b18b9bccf"},
- {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux_2_24_aarch64.whl", hash = "sha256:a1a45e0bb052edf6a1d3a93baef85319733a888363938e1fc9924cb00c8df24c"},
+ {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:9eb5dee2772b0f704ca2e45b1713e4e5198c18f515b52743576d196348f374d3"},
{file = "ruamel.yaml.clib-0.2.8-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:da09ad1c359a728e112d60116f626cc9f29730ff3e0e7db72b9a2dbc2e4beed5"},
- {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:184565012b60405d93838167f425713180b949e9d8dd0bbc7b49f074407c5a8b"},
- {file = "ruamel.yaml.clib-0.2.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:a75879bacf2c987c003368cf14bed0ffe99e8e85acfa6c0bfffc21a090f16880"},
{file = "ruamel.yaml.clib-0.2.8-cp39-cp39-win32.whl", hash = "sha256:84b554931e932c46f94ab306913ad7e11bba988104c5cff26d90d03f68258cd5"},
{file = "ruamel.yaml.clib-0.2.8-cp39-cp39-win_amd64.whl", hash = "sha256:25ac8c08322002b06fa1d49d1646181f0b2c72f5cbc15a85e80b4c30a544bb15"},
{file = "ruamel.yaml.clib-0.2.8.tar.gz", hash = "sha256:beb2e0404003de9a4cab9753a8805a8fe9320ee6673136ed7f04255fe60bb512"},
@@ -2900,7 +2883,7 @@ doc = ["sphinx", "sphinx_rtd_theme"]
type = "git"
url = "https://github.com/sartography/SpiffWorkflow"
reference = "main"
-resolved_reference = "d1ddca6718dabf4fa7e54b83b88b8944154740bd"
+resolved_reference = "91b623c0aa2fe92f73b98e6992a4bd51c363e9b2"
[[package]]
name = "spiffworkflow-connector-command"
diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_service.py
index 6d8521be0..938906c0b 100644
--- a/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_service.py
+++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/process_instance_service.py
@@ -40,6 +40,7 @@
from spiffworkflow_backend.exceptions.error import ProcessInstanceMigrationUnnecessaryError
from spiffworkflow_backend.exceptions.error import UserDoesNotHaveAccessToTaskError
from spiffworkflow_backend.helpers.spiff_enum import ProcessInstanceExecutionMode
+from spiffworkflow_backend.models.bpmn_process import BpmnProcessModel
from spiffworkflow_backend.models.bpmn_process_definition import BpmnProcessDefinitionModel
from spiffworkflow_backend.models.db import db
from spiffworkflow_backend.models.group import GroupModel
@@ -229,10 +230,13 @@ def migrate_process_instance(
subprocesses_diffs,
) = cls.check_process_instance_can_be_migrated(process_instance, target_bpmn_process_hash=target_bpmn_process_hash)
+ # does this get from subprocesses as well?
+ pre_migration_tasks = processor.bpmn_process_instance.get_tasks()
migrate_workflow(top_level_bpmn_process_diff, processor.bpmn_process_instance, target_bpmn_process_spec)
for sp_id, sp in processor.bpmn_process_instance.subprocesses.items():
migrate_workflow(subprocesses_diffs[sp_id], sp, target_subprocess_specs.get(sp.spec.name))
processor.bpmn_process_instance.subprocess_specs = target_subprocess_specs
+ post_migration_tasks = processor.bpmn_process_instance.get_tasks()
if preserve_old_process_instance:
# TODO: write tests for this code path - no one has a requirement for it yet
@@ -279,6 +283,16 @@ def migrate_process_instance(
"target_bpmn_process_hash": target_bpmn_process_hash or "",
},
)
+ pre_task_guids = [str(t.id) for t in pre_migration_tasks]
+ post_task_guids = [str(t.id) for t in post_migration_tasks]
+ task_guid_diff = set(pre_task_guids) - set(post_task_guids)
+ tasks_to_delete = TaskModel.query.filter(TaskModel.guid.in_(task_guid_diff)).all() # type: ignore
+ bpmn_processes_to_delete = BpmnProcessModel.query.filter(BpmnProcessModel.guid.in_(task_guid_diff)).all() # type: ignore
+ for td in tasks_to_delete:
+ db.session.delete(td)
+ for bpd in bpmn_processes_to_delete:
+ db.session.delete(bpd)
+
db.session.commit()
@classmethod
diff --git a/spiffworkflow-backend/tests/data/migration-test-with-timer/migration-initial.bpmn b/spiffworkflow-backend/tests/data/migration-test-with-timer/migration-initial.bpmn
new file mode 100644
index 000000000..17dc5cf40
--- /dev/null
+++ b/spiffworkflow-backend/tests/data/migration-test-with-timer/migration-initial.bpmn
@@ -0,0 +1,106 @@
+
+
+
+
+ Flow_1khppaq
+
+
+
+
+
+
+ Flow_1khppaq
+ Flow_1if3wm0
+ start_time = round(time.time() * 1000)
+
+
+
+ Flow_0t0n0qg
+ Flow_116lqvv
+
+ "R2/PT60S"
+
+
+
+
+ Flow_0feo9sn
+ Flow_1gbrmax
+ end_time = round(time.time() * 1000)
+duration = round((end_time-start_time)/1000)
+
+
+
+ Duration: {{duration}}
+
+ Flow_1gbrmax
+
+
+
+ Flow_1if3wm0
+ Flow_0t0n0qg
+
+
+
+ Flow_116lqvv
+ Flow_0feo9sn
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/spiffworkflow-backend/tests/data/migration-test-with-timer/migration-new.bpmn b/spiffworkflow-backend/tests/data/migration-test-with-timer/migration-new.bpmn
new file mode 100644
index 000000000..e5fd4dcf8
--- /dev/null
+++ b/spiffworkflow-backend/tests/data/migration-test-with-timer/migration-new.bpmn
@@ -0,0 +1,106 @@
+
+
+
+
+ Flow_1khppaq
+
+
+
+
+
+
+ Flow_1khppaq
+ Flow_1if3wm0
+ start_time = round(time.time() * 1000)
+
+
+
+ Flow_0t0n0qg
+ Flow_116lqvv
+
+ "R2/PT30S"
+
+
+
+
+ Flow_0feo9sn
+ Flow_1gbrmax
+ end_time = round(time.time() * 1000)
+duration = round((end_time-start_time)/1000)
+
+
+
+ Duration: {{duration}}
+
+ Flow_1gbrmax
+
+
+
+ Flow_1if3wm0
+ Flow_0t0n0qg
+
+
+
+ Flow_116lqvv
+ Flow_0feo9sn
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/helpers/base_test.py b/spiffworkflow-backend/tests/spiffworkflow_backend/helpers/base_test.py
index 927de1e35..31ca36386 100644
--- a/spiffworkflow-backend/tests/spiffworkflow_backend/helpers/base_test.py
+++ b/spiffworkflow-backend/tests/spiffworkflow_backend/helpers/base_test.py
@@ -1,3 +1,5 @@
+import copy
+import datetime
import io
import json
import os
@@ -10,6 +12,7 @@
from flask import current_app
from flask.app import Flask
from flask.testing import FlaskClient
+from SpiffWorkflow.task import Task as SpiffTask # type: ignore
from spiffworkflow_backend.exceptions.api_error import ApiError
from spiffworkflow_backend.models.db import db
from spiffworkflow_backend.models.human_task import HumanTaskModel
@@ -27,6 +30,7 @@
from spiffworkflow_backend.models.process_model import NotificationType
from spiffworkflow_backend.models.process_model import ProcessModelInfo
from spiffworkflow_backend.models.process_model import ProcessModelInfoSchema
+from spiffworkflow_backend.models.task import TaskModel
from spiffworkflow_backend.models.user import UserModel
from spiffworkflow_backend.services.authorization_service import AuthorizationService
from spiffworkflow_backend.services.file_system_service import FileSystemService
@@ -35,6 +39,7 @@
from spiffworkflow_backend.services.process_instance_service import ProcessInstanceService
from spiffworkflow_backend.services.process_model_service import ProcessModelService
from spiffworkflow_backend.services.user_service import UserService
+from sqlalchemy.orm.attributes import flag_modified
from werkzeug.test import TestResponse # type: ignore
from tests.spiffworkflow_backend.helpers.test_data import load_test_spec
@@ -644,3 +649,24 @@ def round_last_state_change(self, bpmn_process_dict: dict | list) -> None:
def get_test_file(self, *args: str) -> str:
return os.path.join(current_app.instance_path, "..", "..", "tests", "files", *args)
+
+ def set_timer_event_to_new_time(self, task_model: TaskModel, timedelta_args: dict) -> None:
+ new_time = datetime.datetime.now(datetime.timezone.utc) - datetime.timedelta(**timedelta_args)
+ new_time_formatted = new_time.isoformat()
+
+ new_properties_json = copy.copy(task_model.properties_json)
+ new_properties_json["internal_data"]["event_value"]["next"] = new_time_formatted
+ task_model.properties_json = new_properties_json
+ # make sure we actually commit
+ flag_modified(task_model, "properties_json") # type: ignore
+ db.session.add(task_model)
+ db.session.commit()
+ task_model = TaskModel.query.filter_by(guid=task_model.guid).first()
+ assert task_model.properties_json["internal_data"]["event_value"]["next"] == new_time_formatted
+
+ def get_all_children_of_spiff_task(self, spiff_task: SpiffTask) -> list[SpiffTask]:
+ children = []
+ for child in spiff_task.children:
+ children.append(child)
+ children += self.get_all_children_of_spiff_task(child)
+ return children
diff --git a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_process_instance_service.py b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_process_instance_service.py
index 556025173..38be530c7 100644
--- a/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_process_instance_service.py
+++ b/spiffworkflow-backend/tests/spiffworkflow_backend/unit/test_process_instance_service.py
@@ -1,8 +1,7 @@
import base64
+import datetime
import hashlib
import os
-from datetime import datetime
-from datetime import timezone
from typing import Any
import pytest
@@ -14,6 +13,7 @@
from spiffworkflow_backend.models.process_instance import ProcessInstanceStatus
from spiffworkflow_backend.models.process_instance_event import ProcessInstanceEventModel
from spiffworkflow_backend.models.process_instance_event import ProcessInstanceEventType
+from spiffworkflow_backend.models.task import TaskModel # noqa: F401
from spiffworkflow_backend.services.git_service import GitService
from spiffworkflow_backend.services.process_instance_processor import ProcessInstanceProcessor
from spiffworkflow_backend.services.process_instance_service import ProcessInstanceService
@@ -120,7 +120,7 @@ def test_does_not_skip_events_it_does_not_know_about(self) -> None:
assert not (
ProcessInstanceService.waiting_event_can_be_skipped(
pending_event,
- datetime.now(timezone.utc),
+ datetime.datetime.now(datetime.timezone.utc),
)
)
@@ -131,7 +131,7 @@ def test_does_skip_duration_timer_events_for_the_future(self) -> None:
pending_event = PendingBpmnEvent(name, event_type, value)
assert ProcessInstanceService.waiting_event_can_be_skipped(
pending_event,
- datetime.fromisoformat("2023-04-26T20:15:10.626656+00:00"),
+ datetime.datetime.fromisoformat("2023-04-26T20:15:10.626656+00:00"),
)
def test_does_not_skip_duration_timer_events_for_the_past(self) -> None:
@@ -142,7 +142,7 @@ def test_does_not_skip_duration_timer_events_for_the_past(self) -> None:
assert not (
ProcessInstanceService.waiting_event_can_be_skipped(
pending_event,
- datetime.fromisoformat("2023-04-28T20:15:10.626656+00:00"),
+ datetime.datetime.fromisoformat("2023-04-28T20:15:10.626656+00:00"),
)
)
@@ -154,7 +154,7 @@ def test_does_not_skip_duration_timer_events_for_now(self) -> None:
assert not (
ProcessInstanceService.waiting_event_can_be_skipped(
pending_event,
- datetime.fromisoformat("2023-04-27T20:15:10.626656+00:00"),
+ datetime.datetime.fromisoformat("2023-04-27T20:15:10.626656+00:00"),
)
)
@@ -413,3 +413,78 @@ def test_it_raises_if_a_process_instance_cannot_be_migrated_to_new_process_model
with pytest.raises(ProcessInstanceMigrationNotSafeError):
ProcessInstanceService.check_process_instance_can_be_migrated(process_instance)
+
+ def test_it_can_migrate_a_process_instance_a_timer(
+ self,
+ app: Flask,
+ mocker: MockerFixture,
+ with_db_and_bpmn_file_cleanup: None,
+ ) -> None:
+ initiator_user = self.find_or_create_user("initiator_user")
+ process_model = load_test_spec(
+ process_model_id="test_group/migration-test-with-timer",
+ process_model_source_directory="migration-test-with-timer",
+ bpmn_file_name="migration-initial.bpmn",
+ )
+ mock_get_current_revision = mocker.patch.object(GitService, "get_current_revision")
+
+ # Set the return value for the first call
+ process_instance = self.create_process_instance_from_process_model(
+ process_model=process_model, user=initiator_user, bpmn_version_control_identifier="rev1"
+ )
+ assert process_instance.bpmn_version_control_identifier == "rev1"
+ processor = ProcessInstanceProcessor(process_instance)
+ processor.do_engine_steps(save=True, execution_strategy_name="greedy")
+ initial_bpmn_process_hash = process_instance.bpmn_process_definition.full_process_model_hash
+ assert initial_bpmn_process_hash is not None
+
+ ready_tasks = processor.get_all_ready_or_waiting_tasks()
+ assert len(ready_tasks) == 1
+ assert ready_tasks[0].task_spec.name == "TimerEvent1"
+ task = TaskModel.query.filter_by(guid=str(ready_tasks[0].id)).first()
+ assert task is not None
+ self.set_timer_event_to_new_time(task, {"seconds": 50})
+
+ process_instance = ProcessInstanceModel.query.filter_by(id=process_instance.id).first()
+ processor = ProcessInstanceProcessor(process_instance)
+ processor.do_engine_steps(save=True, execution_strategy_name="greedy")
+ ready_tasks = processor.get_all_ready_or_waiting_tasks()
+ assert len(ready_tasks) == 2
+ ready_task_names = [t.task_spec.name for t in ready_tasks]
+ assert sorted(ready_task_names) == ["Test_Timer_intermediate_catch.EndJoin", "TimerEvent1"]
+
+ timer_event_spiff_task = next(t for t in ready_tasks if t.task_spec.name == "TimerEvent1")
+ assert timer_event_spiff_task is not None
+ timer_event_children = self.get_all_children_of_spiff_task(timer_event_spiff_task)
+ timer_event_children_guids = [str(t.id) for t in timer_event_children]
+ assert len(timer_event_children_guids) == 5
+
+ new_file_path = os.path.join(
+ app.instance_path,
+ "..",
+ "..",
+ "tests",
+ "data",
+ "migration-test-with-timer",
+ "migration-new.bpmn",
+ )
+ with open(new_file_path) as f:
+ new_contents = f.read().encode()
+
+ SpecFileService.update_file(
+ process_model_info=process_model,
+ file_name="migration-initial.bpmn",
+ binary_data=new_contents,
+ update_process_cache_only=True,
+ )
+
+ process_instance = ProcessInstanceModel.query.filter_by(id=process_instance.id).first()
+ mock_get_current_revision.return_value = "rev2"
+ ProcessInstanceService.migrate_process_instance(process_instance, user=initiator_user)
+
+ child_task_models = TaskModel.query.filter(TaskModel.guid.in_(timer_event_children_guids)).all() # type: ignore
+ assert len(child_task_models) == 0
+
+ process_instance = ProcessInstanceModel.query.filter_by(id=process_instance.id).first()
+ processor = ProcessInstanceProcessor(process_instance)
+ processor.do_engine_steps(save=True, execution_strategy_name="greedy")