Repository: fedora-infra/bodhi

Issue:
Add support to the cli to edit the release email template name
The name of the email template used for each release was added to the database in #2469. It should be possible to edit this field from the cli using the `bodhi release` command


Discussion:

Resolving patch:
diff --git a/bodhi/client/__init__.py b/bodhi/client/__init__.py
--- a/bodhi/client/__init__.py
+++ b/bodhi/client/__init__.py
@@ -162,6 +162,7 @@ def _set_logging_debug(ctx, param, value):
     click.option('--state', type=click.Choice(['disabled', 'pending', 'current',
                                                'archived']),
                  help='The state of the release'),
+    click.option('--mail-template', help='Name of the email template for this release'),
     staging_option,
     url_option,
     debug_option]
@@ -1071,6 +1072,7 @@ def print_release(release):
     print("  Pending Stable Tag:  %s" % release['pending_stable_tag'])
     print("  Override Tag:        %s" % release['override_tag'])
     print("  State:               %s" % release['state'])
+    print("  Email Template:      %s" % release['mail_template'])
 
 
 def print_errors(data):
diff --git a/bodhi/server/schemas.py b/bodhi/server/schemas.py
--- a/bodhi/server/schemas.py
+++ b/bodhi/server/schemas.py
@@ -18,10 +18,12 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 """A set of API schemas to validate input and generate documentation."""
 import re
+import os
 
 import colander
 
 from bodhi.server import util
+from bodhi.server.config import config
 from bodhi.server.models import (
     ContentType,
     ReleaseState,
@@ -36,6 +38,10 @@
 
 CVE_REGEX = re.compile(r"CVE-[0-9]{4,4}-[0-9]{4,}")
 
+# Retrieving list of templates from filesystem for `mail_template` validation in SaveReleaseSchema
+template_directory = util.get_absolute_path(config.get('mail.templates_basepath'))
+MAIL_TEMPLATES = [os.path.splitext(file)[0] for file in os.listdir(template_directory)]
+
 
 class CSRFProtectedSchema(colander.MappingSchema):
     """A mixin class to validate csrf tokens."""
@@ -401,6 +407,11 @@ class SaveReleaseSchema(CSRFProtectedSchema, colander.MappingSchema):
         colander.String(),
         missing=None,
     )
+    mail_template = colander.SchemaNode(
+        colander.String(),
+        missing="fedora_errata_template",
+        validator=colander.OneOf(MAIL_TEMPLATES)
+    )
 
 
 class ListStackSchema(PaginatedSchema, SearchableSchema):


Test patch:
diff --git a/bodhi/tests/client/__init__.py b/bodhi/tests/client/__init__.py
--- a/bodhi/tests/client/__init__.py
+++ b/bodhi/tests/client/__init__.py
@@ -872,7 +872,7 @@
     u'long_name': u'Fedora 27', u'state': u'pending', u'version': u'27',
     u'override_tag': u'f27-override', u'branch': u'f27', u'id_prefix': u'FEDORA',
     u'pending_testing_tag': u'f27-updates-testing-pending', u'stable_tag': u'f27-updates',
-    u'candidate_tag': u'f27-updates-candidate'})
+    u'candidate_tag': u'f27-updates-candidate', 'mail_template': 'fedora_errata_template'})
 
 
 EXPECTED_RELEASE_OUTPUT = """Saved release:
@@ -890,6 +890,7 @@
   Pending Stable Tag:  f27-updates-pending
   Override Tag:        f27-override
   State:               pending
+  Email Template:      fedora_errata_template
 """
 
 UNMATCHED_RESP = {"pants": "pants"}
diff --git a/bodhi/tests/client/test___init__.py b/bodhi/tests/client/test___init__.py
--- a/bodhi/tests/client/test___init__.py
+++ b/bodhi/tests/client/test___init__.py
@@ -1560,7 +1560,7 @@ def test_url_flag(self, send_request):
                   'testing_tag': None, 'pending_stable_tag': None, 'long_name': None, 'state': None,
                   'version': None, 'override_tag': None, 'branch': None, 'id_prefix': None,
                   'pending_testing_tag': None, 'pending_signing_tag': None, 'stable_tag': None,
-                  'candidate_tag': None})
+                  'candidate_tag': None, 'mail_template': None})
         self.assertEqual(bindings_client.base_url, 'http://localhost:6543/')
 
     @mock.patch('bodhi.client.bindings.BodhiClient.csrf',
@@ -1619,7 +1619,8 @@ def test_url_flag(self, send_request):
                       'long_name': 'Fedora 27, the Greatest Fedora!', 'state': 'pending',
                       'version': '27', 'override_tag': 'f27-override', 'branch': 'f27',
                       'id_prefix': 'FEDORA', 'pending_testing_tag': 'f27-updates-testing-pending',
-                      'stable_tag': 'f27-updates', 'candidate_tag': 'f27-updates-candidate'}))
+                      'stable_tag': 'f27-updates', 'candidate_tag': 'f27-updates-candidate',
+                      'mail_template': 'fedora_errata_template'}))
         self.assertEqual(bindings_client.base_url, 'http://localhost:6543/')
 
     @mock.patch('bodhi.client.bindings.BodhiClient.csrf',
@@ -1654,7 +1655,8 @@ def test_new_name_flag(self, send_request):
                       'long_name': 'Fedora 27', 'state': 'pending',
                       'version': '27', 'override_tag': 'f27-override', 'branch': 'f27',
                       'id_prefix': 'FEDORA', 'pending_testing_tag': 'f27-updates-testing-pending',
-                      'stable_tag': 'f27-updates', 'candidate_tag': 'f27-updates-candidate'}))
+                      'stable_tag': 'f27-updates', 'candidate_tag': 'f27-updates-candidate',
+                      'mail_template': 'fedora_errata_template'}))
 
     @mock.patch('bodhi.client.bindings.BodhiClient.csrf',
                 mock.MagicMock(return_value='a_csrf_token'))
@@ -1692,6 +1694,40 @@ def test_edit_with_errors(self, send_request):
         self.assertEqual(result.exit_code, 1)
         self.assertEqual(result.output, ("ERROR: an error was encountered... :(\n"))
 
+    @mock.patch('bodhi.client.bindings.BodhiClient.csrf',
+                mock.MagicMock(return_value='a_csrf_token'))
+    @mock.patch('bodhi.client.bindings.BodhiClient.send_request',
+                return_value=client_test_data.EXAMPLE_RELEASE_MUNCH, autospec=True)
+    def test_edit_mail_template(self, send_request):
+        """
+        Assert correct behavior while editing 'mail_template' name.
+        """
+        runner = testing.CliRunner()
+
+        result = runner.invoke(
+            client.edit_release,
+            ['--name', 'F27', '--mail-template', 'edited_fedora_errata_template'])
+
+        self.assertEqual(result.exit_code, 0)
+        self.assertEqual(result.output, client_test_data.EXPECTED_RELEASE_OUTPUT)
+        bindings_client = send_request.mock_calls[0][1][0]
+        self.assertEqual(send_request.call_count, 2)
+        self.assertEqual(send_request.mock_calls[0],
+                         mock.call(bindings_client, 'releases/F27', verb='GET', auth=True))
+        self.assertEqual(
+            send_request.mock_calls[1],
+            mock.call(
+                bindings_client, 'releases/', verb='POST', auth=True,
+                data={'dist_tag': 'f27', 'csrf_token': 'a_csrf_token', 'staging': False,
+                      'name': 'F27', 'testing_tag': 'f27-updates-testing', 'edited': 'F27',
+                      'pending_stable_tag': 'f27-updates-pending',
+                      'pending_signing_tag': 'f27-signing-pending',
+                      'long_name': 'Fedora 27', 'state': 'pending',
+                      'version': '27', 'override_tag': 'f27-override', 'branch': 'f27',
+                      'id_prefix': 'FEDORA', 'pending_testing_tag': 'f27-updates-testing-pending',
+                      'stable_tag': 'f27-updates', 'candidate_tag': 'f27-updates-candidate',
+                      'mail_template': 'edited_fedora_errata_template'}))
+
 
 class TestInfo(unittest.TestCase):
     """
diff --git a/bodhi/tests/server/services/test_releases.py b/bodhi/tests/server/services/test_releases.py
--- a/bodhi/tests/server/services/test_releases.py
+++ b/bodhi/tests/server/services/test_releases.py
@@ -17,9 +17,12 @@
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 import mock
+import os
 
 from bodhi import server
+from bodhi.server.config import config
 from bodhi.server.models import Release, ReleaseState, Update
+from bodhi.server.util import get_absolute_path
 from bodhi.tests.server import base, create_update
 
 
@@ -279,6 +282,59 @@ def test_edit_release(self):
         r = self.db.query(Release).filter(Release.name == name).one()
         self.assertEqual(r.state, ReleaseState.current)
 
+    def test_edit_mail_template(self):
+        """Test `mail_template` is saved correctly in db after release edit."""
+        name = u"F22"
+
+        res = self.app.get('/releases/%s' % name, status=200)
+        r = res.json_body
+
+        r["edited"] = name
+        r["mail_template"] = "fedora_modular_errata_template"
+        r["csrf_token"] = self.get_csrf_token()
+
+        res = self.app.post("/releases/", r, status=200)
+
+        r = self.db.query(Release).filter(Release.name == name).one()
+        self.assertEqual(r.mail_template, "fedora_modular_errata_template")
+
+    def test_edit_mail_template_with_null_value(self):
+        """Test that null value for `mail_template` gets replaced by default value."""
+        name = u"F22"
+
+        res = self.app.get('/releases/%s' % name, status=200)
+        r = res.json_body
+
+        r["edited"] = name
+        r["mail_template"] = ""
+        r["csrf_token"] = self.get_csrf_token()
+
+        res = self.app.post("/releases/", r, status=200)
+
+        r = self.db.query(Release).filter(Release.name == name).one()
+        self.assertEqual(r.mail_template, "fedora_errata_template")
+
+    def test_edit_mail_template_with_invalid_value(self):
+        """Test appropriate error is returned when provided `mail_template` doesn't exist."""
+        name = u"F22"
+        location = config.get('mail.templates_basepath')
+        directory = get_absolute_path(location)
+        template_list = [os.path.splitext(file)[0] for file in os.listdir(directory)]
+        template_vals = ", ".join(template_list)
+
+        res = self.app.get('/releases/%s' % name, status=200)
+        r = res.json_body
+
+        r["edited"] = name
+        r["mail_template"] = "invalid_template_name"
+        r["csrf_token"] = self.get_csrf_token()
+
+        res = self.app.post("/releases/", r, status=400)
+
+        self.assertEquals(res.json_body['errors'][0]['name'], 'mail_template')
+        self.assertEquals(res.json_body['errors'][0]['description'],
+                          u'"invalid_template_name" is not one of {}'.format(template_vals))
+
     def test_get_single_release_html(self):
         res = self.app.get('/releases/f17', headers={'Accept': 'text/html'})
         self.assertEqual(res.content_type, 'text/html')