Blob Blame History Raw
From 23b5b373ec18974bde6ce4dba006e81b8af1169f Mon Sep 17 00:00:00 2001
Message-Id: <23b5b373ec18974bde6ce4dba006e81b8af1169f.1362430417.git.crobinso@redhat.com>
From: Cole Robinson <crobinso@redhat.com>
Date: Tue, 26 Feb 2013 17:47:37 -0500
Subject: [PATCH] Fix uploading attachments as base64, and test it

Use xmlrpclib's base64 support, rather than a hand rolled one.
---
 bugzilla/base.py       | 47 +++++++++++++----------------------------
 tests/rw_functional.py | 57 +++++++++++++++++++++++++-------------------------
 2 files changed, 43 insertions(+), 61 deletions(-)

diff --git a/bugzilla/base.py b/bugzilla/base.py
index 60480dc..faddd0a 100644
--- a/bugzilla/base.py
+++ b/bugzilla/base.py
@@ -9,7 +9,6 @@
 # option) any later version.  See http://www.gnu.org/copyleft/gpl.html for
 # the full text of the license.
 
-import base64
 import cookielib
 import os
 import tempfile
@@ -834,14 +833,22 @@ class BugzillaBase(object):
     # Methods for working with attachments #
     ########################################
 
+    def _attachment_uri(self, attachid):
+        '''Returns the URI for the given attachment ID.'''
+        att_uri = self.url.replace('xmlrpc.cgi', 'attachment.cgi')
+        att_uri = att_uri + '?id=%s' % attachid
+        return att_uri
+
     def attachfile(self, idlist, attachfile, description, **kwargs):
         '''
         Attach a file to the given bug IDs. Returns the ID of the attachment
         or raises xmlrpclib.Fault if something goes wrong.
+
         attachfile may be a filename (which will be opened) or a file-like
         object, which must provide a 'read' method. If it's not one of these,
         this method will raise a TypeError.
         description is the short description of this attachment.
+
         Optional keyword args are as follows:
             file_name:  this will be used as the filename for the attachment.
                        REQUIRED if attachfile is a file-like object with no
@@ -851,9 +858,9 @@ class BugzillaBase(object):
             is_private: Set to True if the attachment should be marked private.
             is_patch:   Set to True if the attachment is a patch.
             content_type: The mime-type of the attached file. Defaults to
-                         application/octet-stream if not set. NOTE that text
-                         files will *not* be viewable in bugzilla unless you
-                         remember to set this to text/plain. So remember that!
+                          application/octet-stream if not set. NOTE that text
+                          files will *not* be viewable in bugzilla unless you
+                          remember to set this to text/plain. So remember that!
 
         Returns the list of attachment ids that were added. If only one
         attachment was added, we return the single int ID for back compat
@@ -876,15 +883,14 @@ class BugzillaBase(object):
             kwargs["file_name"] = kwargs.pop("filename")
 
         kwargs['summary'] = description
+        kwargs['data'] = xmlrpclib.Binary(f.read())
+        kwargs['ids'] = self._listify(idlist)
+
         if 'file_name' not in kwargs:
             kwargs['file_name'] = os.path.basename(f.name)
-
         if 'content_type' not in kwargs:
             kwargs['content_type'] = 'application/octet-stream'
 
-        kwargs['data'] = self._attachment_encode(f)
-        kwargs['ids'] = self._listify(idlist)
-
         ret = self._proxy.Bug.add_attachment(kwargs)
 
         if "attachments" in ret:
@@ -916,31 +922,6 @@ class BugzillaBase(object):
         # Hooray, now we have a file-like object with .read() and .name
         return att
 
-    def _attachment_uri(self, attachid):
-        '''Returns the URI for the given attachment ID.'''
-        att_uri = self.url.replace('xmlrpc.cgi', 'attachment.cgi')
-        att_uri = att_uri + '?id=%s' % attachid
-        return att_uri
-
-    def _attachment_encode(self, fh):
-        '''Return the contents of the file-like object fh in a form
-        appropriate for attaching to a bug in bugzilla. This is the default
-        encoding method, base64.'''
-        # Read data in chunks so we don't end up with two copies of the file
-        # in RAM.
-
-        # base64 encoding wants input in multiples of 3
-        chunksize = 3072
-        data = ''
-        chunk = fh.read(chunksize)
-        while chunk:
-            # we could use chunk.encode('base64') but that throws a newline
-            # at the end of every output chunk, which increases the size of
-            # the output.
-            data = data + base64.b64encode(chunk)
-            chunk = fh.read(chunksize)
-        return data
-
     def updateattachmentflags(self, bugid, attachid, flagname, **kwargs):
         '''
         Updates a flag for the given attachment ID.
diff --git a/tests/rw_functional.py b/tests/rw_functional.py
index 1b8997d..82e444f 100644
--- a/tests/rw_functional.py
+++ b/tests/rw_functional.py
@@ -357,43 +357,16 @@ class RHPartnerTest(BaseTest):
             os.chdir("..")
             os.system("rm -r %s" % tmpdir)
 
-
     def _test8Attachments(self):
         """
         Get and set attachments for a bug
         """
         bz = self.bzclass(url=self.url, cookiefile=cf)
-        getbugid = "663674"
+        getallbugid = "663674"
         setbugid = "461686"
-        attachid = "469147"
         cmd = "bugzilla attach "
         testfile = "../tests/data/bz-attach-get1.txt"
 
-        # Get first attachment
-        out = tests.clicomm(cmd + "--get %s" % attachid, bz).splitlines()
-
-        # Expect format:
-        #   Wrote <filename>
-        fname = out[2].split()[1].strip()
-
-        self.assertEquals(len(out), 3)
-        self.assertEquals(fname, "bugzilla-filename.patch")
-        self.assertEquals(file(fname).read(),
-                          file(testfile).read())
-
-        # Get all attachments
-        getbug = bz.getbug(getbugid)
-        numattach = len(getbug.attachments)
-        out = tests.clicomm(cmd + "--getall %s" % getbugid, bz).splitlines()
-
-        self.assertEquals(len(out), numattach + 2)
-        fnames = [l.split(" ", 1)[1].strip() for l in out[2:]]
-        self.assertEquals(len(fnames), numattach)
-        for f in fnames:
-            if not os.path.exists(f):
-                raise AssertionError("filename '%s' not found" % f)
-            os.unlink(f)
-
         # Add attachment as CLI option
         setbug = bz.getbug(setbugid)
         orignumattach = len(setbug.attachments)
@@ -418,6 +391,7 @@ class RHPartnerTest(BaseTest):
         self.assertEquals(setbug.attachments[-1]["description"], desc2)
         self.assertEquals(setbug.attachments[-1]["id"],
                           int(out2.splitlines()[2].split()[2]))
+        attachid = setbug.attachments[-2]["id"]
 
         # Set attachment flags
         self.assertEquals(setbug.attachments[-1]["flags"], [])
@@ -435,6 +409,33 @@ class RHPartnerTest(BaseTest):
         self.assertEquals(setbug.attachments[-1]["flags"], [])
 
 
+        # Get attachment, verify content
+        out = tests.clicomm(cmd + "--get %s" % attachid, bz).splitlines()
+
+        # Expect format:
+        #   Wrote <filename>
+        fname = out[2].split()[1].strip()
+
+        self.assertEquals(len(out), 3)
+        self.assertEquals(fname, "bz-attach-get1.txt")
+        self.assertEquals(file(fname).read(),
+                          file(testfile).read())
+        os.unlink(fname)
+
+        # Get all attachments
+        getbug = bz.getbug(getallbugid)
+        numattach = len(getbug.attachments)
+        out = tests.clicomm(cmd + "--getall %s" % getallbugid, bz).splitlines()
+
+        self.assertEquals(len(out), numattach + 2)
+        fnames = [l.split(" ", 1)[1].strip() for l in out[2:]]
+        self.assertEquals(len(fnames), numattach)
+        for f in fnames:
+            if not os.path.exists(f):
+                raise AssertionError("filename '%s' not found" % f)
+            os.unlink(f)
+
+
     def test9Whiteboards(self):
         bz = self.bzclass(url=self.url, cookiefile=cf)
         bug_id = "663674"
-- 
1.8.1.4