From e598da95fce7019c2acad0766abc32eff252946e Mon Sep 17 00:00:00 2001
From: Vincent Pelletier <vincent@nexedi.com>
Date: Wed, 12 Jun 2019 14:24:28 +0900
Subject: [PATCH] CMFActivity: Ignore None dependencies.

Simplifies activity spawning when dependencies are conditionally set:
  activate(
    after_tag=some_value if some_condition else None,
  )
instead of having to do a ** dance.
All columns involved in dependency checking are declared NOT NULL, so providing
"None" and expecting a dependency to happen was already not working.
This change pushes this one step further by allowing activity
auto-validation also happen on these activities.
Also, simplify getOrderValidationText: avoid iterating on keys and then
retrieving values, use a list-comprehension, simplify condition.
Add a test for after_tag, also covering activity auto-validation.
---
 product/CMFActivity/Activity/Queue.py        | 31 ++++++++++----------
 product/CMFActivity/tests/testCMFActivity.py | 22 ++++++++++++++
 2 files changed, 38 insertions(+), 15 deletions(-)

diff --git a/product/CMFActivity/Activity/Queue.py b/product/CMFActivity/Activity/Queue.py
index c5c14e9fdb..cf3079f71a 100644
--- a/product/CMFActivity/Activity/Queue.py
+++ b/product/CMFActivity/Activity/Queue.py
@@ -140,21 +140,22 @@ class Queue(object):
 
   def getOrderValidationText(self, message):
     # Return an identifier of validators related to ordering.
-    order_validation_item_list = []
-    key_list = message.activity_kw.keys()
-    key_list.sort()
-    for key in key_list:
-      method_id = "_validate_" + key
-      if getattr(self, method_id, None) is not None:
-        order_validation_item_list.append((key, message.activity_kw[key]))
-    if len(order_validation_item_list) == 0:
-      # When no order validation argument is specified, skip the computation
-      # of the checksum for speed. Here, 'none' is used, because this never be
-      # identical to SHA1 hexdigest (which is always 40 characters), and 'none'
-      # is true in Python. This is important, because dtml-if assumes that an empty
-      # string is false, so we must use a non-empty string for this.
-      return 'none'
-    return sha1(repr(order_validation_item_list)).hexdigest()
+    order_validation_item_list = [
+        (key, value)
+        for key, value in sorted(
+            message.activity_kw.iteritems(), key=lambda x: x[0],
+        )
+        if value is not None and
+        getattr(self, "_validate_" + key, None) is not None
+    ]
+    if order_validation_item_list:
+      return sha1(repr(order_validation_item_list)).hexdigest()
+    # When no order validation argument is specified, skip the computation
+    # of the checksum for speed. Here, 'none' is used, because this never be
+    # identical to SHA1 hexdigest (which is always 40 characters), and 'none'
+    # is true in Python. This is important, because dtml-if assumes that an empty
+    # string is false, so we must use a non-empty string for this.
+    return 'none'
 
   def getMessageList(self, activity_tool, processing_node=None,**kw):
     return []
diff --git a/product/CMFActivity/tests/testCMFActivity.py b/product/CMFActivity/tests/testCMFActivity.py
index b25cded52b..5b65a5dc6e 100644
--- a/product/CMFActivity/tests/testCMFActivity.py
+++ b/product/CMFActivity/tests/testCMFActivity.py
@@ -2603,6 +2603,28 @@ class TestCMFActivity(ERP5TypeTestCase, LogInterceptor):
           self.assertEqual(len(result), 1)
           self.deleteMessageList(activity, result)
 
+  def test_message_auto_validation(self):
+    """
+    Test that messages without dependencies are directly spawned with
+    processing_node=0.
+    """
+    organisation = self.portal.organisation_module.newContent(portal_type='Organisation')
+    self.tic()
+    activity_tool = self.getActivityTool()
+    organisation.activate(tag='1').getId()
+    organisation.activate(tag='2', after_tag=None).getId()
+    organisation.activate(tag='3', after_tag='foo').getId()
+    self.commit()
+    activity_tool.getMessageList()
+    self.assertItemsEqual(
+      [('1', 0), ('2', 0), ('3', -1)],
+      [
+          (x.activity_kw['tag'], x.processing_node)
+          for x in self.getActivityTool().getMessageList()
+      ],
+    )
+    self.tic()
+
 def test_suite():
   suite = unittest.TestSuite()
   suite.addTest(unittest.makeSuite(TestCMFActivity))
-- 
2.30.9