Add unit test for Jira
Add unit test for Jira notifier which achieves 100% coverage. Change-Id: Iae22a35a5bec5f173c3fae60df393d55b805e0fb
This commit is contained in:
parent
fe78b6d698
commit
46a36adde0
@ -30,3 +30,8 @@ max-line-length = 120
|
|||||||
|
|
||||||
[wheel]
|
[wheel]
|
||||||
universal = 1
|
universal = 1
|
||||||
|
|
||||||
|
[extras]
|
||||||
|
jira_plugin =
|
||||||
|
jira
|
||||||
|
jinja2
|
||||||
|
11
tests/resources/test_jiraformat.yml
Normal file
11
tests/resources/test_jiraformat.yml
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
jira_format:
|
||||||
|
summary: Alarm created for {{ notification.alarm_name }} with severity
|
||||||
|
{{ notification.state }} for {{ notification.alarm_id }}
|
||||||
|
description : "{{ notification.alarm_name }}"
|
||||||
|
comments : |
|
||||||
|
AlarmName: {{ notification.alarm_name }}
|
||||||
|
AlarmId: {{ notification.alarm_id }}
|
||||||
|
AlarmTimestamp: {{ notification.alarm_time_stamp}}
|
||||||
|
message: {{ notification.message}}
|
||||||
|
state: {{ notification.state}}
|
||||||
|
severity: {{ notification.severity}}
|
4
tests/resources/test_jiraformat_without_comments.yml
Normal file
4
tests/resources/test_jiraformat_without_comments.yml
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
jira_format:
|
||||||
|
summary: Alarm created for {{ notification.alarm_name }} with severity
|
||||||
|
{{ notification.state }} for {{ notification.alarm_id }}
|
||||||
|
description : "{{ notification.alarm_name }}"
|
2
tests/resources/test_jiraformat_without_summary.yml
Normal file
2
tests/resources/test_jiraformat_without_summary.yml
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
jira_format:
|
||||||
|
key: "Exception is occurred if there is no summary key in jiraformat.yml"
|
@ -15,9 +15,9 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import base64
|
import base64
|
||||||
|
import email.header
|
||||||
import mock
|
import mock
|
||||||
import smtplib
|
import smtplib
|
||||||
import email.header
|
|
||||||
import socket
|
import socket
|
||||||
import time
|
import time
|
||||||
import unittest
|
import unittest
|
||||||
@ -67,7 +67,7 @@ def _decode_headers(email_lines):
|
|||||||
subject, from_addr, to_addr = None, None, None
|
subject, from_addr, to_addr = None, None, None
|
||||||
for key_idx, key in enumerate(keys):
|
for key_idx, key in enumerate(keys):
|
||||||
accummulated = []
|
accummulated = []
|
||||||
for idx in range(3, len(email_lines)-1):
|
for idx in range(3, len(email_lines) - 1):
|
||||||
line = email_lines[idx]
|
line = email_lines[idx]
|
||||||
if not line:
|
if not line:
|
||||||
break
|
break
|
||||||
|
252
tests/test_jira_notification.py
Normal file
252
tests/test_jira_notification.py
Normal file
@ -0,0 +1,252 @@
|
|||||||
|
# Copyright 2017 FUJITSU LIMITED
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
|
||||||
|
# in compliance with the License. You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software distributed under the License
|
||||||
|
# is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
||||||
|
# or implied. See the License for the specific language governing permissions and limitations under
|
||||||
|
# the License.
|
||||||
|
|
||||||
|
import mock
|
||||||
|
from oslotest import base
|
||||||
|
import six
|
||||||
|
|
||||||
|
from monasca_notification import notification as m_notification
|
||||||
|
from monasca_notification.plugins import jira_notifier
|
||||||
|
|
||||||
|
if six.PY2:
|
||||||
|
import Queue as queue
|
||||||
|
else:
|
||||||
|
import queue
|
||||||
|
|
||||||
|
|
||||||
|
def alarm():
|
||||||
|
return {'tenantId': '0',
|
||||||
|
'alarmId': '0',
|
||||||
|
'alarmDefinitionId': 0,
|
||||||
|
'alarmName': 'test Alarm',
|
||||||
|
'alarmDescription': 'test Alarm description',
|
||||||
|
'oldState': 'OK',
|
||||||
|
'newState': 'ALARM',
|
||||||
|
'severity': 'CRITICAL',
|
||||||
|
'link': 'some-link',
|
||||||
|
'lifecycleState': 'OPEN',
|
||||||
|
'stateChangeReason': 'I am alarming!',
|
||||||
|
'timestamp': 1429023453632,
|
||||||
|
'metrics': {'dimension': {'hostname': 'foo1', 'service': 'bar1'}}}
|
||||||
|
|
||||||
|
|
||||||
|
def issue(component=True, custom_config=False):
|
||||||
|
|
||||||
|
issue = {'fields': {'description': 'Monasca alaram',
|
||||||
|
'issuetype': {'name': 'Bug'},
|
||||||
|
'project': {'key': 'MyProject'},
|
||||||
|
'summary': 'Monasca alarm for alarm_defintion test Alarm status '
|
||||||
|
'changed to ALARM for the alarm_id 0'}}
|
||||||
|
if component:
|
||||||
|
issue['fields'].update({'components': [{'name': 'MyComponent'}]})
|
||||||
|
if custom_config:
|
||||||
|
alarm_value = alarm()
|
||||||
|
summary_format_string = 'Alarm created for {0} with severity {1} for {2}'
|
||||||
|
summary = summary_format_string.format(alarm_value.get('alarmName'),
|
||||||
|
alarm_value.get('newState'),
|
||||||
|
alarm_value.get('alarmId'))
|
||||||
|
issue['fields'].update({'summary': summary})
|
||||||
|
return issue
|
||||||
|
|
||||||
|
|
||||||
|
class RequestsResponse(object):
|
||||||
|
def __init__(self, status):
|
||||||
|
self.status_code = status
|
||||||
|
|
||||||
|
|
||||||
|
class TestJira(base.BaseTestCase):
|
||||||
|
|
||||||
|
default_address = 'http://test.jira:3333/?project=MyProject' \
|
||||||
|
'&component=MyComponent'
|
||||||
|
default_transitions_value = [[{'id': 100, 'name': 'reopen'}]]
|
||||||
|
issue_status_resolved = 'resolved'
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(TestJira, self).setUp()
|
||||||
|
|
||||||
|
self._trap = queue.Queue()
|
||||||
|
|
||||||
|
mock_log = mock.Mock()
|
||||||
|
mock_log.info = self._trap.put
|
||||||
|
mock_log.debug = self._trap.put
|
||||||
|
mock_log.warn = self._trap.put
|
||||||
|
mock_log.error = self._trap.put
|
||||||
|
mock_log.exception = self._trap.put
|
||||||
|
|
||||||
|
self._jr = jira_notifier.JiraNotifier(mock_log)
|
||||||
|
|
||||||
|
self._jira_config = {'user': 'username',
|
||||||
|
'password': 'password'}
|
||||||
|
|
||||||
|
@mock.patch('monasca_notification.plugins.jira_notifier.jira')
|
||||||
|
def _notify(self,
|
||||||
|
transitions_value,
|
||||||
|
issue_status,
|
||||||
|
jira_config,
|
||||||
|
address,
|
||||||
|
mock_jira):
|
||||||
|
self._jr.config(jira_config)
|
||||||
|
alarm_dict = alarm()
|
||||||
|
|
||||||
|
mock_jira_obj = mock.Mock()
|
||||||
|
mock_jira.JIRA.return_value = mock_jira_obj
|
||||||
|
mock_jira_issue = mock.Mock()
|
||||||
|
if issue_status:
|
||||||
|
mock_jira_obj.search_issues.return_value = [mock_jira_issue]
|
||||||
|
mock_jira_issue.fields.status.name = issue_status
|
||||||
|
else:
|
||||||
|
mock_jira_obj.search_issues.return_value = []
|
||||||
|
mock_jira_obj.transitions.side_effect = transitions_value
|
||||||
|
|
||||||
|
notification = m_notification.Notification(0, 'jira',
|
||||||
|
'jira notification',
|
||||||
|
address, 0, 0, alarm_dict)
|
||||||
|
|
||||||
|
return mock_jira, mock_jira_obj, self._jr.send_notification(notification)
|
||||||
|
|
||||||
|
def test_send_notification_issue_status_resolved(self):
|
||||||
|
mock_jira, mock_jira_obj, result = self._notify(TestJira.default_transitions_value,
|
||||||
|
TestJira.issue_status_resolved,
|
||||||
|
self._jira_config,
|
||||||
|
TestJira.default_address)
|
||||||
|
self.assertTrue(result)
|
||||||
|
mock_jira_obj.create_issue.assert_not_called()
|
||||||
|
self.assertEqual(
|
||||||
|
"project=MyProject and reporter='username' and summary ~ '0'",
|
||||||
|
mock_jira_obj.search_issues.call_args[0][0])
|
||||||
|
self.assertEqual(100, mock_jira_obj.transition_issue.call_args[0][1])
|
||||||
|
|
||||||
|
def test_send_notification_issue_status_closed(self):
|
||||||
|
issue_status = 'closed'
|
||||||
|
mock_jira, mock_jira_obj, result = self._notify(TestJira.default_transitions_value,
|
||||||
|
issue_status,
|
||||||
|
self._jira_config,
|
||||||
|
TestJira.default_address)
|
||||||
|
self.assertTrue(result)
|
||||||
|
mock_jira_obj.create_issue.assert_not_called()
|
||||||
|
self.assertEqual(
|
||||||
|
"project=MyProject and reporter='username' and summary ~ '0'",
|
||||||
|
mock_jira_obj.search_issues.call_args[0][0])
|
||||||
|
self.assertEqual(100, mock_jira_obj.transition_issue.call_args[0][1])
|
||||||
|
|
||||||
|
def test_send_notification_issue_status_progress(self):
|
||||||
|
issue_status = 'progress'
|
||||||
|
mock_jira, mock_jira_obj, result = self._notify(TestJira.default_transitions_value,
|
||||||
|
issue_status,
|
||||||
|
self._jira_config,
|
||||||
|
TestJira.default_address)
|
||||||
|
self.assertTrue(result)
|
||||||
|
mock_jira_obj.create_issue.assert_not_called()
|
||||||
|
mock_jira_obj.transitions.assert_not_called()
|
||||||
|
|
||||||
|
def test_send_notification_not_allow_transitions(self):
|
||||||
|
transitions_value = [[{'id': 100, 'name': 'not open'}]]
|
||||||
|
mock_jira, mock_jira_obj, result = self._notify(transitions_value,
|
||||||
|
TestJira.issue_status_resolved,
|
||||||
|
self._jira_config,
|
||||||
|
TestJira.default_address)
|
||||||
|
self.assertTrue(result)
|
||||||
|
mock_jira_obj.create_issue.assert_not_called()
|
||||||
|
mock_jira_obj.transition_issue.assert_not_called()
|
||||||
|
|
||||||
|
def test_send_notification_without_issue_list(self):
|
||||||
|
issue_status = None
|
||||||
|
mock_jira, mock_jira_obj, result = self._notify(TestJira.default_transitions_value,
|
||||||
|
issue_status,
|
||||||
|
self._jira_config,
|
||||||
|
TestJira.default_address)
|
||||||
|
self.assertTrue(result)
|
||||||
|
mock_jira_obj.transitions.assert_not_called()
|
||||||
|
self.assertEqual(issue(), mock_jira_obj.create_issue.call_args[1])
|
||||||
|
|
||||||
|
def test_send_notification_without_componet(self):
|
||||||
|
issue_status = None
|
||||||
|
address = 'http://test.jira:3333/?project=MyProject'
|
||||||
|
mock_jira, mock_jira_obj, result = self._notify(TestJira.default_transitions_value,
|
||||||
|
issue_status,
|
||||||
|
self._jira_config,
|
||||||
|
address)
|
||||||
|
self.assertTrue(result)
|
||||||
|
self.assertEqual(issue(component=False), mock_jira_obj.create_issue.call_args[1])
|
||||||
|
mock_jira_obj.transitions.assert_not_called()
|
||||||
|
|
||||||
|
def test_send_notification_create_jira_object_args(self):
|
||||||
|
mock_jira, mock_jira_obj, result = self._notify(TestJira.default_transitions_value,
|
||||||
|
TestJira.issue_status_resolved,
|
||||||
|
self._jira_config,
|
||||||
|
TestJira.default_address)
|
||||||
|
self.assertEqual('http://test.jira:3333/', mock_jira.JIRA.call_args[0][0])
|
||||||
|
self.assertEqual(('username', 'password'), mock_jira.JIRA.call_args[1].get('basic_auth'))
|
||||||
|
self.assertEqual(None, mock_jira.JIRA.call_args[1].get('proxies'))
|
||||||
|
|
||||||
|
def test_send_notification_with_proxy(self):
|
||||||
|
jira_config = self._jira_config
|
||||||
|
jira_config.update({'proxy': 'http://yourid:password@proxyserver:8080'})
|
||||||
|
mock_jira, mock_jira_obj, result = self._notify(TestJira.default_transitions_value,
|
||||||
|
TestJira.issue_status_resolved,
|
||||||
|
jira_config,
|
||||||
|
TestJira.default_address)
|
||||||
|
self.assertTrue(result)
|
||||||
|
self.assertEqual({'https': 'http://yourid:password@proxyserver:8080'},
|
||||||
|
mock_jira.JIRA.call_args[1].get('proxies'))
|
||||||
|
|
||||||
|
def test_send_notification_custom_config_success(self):
|
||||||
|
issue_status = None
|
||||||
|
jira_config = self._jira_config
|
||||||
|
jira_config.update(
|
||||||
|
{'custom_formatter': 'tests/resources/test_jiraformat.yml'})
|
||||||
|
mock_jira, mock_jira_obj, result = self._notify(TestJira.default_transitions_value,
|
||||||
|
issue_status,
|
||||||
|
jira_config,
|
||||||
|
TestJira.default_address)
|
||||||
|
self.assertTrue(result)
|
||||||
|
mock_jira_obj.transitions.assert_not_called()
|
||||||
|
self.assertEqual(issue(custom_config=True), mock_jira_obj.create_issue.call_args[1])
|
||||||
|
|
||||||
|
def test_send_notification_custom_config_failed(self):
|
||||||
|
jira_config = self._jira_config
|
||||||
|
jira_config.update(
|
||||||
|
{'custom_formatter': 'tests/resources/test_jiraformat_without_summary.yml'})
|
||||||
|
mock_jira, mock_jira_obj, result = self._notify(TestJira.default_transitions_value,
|
||||||
|
TestJira.issue_status_resolved,
|
||||||
|
jira_config,
|
||||||
|
TestJira.default_address)
|
||||||
|
self.assertFalse(result)
|
||||||
|
|
||||||
|
def test_send_notification_custom_config_without_comments(self):
|
||||||
|
jira_config = self._jira_config
|
||||||
|
jira_config.update(
|
||||||
|
{'custom_formatter': 'tests/resources/test_jiraformat_without_comments.yml'})
|
||||||
|
mock_jira, mock_jira_obj, result = self._notify(TestJira.default_transitions_value,
|
||||||
|
TestJira.issue_status_resolved,
|
||||||
|
jira_config,
|
||||||
|
TestJira.default_address)
|
||||||
|
self.assertTrue(result)
|
||||||
|
mock_jira_obj.add_comment.assert_not_called()
|
||||||
|
|
||||||
|
def test_send_notification_custom_config_exception(self):
|
||||||
|
jira_config = self._jira_config
|
||||||
|
jira_config.update({'custom_formatter': 'tests/resources/not_exist_file.yml'})
|
||||||
|
self.assertRaises(Exception, self._notify,
|
||||||
|
TestJira.default_transitions_value,
|
||||||
|
TestJira.issue_status_resolved,
|
||||||
|
jira_config,
|
||||||
|
TestJira.default_address)
|
||||||
|
|
||||||
|
def test_type(self):
|
||||||
|
self.assertEqual('jira', self._jr.type)
|
||||||
|
|
||||||
|
def test_statsd_name(self):
|
||||||
|
self.assertEqual('jira_notifier', self._jr.statsd_name)
|
||||||
|
|
||||||
|
def test_config_exception(self):
|
||||||
|
self.assertRaises(Exception, self._jr.config, {})
|
@ -11,18 +11,18 @@
|
|||||||
# the License.
|
# the License.
|
||||||
|
|
||||||
import mock
|
import mock
|
||||||
import sqlalchemy
|
|
||||||
|
|
||||||
from monasca_notification.common.repositories.orm import orm_repo
|
from monasca_notification.common.repositories.orm import orm_repo
|
||||||
from oslotest import base
|
from oslotest import base
|
||||||
|
|
||||||
|
|
||||||
class TestOrmRepo(base.BaseTestCase):
|
class TestOrmRepo(base.BaseTestCase):
|
||||||
@mock.patch('monasca_notification.common.repositories.orm.orm_repo.engine_from_config')
|
@mock.patch('monasca_notification.common.repositories.orm.orm_repo.engine_from_config')
|
||||||
def setUp(self, mock_sql_engine_from_config):
|
def setUp(self, mock_sql_engine_from_config):
|
||||||
super(TestOrmRepo, self).setUp()
|
super(TestOrmRepo, self).setUp()
|
||||||
config = {'database':
|
config = {'database':
|
||||||
{'orm':
|
{'orm':
|
||||||
{'url': 'mysql+pymysql://user:password@hostname:3306/mon'}}}
|
{'url': 'mysql+pymysql://user:password@hostname:3306/mon'}}}
|
||||||
self._rep = orm_repo.OrmRepo(config)
|
self._rep = orm_repo.OrmRepo(config)
|
||||||
self.mock_conn = \
|
self.mock_conn = \
|
||||||
self._rep._orm_engine.connect.return_value.__enter__.return_value
|
self._rep._orm_engine.connect.return_value.__enter__.return_value
|
||||||
@ -68,7 +68,7 @@ class TestOrmRepo(base.BaseTestCase):
|
|||||||
|
|
||||||
def test_fetch_notification_method_types_success(self):
|
def test_fetch_notification_method_types_success(self):
|
||||||
notification_methods = [('EMAIL',), ('WEBHOOK',)]
|
notification_methods = [('EMAIL',), ('WEBHOOK',)]
|
||||||
self.mock_conn.execute.return_value.fetchall.return_value = [('EMAIL',), ('WEBHOOK',)]
|
self.mock_conn.execute.return_value.fetchall.return_value = notification_methods
|
||||||
|
|
||||||
self.assertEqual(self._rep.fetch_notification_method_types(), ['EMAIL', 'WEBHOOK'])
|
self.assertEqual(self._rep.fetch_notification_method_types(), ['EMAIL', 'WEBHOOK'])
|
||||||
|
|
||||||
@ -85,7 +85,7 @@ class TestOrmRepo(base.BaseTestCase):
|
|||||||
self.assertEqual(self._rep._orm_add_notification_type,
|
self.assertEqual(self._rep._orm_add_notification_type,
|
||||||
self.mock_conn.execute.call_args_list[0][0][0])
|
self.mock_conn.execute.call_args_list[0][0][0])
|
||||||
self.assertEqual({'b_name': 'SLACK'},
|
self.assertEqual({'b_name': 'SLACK'},
|
||||||
self.mock_conn.execute.call_args_list[0][1])
|
self.mock_conn.execute.call_args_list[0][1])
|
||||||
|
|
||||||
def test_get_notification_success(self):
|
def test_get_notification_success(self):
|
||||||
notification_id = 'notification-123'
|
notification_id = 'notification-123'
|
||||||
|
5
tox.ini
5
tox.ini
@ -18,6 +18,7 @@ whitelist_externals = bash
|
|||||||
find
|
find
|
||||||
rm
|
rm
|
||||||
deps = -r{toxinidir}/test-requirements.txt
|
deps = -r{toxinidir}/test-requirements.txt
|
||||||
|
.[jira_plugin]
|
||||||
commands =
|
commands =
|
||||||
find . -type f -name "*.pyc" -delete
|
find . -type f -name "*.pyc" -delete
|
||||||
rm -Rf .testrepository/times.dbm
|
rm -Rf .testrepository/times.dbm
|
||||||
@ -65,6 +66,7 @@ commands = {posargs}
|
|||||||
[testenv:flake8]
|
[testenv:flake8]
|
||||||
commands =
|
commands =
|
||||||
flake8 monasca_notification
|
flake8 monasca_notification
|
||||||
|
flake8 tests
|
||||||
|
|
||||||
[testenv:bandit]
|
[testenv:bandit]
|
||||||
commands =
|
commands =
|
||||||
@ -74,6 +76,7 @@ commands =
|
|||||||
max-line-length = 120
|
max-line-length = 120
|
||||||
# TODO: ignored checks should be enabled in the future
|
# TODO: ignored checks should be enabled in the future
|
||||||
# H201 no 'except:' at least use 'except Exception:'
|
# H201 no 'except:' at least use 'except Exception:'
|
||||||
|
# H202: assertRaises Exception too broad
|
||||||
# H405 multi line docstring summary not separated with an empty line
|
# H405 multi line docstring summary not separated with an empty line
|
||||||
ignore = F821,H201,H405
|
ignore = F821,H201,H202,H405
|
||||||
exclude=.venv,.git,.tox,dist,*egg,build
|
exclude=.venv,.git,.tox,dist,*egg,build
|
||||||
|
Loading…
Reference in New Issue
Block a user