Repository: fedora-infra/bodhi

Issue:
RSS feed gives no information about packages build
Since commit 60dc56c that RSS feed doesn't give information about the packages affected in a build.
Example from [Fedora30 Stable - RSS](https://bodhi.fedoraproject.org/rss/updates/?releases=F30&status=stable) :
```xml
<item>
  <title>FEDORA-2019-59d394e0fd</title>
  <link>https://bodhi.fedoraproject.org/updates/createrepo_c-0.14.1-1.fc30</link>
  <description>- Update to 0.14.1
- Add --pkgorigins mode for Koji
- Correct pkg count in headers if there were invalid pkgs (RhBug:1596211)
- Prevent exiting with 0 if errors occur while finalizing repodata.
  </description>
  <pubDate>Fri, 24 May 2019 12:20:49 +0000</pubDate>
</item>
```

Also the link is invalid as in #3248

Looking to the [Fedora30 Stable - Web UI](https://bodhi.fedoraproject.org/updates/?releases=F30&status=stable) it seems that it should be:
```
  <title>createrepo_c-0.14.1-1.fc30</title>
  <link>https://bodhi.fedoraproject.org/updates/FEDORA-2019-59d394e0fd</link>
```


Discussion:

Resolving patch:
diff --git a/bodhi/server/renderers.py b/bodhi/server/renderers.py
--- a/bodhi/server/renderers.py
+++ b/bodhi/server/renderers.py
@@ -23,6 +23,8 @@
 from feedgen.feed import FeedGenerator
 from pyramid.exceptions import HTTPBadRequest
 
+from bodhi.server.util import markup
+
 
 log = logging.getLogger(__name__)
 
@@ -98,11 +100,44 @@ def link_dict(obj):
                 return dict(href=request.route_url(route, **{param: obj[key]}))
             return link_dict
 
+        def describe_update(alias, notes, builds):
+            """
+            Wrap calls to operator.itemgetter to retrieve notes and builds list.
+
+            Methods are used to fill feed entry values, so we must use a wrapper
+            to get an HTML formatted description from the `notes` and the `builds`
+            properties of the update.
+
+            For example:
+            getter = describe_update(operator.itemgetter('notes'),operator.itemgetter('builds'))
+            description_value = getter(update_data)
+
+            Args:
+                alias (operator.itemgetter): A callable object which returns update alias
+                    as string.
+                notes (operator.itemgetter): A callable object which returns update notes
+                    as string.
+                builds (operator.itemgetter): A callable object which returns a list of builds
+                    associated to the update.
+            Returns:
+                function: A function which accepts a dict representing an update as parameter.
+            """
+            def describe(*args, **kwargs):
+                text = f'# {alias(*args, **kwargs)}\n'
+                text += f'## Packages in this update:\n'
+                for p in builds(*args, **kwargs):
+                    text += f'* {p.nvr}\n'
+                text += f'## Update description:\n{notes(*args, **kwargs)}'
+                return markup(None, text)
+            return describe
+
         getters = {
             'updates': {
-                'title': operator.itemgetter('alias'),
+                'title': operator.itemgetter('title'),
                 'link': linker('update', 'id', 'alias'),
-                'description': operator.itemgetter('notes'),
+                'description': describe_update(operator.itemgetter('alias'),
+                                               operator.itemgetter('notes'),
+                                               operator.itemgetter('builds')),
                 'pubDate': lambda obj: utc.localize(obj['date_submitted']),
             },
             'users': {


Test patch:
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
@@ -17,6 +17,7 @@
 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 """This module contains tests for bodhi.server.services.updates."""
 from datetime import datetime, timedelta
+from html import escape
 from unittest import mock
 from urllib import parse as urlparse
 import copy
@@ -1851,6 +1852,21 @@ def test_list_updates_rss(self):
         self.assertIn('Released updates', res)
         self.assertIn('All updates', res)
 
+    def test_list_updates_rss_formatting(self):
+        """Test the formatting of update description in rss feed."""
+        update = Build.query.filter_by(nvr='bodhi-2.0-1.fc17').one().update
+        update_details = f'<h1>FEDORA-{datetime.utcnow().year}-a3bbe1a8f2</h1>\n' \
+                         f'<h2>Packages in this update:</h2>\n' \
+                         f'<ul>\n<li>bodhi-2.0-1.fc17</li>\n</ul>\n' \
+                         f'<h2>Update description:</h2>\n' \
+                         f'<p>Useful details!</p>'
+        res = self.app.get('/rss/updates/',
+                           headers={'Accept': 'application/atom+xml'})
+        self.assertIn('application/rss+xml', res.headers['Content-Type'])
+        self.assertIn(f'<title>{update.title}</title>', res)
+        self.assertIn(f'<link>http://localhost/{update.get_url()}</link>', res)
+        self.assertIn(escape(update_details), res)
+
     def test_list_updates_rss_with_single_filter(self):
         res = self.app.get('/rss/updates/', {'severity': 'low'},
                            headers={'Accept': 'application/atom+xml'})