From ae5362e93dbbdd35ba9fb0e4dfc859f22813f1c9 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Thomas=20NO=C3=8BL?= <tnoel@entrouvert.com>
Date: Sat, 10 Apr 2021 02:54:10 +0200
Subject: [PATCH] workflow: stop workflow after a remove action (#52927)

---
 tests/test_workflows.py | 68 ++++++++++++++++++++++++++++++++++++-----
 wcs/wf/remove.py        |  8 +++--
 wcs/workflows.py        |  6 ++--
 3 files changed, 69 insertions(+), 13 deletions(-)

diff --git a/tests/test_workflows.py b/tests/test_workflows.py
index d35b5e7f..b12078c3 100644
--- a/tests/test_workflows.py
+++ b/tests/test_workflows.py
@@ -843,32 +843,84 @@ def test_anonymise(two_pubs):
     assert formdef.data_class().get(formdata.id).evolution[0].who is None
 
 
-def test_remove(pub):
+def test_remove(two_pubs):
     formdef = FormDef()
     formdef.name = 'baz'
     formdef.store()
 
+    formdef.data_class().wipe()
     formdata = formdef.data_class()()
     formdata.store()
 
     item = RemoveWorkflowStatusItem()
-    assert formdef.data_class().has_key(formdata.id)
-    assert item.perform(formdata) == 'http://example.net'
-    assert not formdef.data_class().has_key(formdata.id)
+    assert formdef.data_class().count() == 1
+    with pytest.raises(AbortActionException) as e:
+        item.perform(formdata)
+        assert e.url == 'http://example.net'
+    assert formdef.data_class().count() == 0
 
     formdata = formdef.data_class()()
     formdata.store()
 
     item = RemoveWorkflowStatusItem()
-    req = pub.get_request()
+    req = two_pubs.get_request()
     req.response.filter['in_backoffice'] = True
-    assert formdef.data_class().has_key(formdata.id)
-    assert item.perform(formdata) == '..'
-    assert not formdef.data_class().has_key(formdata.id)
+    assert formdef.data_class().count() == 1
+    with pytest.raises(AbortActionException) as e:
+        item.perform(formdata)
+        assert e.url == '..'
+    assert formdef.data_class().count() == 0
     req.response.filter = {}
     assert req.session.message
 
 
+def test_stop_on_remove(two_pubs, emails):
+    workflow = Workflow(name='stop-on-remove')
+    st1 = workflow.add_status('Status1', 'st1')
+
+    # sendmail + remove + sendmail
+    mail1 = SendmailWorkflowStatusItem()
+    mail1.to = ['bar@localhost']
+    mail1.subject = 'Foobar'
+    mail1.body = 'email body'
+    st1.items.append(mail1)
+    mail1.parent = st1
+    remove = RemoveWorkflowStatusItem()
+    st1.items.append(remove)
+    remove.parent = st1
+    mail2 = SendmailWorkflowStatusItem()
+    mail2.to = ['bar@localhost']
+    mail2.subject = 'Foobar2'
+    mail2.body = 'email body 2'
+    st1.items.append(mail2)
+    mail2.parent = st1
+
+    workflow.store()
+
+    formdef = FormDef()
+    formdef.name = 'baz%s' % id(two_pubs)
+    formdef.fields = []
+    formdef.workflow_id = workflow.id
+    assert formdef.get_workflow().id == workflow.id
+    formdef.store()
+
+    formdef.data_class().wipe()
+    emails.empty()
+    assert formdef.data_class().count() == 0
+    assert emails.count() == 0
+
+    formdata = formdef.data_class()()
+    formdata.just_created()
+    formdata.store()
+    url = perform_items(st1.items, formdata)
+    get_response().process_after_jobs()
+
+    # formdata is removed, no email were sent
+    assert formdef.data_class().count() == 0
+    assert emails.count() == 1
+    assert url == 'http://example.net'
+
+
 def test_register_comment(pub):
     pub.substitutions.feed(MockSubstitutionVariables())
 
diff --git a/wcs/wf/remove.py b/wcs/wf/remove.py
index ea0b89c4..de5b4d30 100644
--- a/wcs/wf/remove.py
+++ b/wcs/wf/remove.py
@@ -16,7 +16,7 @@
 
 from quixote import get_publisher, get_request, get_response, get_session
 
-from wcs.workflows import WorkflowStatusItem, register_item_class
+from wcs.workflows import AbortActionException, WorkflowStatusItem, register_item_class
 
 from ..qommon import N_, _
 
@@ -30,8 +30,10 @@ class RemoveWorkflowStatusItem(WorkflowStatusItem):
         formdata.remove_self()
         if get_request() and get_response().filter.get('in_backoffice'):
             get_session().message = ('info', _('The form has been deleted.'))
-            return '..'
-        return get_publisher().get_frontoffice_url()
+            url = '..'
+        else:
+            url = get_publisher().get_frontoffice_url()
+        raise AbortActionException(url=url)
 
 
 register_item_class(RemoveWorkflowStatusItem)
diff --git a/wcs/workflows.py b/wcs/workflows.py
index 1656892c..66e69f9e 100644
--- a/wcs/workflows.py
+++ b/wcs/workflows.py
@@ -78,7 +78,8 @@ def perform_items(items, formdata, depth=20):
             continue
         try:
             url = item.perform(formdata) or url
-        except AbortActionException:
+        except AbortActionException as e:
+            url = e.url or url
             break
         if formdata.status != old_status:
             break
@@ -109,7 +110,8 @@ class WorkflowImportError(Exception):
 
 
 class AbortActionException(Exception):
-    pass
+    def __init__(self, url=None):
+        self.url = url
 
 
 class AttachmentSubstitutionProxy:
-- 
2.30.2

