| |
@@ -1,28 +1,169 @@
|
| |
- from nose.tools import eq_
|
| |
-
|
| |
import mock
|
| |
import unittest
|
| |
+ try:
|
| |
+ # Python 3.3 >
|
| |
+ from unittest.mock import MagicMock # noqa: F401
|
| |
+ except ImportError:
|
| |
+ from mock import MagicMock # noqa: F401
|
| |
+
|
| |
|
| |
import sync2jira.downstream as d
|
| |
+ from sync2jira.intermediary import Issue
|
| |
+
|
| |
+ from nose.tools import eq_
|
| |
import jira.client
|
| |
+ from jira import JIRAError
|
| |
+
|
| |
+ PATH = 'sync2jira.downstream.'
|
| |
|
| |
|
| |
class TestDownstream(unittest.TestCase):
|
| |
- config = {
|
| |
- 'sync2jira': {
|
| |
- 'jira': {
|
| |
- # Nothing, really..
|
| |
+ """
|
| |
+ This class tests the downstream.py file under sync2jira
|
| |
+ """
|
| |
+ def setUp(self):
|
| |
+ """
|
| |
+ Setting up the testing environment
|
| |
+ """
|
| |
+ # Mock Config dict
|
| |
+ self.mock_config = {
|
| |
+ 'sync2jira': {
|
| |
+ 'jira': {
|
| |
+ 'mock_jira_instance': {'mock_jira': 'mock_jira'}
|
| |
+ },
|
| |
+ 'testing': {},
|
| |
+ 'legacy_matching': False
|
| |
},
|
| |
- },
|
| |
- }
|
| |
+ }
|
| |
+
|
| |
+ # Mock sync2jira.intermediary.Issue
|
| |
+ self.mock_issue = MagicMock()
|
| |
+ self.mock_issue.assignee = [{'fullname': 'mock_user'}]
|
| |
+ self.mock_issue.downstream = {
|
| |
+ 'project': 'mock_project',
|
| |
+ 'custom_fields': {'somecustumfield': 'somecustumvalue'},
|
| |
+ 'type': 'Fix',
|
| |
+ 'updates': [
|
| |
+ 'comments',
|
| |
+ {'tags': {'overwrite': False}},
|
| |
+ {'fixVersion': {'overwrite': False}},
|
| |
+ 'assignee', 'description', 'title',
|
| |
+ {'transition': 'CUSTOM TRANSITION'}
|
| |
+ ],
|
| |
+ 'owner': 'mock_owner'
|
| |
+ }
|
| |
+ self.mock_issue.content = 'mock_content'
|
| |
+ self.mock_issue.reporter = {'fullname': 'mock_user'}
|
| |
+ self.mock_issue.url = 'mock_url'
|
| |
+ self.mock_issue.title = 'mock_title'
|
| |
+ self.mock_issue.comments = 'mock_comments'
|
| |
+ self.mock_issue.tags = ['tag1', 'tag2']
|
| |
+ self.mock_issue.fixVersion = ['fixVersion3', 'fixVersion4']
|
| |
+ self.mock_issue.fixVersion = ['fixVersion3', 'fixVersion4']
|
| |
+ self.mock_issue.assignee = [{'fullname': 'mock_assignee'}]
|
| |
+ self.mock_issue.status = 'Open'
|
| |
+ self.mock_issue.id = '1234'
|
| |
+
|
| |
+ # Mock issue updates
|
| |
+ self.mock_updates = [
|
| |
+ 'comments',
|
| |
+ {'tags': {'overwrite': False}},
|
| |
+ {'fixVersion': {'overwrite': False}},
|
| |
+ 'assignee', 'description', 'title',
|
| |
+ {'transition': 'CUSTOM TRANSITION'},
|
| |
+ ]
|
| |
+
|
| |
+ # Mock Jira transition
|
| |
+ self.mock_transition = [{
|
| |
+ 'name': 'custom_closed_status',
|
| |
+ 'id': 1234
|
| |
+ }]
|
| |
+
|
| |
+ # Mock jira.resources.Issue
|
| |
+ self.mock_downstream = MagicMock()
|
| |
+ self.mock_downstream.id = 1234
|
| |
+ self.mock_downstream.fields.labels = ['tag3', 'tag4']
|
| |
+ mock_version1 = MagicMock()
|
| |
+ mock_version1.name = 'fixVersion3'
|
| |
+ mock_version2 = MagicMock()
|
| |
+ mock_version2.name = 'fixVersion4'
|
| |
+ self.mock_downstream.fields.fixVersions = [mock_version1, mock_version2]
|
| |
+ self.mock_downstream.update.return_value = True
|
| |
+ self.mock_downstream.fields.description = "This is an existing description"
|
| |
+
|
| |
+ # Mock datetime.today()
|
| |
+ self.mock_today = MagicMock()
|
| |
+ self.mock_today.strftime.return_value = 'mock_today'
|
| |
+
|
| |
+ @mock.patch('jira.client.JIRA')
|
| |
+ def test_get_jira_client_not_issue(self,
|
| |
+ mock_client):
|
| |
+ """
|
| |
+ This tests '_get_jira_client' function where the passed in
|
| |
+ argument is not an Issue instance
|
| |
+ """
|
| |
+ # Call the function
|
| |
+ with self.assertRaises(Exception):
|
| |
+ d._get_jira_client(
|
| |
+ issue='string',
|
| |
+ config=self.mock_config
|
| |
+ )
|
| |
+
|
| |
+ # Assert everything was called correctly
|
| |
+ mock_client.assert_not_called()
|
| |
+
|
| |
+ @mock.patch('jira.client.JIRA')
|
| |
+ def test_get_jira_client_not_instance(self,
|
| |
+ mock_client):
|
| |
+ """
|
| |
+ This tests '_get_jira_client' function there is no JIRA instance
|
| |
+ """
|
| |
+ # Set up return values
|
| |
+ self.mock_issue.downstream = {}
|
| |
+ self.mock_config['sync2jira']['default_jira_instance'] = {}
|
| |
+
|
| |
+ # Call the function
|
| |
+ with self.assertRaises(Exception):
|
| |
+ d._get_jira_client(
|
| |
+ issue=self.mock_issue,
|
| |
+ config=self.mock_config
|
| |
+ )
|
| |
+
|
| |
+ # Assert everything was called correctly
|
| |
+ mock_client.assert_not_called()
|
| |
+
|
| |
+ @mock.patch('jira.client.JIRA')
|
| |
+ def test_get_jira_client(self,
|
| |
+ mock_client):
|
| |
+ """
|
| |
+ This tests '_get_jira_client' function where everything goes smoothly
|
| |
+ """
|
| |
+ # Set up return values
|
| |
+ mock_issue = MagicMock(spec=Issue)
|
| |
+ mock_issue.downstream = {'jira_instance': 'mock_jira_instance'}
|
| |
+ mock_client.return_value = 'Successful call!'
|
| |
+
|
| |
+ # Call the function
|
| |
+
|
| |
+ response = d._get_jira_client(
|
| |
+ issue=mock_issue,
|
| |
+ config=self.mock_config
|
| |
+ )
|
| |
+
|
| |
+ # Assert everything was called correctly
|
| |
+ mock_client.assert_called_with(mock_jira='mock_jira')
|
| |
+ self.assertEqual('Successful call!', response)
|
| |
|
| |
@mock.patch('jira.client.JIRA')
|
| |
def test_get_existing_legacy(self, client):
|
| |
+ """
|
| |
+ This tests '_get_existing_jira_issue_legacy' function
|
| |
+ """
|
| |
class MockIssue(object):
|
| |
downstream = {'key': 'value'}
|
| |
url = 'wat'
|
| |
issue = MockIssue()
|
| |
- config = self.config.copy()
|
| |
+ config = self.mock_config
|
| |
# Ensure that we get results back from the jira client.
|
| |
target1 = "target1"
|
| |
client.return_value.search_issues = mock.MagicMock(return_value=[target1])
|
| |
@@ -36,19 +177,22 @@
|
| |
|
| |
@mock.patch('jira.client.JIRA')
|
| |
def test_get_existing_newstyle(self, client):
|
| |
- config = self.config.copy()
|
| |
+ config = self.mock_config
|
| |
|
| |
class MockIssue(object):
|
| |
downstream = {'key': 'value'}
|
| |
title = 'A title, a title...'
|
| |
url = 'http://threebean.org'
|
| |
|
| |
- # Ensure that we get results back from the jira client.
|
| |
- target1 = "target1"
|
| |
+
|
| |
issue = MockIssue()
|
| |
- client.return_value.search_issues = mock.MagicMock(return_value=[target1])
|
| |
+ mock_results_of_query = MagicMock()
|
| |
+ mock_results_of_query.fields.summary = 'A title, a title...'
|
| |
+
|
| |
+ client.return_value.search_issues.return_value = [mock_results_of_query]
|
| |
result = d._get_existing_jira_issue(jira.client.JIRA(), issue, config)
|
| |
- eq_(result, target1)
|
| |
+ # Ensure that we get the mock_result_of_query as a result
|
| |
+ self.assertEqual(result, mock_results_of_query)
|
| |
|
| |
client.return_value.search_issues.assert_called_once_with(
|
| |
'issueFunction in linkedIssuesOfRemote("Upstream issue") and '
|
| |
@@ -57,7 +201,7 @@
|
| |
|
| |
@mock.patch('jira.client.JIRA')
|
| |
def test_upgrade_oldstyle_jira_issue(self, client):
|
| |
- config = self.config.copy()
|
| |
+ config = self.mock_config
|
| |
|
| |
class MockIssue(object):
|
| |
downstream = {'key': 'value'}
|
| |
@@ -69,6 +213,7 @@
|
| |
client_obj = mock.MagicMock()
|
| |
client.return_value = client_obj
|
| |
d._upgrade_jira_issue(jira.client.JIRA(), downstream, issue, config)
|
| |
+
|
| |
remote = {
|
| |
'url': 'http://threebean.org',
|
| |
'title': 'Upstream issue',
|
| |
@@ -76,62 +221,785 @@
|
| |
client_obj.add_remote_link.assert_called_once_with(downstream.id, remote)
|
| |
|
| |
@mock.patch('jira.client.JIRA')
|
| |
- def test_create_jira_issue(self, client):
|
| |
- config = self.config.copy()
|
| |
- target1 = mock.Mock()
|
| |
- target1.id = "target1 id"
|
| |
- target1.key = "target key"
|
| |
- target1.fields.description = "description"
|
| |
- client.return_value.create_issue = mock.MagicMock(return_value=target1)
|
| |
+ def test_assign_user(self, mock_client):
|
| |
+ """
|
| |
+ Test 'assign_user' function where remove_all flag is False
|
| |
+ """
|
| |
+ # Set up return values
|
| |
+ mock_user = MagicMock()
|
| |
+ mock_user.displayName = 'mock_assignee'
|
| |
+ mock_user.key = 'mock_user_key'
|
| |
+ mock_client.search_assignable_users_for_issues.return_value = [mock_user]
|
| |
+ mock_client.assign_issue.return_value = True
|
| |
|
| |
- class MockIssue(object):
|
| |
- downstream = {
|
| |
- 'project': 'awesome',
|
| |
- 'component': 'alsoawesome',
|
| |
- }
|
| |
- title = 'A title, a title...'
|
| |
- url = 'http://threebean.org'
|
| |
- comments = [{
|
| |
- 'author': 'Ralph',
|
| |
- 'body': 'Super duper.',
|
| |
- }]
|
| |
+ # Call the assign user function
|
| |
+ d.assign_user(
|
| |
+ issue=self.mock_issue,
|
| |
+ downstream=self.mock_downstream,
|
| |
+ client=mock_client
|
| |
+ )
|
| |
|
| |
- config['sync2jira']['testing'] = False
|
| |
- result = d._create_jira_issue(jira.client.JIRA(), MockIssue(), config)
|
| |
- eq_(result, target1)
|
| |
- client.return_value.create_issue.assert_called_with(
|
| |
- components=[{'name': 'alsoawesome'}],
|
| |
- issuetype={'name': 'Bug'},
|
| |
- project={'key': 'awesome'},
|
| |
- description='http://threebean.org',
|
| |
- summary='A title, a title...',
|
| |
+ # Assert that all calls mocked were called properly
|
| |
+ mock_client.assign_issue.assert_called_with(1234, 'mock_user_key')
|
| |
+ mock_client.search_assignable_users_for_issues.assert_called_with(
|
| |
+ 'mock_assignee',
|
| |
+ project='mock_project'
|
| |
)
|
| |
|
| |
@mock.patch('jira.client.JIRA')
|
| |
- def test_create_jira_issue_with_custom_url(self, client):
|
| |
- config = self.config.copy()
|
| |
- target1 = mock.Mock()
|
| |
- target1.id = "target1 id"
|
| |
- target1.key = "target key"
|
| |
- target1.fields.description = "description"
|
| |
- client.return_value.create_issue = mock.MagicMock(return_value=target1)
|
| |
+ def test_assign_user_with_owner(self, mock_client):
|
| |
+ """
|
| |
+ Test 'assign_user' function where remove_all flag is False
|
| |
+ """
|
| |
+ # Set up return values
|
| |
+ mock_user = MagicMock()
|
| |
+ mock_user.displayName = 'mock_assignee'
|
| |
+ mock_user.key = 'mock_user_key'
|
| |
+ mock_client.search_assignable_users_for_issues.return_value = []
|
| |
+ mock_client.assign_issue.return_value = True
|
| |
|
| |
- class MockIssue(object):
|
| |
- downstream = {
|
| |
- 'project': 'awesome',
|
| |
- 'component': 'alsoawesome',
|
| |
+ # Call the assign user function
|
| |
+ d.assign_user(
|
| |
+ issue=self.mock_issue,
|
| |
+ downstream=self.mock_downstream,
|
| |
+ client=mock_client
|
| |
+ )
|
| |
+
|
| |
+ # Assert that all calls mocked were called properly
|
| |
+ mock_client.assign_issue.assert_called_with(1234, 'mock_owner')
|
| |
+ mock_client.search_assignable_users_for_issues.assert_called_with(
|
| |
+ 'mock_assignee',
|
| |
+ project='mock_project'
|
| |
+ )
|
| |
+
|
| |
+ @mock.patch('jira.client.JIRA')
|
| |
+ def test_assign_user_without_owner(self, mock_client):
|
| |
+ """
|
| |
+ Test 'assign_user' function where remove_all flag is False
|
| |
+ """
|
| |
+ # Set up return values
|
| |
+ mock_user = MagicMock()
|
| |
+ mock_user.displayName = 'mock_assignee'
|
| |
+ mock_user.key = 'mock_user_key'
|
| |
+ mock_client.search_assignable_users_for_issues.return_value = []
|
| |
+ mock_client.assign_issue.return_value = True
|
| |
+ self.mock_issue.downstream.pop('owner')
|
| |
+
|
| |
+ # Call the assign user function
|
| |
+ d.assign_user(
|
| |
+ issue=self.mock_issue,
|
| |
+ downstream=self.mock_downstream,
|
| |
+ client=mock_client
|
| |
+ )
|
| |
+
|
| |
+ # Assert that all calls mocked were called properly
|
| |
+ mock_client.assign_issue.assert_not_called()
|
| |
+ mock_client.search_assignable_users_for_issues.assert_called_with(
|
| |
+ 'mock_assignee',
|
| |
+ project='mock_project'
|
| |
+ )
|
| |
+
|
| |
+ @mock.patch('jira.client.JIRA')
|
| |
+ def test_assign_user_remove_all(self, mock_client):
|
| |
+ """
|
| |
+ Test 'assign_user' function where remove_all flag is True
|
| |
+ """
|
| |
+ # Call the assign user function
|
| |
+ d.assign_user(
|
| |
+ issue=self.mock_issue,
|
| |
+ downstream=self.mock_downstream,
|
| |
+ client=mock_client,
|
| |
+ remove_all=True
|
| |
+ )
|
| |
+
|
| |
+ # Assert that all calls mocked were called properly
|
| |
+ self.mock_downstream.update.assert_called_with(assignee={'name': ''})
|
| |
+ mock_client.assign_issue.assert_not_called()
|
| |
+ mock_client.search_assignable_users_for_issues.assert_not_called()
|
| |
+
|
| |
+ @mock.patch(PATH + '_update_jira_issue')
|
| |
+ @mock.patch(PATH + '_attach_link')
|
| |
+ @mock.patch('jira.client.JIRA')
|
| |
+ def test_create_jira_issue(self,
|
| |
+ mock_client,
|
| |
+ mock_attach_link,
|
| |
+ mock_update_jira_issue):
|
| |
+ """
|
| |
+ Tests '_create_jira_issue' function
|
| |
+ """
|
| |
+ # Set up return values
|
| |
+ mock_client.create_issue.return_value = self.mock_downstream
|
| |
+
|
| |
+ # Call the function
|
| |
+ response = d._create_jira_issue(
|
| |
+ client=mock_client,
|
| |
+ issue=self.mock_issue,
|
| |
+ config=self.mock_config
|
| |
+ )
|
| |
+
|
| |
+ # Assert everything was called correctly
|
| |
+ mock_client.create_issue.assert_called_with(
|
| |
+ issuetype={'name': 'Fix'},
|
| |
+ project={'key': 'mock_project'},
|
| |
+ somecustumfield='somecustumvalue',
|
| |
+ description='[1234] Upstream Reporter: mock_user \n Upstream description: {quote}mock_content{quote}',
|
| |
+ summary='mock_title'
|
| |
+ )
|
| |
+ mock_attach_link.assert_called_with(
|
| |
+ mock_client,
|
| |
+ self.mock_downstream,
|
| |
+ {
|
| |
+ 'url': 'mock_url',
|
| |
+ 'title': 'Upstream issue'
|
| |
}
|
| |
- title = 'A title, a title...'
|
| |
- url = 'http://threebean.org'
|
| |
- comments = []
|
| |
+ )
|
| |
+ mock_update_jira_issue.assert_called_with(
|
| |
+ self.mock_downstream,
|
| |
+ self.mock_issue,
|
| |
+ mock_client
|
| |
+ )
|
| |
+ self.assertEqual(response, self.mock_downstream)
|
| |
|
| |
- config['sync2jira']['testing'] = False
|
| |
- result = d._create_jira_issue(jira.client.JIRA(), MockIssue(), config)
|
| |
- eq_(result, target1)
|
| |
- client.return_value.create_issue.assert_called_with(
|
| |
- components=[{'name': 'alsoawesome'}],
|
| |
- issuetype={'name': 'Bug'},
|
| |
- project={'key': 'awesome'},
|
| |
- description='http://threebean.org',
|
| |
- summary='A title, a title...',
|
| |
+ @mock.patch(PATH + '_get_jira_client')
|
| |
+ @mock.patch(PATH + '_get_existing_jira_issue')
|
| |
+ @mock.patch(PATH + '_update_jira_issue')
|
| |
+ @mock.patch(PATH + '_create_jira_issue')
|
| |
+ @mock.patch('jira.client.JIRA')
|
| |
+ @mock.patch(PATH + '_get_existing_jira_issue_legacy')
|
| |
+ def test_sync_with_jira_matching(self,
|
| |
+ mock_existing_jira_issue_legacy,
|
| |
+ mock_client,
|
| |
+ mock_create_jira_issue,
|
| |
+ mock_update_jira_issue,
|
| |
+ mock_existing_jira_issue,
|
| |
+ mock_get_jira_client):
|
| |
+ """
|
| |
+ Tests 'sync_with_jira' function where we do find a matching issue
|
| |
+ This assumes we're not using the legacy matching anymore
|
| |
+ """
|
| |
+ # Set up return values
|
| |
+ mock_get_jira_client.return_value = mock_client
|
| |
+ mock_existing_jira_issue.return_value = self.mock_downstream
|
| |
+
|
| |
+ # Call the function
|
| |
+ d.sync_with_jira(
|
| |
+ issue=self.mock_issue,
|
| |
+ config=self.mock_config
|
| |
+ )
|
| |
+
|
| |
+ # Assert all calls were made correctly
|
| |
+ mock_get_jira_client.assert_called_with(self.mock_issue, self.mock_config)
|
| |
+ mock_update_jira_issue.assert_called_with(self.mock_downstream, self.mock_issue, mock_client)
|
| |
+ mock_create_jira_issue.assert_not_called()
|
| |
+ mock_existing_jira_issue_legacy.assert_not_called()
|
| |
+
|
| |
+ @mock.patch(PATH + '_get_jira_client')
|
| |
+ @mock.patch(PATH + '_get_existing_jira_issue')
|
| |
+ @mock.patch(PATH + '_update_jira_issue')
|
| |
+ @mock.patch(PATH + '_create_jira_issue')
|
| |
+ @mock.patch('jira.client.JIRA')
|
| |
+ @mock.patch(PATH + '_get_existing_jira_issue_legacy')
|
| |
+ def test_sync_with_jira_no_matching(self,
|
| |
+ mock_existing_jira_issue_legacy,
|
| |
+ mock_client,
|
| |
+ mock_create_jira_issue,
|
| |
+ mock_update_jira_issue,
|
| |
+ mock_existing_jira_issue,
|
| |
+ mock_get_jira_client):
|
| |
+ """
|
| |
+ Tests 'sync_with_jira' function where we do NOT find a matching issue
|
| |
+ This assumes we're not using the legacy matching anymore
|
| |
+ """
|
| |
+ # Set up return values
|
| |
+ mock_get_jira_client.return_value = mock_client
|
| |
+ mock_existing_jira_issue.return_value = None
|
| |
+
|
| |
+ # Call the function
|
| |
+ d.sync_with_jira(
|
| |
+ issue=self.mock_issue,
|
| |
+ config=self.mock_config
|
| |
+ )
|
| |
+
|
| |
+ # Assert all calls were made correctly
|
| |
+ mock_get_jira_client.assert_called_with(self.mock_issue, self.mock_config)
|
| |
+ mock_update_jira_issue.assert_not_called()
|
| |
+ mock_create_jira_issue.assert_called_with(mock_client, self.mock_issue, self.mock_config)
|
| |
+ mock_existing_jira_issue_legacy.assert_not_called()
|
| |
+
|
| |
+ @mock.patch(PATH + '_update_description')
|
| |
+ @mock.patch(PATH + '_update_comments')
|
| |
+ @mock.patch(PATH + '_update_tags')
|
| |
+ @mock.patch(PATH + '_update_fixVersion')
|
| |
+ @mock.patch(PATH + '_update_transition')
|
| |
+ @mock.patch('jira.client.JIRA')
|
| |
+ def test_update_jira_issue(self,
|
| |
+ mock_client,
|
| |
+ mock_update_transition,
|
| |
+ mock_update_fixVersion,
|
| |
+ mock_update_tags,
|
| |
+ mock_update_comments,
|
| |
+ mock_update_description):
|
| |
+ """
|
| |
+ This tests '_update_jira_issue' function
|
| |
+ """
|
| |
+ # Call the function
|
| |
+ d._update_jira_issue(
|
| |
+ existing=self.mock_downstream,
|
| |
+ issue=self.mock_issue,
|
| |
+ client=mock_client
|
| |
+ )
|
| |
+
|
| |
+ # Assert all calls were made correctly
|
| |
+ mock_update_comments.assert_called_with(
|
| |
+ mock_client,
|
| |
+ self.mock_downstream,
|
| |
+ self.mock_issue
|
| |
+ )
|
| |
+ mock_update_tags.assert_called_with(
|
| |
+ self.mock_updates,
|
| |
+ self.mock_downstream,
|
| |
+ self.mock_issue
|
| |
+ )
|
| |
+ mock_update_fixVersion.assert_called_with(
|
| |
+ self.mock_updates,
|
| |
+ self.mock_downstream,
|
| |
+ self.mock_issue
|
| |
+ )
|
| |
+ mock_update_description.assert_called_with(
|
| |
+ self.mock_downstream,
|
| |
+ self.mock_issue
|
| |
+ )
|
| |
+ self.mock_downstream.update.assert_called_with({
|
| |
+ 'summary': 'mock_title'
|
| |
+ })
|
| |
+ mock_update_transition.assert_called_with(
|
| |
+ mock_client,
|
| |
+ self.mock_downstream,
|
| |
+ self.mock_issue
|
| |
+ )
|
| |
+
|
| |
+ @mock.patch(PATH + 'datetime')
|
| |
+ @mock.patch('jira.client.JIRA')
|
| |
+ def test_update_transition_JIRAError(self,
|
| |
+ mock_client,
|
| |
+ mock_datetime):
|
| |
+ """
|
| |
+ This function tests the '_update_transition' function where Upstream issue status
|
| |
+ s not in existing.fields.description and transitioning the issue throws an error
|
| |
+ """
|
| |
+ # Set up return values
|
| |
+ self.mock_issue.status = 'Closed'
|
| |
+ self.mock_downstream.fields.description = ''
|
| |
+ mock_datetime.today.return_value = self.mock_today
|
| |
+ mock_client.transitions.return_value = [{'name': 'CUSTOM TRANSITION', 'id': '1234'}]
|
| |
+ mock_client.transition_issue.side_effect = JIRAError
|
| |
+
|
| |
+ # Call the function
|
| |
+ d._update_transition(
|
| |
+ client=mock_client,
|
| |
+ existing=self.mock_downstream,
|
| |
+ issue=self.mock_issue
|
| |
+ )
|
| |
+
|
| |
+ # Assert all calls were made correctly
|
| |
+ self.mock_today.strftime.assert_called_with("%a %b %y - %H:%M")
|
| |
+ self.mock_downstream.update.assert_called_with({'description': '[mock_today] Upstream issue status: Closed\n'})
|
| |
+ mock_client.transitions.assert_called_with(self.mock_downstream)
|
| |
+ mock_client.transition_issue.asert_called_with(self.mock_downstream, 1234)
|
| |
+
|
| |
+ @mock.patch(PATH + 'datetime')
|
| |
+ @mock.patch('jira.client.JIRA')
|
| |
+ def test_update_transition_not_found(self,
|
| |
+ mock_client,
|
| |
+ mock_datetime):
|
| |
+ """
|
| |
+ This function tests the '_update_transition' function where Upstream issue status
|
| |
+ s not in existing.fields.description and we can't find the appropriate closed status
|
| |
+ """
|
| |
+ # Set up return values
|
| |
+ self.mock_issue.status = 'Closed'
|
| |
+ self.mock_issue.downstream['transition'] = 'bad_transition'
|
| |
+ self.mock_downstream.fields.description = ''
|
| |
+ mock_datetime.today.return_value = self.mock_today
|
| |
+ mock_client.transitions.return_value = [{'name': 'CUSTOM TRANSITION', 'id': '1234'}]
|
| |
+
|
| |
+ # Call the function
|
| |
+ d._update_transition(
|
| |
+ client=mock_client,
|
| |
+ existing=self.mock_downstream,
|
| |
+ issue=self.mock_issue
|
| |
+ )
|
| |
+
|
| |
+ # Assert all calls were made correctly
|
| |
+ self.mock_today.strftime.assert_called_with("%a %b %y - %H:%M")
|
| |
+ self.mock_downstream.update.assert_called_with({'description': '[mock_today] Upstream issue status: Closed\n'})
|
| |
+ mock_client.transitions.assert_called_with(self.mock_downstream)
|
| |
+ mock_client.transition_issue.asert_called_with(self.mock_downstream, 1234)
|
| |
+
|
| |
+ @mock.patch(PATH + 'datetime')
|
| |
+ @mock.patch('jira.client.JIRA')
|
| |
+ def test_update_transition_successful(self,
|
| |
+ mock_client,
|
| |
+ mock_datetime):
|
| |
+ """
|
| |
+ This function tests the '_update_transition' function where everything goes smoothly!
|
| |
+ """
|
| |
+ # Set up return values
|
| |
+ self.mock_issue.status = 'Closed'
|
| |
+ self.mock_downstream.fields.description = '[test] Upstream issue status: Open'
|
| |
+ mock_datetime.today.return_value = self.mock_today
|
| |
+ mock_client.transitions.return_value = [{'name': 'CUSTOM TRANSITION', 'id': '1234'}]
|
| |
+
|
| |
+ # Call the function
|
| |
+ d._update_transition(
|
| |
+ client=mock_client,
|
| |
+ existing=self.mock_downstream,
|
| |
+ issue=self.mock_issue
|
| |
+ )
|
| |
+
|
| |
+ # Assert all calls were made correctly
|
| |
+ self.mock_today.strftime.assert_called_with("%a %b %y - %H:%M")
|
| |
+ self.mock_downstream.update.assert_called_with({'description': '[mock_today] Upstream issue status: Closed'})
|
| |
+ mock_client.transitions.assert_called_with(self.mock_downstream)
|
| |
+ mock_client.transition_issue.asert_called_with(self.mock_downstream, 1234)
|
| |
+
|
| |
+ @mock.patch(PATH + '_comment_format')
|
| |
+ @mock.patch(PATH + '_comment_matching')
|
| |
+ @mock.patch('jira.client.JIRA')
|
| |
+ def test_update_comments(self,
|
| |
+ mock_client,
|
| |
+ mock_comment_matching,
|
| |
+ mock_comment_format):
|
| |
+ """
|
| |
+ This function tests the 'update_comments' function
|
| |
+ """
|
| |
+ # Set up return values
|
| |
+ mock_client.comments.return_value = 'mock_comments'
|
| |
+ mock_comment_matching.return_value = ['mock_comments_d']
|
| |
+ mock_comment_format.return_value = 'mock_comment_body'
|
| |
+
|
| |
+ # Call the function
|
| |
+ d._update_comments(
|
| |
+ client=mock_client,
|
| |
+ existing=self.mock_downstream,
|
| |
+ issue=self.mock_issue
|
| |
+ )
|
| |
+
|
| |
+ # Assert all calls were made correctly
|
| |
+ mock_client.comments.assert_called_with(self.mock_downstream)
|
| |
+ mock_comment_matching.assert_called_with(self.mock_issue.comments, 'mock_comments')
|
| |
+ mock_comment_format.assert_called_with('mock_comments_d')
|
| |
+ mock_client.add_comment.assert_called_with(self.mock_downstream, 'mock_comment_body')
|
| |
+
|
| |
+ def test_update_fixVersion_JIRAError(self):
|
| |
+ """
|
| |
+ This function tests the 'update_fixVersion' function where updating the downstream
|
| |
+ issue throws an error
|
| |
+ """
|
| |
+ # Set up return values
|
| |
+ self.mock_downstream.update.side_effect = JIRAError
|
| |
+ self.mock_downstream.fields.fixVersions = []
|
| |
+
|
| |
+ # Call the function
|
| |
+ d._update_fixVersion(
|
| |
+ updates=self.mock_updates,
|
| |
+ existing=self.mock_downstream,
|
| |
+ issue=self.mock_issue
|
| |
+ )
|
| |
+ # Assert all calls were made correctly
|
| |
+ self.mock_downstream.update.assert_called_with(
|
| |
+ {'fixVersions': [{'name': 'fixVersion3'}, {'name': 'fixVersion4'}]})
|
| |
+
|
| |
+ def test_update_fixVersion_no_api_call(self):
|
| |
+ """
|
| |
+ This function tests the 'update_fixVersion' function existing labels are the same
|
| |
+ and thus no API call should be made
|
| |
+ """
|
| |
+ # Set up return values
|
| |
+ self.mock_downstream.update.side_effect = JIRAError
|
| |
+
|
| |
+ # Call the function
|
| |
+ d._update_fixVersion(
|
| |
+ updates=self.mock_updates,
|
| |
+ existing=self.mock_downstream,
|
| |
+ issue=self.mock_issue
|
| |
+ )
|
| |
+ # Assert all calls were made correctly
|
| |
+ self.mock_downstream.update.assert_not_called()
|
| |
+
|
| |
+ def test_update_fixVersion_successful(self):
|
| |
+ """
|
| |
+ This function tests the 'update_fixVersion' function where everything goes smoothly!
|
| |
+ """
|
| |
+ # Set up return values
|
| |
+ self.mock_downstream.fields.fixVersions = []
|
| |
+
|
| |
+ # Call the function
|
| |
+ d._update_fixVersion(
|
| |
+ updates=self.mock_updates,
|
| |
+ existing=self.mock_downstream,
|
| |
+ issue=self.mock_issue
|
| |
+ )
|
| |
+ # Assert all calls were made correctly
|
| |
+ self.mock_downstream.update.assert_called_with(
|
| |
+ {'fixVersions': [{'name': 'fixVersion3'}, {'name': 'fixVersion4'}]})
|
| |
+
|
| |
+ @mock.patch(PATH + 'assign_user')
|
| |
+ @mock.patch('jira.client.JIRA')
|
| |
+ def test_update_assignee_assignee(self,
|
| |
+ mock_client,
|
| |
+ mock_assign_user):
|
| |
+ """
|
| |
+ This function tests the 'update_assignee' function where issue.assignee exists
|
| |
+ """
|
| |
+ # Call the function
|
| |
+ d._update_assignee(
|
| |
+ client=mock_client,
|
| |
+ existing=self.mock_downstream,
|
| |
+ issue=self.mock_issue
|
| |
+ )
|
| |
+
|
| |
+ # Assert all calls were made correctly
|
| |
+ mock_assign_user.assert_called_with(
|
| |
+ mock_client,
|
| |
+ self.mock_issue,
|
| |
+ self.mock_downstream
|
| |
+ )
|
| |
+
|
| |
+ @mock.patch(PATH + 'assign_user')
|
| |
+ @mock.patch('jira.client.JIRA')
|
| |
+ def test_update_assignee_no_assignee(self,
|
| |
+ mock_client,
|
| |
+ mock_assign_user):
|
| |
+ """
|
| |
+ This function tests the '_update_assignee' function where issue.assignee does not exist
|
| |
+ """
|
| |
+ # Set up return values
|
| |
+ self.mock_issue.assignee = None
|
| |
+
|
| |
+ # Call the function
|
| |
+ d._update_assignee(
|
| |
+ client=mock_client,
|
| |
+ existing=self.mock_downstream,
|
| |
+ issue=self.mock_issue
|
| |
+ )
|
| |
+
|
| |
+ # Assert all calls were made correctly
|
| |
+ mock_assign_user.assert_called_with(
|
| |
+ mock_client,
|
| |
+ self.mock_issue,
|
| |
+ self.mock_downstream,
|
| |
+ remove_all=True
|
| |
+ )
|
| |
+
|
| |
+ @mock.patch(PATH + 'verify_tags')
|
| |
+ @mock.patch(PATH + '_label_matching')
|
| |
+ def test_update_tags(self,
|
| |
+ mock_label_matching,
|
| |
+ mock_verify_tags):
|
| |
+ """
|
| |
+ This function tests the '_update_tags' function
|
| |
+ """
|
| |
+ # Set up return values
|
| |
+ mock_label_matching.return_value = 'mock_updated_labels'
|
| |
+ mock_verify_tags.return_value = 'mock_verified_tags'
|
| |
+
|
| |
+ # Call the function
|
| |
+ d._update_tags(
|
| |
+ updates=self.mock_updates,
|
| |
+ existing=self.mock_downstream,
|
| |
+ issue=self.mock_issue
|
| |
+ )
|
| |
+
|
| |
+ # Assert all calls were made correctly
|
| |
+ mock_label_matching.assert_called_with(
|
| |
+ self.mock_issue.tags,
|
| |
+ self.mock_downstream.fields.labels
|
| |
+ )
|
| |
+ mock_verify_tags.assert_called_with('mock_updated_labels')
|
| |
+ self.mock_downstream.update.assert_called_with({'labels': 'mock_verified_tags'})
|
| |
+
|
| |
+ @mock.patch(PATH + 'verify_tags')
|
| |
+ @mock.patch(PATH + '_label_matching')
|
| |
+ def test_update_tags_no_api_call(self,
|
| |
+ mock_label_matching,
|
| |
+ mock_verify_tags):
|
| |
+ """
|
| |
+ This function tests the '_update_tags' function where the existing tags are the same
|
| |
+ as the new ones
|
| |
+ """
|
| |
+ # Set up return values
|
| |
+ mock_label_matching.return_value = 'mock_updated_labels'
|
| |
+ mock_verify_tags.return_value = ['tag3', 'tag4']
|
| |
+
|
| |
+ # Call the function
|
| |
+ d._update_tags(
|
| |
+ updates=self.mock_updates,
|
| |
+ existing=self.mock_downstream,
|
| |
+ issue=self.mock_issue
|
| |
+ )
|
| |
+
|
| |
+ # Assert all calls were made correctly
|
| |
+ mock_label_matching.assert_called_with(
|
| |
+ self.mock_issue.tags,
|
| |
+ self.mock_downstream.fields.labels
|
| |
+ )
|
| |
+ mock_verify_tags.assert_called_with('mock_updated_labels')
|
| |
+ self.mock_downstream.update.assert_not_called()
|
| |
+
|
| |
+ def test_update_description_update(self):
|
| |
+ """
|
| |
+ This function tests '_update_description' where we just have to update the contents of the description
|
| |
+ """
|
| |
+ # Set up return values
|
| |
+ self.mock_downstream.fields.description = 'Upstream description: {quote} test {quote}'
|
| |
+
|
| |
+ # Call the function
|
| |
+ d._update_description(
|
| |
+ existing=self.mock_downstream,
|
| |
+ issue=self.mock_issue
|
| |
+ )
|
| |
+
|
| |
+ # Assert all calls were made correctly
|
| |
+ self.mock_downstream.update.assert_called_with(
|
| |
+ {'description': 'Upstream description: {quote}mock_content{quote}'})
|
| |
+
|
| |
+ def test_update_description_add_field(self):
|
| |
+ """
|
| |
+ This function tests '_update_description' where we just have to add a description field
|
| |
+ """
|
| |
+ # Set up return values
|
| |
+ self.mock_downstream.fields.description = '[123] Upstream Reporter: mock_user \n' \
|
| |
+ 'Upstream description: {quote} test {quote}'
|
| |
+
|
| |
+ # Call the function
|
| |
+ d._update_description(
|
| |
+ existing=self.mock_downstream,
|
| |
+ issue=self.mock_issue
|
| |
+ )
|
| |
+
|
| |
+ # Assert all calls were made correctly
|
| |
+ self.mock_downstream.update.assert_called_with(
|
| |
+ {'description': '[123] Upstream Reporter: mock_user \n'
|
| |
+ 'Upstream description: {quote}mock_content{quote}'})
|
| |
+
|
| |
+ def test_update_description_add_reporter(self):
|
| |
+ """
|
| |
+ This function tests '_update_description' where we have to add a description and upstream reporter field
|
| |
+ """
|
| |
+ # Set up return values
|
| |
+ self.mock_downstream.fields.description = '[123] Upstream issue status: Open\n'
|
| |
+
|
| |
+ # Call the function
|
| |
+ d._update_description(
|
| |
+ existing=self.mock_downstream,
|
| |
+ issue=self.mock_issue
|
| |
+ )
|
| |
+ # Assert all calls were made correctly
|
| |
+ self.mock_downstream.update.assert_called_with({'description': '[123] Upstream issue status: Open\n[1234] Upstream Reporter: mock_user \nUpstream description: {quote}mock_content{quote}'})
|
| |
+
|
| |
+ def test_update_description_add_reporter_no_status(self):
|
| |
+ """
|
| |
+ This function tests '_update_description' where we have to add reporter and description without status
|
| |
+ """
|
| |
+ # Set up return values
|
| |
+ self.mock_downstream.fields.description = ''
|
| |
+
|
| |
+ # Call the function
|
| |
+ d._update_description(
|
| |
+ existing=self.mock_downstream,
|
| |
+ issue=self.mock_issue
|
| |
+ )
|
| |
+
|
| |
+ # Assert all calls were made correctly
|
| |
+ self.mock_downstream.update.assert_called_with(
|
| |
+ {'description': '[1234] Upstream Reporter: mock_user \n'
|
| |
+ 'Upstream description: {quote}mock_content{quote} \n '})
|
| |
+
|
| |
+ def test_update_description_add_description(self):
|
| |
+ """
|
| |
+ This function tests '_update_description' where we have a reporter and status already
|
| |
+ """
|
| |
+ # Set up return values
|
| |
+ self.mock_downstream.fields.description = '[123] Upstream issue status: Open\n' \
|
| |
+ '[123] Upstream Reporter: mock_user\n'
|
| |
+
|
| |
+ # Call the function
|
| |
+ d._update_description(
|
| |
+ existing=self.mock_downstream,
|
| |
+ issue=self.mock_issue
|
| |
+ )
|
| |
+
|
| |
+ # Assert all calls were made correctly
|
| |
+ self.mock_downstream.update.assert_called_with(
|
| |
+ {'description': '[123] Upstream issue status: Open\n'
|
| |
+ '[123] Upstream Reporter: mock_user \n'
|
| |
+ 'Upstream description: {quote}mock_content{quote}\n\n'})
|
| |
+
|
| |
+ def test_verify_tags(self):
|
| |
+ """
|
| |
+ This function tests 'verify_tags' function
|
| |
+ """
|
| |
+ # Call the function
|
| |
+ response = d.verify_tags(
|
| |
+ tags=['this is a tag']
|
| |
+ )
|
| |
+
|
| |
+ # Assert everything was called correctly
|
| |
+ self.assertEqual(response, ['this_is_a_tag'])
|
| |
+
|
| |
+ @mock.patch(PATH + '_get_jira_client')
|
| |
+ @mock.patch(PATH + '_matching_jira_issue_query')
|
| |
+ @mock.patch(PATH + '_close_as_duplicate')
|
| |
+ @mock.patch('jira.client.JIRA')
|
| |
+ def test_close_duplicates_no_matching(self,
|
| |
+ mock_client,
|
| |
+ mock_close_as_duplicate,
|
| |
+ mock_matching_jira_issue_query,
|
| |
+ mock_get_jira_client):
|
| |
+ """
|
| |
+ This tests 'close_duplicates' function where len(results) <= 1
|
| |
+ """
|
| |
+ # Set up return values
|
| |
+ mock_get_jira_client.return_value = mock_client
|
| |
+ mock_matching_jira_issue_query.return_value = ['only_one_response']
|
| |
+
|
| |
+ # Call the function
|
| |
+ response = d.close_duplicates(
|
| |
+ issue=self.mock_issue,
|
| |
+ config=self.mock_config
|
| |
+ )
|
| |
+
|
| |
+ # Assert everything was called correctly
|
| |
+ mock_get_jira_client.assert_called_with(self.mock_issue, self.mock_config)
|
| |
+ mock_matching_jira_issue_query.assert_called_with(
|
| |
+ mock_client,
|
| |
+ self.mock_issue,
|
| |
+ self.mock_config,
|
| |
+ free=True
|
| |
+ )
|
| |
+ mock_close_as_duplicate.assert_not_called()
|
| |
+ self.assertEqual(None, response)
|
| |
+
|
| |
+ @mock.patch(PATH + '_get_jira_client')
|
| |
+ @mock.patch(PATH + '_matching_jira_issue_query')
|
| |
+ @mock.patch(PATH + '_close_as_duplicate')
|
| |
+ @mock.patch('jira.client.JIRA')
|
| |
+ def test_close_duplicates(self,
|
| |
+ mock_client,
|
| |
+ mock_close_as_duplicate,
|
| |
+ mock_matching_jira_issue_query,
|
| |
+ mock_get_jira_client):
|
| |
+ """
|
| |
+ This tests 'close_duplicates' function where len(results) > 1
|
| |
+ """
|
| |
+ # Set up return values
|
| |
+ mock_get_jira_client.return_value = mock_client
|
| |
+ mock_item = MagicMock()
|
| |
+ mock_item.fields.created = 1
|
| |
+ mock_matching_jira_issue_query.return_value = [mock_item, mock_item, mock_item]
|
| |
+
|
| |
+ # Call the function
|
| |
+ response = d.close_duplicates(
|
| |
+ issue=self.mock_issue,
|
| |
+ config=self.mock_config
|
| |
+ )
|
| |
+
|
| |
+ # Assert everything was called correctly
|
| |
+ mock_get_jira_client.assert_called_with(self.mock_issue, self.mock_config)
|
| |
+ mock_matching_jira_issue_query.assert_called_with(
|
| |
+ mock_client,
|
| |
+ self.mock_issue,
|
| |
+ self.mock_config,
|
| |
+ free=True
|
| |
+ )
|
| |
+ mock_close_as_duplicate.assert_called_with(
|
| |
+ mock_client,
|
| |
+ mock_item,
|
| |
+ mock_item,
|
| |
+ self.mock_config
|
| |
+ )
|
| |
+ self.assertEqual(None, response)
|
| |
+
|
| |
+ @mock.patch('jira.client.JIRA')
|
| |
+ def test_close_as_duplicate_errors(self,
|
| |
+ mock_client):
|
| |
+ """
|
| |
+ This tests '_close_as_duplicate' function where client.transition_issue throws an exception
|
| |
+ """
|
| |
+ # Set up return values
|
| |
+ class HTTPExceptionHelper():
|
| |
+ text = "Field 'resolution' cannot be set"
|
| |
+
|
| |
+ class HTTPException(Exception):
|
| |
+ response = HTTPExceptionHelper
|
| |
+
|
| |
+ mock_duplicate = MagicMock()
|
| |
+ mock_duplicate.permalink.return_value = 'mock_url'
|
| |
+ mock_duplicate.key = 'mock_key'
|
| |
+ mock_keeper = MagicMock()
|
| |
+ mock_keeper.key = 'mock_key'
|
| |
+ mock_keeper.permalink.return_value = 'mock_url'
|
| |
+ mock_client.transitions.return_value = [{'name': 'Dropped', 'id': '1234'}]
|
| |
+ mock_client.comments.return_value = []
|
| |
+ mock_client.transition_issue.side_effect = HTTPException
|
| |
+
|
| |
+ # Call the function
|
| |
+ d._close_as_duplicate(
|
| |
+ client=mock_client,
|
| |
+ duplicate=mock_duplicate,
|
| |
+ keeper=mock_keeper,
|
| |
+ config=self.mock_config
|
| |
+ )
|
| |
+
|
| |
+ # Assert everything was called correctly
|
| |
+ mock_client.comments.assert_any_call(mock_keeper)
|
| |
+ mock_client.comments.assert_any_call(mock_duplicate)
|
| |
+ mock_client.transitions.assert_called_with(mock_duplicate)
|
| |
+ mock_client.add_comment.assert_any_call(mock_duplicate, 'Marking as duplicate of mock_key')
|
| |
+ mock_client.add_comment.assert_any_call(mock_keeper, 'mock_key is a duplicate.')
|
| |
+ mock_client.transition_issue.assert_any_call(
|
| |
+ mock_duplicate,
|
| |
+ '1234',
|
| |
+ resolution={'name': 'Duplicate'}
|
| |
+ )
|
| |
+ mock_client.transition_issue.assert_any_call(
|
| |
+ mock_duplicate,
|
| |
+ '1234'
|
| |
+ )
|
| |
+
|
| |
+ @mock.patch('jira.client.JIRA')
|
| |
+ def test_close_as_duplicate(self,
|
| |
+ mock_client):
|
| |
+ """
|
| |
+ This tests '_close_as_duplicate' function where everything goes smoothly
|
| |
+ """
|
| |
+ # Set up return values
|
| |
+ mock_duplicate = MagicMock()
|
| |
+ mock_duplicate.permalink.return_value = 'mock_url'
|
| |
+ mock_duplicate.key = 'mock_key'
|
| |
+ mock_keeper = MagicMock()
|
| |
+ mock_keeper.key = 'mock_key'
|
| |
+ mock_keeper.permalink.return_value = 'mock_url'
|
| |
+ mock_client.transitions.return_value = [{'name': 'Dropped', 'id': '1234'}]
|
| |
+ mock_client.comments.return_value = []
|
| |
+
|
| |
+ # Call the function
|
| |
+ d._close_as_duplicate(
|
| |
+ client=mock_client,
|
| |
+ duplicate=mock_duplicate,
|
| |
+ keeper=mock_keeper,
|
| |
+ config=self.mock_config
|
| |
+ )
|
| |
+
|
| |
+ # Assert everything was called correctly
|
| |
+ mock_client.comments.assert_any_call(mock_keeper)
|
| |
+ mock_client.comments.assert_any_call(mock_duplicate)
|
| |
+ mock_client.transitions.assert_called_with(mock_duplicate)
|
| |
+ mock_client.add_comment.assert_any_call(mock_duplicate, 'Marking as duplicate of mock_key')
|
| |
+ mock_client.add_comment.assert_any_call(mock_keeper, 'mock_key is a duplicate.')
|
| |
+ mock_client.transition_issue.assert_called_with(
|
| |
+ mock_duplicate,
|
| |
+ '1234',
|
| |
+ resolution={'name': 'Duplicate'}
|
| |
)
|
| |