Add unit test for Jira

Add unit test for Jira notifier which achieves 100% coverage.

Change-Id: Iae22a35a5bec5f173c3fae60df393d55b805e0fb
This commit is contained in:
Haruki Yamanashi 2017-07-19 16:43:25 +09:00
parent fe78b6d698
commit 46a36adde0
8 changed files with 285 additions and 8 deletions

View File

@ -30,3 +30,8 @@ max-line-length = 120
[wheel] [wheel]
universal = 1 universal = 1
[extras]
jira_plugin =
jira
jinja2

View 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}}

View 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 }}"

View File

@ -0,0 +1,2 @@
jira_format:
key: "Exception is occurred if there is no summary key in jiraformat.yml"

View File

@ -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

View 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, {})

View File

@ -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'

View File

@ -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