Repository: fedora-infra/bodhi

Issue:
Have bodhi send a dedicated fedora-messaging message when an update is ready to be tested
Updates are manually created in a "Pending" state, where they are signed before they are pushed to a "Testing" where they are tested by the CI system.
So we can't trigger the CI system off new updates being created.

The easiest solution is thus to send a dedicated fedora-messaging message to tell the CI System: "Now this update can be tested".

This need coordination with the OSCI team maintaining the CI system.


Discussion:
Could you explain a bit more what is needed and what the problem currently is?
If I understand correctly, CI reacts on Koji builds, and that won't work for multi-package updates because we need to test them all together. So we can solve that problem by sending a (new) message when an update is ready to be tested.

I'm guessing we want to send that message when the update / update set lands in the testing repository? Thus after a compose?

What kind of information do we want in that message?
>    Could you explain a bit more what is needed and what the problem currently
>    is?
>    If I understand correctly, CI reacts on Koji builds, and that won't work
>    for multi-package updates because we need to test them all together. So we
>    can solve that problem by sending a (new) message when an update is ready
>    to be tested.

So far so good :)

>    I'm guessing we want to send that message when the update / update set
>    lands in the testing repository? Thus after a compose?

Rawhide has no testing repository so that won't work then :)

For stable releases, we gate going to testing and going to stable, so we likely
will send the message when the update is submitted to testing and when it's
submitted to stable.

>    What kind of information do we want in that message?

This is *the* question which we're still trying to figure out :)


Resolving patch:
diff --git a/bodhi/messages/schemas/update.py b/bodhi/messages/schemas/update.py
--- a/bodhi/messages/schemas/update.py
+++ b/bodhi/messages/schemas/update.py
@@ -568,3 +568,39 @@ def summary(self) -> str:
             A summary for this message.
         """
         return f'{self.update.alias} has met stable testing requirements'
+
+
+class UpdateReadyForTestingV1(UpdateMessage):
+    """Sent when an update is ready to be tested."""
+
+    body_schema = {
+        'id': f'{SCHEMA_URL}/v1/bodhi.update.status.testing#',
+        '$schema': 'http://json-schema.org/draft-04/schema#',
+        'description': 'Schema for message sent when an update is ready for testing',
+        'type': 'object',
+        'properties': {
+            'update': UpdateV1.schema(),
+        },
+        'required': ['update'],
+        'definitions': {
+            'build': BuildV1.schema(),
+        }
+    }
+
+    topic = "bodhi.update.status.testing"
+
+    @property
+    def summary(self) -> str:
+        """
+        Return a short, human-readable representation of this message.
+
+        This should provide a short summary of the message, much like the subject line
+        of an email.
+
+        Returns:
+            A summary for this message.
+        """
+        return (
+            f"{self.update.user.name}'s "
+            f"{truncate(' '.join([b.nvr for b in self.update.builds]))} "
+            f"bodhi update is ready for {self.update.status}")
diff --git a/bodhi/server/models.py b/bodhi/server/models.py
--- a/bodhi/server/models.py
+++ b/bodhi/server/models.py
@@ -34,6 +34,7 @@
                         Integer, or_, Table, Unicode, UnicodeText, UniqueConstraint)
 from sqlalchemy.ext.declarative import declarative_base
 from sqlalchemy.orm import class_mapper, relationship, backref, validates
+from sqlalchemy.orm.base import NEVER_SET
 from sqlalchemy.orm.exc import NoResultFound
 from sqlalchemy.orm.properties import RelationshipProperty
 from sqlalchemy.types import SchemaType, TypeDecorator, Enum
@@ -1799,6 +1800,9 @@ def __init__(self, *args, **kwargs):
 
         log.debug('Set alias for %s to %s' % (self.get_title(), alias))
 
+        if self.status == UpdateStatus.testing:
+            self._ready_for_testing(self, self.status, None, None)
+
     @property
     def side_tag_locked(self):
         """
@@ -3753,6 +3757,36 @@ def comment_on_test_gating_status_change(target, value, old, initiator):
                 email_notification=notify,
             )
 
+    @staticmethod
+    def _ready_for_testing(target, value, old, initiator):
+        """
+        Signal that the update has been moved to testing.
+
+        This happens in the following cases:
+        - for stable releases: the update lands in the testing repository
+        - for rawhide: all packages in an update have been built by koji
+
+        Args:
+            target (Update): The update that has had a change to its status attribute.
+            value (EnumSymbol): The new value of Update.status.
+            old (EnumSymbol): The old value of the Update.status
+            initiator (sqlalchemy.orm.attributes.Event): The event object that is initiating this
+                transition.
+        """
+        if value != UpdateStatus.testing or value == old:
+            return
+        if old == NEVER_SET:
+            # This is the object initialization phase. This instance is not ready, don't create
+            # the message now. This method will be called again at the end of __init__
+            return
+        message = update_schemas.UpdateReadyForTestingV1.from_dict(
+            message={'update': target, 'agent': 'bodhi'}
+        )
+        # This method is called before the new attribute value is actually set,
+        # so the message needs to be updated.
+        message.body["update"]["status"] = str(value)
+        notifications.publish(message)
+
 
 event.listen(
     Update.test_gating_status,
@@ -3762,6 +3796,14 @@ def comment_on_test_gating_status_change(target, value, old, initiator):
 )
 
 
+event.listen(
+    Update.status,
+    'set',
+    Update._ready_for_testing,
+    active_history=True,
+)
+
+
 class Compose(Base):
     """
     Express the status of an in-progress compose job.
diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -169,6 +169,10 @@ def get_requirements(requirements_file='requirements.txt'):
                 "bodhi.update.complete.testing.v1="
                 "bodhi.messages.schemas.update:UpdateCompleteTestingV1"
             ),
+            (
+                "bodhi.update.status.testing.v1="
+                "bodhi.messages.schemas.update:UpdateReadyForTestingV1"
+            ),
             "bodhi.update.edit.v1=bodhi.messages.schemas.update:UpdateEditV1",
             "bodhi.update.eject.v1=bodhi.messages.schemas.update:UpdateEjectV1",
             "bodhi.update.karma.threshold.v1=bodhi.messages.schemas.update:UpdateKarmaThresholdV1",


Test patch:
diff --git a/bodhi/tests/messages/schemas/test_update.py b/bodhi/tests/messages/schemas/test_update.py
--- a/bodhi/tests/messages/schemas/test_update.py
+++ b/bodhi/tests/messages/schemas/test_update.py
@@ -25,6 +25,7 @@
     UpdateEditV1,
     UpdateEjectV1,
     UpdateKarmaThresholdV1,
+    UpdateReadyForTestingV1,
     UpdateRequestRevokeV1,
     UpdateRequestStableV1,
     UpdateRequestTestingV1,
@@ -160,6 +161,44 @@ def test_complete_testing_v1(self):
         )
         check_message(msg, expected)
 
+    def test_ready_for_testing_v1(self):
+        expected = {
+            "topic": "bodhi.update.status.testing",
+            "summary": (
+                "eclipseo's golang-github-SAP-go-hdb-0.14.1-1.fc29 t… bodhi update "
+                "is ready for testing"
+            ),
+            "app_icon": "https://apps.fedoraproject.org/img/icons/bodhi.png",
+            "url": "https://bodhi.fedoraproject.org/updates/FEDORA-2019-d64d0caab3",
+            "agent_avatar": (
+                "https://seccdn.libravatar.org/avatar/"
+                "20652954adacfd9f6e26536bbcf3b5fbc850dc61f8a2e67c5bfbc6e345032976"
+                "?s=64&d=retro"
+            ),
+            "usernames": ["eclipseo", 'mohanboddu'],
+            "packages": ["golang-github-SAP-go-hdb", 'texworks'],
+            'update': UpdateV1(
+                'FEDORA-2019-d64d0caab3',
+                [BuildV1('golang-github-SAP-go-hdb-0.14.1-1.fc29'),
+                 BuildV1('texworks-0.6.3-1.fc29')],
+                UserV1('eclipseo'), 'testing', None)
+        }
+        msg = UpdateReadyForTestingV1(
+            body={
+                "update": {
+                    "alias": "FEDORA-2019-d64d0caab3",
+                    "builds": [{"nvr": "golang-github-SAP-go-hdb-0.14.1-1.fc29"},
+                               {'nvr': 'texworks-0.6.3-1.fc29'}],
+                    "title": "fedmsg-0.2.7-2.el6",
+                    'request': None,
+                    "status": "testing",
+                    "user": {"name": "eclipseo"}
+                },
+                'agent': 'mohanboddu'
+            }
+        )
+        check_message(msg, expected)
+
     def test_request_testing_v1_multiple(self):
         expected = {
             "topic": "bodhi.update.request.testing",
diff --git a/bodhi/tests/server/consumers/test_automatic_updates.py b/bodhi/tests/server/consumers/test_automatic_updates.py
--- a/bodhi/tests/server/consumers/test_automatic_updates.py
+++ b/bodhi/tests/server/consumers/test_automatic_updates.py
@@ -22,8 +22,10 @@
 import logging
 
 from fedora_messaging.api import Message
+from fedora_messaging.testing import mock_sends
 import pytest
 
+from bodhi.messages.schemas import update as update_schemas
 from bodhi.server.config import config
 from bodhi.server.consumers.automatic_updates import AutomaticUpdateHandler
 from bodhi.server.models import (
@@ -71,7 +73,8 @@ def test_consume(self, caplog):
         caplog.set_level(logging.DEBUG)
 
         # process the message
-        self.handler(self.sample_message)
+        with mock_sends(update_schemas.UpdateReadyForTestingV1):
+            self.handler(self.sample_message)
 
         # check if the update exists...
         update = self.db.query(Update).filter(
@@ -98,7 +101,8 @@ def test_consume_with_orphan_build(self, caplog):
         caplog.set_level(logging.DEBUG)
 
         # Run the handler to create the build & update, then remove the update.
-        self.handler(self.sample_message)
+        with mock_sends(update_schemas.UpdateReadyForTestingV1):
+            self.handler(self.sample_message)
         build = self.db.query(Build).filter_by(nvr=self.sample_nvr).one()
         update = build.update
         build.update = None  # satisfy foreign key constraint
@@ -106,7 +110,8 @@ def test_consume_with_orphan_build(self, caplog):
 
         # Now test with the same message again which should encounter the
         # build already existing in the database.
-        self.handler(self.sample_message)
+        with mock_sends(update_schemas.UpdateReadyForTestingV1):
+            self.handler(self.sample_message)
 
         # check if the update exists...
         update = self.db.query(Update).filter(
@@ -148,7 +153,8 @@ def test_consume_with_gating(self, caplog, gated):
                         'summary': "no tests are required",
                     }
                 mock_greenwave.return_value = greenwave_response
-            self.handler(self.sample_message)
+            with mock_sends(update_schemas.UpdateReadyForTestingV1):
+                self.handler(self.sample_message)
 
         # check if the update exists...
         update = self.db.query(Update).filter(
@@ -201,7 +207,8 @@ def test_existing_pending_update(self, caplog):
         """
         caplog.set_level(logging.DEBUG)
 
-        self.handler(self.sample_message)
+        with mock_sends(update_schemas.UpdateReadyForTestingV1):
+            self.handler(self.sample_message)
         update = self.db.query(Update).filter(
             Update.builds.any(Build.nvr == self.sample_nvr)
         ).first()
@@ -212,10 +219,13 @@ def test_existing_pending_update(self, caplog):
         update.request = UpdateRequest.testing
         self.db.add(update)
         self.db.flush()
+        # Clear pending messages
+        self.db.info['messages'] = []
 
         caplog.clear()
 
-        self.handler(self.sample_message)
+        with mock_sends(update_schemas.UpdateReadyForTestingV1):
+            self.handler(self.sample_message)
 
         assert (f"Build, active update for {self.sample_nvr} exists already in "
                 "Pending, moving it along." in caplog.messages)
@@ -254,7 +264,8 @@ def _call_handler():
                             'summary': "no tests are required",
                         }
                     mock_greenwave.return_value = greenwave_response
-                self.handler(self.sample_message)
+                with mock_sends(update_schemas.UpdateReadyForTestingV1):
+                    self.handler(self.sample_message)
 
                 # Quick sanity checks
                 assert mock_greenwave.call_count == 1
@@ -282,6 +293,8 @@ def _call_handler():
         update.request = UpdateRequest.testing
         self.db.add(update)
         self.db.flush()
+        # Clear pending messages
+        self.db.info['messages'] = []
 
         caplog.clear()
 
@@ -378,7 +391,8 @@ def test_missing_user(self, caplog):
         self.db.query(User).filter_by(name=expected_username).delete()
         self.db.flush()
 
-        self.handler(self.sample_message)
+        with mock_sends(update_schemas.UpdateReadyForTestingV1):
+            self.handler(self.sample_message)
 
         assert(f"Creating bodhi user for '{expected_username}'."
                in caplog.messages)
@@ -397,7 +411,8 @@ def test_existing_user(self, caplog):
         self.db.flush()
 
         with mock.patch('bodhi.server.models.handle_update'):
-            self.handler(self.sample_message)
+            with mock_sends(update_schemas.UpdateReadyForTestingV1):
+                self.handler(self.sample_message)
 
         assert(f"Creating bodhi user for '{expected_username}'."
                not in caplog.messages)
@@ -411,7 +426,8 @@ def test_ignored_tag(self, caplog):
         msg = deepcopy(self.sample_message)
         bogus_tag = 'thisisntthetagyourelookingfor'
         msg.body['tag'] = bogus_tag
-        self.handler(msg)
+        with mock_sends():
+            self.handler(msg)
 
         assert any(x.startswith(f"Ignoring build being tagged into '{bogus_tag}'")
                    for x in caplog.messages)
@@ -420,11 +436,13 @@ def test_duplicate_message(self, caplog):
         """Assert that duplicate messages ignore existing build/update."""
         caplog.set_level(logging.DEBUG)
 
-        self.handler(self.sample_message)
+        with mock_sends(update_schemas.UpdateReadyForTestingV1):
+            self.handler(self.sample_message)
 
         caplog.clear()
 
-        self.handler(self.sample_message)
+        with mock_sends():
+            self.handler(self.sample_message)
 
         assert (f"Build, active update for {self.sample_nvr} exists already, skipping."
                 in caplog.messages)
diff --git a/bodhi/tests/server/scripts/test_approve_testing.py b/bodhi/tests/server/scripts/test_approve_testing.py
--- a/bodhi/tests/server/scripts/test_approve_testing.py
+++ b/bodhi/tests/server/scripts/test_approve_testing.py
@@ -53,6 +53,9 @@ def test_autokarma_update_meeting_time_requirements_gets_one_comment(self, mail)
         update.request = None
         update.stable_karma = 10
         update.status = models.UpdateStatus.testing
+        # Clear pending messages
+        self.db.info['messages'] = []
+
         update.date_testing = datetime.utcnow() - timedelta(days=7)
         with fml_testing.mock_sends():
             self.db.commit()
@@ -90,6 +93,9 @@ def test_autokarma_update_without_mandatory_days_in_testing(self):
         update.autotime = False
         update.request = None
         update.status = models.UpdateStatus.testing
+        # Clear pending messages
+        self.db.info['messages'] = []
+
         update.date_testing = datetime.utcnow() - timedelta(days=7)
         # Let's delete all the comments to make our assertion at the end of this simpler.
         for c in update.comments:
@@ -115,6 +121,8 @@ def test_autokarma_update_not_meeting_testing_requirements(self):
         update.autokarma = True
         update.request = None
         update.status = models.UpdateStatus.testing
+        # Clear pending messages
+        self.db.info['messages'] = []
         # 6 days isn't enough time to meet the testing requirements.
         update.date_testing = datetime.utcnow() - timedelta(days=6)
         # Let's delete all the comments to make our assertion at the end of this simpler.
@@ -235,6 +243,9 @@ def test_non_autokarma_critpath_update_meeting_karma_requirements_gets_one_comme
         update.request = None
         update.stable_karma = 1
         update.status = models.UpdateStatus.testing
+        # Clear pending messages
+        self.db.info['messages'] = []
+
         update.comment(self.db, 'testing', author='hunter2', karma=1)
         with fml_testing.mock_sends(api.Message):
             self.db.commit()
@@ -307,6 +318,9 @@ def test_non_autokarma_update_meeting_karma_requirements_gets_one_comment(self):
         update.request = None
         update.stable_karma = 1
         update.status = models.UpdateStatus.testing
+        # Clear pending messages
+        self.db.info['messages'] = []
+
         update.comment(self.db, 'testing', author='hunter2', karma=1)
         with fml_testing.mock_sends(api.Message):
             self.db.commit()
@@ -342,8 +356,9 @@ def test_non_autokarma_critpath_update_meeting_time_requirements_gets_one_commen
         update.critpath = True
         update.status = models.UpdateStatus.testing
         update.date_testing = datetime.utcnow() - timedelta(days=14)
-        with fml_testing.mock_sends():
-            self.db.commit()
+        # Clear pending messages
+        self.db.info['messages'] = []
+        self.db.commit()
 
         with patch('bodhi.server.scripts.approve_testing.initialize_db'):
             with patch('bodhi.server.scripts.approve_testing.get_appsettings', return_value=''):
@@ -407,6 +422,8 @@ def test_non_autokarma_update_with_unmet_karma_requirement_after_time_met(self):
         update.status = models.UpdateStatus.testing
         update.date_testing = datetime.utcnow() - timedelta(days=7)
         self.db.flush()
+        # Clear pending messages
+        self.db.info['messages'] = []
 
         with patch('bodhi.server.scripts.approve_testing.initialize_db'):
             with patch('bodhi.server.scripts.approve_testing.get_appsettings', return_value=''):
@@ -464,6 +481,8 @@ def test_subsequent_comments_after_initial_push_comment(self):
         update.date_testing = datetime.utcnow() - timedelta(days=14)
         update.autotime = False
         self.db.flush()
+        # Clear pending messages
+        self.db.info['messages'] = []
 
         with patch('bodhi.server.scripts.approve_testing.initialize_db'):
             with patch('bodhi.server.scripts.approve_testing.get_appsettings', return_value=''):
@@ -507,6 +526,8 @@ def test_autotime_update_meeting_test_requirements_gets_pushed(self):
         update.stable_days = 7
         update.date_testing = datetime.utcnow() - timedelta(days=7)
         update.status = models.UpdateStatus.testing
+        # Clear pending messages
+        self.db.info['messages'] = []
         self.db.commit()
 
         with patch('bodhi.server.scripts.approve_testing.initialize_db'):
@@ -529,6 +550,8 @@ def test_autotime_update_does_not_meet_stable_days_doesnt_get_pushed(self):
         update.stable_days = 10
         update.date_testing = datetime.utcnow() - timedelta(days=7)
         update.status = models.UpdateStatus.testing
+        # Clear pending messages
+        self.db.info['messages'] = []
         self.db.commit()
 
         with patch('bodhi.server.scripts.approve_testing.initialize_db'):
@@ -550,6 +573,8 @@ def test_autotime_update_meeting_stable_days_get_pushed(self):
         update.stable_days = 10
         update.date_testing = datetime.utcnow() - timedelta(days=10)
         update.status = models.UpdateStatus.testing
+        # Clear pending messages
+        self.db.info['messages'] = []
         self.db.commit()
 
         with patch('bodhi.server.scripts.approve_testing.initialize_db'):
@@ -572,6 +597,8 @@ def test_no_autotime_update_meeting_stable_days_and_test_requirement(self):
         update.stable_days = 10
         update.date_testing = datetime.utcnow() - timedelta(days=10)
         update.status = models.UpdateStatus.testing
+        # Clear pending messages
+        self.db.info['messages'] = []
         self.db.commit()
 
         with patch('bodhi.server.scripts.approve_testing.initialize_db'):
@@ -595,6 +622,8 @@ def test_autotime_update_does_not_meet_test_requirements(self):
         update.stable_karma = 10
         update.date_testing = datetime.utcnow() - timedelta(days=1)
         update.status = models.UpdateStatus.testing
+        # Clear pending messages
+        self.db.info['messages'] = []
         self.db.commit()
 
         with patch('bodhi.server.scripts.approve_testing.initialize_db'):
@@ -616,6 +645,8 @@ def test_autotime_update_does_no_mandatory_days_in_testing(self):
         update.stable_karma = 10
         update.date_testing = datetime.utcnow()
         update.status = models.UpdateStatus.testing
+        # Clear pending messages
+        self.db.info['messages'] = []
         self.db.commit()
 
         with patch('bodhi.server.scripts.approve_testing.initialize_db'):
@@ -639,6 +670,8 @@ def test_autotime_update_zero_day_in_testing_meeting_test_requirements_gets_push
         update.stable_days = 0
         update.date_testing = datetime.utcnow() - timedelta(days=0)
         update.status = models.UpdateStatus.testing
+        # Clear pending messages
+        self.db.info['messages'] = []
         self.db.commit()
 
         with patch('bodhi.server.scripts.approve_testing.initialize_db'):
@@ -669,6 +702,8 @@ def test_autotime_update_zero_day_in_testing_no_gated_gets_pushed_to_rawhide(
         update.stable_days = 0
         update.date_testing = datetime.utcnow() - timedelta(days=0)
         update.status = models.UpdateStatus.testing
+        # Clear pending messages
+        self.db.info['messages'] = []
         self.db.commit()
 
         with patch('bodhi.server.scripts.approve_testing.initialize_db'):
@@ -707,6 +742,8 @@ def test_autotime_update_zero_day_in_testing_fail_gating_is_not_pushed(self):
         update.test_gating_status = models.TestGatingStatus.failed
         update.date_testing = datetime.utcnow() - timedelta(days=0)
         update.status = models.UpdateStatus.testing
+        # Clear pending messages
+        self.db.info['messages'] = []
         self.db.commit()
 
         with patch('bodhi.server.scripts.approve_testing.initialize_db'):
@@ -727,6 +764,9 @@ def test_autotime_update_negative_karma_does_not_get_pushed(self):
         update.stable_days = 0
         update.date_testing = datetime.utcnow() - timedelta(days=0)
         update.status = models.UpdateStatus.testing
+        # Clear pending messages
+        self.db.info['messages'] = []
+
         update.comment(self.db, u'Failed to work', author=u'luke', karma=-1)
         with fml_testing.mock_sends(api.Message):
             self.db.commit()
@@ -750,6 +790,9 @@ def test_autotime_update_no_autokarma_met_karma_requirements_get_comments(self):
         update.stable_days = 10
         update.date_testing = datetime.utcnow() - timedelta(days=0)
         update.status = models.UpdateStatus.testing
+        # Clear pending messages
+        self.db.info['messages'] = []
+
         update.comment(self.db, u'Works great', author=u'luke', karma=1)
         with fml_testing.mock_sends(api.Message):
             self.db.commit()
@@ -779,6 +822,9 @@ def test_autotime_update_no_autokarma_met_karma_and_time_requirements_get_pushed
         update.stable_days = 0
         update.date_testing = datetime.utcnow() - timedelta(days=0)
         update.status = models.UpdateStatus.testing
+        # Clear pending messages
+        self.db.info['messages'] = []
+
         update.comment(self.db, u'Works great', author=u'luke', karma=1)
         with fml_testing.mock_sends(api.Message):
             self.db.commit()
@@ -809,6 +855,9 @@ def test_autotime_update_with_autokarma_met_karma_and_time_requirements_get_push
         update.stable_days = 0
         update.date_testing = datetime.utcnow() - timedelta(days=0)
         update.status = models.UpdateStatus.testing
+        # Clear pending messages
+        self.db.info['messages'] = []
+
         update.comment(self.db, u'Works great', author=u'luke', karma=1)
         with fml_testing.mock_sends(api.Message, api.Message, api.Message):
             self.db.commit()
@@ -836,6 +885,8 @@ def test_autotime_update_no_autokarma_negative_karma_not_pushed(self):
         update.stable_days = 0
         update.date_testing = datetime.utcnow() - timedelta(days=8)
         update.status = models.UpdateStatus.testing
+        # Clear pending messages
+        self.db.info['messages'] = []
         update.comment(self.db, u'Broken', author=u'luke', karma=-1)
         with fml_testing.mock_sends(api.Message):
             self.db.commit()
diff --git a/bodhi/tests/server/scripts/test_check_policies.py b/bodhi/tests/server/scripts/test_check_policies.py
--- a/bodhi/tests/server/scripts/test_check_policies.py
+++ b/bodhi/tests/server/scripts/test_check_policies.py
@@ -36,6 +36,8 @@ def test_policies_satisfied(self):
         runner = testing.CliRunner()
         update = self.db.query(models.Update).all()[0]
         update.status = models.UpdateStatus.testing
+        # Clear pending messages
+        self.db.info['messages'] = []
         self.db.commit()
         with patch('bodhi.server.models.util.greenwave_api_post') as mock_greenwave:
             greenwave_response = {
@@ -99,6 +101,8 @@ def test_policies_unsatisfied(self):
         runner = testing.CliRunner()
         update = self.db.query(models.Update).all()[0]
         update.status = models.UpdateStatus.testing
+        # Clear pending messages
+        self.db.info['messages'] = []
         self.db.commit()
         with patch('bodhi.server.models.util.greenwave_api_post') as mock_greenwave:
             greenwave_response = {
@@ -142,6 +146,8 @@ def test_no_policies_enforced(self):
         runner = testing.CliRunner()
         update = self.db.query(models.Update).all()[0]
         update.status = models.UpdateStatus.testing
+        # Clear pending messages
+        self.db.info['messages'] = []
         update.test_gating_status = None
         self.db.commit()
         with patch('bodhi.server.models.util.greenwave_api_post') as mock_greenwave:
@@ -170,6 +176,8 @@ def test_pushed_update(self):
         runner = testing.CliRunner()
         update = self.db.query(models.Update).all()[0]
         update.status = models.UpdateStatus.testing
+        # Clear pending messages
+        self.db.info['messages'] = []
         update.pushed = True
         self.db.commit()
         with patch('bodhi.server.models.util.greenwave_api_post') as mock_greenwave:
@@ -204,6 +212,8 @@ def test_unrestricted_policy(self):
         runner = testing.CliRunner()
         update = self.db.query(models.Update).all()[0]
         update.status = models.UpdateStatus.testing
+        # Clear pending messages
+        self.db.info['messages'] = []
         self.db.commit()
         with patch('bodhi.server.models.util.greenwave_api_post') as mock_greenwave:
             greenwave_response = {
@@ -244,6 +254,8 @@ def test_archived_release_updates(self):
         runner = testing.CliRunner()
         update = self.db.query(models.Update).all()[0]
         update.status = models.UpdateStatus.testing
+        # Clear pending messages
+        self.db.info['messages'] = []
         self.db.commit()
         with patch('bodhi.server.models.util.greenwave_api_post') as mock_greenwave:
             mock_greenwave.side_effect = Exception(
diff --git a/bodhi/tests/server/services/test_comments.py b/bodhi/tests/server/services/test_comments.py
--- a/bodhi/tests/server/services/test_comments.py
+++ b/bodhi/tests/server/services/test_comments.py
@@ -614,6 +614,8 @@ def test_comment_on_locked_update(self):
         up.request = None
         self.assertEqual(len(up.comments), 0)  # Before
         self.assertEqual(up.karma, 0)          # Before
+        # Clear pending messages
+        self.db.info['messages'] = []
         self.db.flush()
 
         comment = self.make_comment(up2, karma=1)
@@ -635,6 +637,8 @@ def test_comment_on_locked_update_no_threshhold_action(self):
         up.unstable_karma = -1
         self.assertEqual(len(up.comments), 0)  # Before
         self.assertEqual(up.karma, 0)          # Before
+        # Clear pending messages
+        self.db.info['messages'] = []
         self.db.flush()
 
         comment = self.make_comment(up2, karma=-1)
diff --git a/bodhi/tests/server/services/test_schemas.py b/bodhi/tests/server/services/test_schemas.py
--- a/bodhi/tests/server/services/test_schemas.py
+++ b/bodhi/tests/server/services/test_schemas.py
@@ -62,7 +62,8 @@ def test_get(self):
                 'bodhi.compose.sync.done', 'bodhi.compose.sync.wait',
                 'bodhi.errata.publish', 'bodhi.repo.done', 'bodhi.update.comment',
                 'bodhi.update.complete.stable', 'bodhi.update.complete.testing',
-                'bodhi.update.edit', 'bodhi.update.eject', 'bodhi.update.karma.threshold.reach',
+                'bodhi.update.status.testing', 'bodhi.update.karma.threshold.reach',
+                'bodhi.update.edit', 'bodhi.update.eject',
                 'bodhi.update.request.obsolete', 'bodhi.update.request.revoke',
                 'bodhi.update.request.stable', 'bodhi.update.request.testing',
                 'bodhi.update.request.unpush', 'bodhi.update.requirements_met.stable']))
diff --git a/bodhi/tests/server/services/test_updates.py b/bodhi/tests/server/services/test_updates.py
--- a/bodhi/tests/server/services/test_updates.py
+++ b/bodhi/tests/server/services/test_updates.py
@@ -686,6 +686,8 @@ def test_obsoletion(self, *args):
         up = self.db.query(Build).filter_by(nvr=nvr).one().update
         up.status = UpdateStatus.testing
         up.request = None
+        # Clear pending messages
+        self.db.info['messages'] = []
 
         args = self.get_update('bodhi-2.0.0-3.fc17')
         with mock.patch(**mock_uuid4_version2):
@@ -732,6 +734,8 @@ def test_create_new_nonsecurity_update_when_previous_security_one_exists(self, *
         up = self.db.query(Build).filter_by(nvr=nvr).one().update
         up.status = UpdateStatus.testing
         up.request = None
+        # Clear pending messages
+        self.db.info['messages'] = []
 
         args = self.get_update('bodhi-2.0.0-3.fc17')
         with mock.patch(**mock_uuid4_version2):
@@ -784,6 +788,8 @@ def test_obsoletion_security_update(self, *args):
         up = self.db.query(Build).filter_by(nvr=nvr).one().update
         up.status = UpdateStatus.testing
         up.request = None
+        # Clear pending messages
+        self.db.info['messages'] = []
 
         args = self.get_update('bodhi-2.0.0-3.fc17')
         args["type"] = "security"
@@ -846,6 +852,8 @@ def test_obsoletion_with_exception(self, *args):
         up = self.db.query(Build).filter_by(nvr=nvr).one().update
         up.status = UpdateStatus.testing
         up.request = None
+        # Clear pending messages
+        self.db.info['messages'] = []
         args = self.get_update('bodhi-2.0.0-3.fc17')
 
         with mock.patch(**mock_uuid4_version2):
@@ -1156,6 +1164,8 @@ def test_decision_context_testing(self):
         u = Update.query.one()
         u.request = None
         u.status = UpdateStatus.testing
+        # Clear pending messages
+        self.db.info['messages'] = []
 
         res = self.app.get(f'/updates/{u.alias}', status=200, headers={'Accept': 'text/html'})
 
@@ -1382,6 +1392,8 @@ def test_provenpackager_request_privs(self, *args):
         update.request = None
         update.status = UpdateStatus.testing
         update.pushed = True
+        # Clear pending messages
+        self.db.info['messages'] = []
         self.db.commit()
 
         self.assertEqual(update.karma, 0)
@@ -2969,6 +2981,8 @@ def test_edit_testing_update_with_new_builds(self, *args):
         upd = Update.get(r.json['alias'])
         upd.status = UpdateStatus.testing
         upd.request = None
+        # Clear pending messages
+        self.db.info['messages'] = []
         self.db.commit()
 
         args['edited'] = upd.alias
@@ -3017,6 +3031,8 @@ def test_edit_testing_update_with_new_builds_with_stable_request(self, *args):
         upd = Update.get(r.json['alias'])
         upd.status = UpdateStatus.testing
         upd.request = UpdateRequest.stable
+        # Clear pending messages
+        self.db.info['messages'] = []
         self.db.commit()
 
         args['edited'] = upd.alias
@@ -3126,6 +3142,8 @@ def test_edit_locked_update(self, *args):
         up.status = UpdateStatus.testing
         up.request = None
         up_id = up.id
+        # Clear pending messages
+        self.db.info['messages'] = []
 
         # Changing the notes should work
         args['edited'] = up.alias
@@ -3366,6 +3384,8 @@ def test_push_to_stable_for_obsolete_update(self, *args):
         up = self.db.query(Build).filter_by(nvr=nvr).one().update
         up.status = UpdateStatus.testing
         up.request = None
+        # Clear pending messages
+        self.db.info['messages'] = []
 
         new_nvr = 'bodhi-2.0.0-3.fc17'
         args = self.get_update(new_nvr)
@@ -3696,6 +3716,8 @@ def test_request_to_stable_based_on_stable_karma(self, *args):
         up.request = None
         self.assertEqual(len(up.builds), 1)
         up.test_gating_status = TestGatingStatus.passed
+        # Clear pending messages
+        self.db.info['messages'] = []
         self.db.commit()
 
         # Checks failure for requesting to stable push before the update reaches stable karma
@@ -3925,6 +3947,8 @@ def test_update_with_older_build_in_testing_from_diff_user(self, r):
         up.status = UpdateStatus.testing
         up.request = None
         up.user = newuser
+        # Clear pending messages
+        self.db.info['messages'] = []
         self.db.commit()
 
         newtitle = 'bodhi-2.0-3.fc17'
@@ -4058,6 +4082,8 @@ def test_edit_update_bugs(self, *args):
         update.request = None
         update.status = UpdateStatus.testing
         update.pushed = True
+        # Clear pending messages
+        self.db.info['messages'] = []
         self.db.commit()
 
         # Mark it as testing
@@ -4122,6 +4148,8 @@ def test_edit_update_and_disable_features(self, *args):
         update.request = None
         update.status = UpdateStatus.testing
         update.pushed = True
+        # Clear pending messages
+        self.db.info['messages'] = []
         self.db.commit()
 
         # Mark it as testing
@@ -4161,6 +4189,8 @@ def test_edit_update_change_type(self, *args):
         update.request = None
         update.status = UpdateStatus.testing
         update.pushed = True
+        # Clear pending messages
+        self.db.info['messages'] = []
         self.db.commit()
 
         # Mark it as testing
@@ -4200,6 +4230,7 @@ def test_edit_testing_update_reset_karma(self, *args):
         upd.comment(self.db, 'LGTM', author='bob', karma=1)
         upd.comment(self.db, 'LGTM2ME2', author='other_bob', karma=1)
         self.assertEqual(upd.karma, 2)
+        # Clear pending messages
         self.db.info['messages'] = []
 
         # Then.. edit it and change the builds!
@@ -4234,6 +4265,8 @@ def test_edit_testing_update_reset_karma_with_same_tester(self, *args):
         upd = Update.get(r.json['alias'])
         upd.status = UpdateStatus.testing
         upd.request = None
+        # Clear pending messages
+        self.db.info['messages'] = []
         self.db.commit()
 
         # Have bob +1 it
@@ -4294,6 +4327,8 @@ def test__composite_karma_with_one_negative(self, *args):
         up = self.db.query(Build).filter_by(nvr=nvr).one().update
         up.request = None
         up.status = UpdateStatus.testing
+        # Clear pending messages
+        self.db.info['messages'] = []
         self.db.commit()
 
         # The user gives negative karma first
@@ -4320,6 +4355,8 @@ def test__composite_karma_with_changed_karma(self, *args):
         up = self.db.query(Build).filter_by(nvr=nvr).one().update
         up.request = None
         up.status = UpdateStatus.testing
+        # Clear pending messages
+        self.db.info['messages'] = []
         self.db.commit()
 
         # The user gives negative karma first
@@ -4352,6 +4389,8 @@ def test__composite_karma_with_positive_karma_first(self, *args):
         up = self.db.query(Build).filter_by(nvr=nvr).one().update
         up.request = None
         up.status = UpdateStatus.testing
+        # Clear pending messages
+        self.db.info['messages'] = []
         self.db.commit()
 
         #  user gives positive karma first
@@ -4381,6 +4420,8 @@ def test__composite_karma_with_no_negative_karma(self, *args):
         up = self.db.query(Build).filter_by(nvr=nvr).one().update
         up.request = None
         up.status = UpdateStatus.testing
+        # Clear pending messages
+        self.db.info['messages'] = []
         self.db.commit()
 
         up.comment(self.db, 'LGTM', author='mac', karma=1)
@@ -4409,6 +4450,8 @@ def test__composite_karma_with_no_feedback(self, *args):
         up = self.db.query(Build).filter_by(nvr=nvr).one().update
         up.request = None
         up.status = UpdateStatus.testing
+        # Clear pending messages
+        self.db.info['messages'] = []
         self.db.commit()
 
         up = self.db.query(Build).filter_by(nvr=nvr).one().update
@@ -4436,6 +4479,8 @@ def test_karma_threshold_with_disabled_autopush(self, *args):
         update.request = None
         update.status = UpdateStatus.testing
         update.pushed = True
+        # Clear pending messages
+        self.db.info['messages'] = []
         self.db.commit()
 
         # Mark it as testing
@@ -4474,6 +4519,8 @@ def test_disable_autopush_for_critical_updates(self, *args):
         up = self.db.query(Update).filter_by(alias=resp.json['alias']).one()
         up.status = UpdateStatus.testing
         up.request = None
+        # Clear pending messages
+        self.db.info['messages'] = []
         self.db.commit()
 
         # A user gives negative karma first
@@ -4511,6 +4558,8 @@ def test_autopush_critical_update_with_no_negative_karma(self, *args):
         self.assertEqual(resp.json['request'], 'testing')
         up = self.db.query(Update).filter_by(alias=resp.json['alias']).one()
         up.status = UpdateStatus.testing
+        # Clear pending messages
+        self.db.info['messages'] = []
         self.db.commit()
 
         up.comment(self.db, 'LGTM', author='ralph', karma=1)
@@ -4552,6 +4601,8 @@ def test_manually_push_critical_update_with_negative_karma(self, *args):
 
         up = self.db.query(Update).filter_by(alias=resp.json['alias']).one()
         up.status = UpdateStatus.testing
+        # Clear pending messages
+        self.db.info['messages'] = []
         self.db.commit()
 
         up.comment(self.db, 'Failed to work', author='ralph', karma=-1)
@@ -4607,6 +4658,8 @@ def test_manually_push_critical_update_with_autopush_turned_off(self, *args):
         self.assertEqual(resp.json['request'], 'testing')
         up = self.db.query(Update).filter_by(alias=resp.json['alias']).one()
         up.status = UpdateStatus.testing
+        # Clear pending messages
+        self.db.info['messages'] = []
         self.db.commit()
 
         up.comment(self.db, 'LGTM Now', author='ralph', karma=1)
@@ -4652,6 +4705,8 @@ def test_disable_autopush_non_critical_update_with_negative_karma(self, *args):
         self.assertEqual(resp.json['request'], 'testing')
         up = self.db.query(Update).filter_by(alias=resp.json['alias']).one()
         up.status = UpdateStatus.testing
+        # Clear pending messages
+        self.db.info['messages'] = []
         self.db.commit()
 
         up.comment(self.db, 'Failed to work', author='ralph', karma=-1)
@@ -4700,6 +4755,8 @@ def test_autopush_non_critical_update_with_no_negative_karma(self, *args):
         self.assertEqual(resp.json['request'], 'testing')
         up = self.db.query(Update).filter_by(alias=resp.json['alias']).one()
         up.status = UpdateStatus.testing
+        # Clear pending messages
+        self.db.info['messages'] = []
         self.db.commit()
 
         up.comment(self.db, 'LGTM Now', author='ralph', karma=1)
@@ -5062,6 +5119,8 @@ def test_manually_push_to_stable_based_on_karma_request_none(self, *args):
         upd.request = None
         upd.pushed = True
         upd.date_testing = datetime.now() - timedelta(days=1)
+        # Clear pending messages
+        self.db.info['messages'] = []
         self.db.commit()
 
         # Checks karma threshold is reached
@@ -5114,6 +5173,8 @@ def test_manually_push_to_stable_based_on_karma_request_testing(self, *args):
         upd.pushed = True
         upd.request = None
         upd.date_testing = datetime.now() - timedelta(days=1)
+        # Clear pending messages
+        self.db.info['messages'] = []
         self.db.commit()
 
         # Checks karma threshold is reached
@@ -5238,6 +5299,8 @@ def test_edit_testing_update_with_build_from_different_update(self, *args):
         upd = Update.get(r.json['alias'])
         upd.status = UpdateStatus.testing
         upd.request = None
+        # Clear pending messages
+        self.db.info['messages'] = []
         self.db.commit()
 
         # Create an update for a different build
@@ -5251,6 +5314,8 @@ def test_edit_testing_update_with_build_from_different_update(self, *args):
         upd = Update.get(r.json['alias'])
         upd.status = UpdateStatus.testing
         upd.request = None
+        # Clear pending messages
+        self.db.info['messages'] = []
         self.db.commit()
 
         # Edit the nvr2 update and add nvr1
diff --git a/bodhi/tests/server/tasks/test_composer.py b/bodhi/tests/server/tasks/test_composer.py
--- a/bodhi/tests/server/tasks/test_composer.py
+++ b/bodhi/tests/server/tasks/test_composer.py
@@ -435,6 +435,7 @@ def test_tags(self, *args):
                 'ctype': 'rpm',
                 'updates': ['bodhi-2.0-1.fc17'],
                 'agent': 'bowlofeggs'}),
+            update_schemas.UpdateReadyForTestingV1,
             update_schemas.UpdateCompleteTestingV1,
             compose_schemas.ComposeCompleteV1.from_dict(dict(
                 success=True, repo='f17-updates-testing', ctype='rpm', agent='bowlofeggs')))
@@ -530,6 +531,8 @@ def test_tag_ordering(self, *args):
                 'ctype': 'rpm',
                 'updates': ['bodhi-2.0-2.fc17', 'bodhi-2.0-1.fc17'],
                 'agent': 'bowlofeggs'}),
+            update_schemas.UpdateReadyForTestingV1,
+            update_schemas.UpdateReadyForTestingV1,
             update_schemas.UpdateCompleteTestingV1,
             update_schemas.UpdateCompleteTestingV1,
             compose_schemas.ComposeCompleteV1.from_dict(dict(
@@ -578,7 +581,7 @@ def test_testing_digest(self, mail, *args):
         t = RPMComposerThread(self.semmock, task['composes'][0],
                               'ralph', self.db_factory, self.tempdir)
 
-        with mock_sends(*[base_schemas.BodhiMessage] * 3):
+        with mock_sends(*[base_schemas.BodhiMessage] * 4):
             t.run()
 
         self.assertEqual(t.testing_digest['Fedora 17']['bodhi-2.0-1.fc17'], """\
@@ -894,6 +897,7 @@ def test_security_update_priority(self, *args):
                 'ctype': 'rpm',
                 'updates': [u'bodhi-2.0-1.fc17'],
                 'agent': 'bowlofeggs'}),
+            update_schemas.UpdateReadyForTestingV1,
             update_schemas.UpdateCompleteTestingV1,
             compose_schemas.ComposeCompleteV1.from_dict(
                 {'success': True,
@@ -941,6 +945,9 @@ def test_security_update_priority(self, *args):
             # Wipe out the tag cache so it picks up our new release
             Release._tag_cache = None
 
+            # Clear pending messages
+            self.db.info['messages'] = []
+
         with mock_sends(*expected_messages):
             task = self._make_task()
             api_version = task.pop("api_version")
@@ -964,6 +971,7 @@ def test_security_update_priority_testing(self, *args):
                 'ctype': 'rpm',
                 'updates': [u'bodhi-2.0-1.fc17'],
                 'agent': 'bowlofeggs'}),
+            update_schemas.UpdateReadyForTestingV1,
             update_schemas.UpdateCompleteTestingV1,
             compose_schemas.ComposeCompleteV1.from_dict(
                 {'success': True,
@@ -1022,6 +1030,9 @@ def test_security_update_priority_testing(self, *args):
             # Wipe out the tag cache so it picks up our new release
             Release._tag_cache = None
 
+            # Clear pending messages
+            self.db.info['messages'] = []
+
         with mock_sends(*expected_messages):
             task = self._make_task()
             api_version = task.pop("api_version")
@@ -1094,6 +1105,9 @@ def test_security_updates_parallel(self, *args):
             # Wipe out the tag cache so it picks up our new release
             Release._tag_cache = None
 
+            # Clear pending messages
+            self.db.info['messages'] = []
+
         with mock_sends(*expected_messages):
             task = self._make_task()
             api_version = task.pop("api_version")
@@ -1479,6 +1493,9 @@ def test_compose_module(self, *args):
             # Wipe out the tag cache so it picks up our new release
             Release._tag_cache = None
 
+            # Clear pending messages
+            self.db.info['messages'] = []
+
         task = self._make_task(['--releases', 'F27M'])
         t = ModuleComposerThread(self.semmock, task['composes'][0],
                                  'puiterwijk', self.db_factory, self.tempdir)
@@ -1769,7 +1786,7 @@ def test_absent_gating(self, *args):
     def test_modify_testing_bugs(self, on_qa, modified, *args):
         self.expected_sems = 1
 
-        with mock_sends(*[base_schemas.BodhiMessage] * 4):
+        with mock_sends(*[base_schemas.BodhiMessage] * 5):
             task = self._make_task()
             api_version = task.pop("api_version")
             self.handler.run(api_version, task)
@@ -1829,7 +1846,7 @@ def test_status_comment_testing(self, *args):
             up = session.query(Update).one()
             self.assertEqual(len(up.comments), 2)
 
-        with mock_sends(*[base_schemas.BodhiMessage] * 4):
+        with mock_sends(*[base_schemas.BodhiMessage] * 5):
             task = self._make_task()
             api_version = task.pop("api_version")
             self.handler.run(api_version, task)
@@ -1883,6 +1900,8 @@ def test_get_security_updates(self, *args):
             u.type = UpdateType.security
             u.status = UpdateStatus.testing
             u.request = None
+            # Clear pending messages
+            self.db.info['messages'] = []
             session.commit()
             release = session.query(Release).one()
 
@@ -1952,7 +1971,7 @@ def test_resume_push(self, *args):
         task = self._make_task()
         api_version = task.pop("api_version")
         task['resume'] = True
-        with mock_sends(*[base_schemas.BodhiMessage] * 4):
+        with mock_sends(*[base_schemas.BodhiMessage] * 5):
             self.handler.run(api_version, task)
 
         with self.db_factory() as session:
@@ -2010,7 +2029,7 @@ def test_retry_done_compose(self, mock_cmd, sleep,
         task = self._make_task()
         api_version = task.pop("api_version")
         task['resume'] = True
-        with mock_sends(*[base_schemas.BodhiMessage] * 4):
+        with mock_sends(*[base_schemas.BodhiMessage] * 5):
             self.handler.run(api_version, task)
 
         # Assert we did not actually recompose
@@ -2077,7 +2096,7 @@ def test_stable_requirements_met_during_push(self, *args):
         task = self._make_task()
         api_version = task.pop("api_version")
         task['resume'] = True
-        with mock_sends(*[base_schemas.BodhiMessage] * 6):
+        with mock_sends(*[base_schemas.BodhiMessage] * 7):
             self.handler.run(api_version, task)
 
         with mock_sends(*[base_schemas.BodhiMessage] * 2):
@@ -2103,6 +2122,7 @@ def test_push_timestamps(self, *args):
         expected_messages = (
             compose_schemas.ComposeStartV1,
             compose_schemas.ComposeComposingV1,
+            update_schemas.UpdateReadyForTestingV1,
             update_schemas.UpdateCompleteTestingV1,
             compose_schemas.ComposeCompleteV1.from_dict(dict(
                 success=True, repo='f17-updates-testing', ctype='rpm', agent='bowlofeggs')))
@@ -2176,8 +2196,10 @@ def test_obsolete_older_updates(self, *args):
             update.release = oldupdate.release
             session.add(update)
             session.flush()
+            # Clear pending messages
+            self.db.info['messages'] = []
 
-        with mock_sends(*[base_schemas.BodhiMessage] * 4):
+        with mock_sends(*[base_schemas.BodhiMessage] * 5):
             task = self._make_task()
             api_version = task.pop("api_version")
             self.handler.run(api_version, task)
@@ -3285,6 +3307,9 @@ def test_stable_update(self):
         update = Update.query.one()
         update.status = UpdateStatus.testing
         update.request = UpdateRequest.stable
+        # Clear pending messages
+        self.db.info['messages'] = []
+
         t = ComposerThread(self.semmock, self._make_task()['composes'][0],
                            'bowlofeggs', self.Session, self.tempdir)
         t.compose = self.db.query(Compose).one()
@@ -3359,6 +3384,8 @@ def test_critpath_updates(self, SMTP):
         t._checkpoints = {}
         t.db = self.Session
         self.db.flush()
+        # Clear pending messages
+        self.db.info['messages'] = []
 
         with mock.patch.dict(config, {'smtp_server': 'smtp.example.com'}):
             t.send_testing_digest()
@@ -3390,6 +3417,8 @@ def test_security_updates(self, SMTP):
         t._checkpoints = {}
         t.db = self.Session
         self.db.flush()
+        # Clear pending messages
+        self.db.info['messages'] = []
 
         with mock.patch.dict(config, {'smtp_server': 'smtp.example.com'}):
             t.send_testing_digest()
diff --git a/bodhi/tests/server/test_models.py b/bodhi/tests/server/test_models.py
--- a/bodhi/tests/server/test_models.py
+++ b/bodhi/tests/server/test_models.py
@@ -254,7 +254,9 @@ def test_epel_with_testing_bug_epel_msg(self):
     @mock.patch('bodhi.server.models.log.warning')
     @mock.patch.dict(
         config,
-        {'stable_bug_msg': '%s%s', 'testing_bug_msg': '%s', 'base_address': 'b'}, clear=True)
+        {'stable_bug_msg': '%s%s', 'testing_bug_msg': '%s', 'base_address': 'b',
+         'critpath.min_karma': 1, 'fedora_epel.mandatory_days_in_testing': 0},
+        clear=True)
     def test_epel_without_testing_bug_epel_msg(self, warning):
         """Test with testing_bug_epel_msg undefined."""
         bug = model.Bug()
@@ -2324,7 +2326,7 @@ def do_get_dependencies():
             release=release,
             user=model.User(name='lmacken'))
 
-    def get_update(self, name='TurboGears-1.0.8-3.fc11'):
+    def get_update(self, name='TurboGears-1.0.8-3.fc11', override_args=None):
         """Return an Update instance for testing."""
         attrs = self.attrs.copy()
         pkg = self.db.query(model.RpmPackage).filter_by(name='TurboGears').one()
@@ -2332,6 +2334,7 @@ def get_update(self, name='TurboGears-1.0.8-3.fc11'):
         attrs.update(dict(
             builds=[model.RpmBuild(nvr=name, package=pkg, release=rel)],
             release=rel))
+        attrs.update(override_args or {})
         return self.klass(**attrs)
 
     def test___json___with_no_builds(self):
@@ -3331,6 +3334,7 @@ def test_set_request_stable_after_week_in_testing(self):
         expected_message = update_schemas.UpdateRequestStableV1.from_dict(
             {'update': self.obj, 'agent': req.user.name})
 
+        self.db.info['messages'] = []
         with mock_sends(expected_message):
             self.obj.set_request(self.db, UpdateRequest.stable, req.user.name)
             # set_request alters the update a bit, so we need to adjust the expected message to
@@ -3366,6 +3370,7 @@ def test_set_request_stable_when_release_is_frozen(self):
         expected_message = update_schemas.UpdateRequestStableV1.from_dict(
             {'update': self.obj, 'agent': req.user.name})
 
+        self.db.info['messages'] = []
         with mock_sends(expected_message):
             self.obj.set_request(self.db, UpdateRequest.stable, req.user.name)
             # set_request alters the update a bit, so we need to adjust the expected message to
@@ -3740,6 +3745,7 @@ def test_send_update_notice_status_testing(self, SMTP):
         expected_message = errata_schemas.ErrataPublishV1.from_dict({
             'subject': subject, 'body': body, 'update': self.obj})
 
+        self.db.info['messages'] = []
         with mock_sends(expected_message):
             self.obj.send_update_notice()
             self.db.commit()
@@ -4196,6 +4202,28 @@ def test_comment_on_test_gating_status_change_email(self, mail):
         # We should have two comments, one for each test_gating_status change
         self.assertEqual(len(self.obj.comments), 2)
 
+    def test_set_status_testing(self):
+        """Test that setting an update's status to testing sends a message."""
+        self.db.info['messages'] = []
+        with mock_sends(update_schemas.UpdateReadyForTestingV1):
+            self.obj.status = UpdateStatus.testing
+            msg = self.db.info['messages'][0]
+            self.db.commit()
+        assert msg.body["update"]["status"] == "testing"
+
+    def test_create_with_status_testing(self):
+        """Test that creating an update with the status set to testing sends a message."""
+        self.db.info['messages'] = []
+        with mock_sends(update_schemas.UpdateReadyForTestingV1):
+            self.get_update(name="TurboGears-1.0.8-4.fc11", override_args={
+                "status": UpdateStatus.testing,
+                "user": self.db.query(model.User).filter_by(name='lmacken').one()
+            })
+            assert len(self.db.info['messages']) == 1
+            msg = self.db.info['messages'][0]
+            self.db.commit()
+        assert msg.body["update"]["status"] == "testing"
+
 
 class TestUser(ModelTest):
     klass = model.User
diff --git a/bodhi/tests/server/views/test_generic.py b/bodhi/tests/server/views/test_generic.py
--- a/bodhi/tests/server/views/test_generic.py
+++ b/bodhi/tests/server/views/test_generic.py
@@ -74,6 +74,8 @@ def test_critical_update_link_home(self):
         update = Update.query.first()
         update.critpath = True
         update.status = UpdateStatus.testing
+        # Clear pending messages
+        self.db.info['messages'] = []
         self.db.commit()
 
         res = self.app.get('/', headers={'Accept': 'text/html'})
@@ -512,6 +514,8 @@ def _add_updates(updateslist, user, release, packagesuffix):
         # Clear the caches
         Release._tag_cache = None
         generic._generate_home_page_stats.invalidate()
+        # Clear pending messages
+        self.db.info['messages'] = []
 
     def test_home_counts(self):
         """Test the frontpage update counts"""