Mercurial > pybugzilla
changeset 9:295c54288dd1
Added Bug and Attachment classes.
author | Atul Varma <avarma@mozilla.com> |
---|---|
date | Tue, 13 Apr 2010 15:52:28 -0700 |
parents | 993fc3b27263 |
children | b85bd0fc50e4 |
files | bugzilla.py test_bugzilla.py |
diffstat | 2 files changed, 191 insertions(+), 38 deletions(-) [+] |
line wrap: on
line diff
--- a/bugzilla.py Tue Apr 13 11:36:56 2010 -0700 +++ b/bugzilla.py Tue Apr 13 15:52:28 2010 -0700 @@ -6,6 +6,7 @@ import httplib import urllib import mimetypes +import datetime from urlparse import urlparse from getpass import getpass @@ -100,7 +101,7 @@ return password -def load_config(filename=None, getpass=getpass_or_die): +def load_config(filename=None, getpass=None): config = {} config.update(DEFAULT_CONFIG) @@ -118,7 +119,8 @@ return config class BugzillaApi(object): - def __init__(self, config=None, jsonreq=None): + def __init__(self, config=None, jsonreq=None, + getpass=getpass_or_die): if config is None: config = load_config() @@ -136,6 +138,33 @@ content_type=None, is_patch=False, is_private=False, is_obsolete=False, guess_mime_type=mimetypes.guess_type): + """ + >>> jsonreq = Mock('jsonreq') + >>> jsonreq.mock_returns = { + ... "status": 201, + ... "body": {"ref": "http://foo/latest/attachment/1"}, + ... "reason": "Created", + ... "content_type": "application/json" + ... } + >>> bzapi = BugzillaApi(config=TEST_CFG_WITH_LOGIN, + ... jsonreq=jsonreq) + >>> bzapi.post_attachment(bug_id=536619, + ... contents="testing!", + ... filename="contents.txt", + ... description="test upload") + Called jsonreq( + body={'is_obsolete': False, 'flags': [], + 'description': 'test upload', + 'content_type': 'text/plain', 'encoding': 'base64', + 'file_name': 'contents.txt', 'is_patch': False, + 'data': 'dGVzdGluZyE=', 'is_private': False, + 'size': 8}, + method='POST', + query_args={'username': 'bar', 'password': 'baz'}, + url='http://foo/latest/bug/536619/attachment') + {'ref': 'http://foo/latest/attachment/1'} + """ + if content_type is None: content_type = guess_mime_type(filename)[0] if not content_type: @@ -183,6 +212,103 @@ class BugzillaApiError(Exception): pass +def iso8601_to_datetime(timestamp): + """ + >>> iso8601_to_datetime('2010-04-11T19:16:59Z') + datetime.datetime(2010, 4, 11, 19, 16, 59) + """ + + return datetime.datetime.strptime(timestamp, + "%Y-%m-%dT%H:%M:%SZ") + +class Attachment(object): + """ + >>> bzapi = Mock('bzapi') + >>> bzapi.request.mock_returns = TEST_ATTACHMENT_WITH_DATA + >>> a = Attachment(TEST_ATTACHMENT_WITHOUT_DATA, bzapi) + >>> a.data + Called bzapi.request( + 'GET', + '/attachment/438797', + query_args={'attachmentdata': '1'}) + 'testing!' + """ + + def __init__(self, jsonobj, bzapi=None): + self.bzapi = bzapi + self.id = int(jsonobj['id']) + self.description = jsonobj['description'] + self.content_type = jsonobj['content_type'] + for timeprop in ['last_change_time']: + setattr(self, timeprop, jsonobj[timeprop]) + if 'data' in jsonobj: + self.__data = self.__decode_data(jsonobj) + else: + self.__data = None + + @property + def data(self): + if self.__data is None and self.bzapi: + jsonobj = self.__get_full_attachment(self.bzapi, self.id) + self.__data = self.__decode_data(jsonobj) + return self.__data + + def __decode_data(self, jsonobj): + if jsonobj['encoding'] != 'base64': + raise NotImplementedError("unrecognized encoding: %s" % + jsonobj['encoding']) + return base64.b64decode(jsonobj['data']) + + def __repr__(self): + return '<Attachment %d - %s>' % (self.id, repr(self.description)) + + @staticmethod + def __get_full_attachment(bzapi, attach_id): + return bzapi.request('GET', '/attachment/%d' % attach_id, + query_args={'attachmentdata': '1'}) + + @classmethod + def fetch(klass, bzapi, attach_id): + """ + >>> bzapi = Mock('bzapi') + >>> bzapi.request.mock_returns = TEST_ATTACHMENT_WITH_DATA + >>> Attachment.fetch(bzapi, 438797) + Called bzapi.request( + 'GET', + '/attachment/438797', + query_args={'attachmentdata': '1'}) + <Attachment 438797 - u'test upload'> + """ + + return klass(klass.__get_full_attachment(bzapi, attach_id)) + +class Bug(object): + """ + >>> Bug(TEST_BUG) + <Bug 558680 - u'Here is a summary'> + """ + + def __init__(self, jsonobj): + self.id = int(jsonobj['id']) + self.summary = jsonobj['summary'] + self.attachments = [Attachment(attach) + for attach in jsonobj['attachments']] + + def __repr__(self): + return '<Bug %d - %s>' % (self.id, repr(self.summary)) + + @classmethod + def fetch(klass, bzapi, bug_id): + """ + >>> bzapi = Mock('bzapi') + >>> bzapi.request.mock_returns = TEST_BUG + >>> Bug.fetch(bzapi, 558680) + Called bzapi.request('GET', '/bug/558680') + <Bug 558680 - u'Here is a summary'> + """ + + return klass(bzapi.request('GET', '/bug/%d' % bug_id)) + if __name__ == '__main__': bzapi = BugzillaApi() print bzapi.request('GET', '/attachment/436897',
--- a/test_bugzilla.py Tue Apr 13 11:36:56 2010 -0700 +++ b/test_bugzilla.py Tue Apr 13 15:52:28 2010 -0700 @@ -1,44 +1,65 @@ +import os import doctest import unittest from minimock import Mock import bugzilla -CFG_WITH_LOGIN = {'api_server': 'http://foo/latest', - 'username': 'bar', - 'password': 'baz'} +TEST_CFG_WITH_LOGIN = {'api_server': 'http://foo/latest', + 'username': 'bar', + 'password': 'baz'} + +TEST_ATTACHMENT_WITHOUT_DATA = { + u'is_obsolete': u'0', + u'description': u'test upload', u'encoding': u'base64', + u'file_name': u'contents.txt', u'is_patch': u'0', + u'creation_time': u'2010-04-13T18:02:00Z', + u'bug_id': u'536619', + u'last_change_time': u'2010-04-13T18:02:00Z', + u'content_type': u'text/plain', + u'attacher': {u'name': u'avarma'}, u'is_url': u'0', + u'id': u'438797', u'is_private': u'0', + u'size': u'8' +} + +TEST_ATTACHMENT_WITH_DATA = { + u'is_obsolete': u'0', + u'description': u'test upload', u'encoding': u'base64', + u'file_name': u'contents.txt', u'is_patch': u'0', + u'creation_time': u'2010-04-13T18:02:00Z', + u'data': u'dGVzdGluZyE=', u'bug_id': u'536619', + u'last_change_time': u'2010-04-13T18:02:00Z', + u'content_type': u'text/plain', + u'attacher': {u'name': u'avarma'}, u'is_url': u'0', + u'id': u'438797', u'is_private': u'0', + u'size': u'8' +} + +TEST_BUG = { + u'attachments': + [{u'is_obsolete': u'0', + u'description': u'here is a description', + u'file_name': u'traceback-on-package-exception', + u'is_patch': u'1', u'creation_time': u'2010-04-11T19:16:00Z', + u'last_change_time': u'2010-04-11T19:16:59Z', + u'bug_id': u'558680', u'content_type': u'text/plain', + u'attacher': {u'name': u'asqueella'}, + u'id': u'438381', u'size': u'1320'}], + u'summary': u'Here is a summary', + u'id': u'558680' + } + +DOCTEST_EXTRA_GLOBS = { + 'Mock': Mock + } + +for globname in globals().keys(): + if globname.startswith('TEST_'): + DOCTEST_EXTRA_GLOBS[globname] = globals()[globname] +del globname class Tests(unittest.TestCase): - pass - -def test_upload(self): - """ - >>> jsonreq = Mock('jsonreq') - >>> jsonreq.mock_returns = { - ... "status": 201, - ... "body": {"ref": "http://foo/latest/attachment/1"}, - ... "reason": "Created", - ... "content_type": "application/json" - ... } - >>> bzapi = bugzilla.BugzillaApi(config=CFG_WITH_LOGIN, - ... jsonreq=jsonreq) - >>> bzapi.post_attachment(bug_id=536619, - ... contents="testing!", - ... filename="contents.txt", - ... description="test upload") - Called jsonreq( - body={'is_obsolete': False, 'flags': [], - 'description': 'test upload', - 'content_type': 'text/plain', 'encoding': 'base64', - 'file_name': 'contents.txt', 'is_patch': False, - 'data': 'dGVzdGluZyE=', 'is_private': False, - 'size': 8}, - method='POST', - query_args={'username': 'bar', 'password': 'baz'}, - url='http://foo/latest/bug/536619/attachment') - {'ref': 'http://foo/latest/attachment/1'} - """ - + # TODO: Add unit tests here. pass def get_tests_in_module(module): @@ -52,7 +73,7 @@ optionflags = (doctest.NORMALIZE_WHITESPACE | doctest.REPORT_UDIFF) finder = doctest.DocTestFinder() - doctests = finder.find(module) + doctests = finder.find(module, extraglobs=DOCTEST_EXTRA_GLOBS) for test in doctests: if len(test.examples) > 0: tests.append(doctest.DocTestCase(test, @@ -61,8 +82,14 @@ return tests def run_tests(verbosity=2): - module = __import__(__name__) - tests = get_tests_in_module(module) + modules = [filename[:-3] for filename in os.listdir('.') + if filename.endswith('.py') + and filename not in ['minimock.py']] + tests = [] + for modulename in modules: + module = __import__(modulename) + tests.extend(get_tests_in_module(module)) + suite = unittest.TestSuite(tests) runner = unittest.TextTestRunner(verbosity=verbosity) runner.run(suite)