From 09685d6714764e10fff1ed4808ce9b68ab119462 Mon Sep 17 00:00:00 2001 From: Adam Coddington Date: Sat, 1 Mar 2014 09:54:54 -0800 Subject: [PATCH] Adding tests. --- bugwarrior/README.rst | 105 +++++++++++++-------------- bugwarrior/db.py | 43 +++++++---- bugwarrior/services/__init__.py | 27 +++++-- bugwarrior/services/activecollab2.py | 40 ++++------ bugwarrior/services/activecollab3.py | 23 +++--- bugwarrior/services/bitbucket.py | 15 ++-- bugwarrior/services/bz.py | 38 +++++----- bugwarrior/services/github.py | 44 +++++------ bugwarrior/services/jira.py | 17 +++-- bugwarrior/services/mplan.py | 25 ++++--- bugwarrior/services/redmine.py | 19 ++--- bugwarrior/services/teamlab.py | 31 ++++---- bugwarrior/services/trac.py | 13 ++-- setup.py | 7 ++ tests/__init__.py | 0 tests/base.py | 45 ++++++++++++ tests/test_activecollab2.py | 58 +++++++++++++++ tests/test_activecollab3.py | 71 ++++++++++++++++++ tests/test_bitbucket.py | 44 +++++++++++ tests/test_bugzilla.py | 47 ++++++++++++ tests/test_github.py | 45 ++++++++++++ tests/test_jira.py | 58 +++++++++++++++ tests/test_megaplan.py | 52 +++++++++++++ tests/test_redmine.py | 45 ++++++++++++ tests/test_teamlab.py | 49 +++++++++++++ tests/test_trac.py | 46 ++++++++++++ 26 files changed, 792 insertions(+), 215 deletions(-) create mode 100644 tests/__init__.py create mode 100644 tests/base.py create mode 100644 tests/test_activecollab2.py create mode 100644 tests/test_activecollab3.py create mode 100644 tests/test_bitbucket.py create mode 100644 tests/test_bugzilla.py create mode 100644 tests/test_github.py create mode 100644 tests/test_jira.py create mode 100644 tests/test_megaplan.py create mode 100644 tests/test_redmine.py create mode 100644 tests/test_teamlab.py create mode 100644 tests/test_trac.py diff --git a/bugwarrior/README.rst b/bugwarrior/README.rst index 2d9455616..7f38737de 100644 --- a/bugwarrior/README.rst +++ b/bugwarrior/README.rst @@ -101,12 +101,11 @@ Create a ``~/.bugwarriorrc`` file with the following contents. # a one-line Jinja template like the below; in addition to the default # taskwarrior issue properties (project, priority, due, etc), the # following properties are available for Github issues: - # - github_title: The title of the issue in Github - # - github_url: This issue or pull request's URL. - # - github_pr: The pull request # of the pull request in Github. - # - github_issue: The issue # of this issue in Github. - # - github_type: The type of github entry this is ('pull_request' or 'issue') - #description_template = {% if type == 'pull_request' %}PR #{{ github_pr }}{% else %}Issue #{{ github_issue }}{% endif %}: {{ github_title }} + # - githubtitle: The title of the issue in Github + # - githuburl: This issue or pull request's URL. + # - githubnumber: The pull request # or issue # in Github. + # - githubtype: The type of github entry this is ('pullrequest' or 'issue') + #description_template = {% if type == 'pull_request' %}PR #{{ githubpr }}{% else %}Issue #{{ githubissue }}{% endif %}: {{ githubtitle }} # I want taskwarrior to include issues from all my repos, except these # two because they're spammy or something. @@ -134,10 +133,10 @@ Create a ``~/.bugwarriorrc`` file with the following contents. # a one-line Jinja template like the below; in addition to the default # taskwarrior issue properties (project, priority, due, etc), the # following properties are available for Bitbucket issues: - # - bitbucket_title - # - bitbucket_url - # - bitbucket_id - #description_template = #{{ bitbucket_id }}: {{ bitbucket_title }} + # - bitbuckettitle + # - bitbucketurl + # - bitbucketid + #description_template = #{{ bitbucketid }}: {{ bitbuckettitle }} # Here's another bitbucket one. Here we want to scrape the issues from repos of # another user, but only include them in the taskwarrior db if they're assigned @@ -167,10 +166,10 @@ Create a ``~/.bugwarriorrc`` file with the following contents. # a one-line Jinja template like the below; in addition to the default # taskwarrior issue properties (project, priority, due, etc), the # following properties are available for Trac issues: - # - trac_summary - # - trac_url - # - trac_number - #description_template = #{{ trac_number }}: {{ trac_summary }} + # - tracsummary + # - tracurl + # - tracnumber + #description_template = #{{ tracnumber }}: {{ tracsummary }} # Here's an example of a bugzilla target. This will scrape every ticket # 1) that is not closed and 2) that rbean@redhat.com is either the @@ -188,9 +187,9 @@ Create a ``~/.bugwarriorrc`` file with the following contents. # a one-line Jinja template like the below; in addition to the default # taskwarrior issue properties (project, priority, due, etc), the # following properties are available for Bugzilla issues: - # - bugzilla_url - # - bugzilla_summary - #description_template = {{ bugzilla_summary }} + # - bugzillaurl + # - bugzillasummary + #description_template = {{ bugzillasummary }} # Here's an example of a megaplan target. [my_megaplan] @@ -207,10 +206,10 @@ Create a ``~/.bugwarriorrc`` file with the following contents. # a one-line Jinja template like the below; in addition to the default # taskwarrior issue properties (project, priority, due, etc), the # following properties are available for Megaplan issues: - # - megaplan_url - # - megaplan_id - # - megaplan_title - #description_template = #{{ megaplan_id }}: {{ megaplan_title }} + # - megaplanurl + # - megaplanid + # - megaplantitle + #description_template = #{{ megaplanid }}: {{ megaplantitle }} # Here's an example of a jira project. The ``jira-python`` module is # a bit particular, and jira deployments, like Bugzilla, tend to be @@ -233,10 +232,10 @@ Create a ``~/.bugwarriorrc`` file with the following contents. # a one-line Jinja template like the below; in addition to the default # taskwarrior issue properties (project, priority, due, etc), the # following properties are available for JIRA issues: - # - jira_summary - # - jira_url - # - jira_id - #description_template = {{ jira_id }}: {{ jira_summary }} + # - jirasummary + # - jiraurl + # - jiraid + #description_template = {{ jiraid }}: {{ jirasummary }} # Here's an example of a teamlab target. [my_teamlab] @@ -251,11 +250,11 @@ Create a ``~/.bugwarriorrc`` file with the following contents. # a one-line Jinja template like the below; in addition to the default # taskwarrior issue properties (project, priority, due, etc), the # following properties are available for Teamlab issues: - # - teamlab_url - # - teamlab_id - # - teamlab_title - # - teamlab_projectowner_id - #description_template = #{{ teamlab_id }}: {{ teamlab_title }} + # - teamlaburl + # - teamlabid + # - teamlabtitle + # - teamlabprojectowner_id + #description_template = #{{ teamlabid }}: {{ teamlabtitle }} # Here's an example of a redmine target. [my_redmine] @@ -269,10 +268,10 @@ Create a ``~/.bugwarriorrc`` file with the following contents. # a one-line Jinja template like the below; in addition to the default # taskwarrior issue properties (project, priority, due, etc), the # following properties are available for Redmine issues: - # - redmine_url - # - redmine_subject - # - redmine_id - #description_template = #{{ redmine_id }}: {{ redmine_subject }} + # - redmineurl + # - redminesubject + # - redmineid + #description_template = #{{ redmineid }}: {{ redminesubject }} # Here's an example of an activecollab3 target. This is only valid for # activeCollab 3.x, see below for activeCollab 2.x. @@ -299,16 +298,16 @@ Create a ``~/.bugwarriorrc`` file with the following contents. # a one-line Jinja template like the below; in addition to the default # taskwarrior issue properties (project, priority, due, etc), the # following properties are available for ActiveCollab3 issues: - # - ac3_body - # - ac3_name - # - ac3_permalink - # - ac3_task_id - # - ac3_id - # - ac3_project_id - # - ac3_type - # - ac3_created_on - # - ac3_created_by_id - #description_template = #{{ac3_id}} - {% if ac3_name %}{{ ac3_name }}{% else %}{{ ac3_body }}{% endif %} + # - ac3body + # - ac3name + # - ac3permalink + # - ac3taskid + # - ac3id + # - ac3projectid + # - ac3type + # - ac3createdon + # - ac3createdbyid + #description_template = #{{ac3id}} - {% if ac3name %}{{ ac3name }}{% else %}{{ ac3body }}{% endif %} # Here's an example of an activecollab2 target. Note that this will only work # with ActiveCollab 2.x - see above for 3.x. @@ -340,15 +339,15 @@ Create a ``~/.bugwarriorrc`` file with the following contents. # a one-line Jinja template like the below; in addition to the default # taskwarrior issue properties (project, priority, due, etc), the # following properties are available for ActiveCollab2 issues: - # - ac2_body - # - ac2_name - # - ac2_permalink - # - ac2_ticket_id - # - ac2_project_id - # - ac2_type - # - ac2_created_on - # - ac2_created_by_id - #description_template = #{{ac2_ticket_id}} - {% if ac2_name %}{{ ac2_name }}{% else %}{{ ac2_body }}{% endif %} + # - ac2body + # - ac2name + # - ac2permalink + # - ac2ticketid + # - ac2projectid + # - ac2type + # - ac2createdon + # - ac2createdbyid + #description_template = #{{ac2ticketid}} - {% if ac2name %}{{ ac2name }}{% else %}{{ ac2body }}{% endif %} .. example diff --git a/bugwarrior/db.py b/bugwarrior/db.py index f4106234e..af7d02dfb 100644 --- a/bugwarrior/db.py +++ b/bugwarrior/db.py @@ -1,9 +1,11 @@ import copy import re +import warnings import six from twiggy import log from taskw import TaskWarriorShellout +from taskw.utils import get_annotation_value from bugwarrior.config import asbool, NoOptionError from bugwarrior.notifications import send_notification @@ -36,16 +38,15 @@ def tasks_differ(left, right): if set(left) - set(right): return True for k in left: - if k == 'annotations': - left[k] = [v['description'] for v in left[k]] - right[k] = [v['description'] for v in right[k]] + if k in ('annotations', 'urgency', ): + continue if ( isinstance(left[k], (list, tuple)) and isinstance(right[k], (list, tuple)) ): left[k] = set(left[k]) right[k] = set(right[k]) - if left[k] != right[k]: + if str(left[k]) != str(right[k]): return True return False @@ -80,14 +81,15 @@ def find_local_uuid(tw, keys, issue, legacy_matching=True): possibilities = possibilities | set([ task['uuid'] for task in results ]) - for key in keys: - if key in issue: - results = tw.filter_tasks({ - key: issue[key] - }) - possibilities = possibilities | set([ - task['uuid'] for task in results - ]) + for service, key_list in six.iteritems(keys): + for key in key_list: + if key in issue: + results = tw.filter_tasks({ + key: issue[key] + }) + possibilities = possibilities | set([ + task['uuid'] for task in results + ]) if len(possibilities) == 1: return possibilities.pop() if len(possibilities) > 1: @@ -164,6 +166,7 @@ def _bool_option(section, option, default): task_copy = copy.deepcopy(task) # Handle merging annotations + annotations_changed = False for annotation in [ a['description'] for a in task.get('annotations', []) ]: @@ -176,6 +179,7 @@ def _bool_option(section, option, default): ) == 0: found = True if not found: + annotations_changed = True issue_dict['annotations'].append(annotation) # Merging tags, too @@ -186,7 +190,7 @@ def _bool_option(section, option, default): issue_dict['tags'].append(tag) task.update(issue_dict) - if tasks_differ(task_copy, task): + if tasks_differ(task_copy, task) or annotations_changed: issue_updates['changed'].append(task) else: issue_updates['existing'].append(task) @@ -216,7 +220,7 @@ def _bool_option(section, option, default): tw.task_update(issue) for issue in issue_updates['closed']: - task_info = tw.get(uuid=issue) + task_info = tw.get_task(uuid=issue) log.name('db').info( "Completing task {0}", task_info @@ -258,6 +262,17 @@ def build_uda_list(targets): targets_udas.update(SERVICES[target].ISSUE_CLASS.UDAS) for name, uda_attributes in six.iteritems(targets_udas): for attrib, value in six.iteritems(uda_attributes): + if '_' in name: + warnings.warn( + "Service '%s' has defined a potentially invalid UDA " + "named '%s'. As of the time of this writing, UDAs " + "must be alphanumeric, and may not contain " + "underscores." % ( + target, + name, + ), + RuntimeWarning + ) uda_output.append( ( 'uda.%s.%s' % (name, attrib, ), diff --git a/bugwarrior/services/__init__.py b/bugwarrior/services/__init__.py index 46b8b6b21..cb5f01bae 100644 --- a/bugwarrior/services/__init__.py +++ b/bugwarrior/services/__init__.py @@ -9,7 +9,7 @@ from bugwarrior.db import MARKUP -# Semaphores for process completion status +# Sentinels for process completion status SERVICE_FINISHED_OK = object() SERVICE_FINISHED_ERROR = object() @@ -17,6 +17,7 @@ class IssueService(object): """ Abstract base class for each service """ ISSUE_CLASS = None + CONFIG_PREFIX = '' def __init__(self, config, target): self.config = config @@ -35,8 +36,27 @@ def __init__(self, config, target): ) else: self.description_template = None + if config.has_option(self.target, 'default_priority'): + self.default_priority = config.get(self.target, 'default_priority') + else: + self.default_priority = 'M' log.name(target).info("Working on [{0}]", self.target) + def config_get_default(self, key, default=None): + try: + return self.config_get(key) + except: + return default + + def config_get(self, key=None): + return self.config.get(self.target, self._get_key(key)) + + def _get_key(self, key): + return '%s.%s' % ( + self.CONFIG_PREFIX, + key + ) + def get_service_metadata(self): return {} @@ -69,10 +89,7 @@ def build_annotations(self, annotations): @classmethod def validate_config(cls, config, target): """ Validate generic options for a particular target """ - - cls.default_priority = 'M' - if config.has_option(target, 'default_priority'): - cls.default_priority = config.get(target, 'default_priority') + pass def include(self, issue): """ Return true if the issue in question should be included """ diff --git a/bugwarrior/services/activecollab2.py b/bugwarrior/services/activecollab2.py index 8a3c0f7be..0f1ba010d 100644 --- a/bugwarrior/services/activecollab2.py +++ b/bugwarrior/services/activecollab2.py @@ -74,14 +74,14 @@ def call_api(self, uri, get=None): class ActiveCollab2Issue(Issue): - BODY = 'ac2_body' - NAME = 'ac2_name' - PERMALINK = 'ac2_permalink' - TICKET_ID = 'ac2_ticket_id' - PROJECT_ID = 'ac2_project_id' - TYPE = 'ac2_type' - CREATED_ON = 'ac2_created_on' - CREATED_BY_ID = 'ac2_created_by_id' + BODY = 'ac2body' + NAME = 'ac2name' + PERMALINK = 'ac2permalink' + TICKET_ID = 'ac2ticketid' + PROJECT_ID = 'ac2projectid' + TYPE = 'ac2type' + CREATED_ON = 'ac2createdon' + CREATED_BY_ID = 'ac2createdbyid' UDAS = { BODY: { @@ -159,28 +159,20 @@ def get_default_description(self): class ActiveCollab2Service(IssueService): ISSUE_CLASS = ActiveCollab2Issue + CONFIG_PREFIX = 'activecollab2' def __init__(self, *args, **kw): super(ActiveCollab2Service, self).__init__(*args, **kw) - self.url = self.config.get( - self.target, 'activecollab2.url' - ).rstrip("/") - self.key = self.config.get( - self.target, 'activecollab2.key' - ) - self.user_id = self.config.get( - self.target, 'activecollab2.user_id' - ) + self.url = self.config_get('url').rstrip('/') + self.key = self.config_get('key') + self.user_id = self.config_get('user_id') + projects_raw = self.config_get('projects') - # Make a list of projects from the config - projects_raw = str( - self.config.get(self.target, 'activecollab2.projects') - ) - projects_list = projects_raw.split(', ') + projects_list = projects_raw.split(',') projects = [] for k, v in enumerate(projects_list): - project_data = v.split(":") + project_data = v.strip().split(":") project = dict([(project_data[0], project_data[1])]) projects.append(project) @@ -201,7 +193,7 @@ def validate_config(cls, config, target): if not config.has_option(target, k): die("[%s] has no '%s'" % (target, k)) - IssueService.validate_config(config, target) + super(ActiveCollab2Service, cls).validate_config(config, target) def issues(self): # Loop through each project diff --git a/bugwarrior/services/activecollab3.py b/bugwarrior/services/activecollab3.py index 2583652a2..3a3b4f874 100644 --- a/bugwarrior/services/activecollab3.py +++ b/bugwarrior/services/activecollab3.py @@ -58,15 +58,15 @@ def get_issue_generator(self, user_id, project_id, project_name): class ActiveCollab3Issue(ActiveCollab2Issue): - BODY = 'ac3_body' - NAME = 'ac3_name' - PERMALINK = 'ac3_permalink' - TASK_ID = 'ac3_task_id' - FOREIGN_ID = 'ac3_id' - PROJECT_ID = 'ac3_project_id' - TYPE = 'ac3_type' - CREATED_ON = 'ac3_created_on' - CREATED_BY_ID = 'ac3_created_by_id' + BODY = 'ac3body' + NAME = 'ac3name' + PERMALINK = 'ac3permalink' + TASK_ID = 'ac3taskid' + FOREIGN_ID = 'ac3id' + PROJECT_ID = 'ac3projectid' + TYPE = 'ac3type' + CREATED_ON = 'ac3createdon' + CREATED_BY_ID = 'ac3createdbyid' UDAS = { BODY: { @@ -123,12 +123,12 @@ def to_taskwarrior(self): self.TYPE: self.record['type'], self.CREATED_BY_ID: self.record['created_by_id'], } - if isinstance(self.record.get(self.CREATED_ON), basestring): + if isinstance(self.record.get('created_on'), basestring): record[self.CREATED_ON] = self.parse_date( self.record['created_on'] ) elif isinstance( - self.record.get(self.CREATED_ON, {}).get('mysql'), basestring + self.record.get('created_on', {}).get('mysql'), basestring ): record[self.CREATED_ON] = self.parse_date( self.record['created_on']['mysql'] @@ -157,6 +157,7 @@ def get_default_description(self): class ActiveCollab3Service(ActiveCollab2Service): ISSUE_CLASS = ActiveCollab3Issue + CONFIG_PREFIX = 'activecollab3' def __init__(self, *args, **kw): super(ActiveCollab3Service, self).__init__(*args, **kw) diff --git a/bugwarrior/services/bitbucket.py b/bugwarrior/services/bitbucket.py index e371858d7..d20d40e69 100644 --- a/bugwarrior/services/bitbucket.py +++ b/bugwarrior/services/bitbucket.py @@ -6,9 +6,9 @@ class BitbucketIssue(Issue): - TITLE = 'bitbucket_title' - URL = 'bitbucket_url' - FOREIGN_ID = 'bitbucket_id' + TITLE = 'bitbuckettitle' + URL = 'bitbucketurl' + FOREIGN_ID = 'bitbucketid' UDAS = { TITLE: { @@ -56,6 +56,7 @@ def get_default_description(self): class BitbucketService(IssueService): ISSUE_CLASS = BitbucketIssue + CONFIG_PREFIX = 'bitbucket' BASE_API = 'https://api.bitbucket.org/1.0' BASE_URL = 'http://bitbucket.org/' @@ -64,11 +65,11 @@ def __init__(self, *args, **kw): super(BitbucketService, self).__init__(*args, **kw) self.auth = None - if self.config.has_option(self.target, 'bitbucket.login'): - login = self.config.get(self.target, 'bitbucket.login') - password = self.config.get(self.target, 'bitbucket.password') + if self.config_get_default('login'): + login = self.config_get('login') + password = self.config_get_default('password') if not password or password.startswith('@oracle:'): - username = self.config.get(self.target, 'bitbucket.username') + username = self.config_get('username') service = "bitbucket://%s@bitbucket.org/%s" % (login, username) password = get_service_password( service, login, oracle=password, diff --git a/bugwarrior/services/bz.py b/bugwarrior/services/bz.py index a00809aa0..134ce5ff3 100644 --- a/bugwarrior/services/bz.py +++ b/bugwarrior/services/bz.py @@ -6,8 +6,8 @@ class BugzillaIssue(Issue): - URL = 'bugzilla_url' - SUMMARY = 'bugzilla_summary' + URL = 'bugzillaurl' + SUMMARY = 'bugzillasummary' UDAS = { URL: { @@ -50,6 +50,7 @@ def get_default_description(self): class BugzillaService(IssueService): ISSUE_CLASS = BugzillaIssue + CONFIG_PREFIX = 'bugzilla' OPEN_STATUSES = [ 'NEW', @@ -73,9 +74,9 @@ class BugzillaService(IssueService): def __init__(self, *args, **kw): super(BugzillaService, self).__init__(*args, **kw) - base_uri = self.config.get(self.target, 'bugzilla.base_uri') - username = self.config.get(self.target, 'bugzilla.username') - password = self.config.get(self.target, 'bugzilla.password') + self.base_uri = self.config_get('base_uri') + self.username = self.config_get('username') + self.password = self.config_get('password') # So more modern bugzilla's require that we specify # query_format=advanced along with the xmlrpc request. @@ -83,21 +84,18 @@ def __init__(self, *args, **kw): # ...but older bugzilla's don't know anything about that argument. # Here we make it possible for the user to specify whether they want # to pass that argument or not. - self.advanced = True # Default to True. - if self.config.has_option(self.target, 'bugzilla.advanced'): - self.advanced = asbool(self.config.get( - self.target, 'bugzilla.advanced')) - - if not password or password.startswith("@oracle:"): - service = "bugzilla://%s@%s" % (username, base_uri) - password = get_service_password( - service, username, oracle=password, + self.advanced = asbool(self.config_get_default('advanced', 'no')) + + if not self.password or self.password.startswith("@oracle:"): + service = "bugzilla://%s@%s" % (self.username, self.base_uri) + self.password = get_service_password( + service, self.username, oracle=self.password, interactive=self.config.interactive ) - url = 'https://%s/xmlrpc.cgi' % base_uri + url = 'https://%s/xmlrpc.cgi' % self.base_uri self.bz = bugzilla.Bugzilla(url=url) - self.bz.login(username, password) + self.bz.login(self.username, self.password) @classmethod def validate_config(cls, config, target): @@ -106,7 +104,7 @@ def validate_config(cls, config, target): if not config.has_option(target, option): die("[%s] has no '%s'" % (target, option)) - IssueService.validate_config(config, target) + super(BugzillaService, cls).validate_config(config, target) def get_owner(self, issue): # NotImplemented, but we should never get called since .include() isn't @@ -146,7 +144,7 @@ def _parse_body(obj): ) def issues(self): - email = self.config.get(self.target, 'bugzilla.username') + email = self.username # TODO -- doing something with blockedby would be nice. query = dict( @@ -178,9 +176,7 @@ def issues(self): log.name(self.target).debug(" Found {0} total.", len(issues)) # Build a url for each issue - base_url = "https://%s/show_bug.cgi?id=" % ( - self.config.get(self.target, 'bugzilla.base_uri') - ) + base_url = "https://%s/show_bug.cgi?id=" % (self.base_uri) for issue in issues: extra = { 'url': base_url + str(issue['id']) diff --git a/bugwarrior/services/github.py b/bugwarrior/services/github.py index abcdf8250..b043f1dd1 100644 --- a/bugwarrior/services/github.py +++ b/bugwarrior/services/github.py @@ -7,11 +7,10 @@ class GithubIssue(Issue): - TITLE = 'github_title' - URL = 'github_url' - PR = 'github_pr' - ISSUE = 'github_issue' - TYPE = 'github_type' + TITLE = 'githubtitle' + URL = 'githuburl' + TYPE = 'githubtype' + NUMBER = 'githubnumber' UDAS = { TITLE: { @@ -26,19 +25,15 @@ class GithubIssue(Issue): 'type': 'string', 'label': 'Github Type', }, - PR: { + NUMBER: { 'type': 'numeric', - 'label': 'Github Pull Request #', - }, - ISSUE: { - 'type': 'numeric', - 'label': 'Github Issue #', + 'label': 'Github Issue/PR #', }, } UNIQUE_KEY = (URL, ) def to_taskwarrior(self): - record = { + return { 'project': self.extra['project'], 'priority': self.origin['default_priority'], 'annotations': self.extra.get('annotations', []), @@ -46,12 +41,8 @@ def to_taskwarrior(self): self.URL: self.record['html_url'], self.TYPE: self.extra['type'], self.TITLE: self.record['title'], + self.NUMBER: self.record['number'], } - if self.extra['type'] == 'issue': - record[self.ISSUE] = self.record['number'] - elif self.extra['type'] == 'pull_request': - record[self.PR] = self.record['number'] - return record def get_default_description(self): return self.build_default_description( @@ -64,14 +55,15 @@ def get_default_description(self): class GithubService(IssueService): ISSUE_CLASS = GithubIssue + CONFIG_PREFIX = 'github' def __init__(self, *args, **kw): super(GithubService, self).__init__(*args, **kw) - login = self.config.get(self.target, 'github.login') - password = self.config.get(self.target, 'github.password') + login = self.config_get('login') + password = self.config_get_default('password') if not password or password.startswith('@oracle:'): - username = self.config.get(self.target, 'github.username') + username = self.config_get('username') service = "github://%s@github.com/%s" % (login, username) password = get_service_password( service, login, oracle=password, @@ -82,18 +74,16 @@ def __init__(self, *args, **kw): self.exclude_repos = [] self.include_repos = [] - if self.config.has_option(self.target, 'github.exclude_repos'): + if self.config_get_default('exclude_repos', None): self.exclude_repos = [ item.strip() for item in - self.config.get(self.target, 'github.exclude_repos') - .strip().split(',') + self.config_get('exclude_repos').strip().split(',') ] - if self.config.has_option(self.target, 'github.include_repos'): + if self.config_get_default('include_repos', None): self.include_repos = [ item.strip() for item in - self.config.get(self.target, 'github.include_repos') - .strip().split(',') + self.config_get('include_repos').strip().split(',') ] def _issues(self, tag): @@ -194,4 +184,4 @@ def validate_config(cls, config, target): if not config.has_option(target, 'github.username'): die("[%s] has no 'github.username'" % target) - IssueService.validate_config(config, target) + super(GithubService, cls).validate_config(config, target) diff --git a/bugwarrior/services/jira.py b/bugwarrior/services/jira.py index 1cc6bbd50..77ffb1212 100644 --- a/bugwarrior/services/jira.py +++ b/bugwarrior/services/jira.py @@ -7,9 +7,9 @@ class JiraIssue(Issue): - SUMMARY = 'jira_summary' - URL = 'jira_url' - FOREIGN_ID = 'jira_id' + SUMMARY = 'jirasummary' + URL = 'jiraurl' + FOREIGN_ID = 'jiraid' UDAS = { SUMMARY: { @@ -82,12 +82,13 @@ def get_default_description(self): class JiraService(IssueService): ISSUE_CLASS = JiraIssue + CONFIG_PREFIX = 'jira' def __init__(self, *args, **kw): super(JiraService, self).__init__(*args, **kw) - self.username = self.config.get(self.target, 'jira.username') - self.url = self.config.get(self.target, 'jira.base_uri') - password = self.config.get(self.target, 'jira.password') + self.username = self.config_get('username') + self.url = self.config_get('base_uri') + password = self.config_get('password') if not password or password.startswith("@oracle:"): service = "jira://%s@%s" % (self.username, self.url) password = get_service_password( @@ -98,10 +99,10 @@ def __init__(self, *args, **kw): default_query = 'assignee=' + self.username + \ ' AND status != closed and status != resolved' - self.query = self.config.get(self.target, 'jira.query', default_query) + self.query = self.config_get_default('query', default_query) self.jira = JIRA( options={ - 'server': self.config.get(self.target, 'jira.base_uri'), + 'server': self.config_get('base_uri'), 'rest_api_version': 'latest', }, basic_auth=(self.username, password) diff --git a/bugwarrior/services/mplan.py b/bugwarrior/services/mplan.py index 074460040..464021bae 100644 --- a/bugwarrior/services/mplan.py +++ b/bugwarrior/services/mplan.py @@ -1,3 +1,5 @@ +from __future__ import absolute_import + import megaplan from twiggy import log @@ -6,9 +8,9 @@ class MegaplanIssue(Issue): - URL = 'megaplan_url' - FOREIGN_ID = 'megaplan_id' - TITLE = 'megaplan_title' + URL = 'megaplanurl' + FOREIGN_ID = 'megaplanid' + TITLE = 'megaplantitle' UDAS = { TITLE: { @@ -43,7 +45,7 @@ def get_default_description(self): return self.build_default_description( title=self.get_issue_title(), url=self.get_issue_url(), - number=self.get_number(), + number=self.record['Id'], cls='issue', ) @@ -64,13 +66,14 @@ def get_issue_id(self): class MegaplanService(IssueService): ISSUE_CLASS = MegaplanIssue + CONFIG_PREFIX = 'megaplan' def __init__(self, *args, **kw): super(MegaplanService, self).__init__(*args, **kw) - self.hostname = self.config.get(self.target, 'megaplan.hostname') - _login = self.config.get(self.target, 'megaplan.login') - _password = self.config.get(self.target, 'megaplan.password') + self.hostname = self.config_get('hostname') + _login = self.config_get('login') + _password = self.config_get('password') if not _password or _password.startswith("@oracle:"): service = "megaplan://%s@%s" % (_login, self.hostname) _password = get_service_password( @@ -81,11 +84,9 @@ def __init__(self, *args, **kw): self.client = megaplan.Client(self.hostname) self.client.authenticate(_login, _password) - self.project_name = self.hostname - if self.config.has_option(self.target, "megaplan.project_name"): - self.project_name = self.config.get( - self.target, "megaplan.project_name" - ) + self.project_name = self.config_get_default( + 'project_name', self.hostname + ) def get_service_metadata(self): return { diff --git a/bugwarrior/services/redmine.py b/bugwarrior/services/redmine.py index 9ec27b786..a1c28d8d5 100644 --- a/bugwarrior/services/redmine.py +++ b/bugwarrior/services/redmine.py @@ -34,9 +34,9 @@ def call_api(self, uri, get=None): class RedMineIssue(Issue): - URL = 'redmine_url' - SUBJECT = 'redmine_subject' - ID = 'redmine_id' + URL = 'redmineurl' + SUBJECT = 'redminesubject' + ID = 'redmineid' UDAS = { URL: { @@ -83,21 +83,18 @@ def get_default_description(self): class RedMineService(IssueService): ISSUE_CLASS = RedMineIssue + CONFIG_PREFIX = 'redmine' def __init__(self, *args, **kw): super(RedMineService, self).__init__(*args, **kw) - self.url = self.config.get(self.target, 'redmine.url').rstrip("/") - self.key = self.config.get(self.target, 'redmine.key') - self.user_id = self.config.get(self.target, 'redmine.user_id') + self.url = self.config_get('url').rstrip("/") + self.key = self.config_get('key') + self.user_id = self.config_get('user_id') self.client = RedMineClient(self.url, self.key) - self.project_name = None - if self.config.has_option(self.target, "redmine.project_name"): - self.project_name = self.config.get( - self.target, "redmine.project_name" - ) + self.project_name = self.config_get_default('project_name') def get_service_metadata(self): return { diff --git a/bugwarrior/services/teamlab.py b/bugwarrior/services/teamlab.py index 847925739..4c1d36d44 100644 --- a/bugwarrior/services/teamlab.py +++ b/bugwarrior/services/teamlab.py @@ -52,17 +52,17 @@ def call_api(self, uri, post=None, get=None): class TeamLabIssue(Issue): - URL = 'teamlab_url' - ID = 'teamlab_id' - TITLE = 'teamlab_title' - PROJECTOWNER_ID = 'teamlab_projectowner_id' + URL = 'teamlaburl' + FOREIGN_ID = 'teamlabid' + TITLE = 'teamlabtitle' + PROJECTOWNER_ID = 'teamlabprojectownerid' UDAS = { URL: { 'type': 'string', 'label': 'Teamlab URL', }, - ID: { + FOREIGN_ID: { 'type': 'string', 'label': 'Teamlab ID', }, @@ -83,7 +83,7 @@ def to_taskwarrior(self): 'priority': self.get_priority(), self.TITLE: self.record['title'], - self.ID: self.record['id'], + self.FOREIGN_ID: self.record['id'], self.URL: self.get_issue_url(), self.PROJECTOWNER_ID: self.record['projectOwner']['id'], } @@ -107,20 +107,21 @@ def get_issue_url(self): ) def get_priority(self): - if self.record["priority"] == 1: + if self.record.get("priority") == 1: return "H" - return "M" + return self.origin['default_priority'] class TeamLabService(IssueService): ISSUE_CLASS = TeamLabIssue + CONFIG_PREFIX = 'teamlab' def __init__(self, *args, **kw): super(TeamLabService, self).__init__(*args, **kw) - self.hostname = self.config.get(self.target, 'teamlab.hostname') - _login = self.config.get(self.target, 'teamlab.login') - _password = self.config.get(self.target, 'teamlab.password') + self.hostname = self.config_get('hostname') + _login = self.config_get('login') + _password = self.config_get('password') if not _password or _password.startswith("@oracle:"): service = "teamlab://%s@%s" % (_login, self.hostname) _password = get_service_password( @@ -131,11 +132,9 @@ def __init__(self, *args, **kw): self.client = TeamLabClient(self.hostname) self.client.authenticate(_login, _password) - self.project_name = self.hostname - if self.config.has_option(self.target, "teamlab.project_name"): - self.project_name = self.config.get( - self.target, "teamlab.project_name" - ) + self.project_name = self.config_get_default( + 'project_name', self.hostname + ) def get_service_metadata(self): return { diff --git a/bugwarrior/services/trac.py b/bugwarrior/services/trac.py index 864cf832c..6647f74e8 100644 --- a/bugwarrior/services/trac.py +++ b/bugwarrior/services/trac.py @@ -6,9 +6,9 @@ class TracIssue(Issue): - SUMMARY = 'trac_summary' - URL = 'trac_url' - NUMBER = 'trac_number' + SUMMARY = 'tracsummary' + URL = 'tracurl' + NUMBER = 'tracnumber' UDAS = { SUMMARY: { @@ -62,12 +62,13 @@ def get_priority(self): class TracService(IssueService): ISSUE_CLASS = TracIssue + CONFIG_PREFIX = 'trac' def __init__(self, *args, **kw): super(TracService, self).__init__(*args, **kw) - base_uri = self.config.get(self.target, 'trac.base_uri') - username = self.config.get(self.target, 'trac.username') - password = self.config.get(self.target, 'trac.password') + base_uri = self.config_get('base_uri') + username = self.config_get('username') + password = self.config_get('password') if not password or password.startswith('@oracle:'): service = "https://%s@%s/" % (username, base_uri) password = get_service_password( diff --git a/setup.py b/setup.py index ee48111d8..63ac097f6 100644 --- a/setup.py +++ b/setup.py @@ -40,6 +40,13 @@ "jinja2>=2.7.2", "pycurl", ], + tests_require=[ + "Mock", + "unittest2", + "nose", + "jira>=0.22", + "megaplan>=1.4", + ], entry_points=""" [console_scripts] bugwarrior-pull = bugwarrior:pull diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/base.py b/tests/base.py new file mode 100644 index 000000000..0497965d6 --- /dev/null +++ b/tests/base.py @@ -0,0 +1,45 @@ +import mock +import unittest2 + + +class ServiceTest(unittest2.TestCase): + GENERAL_CONFIG = { + 'annotation_length': 100, + 'description_length': 100, + } + SERVICE_CONFIG = { + } + + def get_mock_service( + self, service, section='unspecified', + config_overrides=None, general_overrides=None + ): + options = { + 'general': self.GENERAL_CONFIG.copy(), + section: self.SERVICE_CONFIG.copy(), + } + if config_overrides: + options[section].update(config_overrides) + if general_overrides: + options['general'].update(general_overrides) + + def has_option(section, name): + try: + return options[section][name] + except KeyError: + return False + + def get_option(section, name): + return options[section][name] + + def get_int(section, name): + return int(get_option(section, name)) + + config = mock.Mock() + config.has_option = mock.Mock(side_effect=has_option) + config.get = mock.Mock(side_effect=get_option) + config.getint = mock.Mock(side_effect=get_int) + + service = service(config, section) + + return service diff --git a/tests/test_activecollab2.py b/tests/test_activecollab2.py new file mode 100644 index 000000000..dd39b6a90 --- /dev/null +++ b/tests/test_activecollab2.py @@ -0,0 +1,58 @@ +import datetime + +from bugwarrior.services.activecollab2 import ActiveCollab2Service + +from .base import ServiceTest + + +class TestActiveCollab2Issue(ServiceTest): + SERVICE_CONFIG = { + 'activecollab2.url': 'hello', + 'activecollab2.key': 'howdy', + 'activecollab2.user_id': 'hola', + 'activecollab2.projects': '1:one, 2:two' + } + + def setUp(self): + self.service = self.get_mock_service(ActiveCollab2Service) + + def test_to_taskwarrior(self): + arbitrary_due_on = ( + datetime.datetime.now() - datetime.timedelta(hours=1) + ) + arbitrary_created_on = ( + datetime.datetime.now() - datetime.timedelta(hours=2) + ) + arbitrary_issue = { + 'project': 'something', + 'priority': 2, + 'due_on': arbitrary_due_on.isoformat(), + 'permalink': 'http://wherever/', + 'ticket_id': 10, + 'project_id': 20, + 'type': 'issue', + 'created_on': arbitrary_created_on.isoformat(), + 'created_by_id': '10', + 'body': 'Ticket Body', + 'name': 'Anonymous', + } + + issue = self.service.get_issue_for_record(arbitrary_issue) + + expected_output = { + 'project': arbitrary_issue['project'], + 'priority': issue.PRIORITY_MAP[arbitrary_issue['priority']], + 'due': arbitrary_due_on, + + issue.PERMALINK: arbitrary_issue['permalink'], + issue.TICKET_ID: arbitrary_issue['ticket_id'], + issue.PROJECT_ID: arbitrary_issue['project_id'], + issue.TYPE: arbitrary_issue['type'], + issue.CREATED_ON: arbitrary_created_on, + issue.CREATED_BY_ID: arbitrary_issue['created_by_id'], + issue.BODY: arbitrary_issue['body'], + issue.NAME: arbitrary_issue['name'], + } + actual_output = issue.to_taskwarrior() + + self.assertEqual(actual_output, expected_output) diff --git a/tests/test_activecollab3.py b/tests/test_activecollab3.py new file mode 100644 index 000000000..69f94c75c --- /dev/null +++ b/tests/test_activecollab3.py @@ -0,0 +1,71 @@ +import datetime + +import mock + +from bugwarrior.services.activecollab3 import ( + ActiveCollab3Client, + ActiveCollab3Service +) + +from .base import ServiceTest + + +class TestActiveCollab3Issue(ServiceTest): + SERVICE_CONFIG = { + 'activecollab3.url': 'hello', + 'activecollab3.key': 'howdy', + 'activecollab3.user_id': 'hola', + 'activecollab3.projects': '1:one, 2:two' + } + + def setUp(self): + with mock.patch( + 'bugwarrior.services.activecollab3.ActiveCollab3Client.call_api' + ): + self.service = self.get_mock_service(ActiveCollab3Service) + + def test_to_taskwarrior(self): + arbitrary_due_on = ( + datetime.datetime.now() - datetime.timedelta(hours=1) + ) + arbitrary_created_on = ( + datetime.datetime.now() - datetime.timedelta(hours=2) + ) + arbitrary_issue = { + 'project': 'something', + 'priority': 2, + 'due_on': arbitrary_due_on.isoformat(), + + 'permalink': 'http://wherever/', + 'task_id': 10, + 'project_id': 20, + 'id': '30', + 'type': 'issue', + 'created_on': { + 'mysql': arbitrary_created_on.isoformat() + }, + 'created_by_id': '10', + 'body': 'Ticket Body', + 'name': 'Anonymous', + } + + issue = self.service.get_issue_for_record(arbitrary_issue) + + expected_output = { + 'project': arbitrary_issue['project'], + 'priority': issue.PRIORITY_MAP[arbitrary_issue['priority']], + 'due': arbitrary_due_on, + + issue.PERMALINK: arbitrary_issue['permalink'], + issue.PROJECT_ID: arbitrary_issue['project_id'], + issue.TYPE: arbitrary_issue['type'], + issue.CREATED_ON: arbitrary_created_on, + issue.CREATED_BY_ID: arbitrary_issue['created_by_id'], + issue.BODY: arbitrary_issue['body'], + issue.NAME: arbitrary_issue['name'], + issue.FOREIGN_ID: arbitrary_issue['id'], + issue.TASK_ID: arbitrary_issue['task_id'], + } + actual_output = issue.to_taskwarrior() + + self.assertEqual(actual_output, expected_output) diff --git a/tests/test_bitbucket.py b/tests/test_bitbucket.py new file mode 100644 index 000000000..fb3cb256d --- /dev/null +++ b/tests/test_bitbucket.py @@ -0,0 +1,44 @@ +from bugwarrior.services.bitbucket import BitbucketService + +from .base import ServiceTest + + +class TestBitbucketIssue(ServiceTest): + SERVICE_CONFIG = { + 'bitbucket.login': 'something', + 'bitbucket.password': 'something else', + } + + def setUp(self): + self.service = self.get_mock_service(BitbucketService) + + def test_to_taskwarrior(self): + arbitrary_issue = { + 'priority': 'trivial', + 'local_id': '100', + 'title': 'Some Title', + } + arbitrary_extra = { + 'url': 'http://hello-there.com/', + 'project': 'Something', + 'annotations': [ + 'One', + ] + } + + issue = self.service.get_issue_for_record( + arbitrary_issue, arbitrary_extra + ) + + expected_output = { + 'project': arbitrary_extra['project'], + 'priority': issue.PRIORITY_MAP[arbitrary_issue['priority']], + 'annotations': arbitrary_extra['annotations'], + + issue.URL: arbitrary_extra['url'], + issue.FOREIGN_ID: arbitrary_issue['local_id'], + issue.TITLE: arbitrary_issue['title'], + } + actual_output = issue.to_taskwarrior() + + self.assertEqual(actual_output, expected_output) diff --git a/tests/test_bugzilla.py b/tests/test_bugzilla.py new file mode 100644 index 000000000..f66ae14be --- /dev/null +++ b/tests/test_bugzilla.py @@ -0,0 +1,47 @@ +import mock + +from bugwarrior.services.bz import BugzillaService + +from .base import ServiceTest + + +class TestBugzillaService(ServiceTest): + SERVICE_CONFIG = { + 'bugzilla.base_uri': 'http://one.com/', + 'bugzilla.username': 'hello', + 'bugzilla.password': 'there', + } + + def setUp(self): + with mock.patch('bugzilla.Bugzilla'): + self.service = self.get_mock_service(BugzillaService) + + def test_to_taskwarrior(self): + arbitrary_record = { + 'component': 'Something', + 'priority': 'urgent', + 'annotations': [ + 'Two', + ], + 'summary': 'This is the issue summary' + } + arbitrary_extra = { + 'url': 'http://path/to/issue/', + } + + issue = self.service.get_issue_for_record( + arbitrary_record, + arbitrary_extra, + ) + + expected_output = { + 'project': arbitrary_record['component'], + 'priority': issue.PRIORITY_MAP[arbitrary_record['priority']], + 'annotations': arbitrary_record['annotations'], + + issue.URL: arbitrary_extra['url'], + issue.SUMMARY: arbitrary_record['summary'], + } + actual_output = issue.to_taskwarrior() + + self.assertEqual(actual_output, expected_output) diff --git a/tests/test_github.py b/tests/test_github.py new file mode 100644 index 000000000..4884b5af3 --- /dev/null +++ b/tests/test_github.py @@ -0,0 +1,45 @@ +from bugwarrior.services.github import GithubService + +from .base import ServiceTest + + +class TestGithubIssue(ServiceTest): + SERVICE_CONFIG = { + 'github.login': 'arbitrary_login', + 'github.password': 'arbitrary_password', + 'github.username': 'arbitrary_username', + } + + def setUp(self): + self.service = self.get_mock_service(GithubService) + + def test_to_taskwarrior(self): + arbitrary_issue = { + 'title': 'Hallo', + 'html_url': 'http://whanot.com/', + 'number': 10 + } + arbitrary_extra = { + 'project': 'one', + 'type': 'issue', + 'annotations': [], + } + + issue = self.service.get_issue_for_record( + arbitrary_issue, + arbitrary_extra + ) + + expected_output = { + 'project': arbitrary_extra['project'], + 'priority': self.service.default_priority, + 'annotations': [], + + issue.URL: arbitrary_issue['html_url'], + issue.TYPE: arbitrary_extra['type'], + issue.TITLE: arbitrary_issue['title'], + issue.NUMBER: arbitrary_issue['number'], + } + actual_output = issue.to_taskwarrior() + + self.assertEqual(actual_output, expected_output) diff --git a/tests/test_jira.py b/tests/test_jira.py new file mode 100644 index 000000000..d7c51fe86 --- /dev/null +++ b/tests/test_jira.py @@ -0,0 +1,58 @@ +import mock + +from bugwarrior.services.jira import JiraService + +from .base import ServiceTest + + +class TestJiraIssue(ServiceTest): + SERVICE_CONFIG = { + 'jira.username': 'one', + 'jira.base_uri': 'two', + 'jira.password': 'three', + } + + def setUp(self): + with mock.patch('jira.client.JIRA._get_json'): + self.service = self.get_mock_service(JiraService) + + def test_to_taskwarrior(self): + arbitrary_project = 'DONUT' + arbitrary_id = '10' + arbitrary_url = 'http://one' + arbitrary_summary = 'lkjaldsfjaldf' + arbitrary_record = { + 'fields': { + 'priority': 'Blocker', + 'summary': arbitrary_summary, + }, + 'key': '%s-%s' % (arbitrary_project, arbitrary_id, ), + } + arbitrary_extra = { + 'jira_version': 5, + 'annotations': ['an annotation'], + } + + issue = self.service.get_issue_for_record( + arbitrary_record, arbitrary_extra + ) + + expected_output = { + 'project': arbitrary_project, + 'priority': ( + issue.PRIORITY_MAP[arbitrary_record['fields']['priority']] + ), + 'annotations': arbitrary_extra['annotations'], + + issue.URL: arbitrary_url, + issue.FOREIGN_ID: arbitrary_record['key'], + issue.SUMMARY: arbitrary_summary, + } + + def get_url(*args): + return arbitrary_url + + with mock.patch.object(issue, 'get_url', side_effect=get_url): + actual_output = issue.to_taskwarrior() + + self.assertEqual(actual_output, expected_output) diff --git a/tests/test_megaplan.py b/tests/test_megaplan.py new file mode 100644 index 000000000..1c7c378a3 --- /dev/null +++ b/tests/test_megaplan.py @@ -0,0 +1,52 @@ +import mock + +from bugwarrior.services.mplan import MegaplanService + +from .base import ServiceTest + + +class TestMegaplanIssue(ServiceTest): + SERVICE_CONFIG = { + 'megaplan.hostname': 'something', + 'megaplan.login': 'something_else', + 'megaplan.password': 'aljlkj', + } + + def setUp(self): + with mock.patch('megaplan.Client'): + self.service = self.get_mock_service(MegaplanService) + + def test_to_taskwarrior(self): + arbitrary_project = 'one' + arbitrary_url = 'http://one.com/' + name_parts = ['one', 'two', 'three'] + arbitrary_issue = { + 'Id': 10, + 'Name': '|'.join(name_parts) + } + + issue = self.service.get_issue_for_record(arbitrary_issue) + + expected_output = { + 'project': arbitrary_project, + 'priority': self.service.default_priority, + + issue.FOREIGN_ID: arbitrary_issue['Id'], + issue.URL: arbitrary_url, + issue.TITLE: name_parts[-1] + } + + def get_url(*args): + return arbitrary_url + + def get_project(*args): + return arbitrary_project + + with mock.patch.multiple( + issue, get_project=mock.DEFAULT, get_issue_url=mock.DEFAULT + ) as mocked: + mocked['get_project'].side_effect = get_project + mocked['get_issue_url'].side_effect = get_url + actual_output = issue.to_taskwarrior() + + self.assertEqual(actual_output, expected_output) diff --git a/tests/test_redmine.py b/tests/test_redmine.py new file mode 100644 index 000000000..eb71e6af5 --- /dev/null +++ b/tests/test_redmine.py @@ -0,0 +1,45 @@ +import mock + +from bugwarrior.services.redmine import RedMineService + +from .base import ServiceTest + + +class TestRedmineIssue(ServiceTest): + SERVICE_CONFIG = { + 'redmine.url': 'something', + 'redmine.key': 'something_else', + 'redmine.user_id': '10834u0234', + } + + def setUp(self): + self.service = self.get_mock_service(RedMineService) + + def test_to_taskwarrior(self): + arbitrary_url = 'http://lkjlj.com' + arbitrary_issue = { + 'project': { + 'name': 'Something', + }, + 'subject': 'The Subject', + 'id': 'The ID', + } + + issue = self.service.get_issue_for_record(arbitrary_issue) + + expected_output = { + 'project': arbitrary_issue['project']['name'], + 'priority': self.service.default_priority, + + issue.URL: arbitrary_url, + issue.SUBJECT: arbitrary_issue['subject'], + issue.ID: arbitrary_issue['id'], + } + + def get_url(*args): + return arbitrary_url + + with mock.patch.object(issue, 'get_issue_url', side_effect=get_url): + actual_output = issue.to_taskwarrior() + + self.assertEqual(actual_output, expected_output) diff --git a/tests/test_teamlab.py b/tests/test_teamlab.py new file mode 100644 index 000000000..1049c5d24 --- /dev/null +++ b/tests/test_teamlab.py @@ -0,0 +1,49 @@ +import mock + +from bugwarrior.services.teamlab import TeamLabService + +from .base import ServiceTest + + +class TestTeamlabIssue(ServiceTest): + SERVICE_CONFIG = { + 'teamlab.hostname': 'something', + 'teamlab.login': 'alkjdsf', + 'teamlab.password': 'lkjklj', + 'teamlab.project_name': 'abcdef', + } + + def setUp(self): + with mock.patch( + 'bugwarrior.services.teamlab.TeamLabClient.authenticate' + ): + self.service = self.get_mock_service(TeamLabService) + + def test_to_taskwarrior(self): + arbitrary_url = 'http://galkjsdflkj.com/' + arbitrary_issue = { + 'title': 'Hello', + 'id': 10, + 'projectOwner': { + 'id': 140, + } + } + + issue = self.service.get_issue_for_record(arbitrary_issue) + + expected_output = { + 'project': self.SERVICE_CONFIG['teamlab.project_name'], + 'priority': self.service.default_priority, + issue.TITLE: arbitrary_issue['title'], + issue.FOREIGN_ID: arbitrary_issue['id'], + issue.URL: arbitrary_url, + issue.PROJECTOWNER_ID: arbitrary_issue['projectOwner']['id'] + } + + def get_url(*args): + return arbitrary_url + + with mock.patch.object(issue, 'get_issue_url', side_effect=get_url): + actual_output = issue.to_taskwarrior() + + self.assertEqual(actual_output, expected_output) diff --git a/tests/test_trac.py b/tests/test_trac.py new file mode 100644 index 000000000..54baf376d --- /dev/null +++ b/tests/test_trac.py @@ -0,0 +1,46 @@ +from bugwarrior.services.trac import TracService + +from .base import ServiceTest + + +class TestTracIssue(ServiceTest): + SERVICE_CONFIG = { + 'trac.base_uri': 'http://ljlkajsdfl.com', + 'trac.username': 'something', + 'trac.password': 'somepwd', + } + + def setUp(self): + self.service = self.get_mock_service(TracService) + + def test_to_taskwarrior(self): + arbitrary_issue = { + 'url': 'http://some/url.com/', + 'summary': 'Some Summary', + 'number': 204, + 'priority': 'critical', + } + arbitrary_extra = { + 'annotations': [ + 'alpha', + 'beta', + ], + 'project': 'some project', + } + + issue = self.service.get_issue_for_record( + arbitrary_issue, + arbitrary_extra, + ) + + expected_output = { + 'project': arbitrary_extra['project'], + 'priority': issue.PRIORITY_MAP[arbitrary_issue['priority']], + 'annotations': arbitrary_extra['annotations'], + issue.URL: arbitrary_issue['url'], + issue.SUMMARY: arbitrary_issue['summary'], + issue.NUMBER: arbitrary_issue['number'], + } + actual_output = issue.to_taskwarrior() + + self.assertEquals(actual_output, expected_output)