Repository: fedora-infra/bodhi

Issue:
Encoding error in bodhi.client.bindings.BodhiClient.update_str()
Bastien Nocera [reported](https://bugzilla.redhat.com/show_bug.cgi?id=1585660) a traceback when creating an update that used the [‘](https://www.fileformat.info/info/unicode/char/2018/index.htm) character in the update text:

```
Traceback (most recent call last):
  File "/usr/lib/python2.7/site-packages/bodhi/client/__init__.py", line 273, in new
    print_resp(resp, client)
  File "/usr/lib/python2.7/site-packages/bodhi/client/__init__.py", line 736, in print_resp
    click.echo(client.update_str(resp))
  File "/usr/lib/python2.7/site-packages/bodhi/client/bindings.py", line 712, in update_str
    buf = six.moves.cStringIO(update['notes'])
UnicodeEncodeError: 'ascii' codec can't encode character u'\u2018' in position 98: ordinal not in range(128)
```


Discussion:

Resolving patch:
diff --git a/bodhi/client/bindings.py b/bodhi/client/bindings.py
--- a/bodhi/client/bindings.py
+++ b/bodhi/client/bindings.py
@@ -738,7 +738,7 @@ def update_str(self, update, minimal=False):
         #  |-->          80 chars in total         <--|
         wrap_width = 66
         wrap_line = functools.partial(textwrap.wrap, width=wrap_width)
-        line_formatter = '{0:>12}: {1}\n'
+        line_formatter = u'{0:>12}: {1}\n'
 
         update_lines = ['{:=^80}\n'.format('=')]
         update_lines += [
@@ -797,16 +797,12 @@ def update_str(self, update, minimal=False):
             ]
 
         if update['notes']:
-            buf = six.moves.cStringIO(update['notes'])
             notes_lines = list(itertools.chain(
-                *[wrap_line(line) for line in buf]
+                *[wrap_line(update['notes'])]
             ))
-            buf.close()
             indent_lines = ['Notes'] + [' '] * (len(notes_lines) - 1)
-            update_lines += [
-                line_formatter.format(indent, line)
-                for indent, line in six.moves.zip(indent_lines, notes_lines)
-            ]
+            for indent, line in six.moves.zip(indent_lines, notes_lines):
+                update_lines.append(line_formatter.format(indent, line))
 
         update_lines += [
             line_formatter.format('Submitter', update['user']['name']),


Test patch:
diff --git a/bodhi/tests/client/test_bindings.py b/bodhi/tests/client/test_bindings.py
--- a/bodhi/tests/client/test_bindings.py
+++ b/bodhi/tests/client/test_bindings.py
@@ -1182,6 +1182,34 @@ def test_update_with_anon_comment(self):
                 "Comments: bodhi ",
                 "Comments: bodhi (unauthenticated) ")))
 
+    @mock.patch.dict(
+        client_test_data.EXAMPLE_UPDATE_MUNCH.comments[0],
+        {u'text': u'This comment contains a unicode char ☺. '})
+    def test_update_with_unicode_comment(self):
+        """Ensure unicode content in update comments is correctly handled"""
+        client = bindings.BodhiClient()
+        client.base_url = 'http://example.com/tests/'
+
+        text = client.update_str(client_test_data.EXAMPLE_UPDATE_MUNCH)
+
+        self.assertTrue(compare_output(
+            text,
+            client_test_data.EXPECTED_UPDATE_OUTPUT.replace(
+                u'Comments: This update has been submitted for testing by bowlofeggs. ',
+                u'Comments: This comment contains a unicode char ☺. ')))
+
+    @mock.patch.dict(
+        client_test_data.EXAMPLE_UPDATE_MUNCH,
+        {u'notes': u'This note contains a unicode char ☺'})
+    def test_update_with_unicode_note(self):
+        """Ensure unicode content in update notes is correctly handled"""
+        client = bindings.BodhiClient()
+        client.base_url = 'http://example.com/tests/'
+
+        text = client.update_str(client_test_data.EXAMPLE_UPDATE_MUNCH)
+
+        self.assertIn(u'Notes: This note contains a unicode char ☺', text)
+
     @mock.patch.dict(
         client_test_data.EXAMPLE_UPDATE_MUNCH, {u'alias': None})
     def test_update_with_no_alias(self):