Fix some notifications issues and write some tests

* Fixes some notifier merging issues
* Fixes some more unicode problems
* Implements unit tests
This commit is contained in:
Matthew Jones 2016-02-29 12:30:00 -05:00
parent b35d7a3c6b
commit 0ee12901fe
8 changed files with 302 additions and 20 deletions

View File

@ -1185,11 +1185,10 @@ class InventorySource(UnifiedJobTemplate, InventorySourceOptions):
@property
def notifiers(self):
# Return all notifiers defined on the Project, and on the Organization for each trigger type
base_notifiers = Notifier.objects.filter(active=True)
error_notifiers = list(base_notifiers.filter(organization_notifiers_for_errors__in=[self]))
success_notifiers = list(base_notifiers.filter(organization_notifiers_for_success__in=[self]))
any_notifiers = list(base_notifiers.filter(organization_notifiers_for_any__in=[self]))
error_notifiers = list(base_notifiers.filter(organization_notifiers_for_errors=self.inventory.organization))
success_notifiers = list(base_notifiers.filter(organization_notifiers_for_success=self.inventory.organization))
any_notifiers = list(base_notifiers.filter(organization_notifiers_for_any=self.inventory.organization))
return dict(error=error_notifiers, success=success_notifiers, any=any_notifiers)
def clean_source(self):

View File

@ -341,7 +341,11 @@ class JobTemplate(UnifiedJobTemplate, JobOptions):
error_notifiers = list(base_notifiers.filter(unifiedjobtemplate_notifiers_for_errors__in=[self, self.project]))
success_notifiers = list(base_notifiers.filter(unifiedjobtemplate_notifiers_for_success__in=[self, self.project]))
any_notifiers = list(base_notifiers.filter(unifiedjobtemplate_notifiers_for_any__in=[self, self.project]))
return dict(error=error_notifiers, success=success_notifiers, any=any_notifiers)
# Get Organization Notifiers
error_notifiers = set(error_notifiers + list(base_notifiers.filter(organization_notifiers_for_errors__in=self.project.organizations.all())))
success_notifiers = set(success_notifiers + list(base_notifiers.filter(organization_notifiers_for_success__in=self.project.organizations.all())))
any_notifiers = set(any_notifiers + list(base_notifiers.filter(organization_notifiers_for_any__in=self.project.organizations.all())))
return dict(error=list(error_notifiers), success=list(success_notifiers), any=list(any_notifiers))
class Job(UnifiedJob, JobOptions):
'''

View File

@ -313,20 +313,15 @@ class Project(UnifiedJobTemplate, ProjectOptions):
@property
def notifiers(self):
# Return all notifiers defined on the Project, and on the Organization for each trigger type
# TODO: Currently there is no org fk on project so this will need to be added back once that is
# available after the rbac pr
base_notifiers = Notifier.objects.filter(active=True)
# error_notifiers = list(base_notifiers.filter(Q(project_notifications_for_errors__in=self) |
# Q(organization_notifications_for_errors__in=self.organization)))
# success_notifiers = list(base_notifiers.filter(Q(project_notifications_for_success__in=self) |
# Q(organization_notifications_for_success__in=self.organization)))
# any_notifiers = list(base_notifiers.filter(Q(project_notifications_for_any__in=self) |
# Q(organization_notifications_for_any__in=self.organization)))
error_notifiers = list(base_notifiers.filter(unifiedjobtemplate_notifiers_for_errors=self))
success_notifiers = list(base_notifiers.filter(unifiedjobtemplate_notifiers_for_success=self))
any_notifiers = list(base_notifiers.filter(unifiedjobtemplate_notifiers_for_any=self))
return dict(error=error_notifiers, success=success_notifiers, any=any_notifiers)
# Get Organization Notifiers
error_notifiers = set(error_notifiers + list(base_notifiers.filter(organization_notifiers_for_errors__in=self.organizations.all())))
success_notifiers = set(success_notifiers + list(base_notifiers.filter(organization_notifiers_for_success__in=self.organizations.all())))
any_notifiers = set(any_notifiers + list(base_notifiers.filter(organization_notifiers_for_any__in=self.organizations.all())))
return dict(error=list(error_notifiers), success=list(success_notifiers), any=list(any_notifiers))
def get_absolute_url(self):
return reverse('api:project_detail', args=(self.pk,))

View File

@ -17,6 +17,7 @@ from django.db import models
from django.core.exceptions import NON_FIELD_ERRORS
from django.utils.translation import ugettext_lazy as _
from django.utils.timezone import now
from django.utils.encoding import smart_text
# Django-JSONField
from jsonfield import JSONField
@ -741,7 +742,7 @@ class UnifiedJob(PolymorphicModel, PasswordFieldsModel, CommonModelNameNotUnique
return dict(id=self.id,
name=self.name,
url=self.get_ui_url(),
created_by=str(self.created_by),
created_by=smart_text(self.created_by),
started=self.started.isoformat(),
finished=self.finished.isoformat(),
status=self.status,

View File

@ -20,11 +20,10 @@ class TwilioBackend(TowerBaseEmailBackend):
recipient_parameter = "to_numbers"
sender_parameter = "from_number"
def __init__(self, account_sid, account_token, from_phone, fail_silently=False, **kwargs):
def __init__(self, account_sid, account_token, fail_silently=False, **kwargs):
super(TwilioBackend, self).__init__(fail_silently=fail_silently)
self.account_sid = account_sid
self.account_token = account_token
self.from_phone = from_phone
def send_messages(self, messages):
sent_messages = 0

View File

@ -235,7 +235,7 @@ def handle_work_success(self, result, task_actual):
instance_name,
notification_body['url'])
send_notifications.delay([n.generate_notification(notification_subject, notification_body)
for n in notifiers.get('success', []) + notifiers.get('any', [])],
for n in set(notifiers.get('success', []) + notifiers.get('any', []))],
job_id=task_actual['id'])
@task(bind=True)
@ -292,7 +292,7 @@ def handle_work_error(self, task_id, subtasks=None):
notification_body['url'])
notification_body['friendly_name'] = first_task_friendly_name
send_notifications.delay([n.generate_notification(notification_subject, notification_body).id
for n in notifiers.get('error', []) + notifiers.get('any', [])],
for n in set(notifiers.get('error', []) + notifiers.get('any', []))],
job_id=first_task_id)

View File

@ -0,0 +1,160 @@
import pytest
import mock
from django.core.urlresolvers import resolve
from django.utils.six.moves.urllib.parse import urlparse
from awx.main.models.organization import Organization
from awx.main.models.projects import Project
from awx.main.models.ha import Instance
from django.contrib.auth.models import User
from rest_framework.test import (
APIRequestFactory,
force_authenticate,
)
@pytest.fixture
def user():
def u(name, is_superuser=False):
try:
user = User.objects.get(username=name)
except User.DoesNotExist:
user = User(username=name, is_superuser=is_superuser, password=name)
user.save()
return user
return u
@pytest.fixture
def post():
def rf(url, data, user=None, middleware=None, **kwargs):
view, view_args, view_kwargs = resolve(urlparse(url)[2])
if 'format' not in kwargs:
kwargs['format'] = 'json'
request = APIRequestFactory().post(url, data, **kwargs)
if middleware:
middleware.process_request(request)
if user:
force_authenticate(request, user=user)
response = view(request, *view_args, **view_kwargs)
if middleware:
middleware.process_response(request, response)
return response
return rf
@pytest.fixture
def get():
def rf(url, user=None, middleware=None, **kwargs):
view, view_args, view_kwargs = resolve(urlparse(url)[2])
if 'format' not in kwargs:
kwargs['format'] = 'json'
request = APIRequestFactory().get(url, **kwargs)
if middleware:
middleware.process_request(request)
if user:
force_authenticate(request, user=user)
response = view(request, *view_args, **view_kwargs)
if middleware:
middleware.process_response(request, response)
return response
return rf
@pytest.fixture
def put():
def rf(url, data, user=None, middleware=None, **kwargs):
view, view_args, view_kwargs = resolve(urlparse(url)[2])
if 'format' not in kwargs:
kwargs['format'] = 'json'
request = APIRequestFactory().put(url, data, **kwargs)
if middleware:
middleware.process_request(request)
if user:
force_authenticate(request, user=user)
response = view(request, *view_args, **view_kwargs)
if middleware:
middleware.process_response(request, response)
return response
return rf
@pytest.fixture
def patch():
def rf(url, data, user=None, middleware=None, **kwargs):
view, view_args, view_kwargs = resolve(urlparse(url)[2])
if 'format' not in kwargs:
kwargs['format'] = 'json'
request = APIRequestFactory().patch(url, data, **kwargs)
if middleware:
middleware.process_request(request)
if user:
force_authenticate(request, user=user)
response = view(request, *view_args, **view_kwargs)
if middleware:
middleware.process_response(request, response)
return response
return rf
@pytest.fixture
def delete():
def rf(url, user=None, middleware=None, **kwargs):
view, view_args, view_kwargs = resolve(urlparse(url)[2])
if 'format' not in kwargs:
kwargs['format'] = 'json'
request = APIRequestFactory().delete(url, **kwargs)
if middleware:
middleware.process_request(request)
if user:
force_authenticate(request, user=user)
response = view(request, *view_args, **view_kwargs)
if middleware:
middleware.process_response(request, response)
return response
return rf
@pytest.fixture
def head():
def rf(url, user=None, middleware=None, **kwargs):
view, view_args, view_kwargs = resolve(urlparse(url)[2])
if 'format' not in kwargs:
kwargs['format'] = 'json'
request = APIRequestFactory().head(url, **kwargs)
if middleware:
middleware.process_request(request)
if user:
force_authenticate(request, user=user)
response = view(request, *view_args, **view_kwargs)
if middleware:
middleware.process_response(request, response)
return response
return rf
@pytest.fixture
def options():
def rf(url, data, user=None, middleware=None, **kwargs):
view, view_args, view_kwargs = resolve(urlparse(url)[2])
if 'format' not in kwargs:
kwargs['format'] = 'json'
request = APIRequestFactory().options(url, data, **kwargs)
if middleware:
middleware.process_request(request)
if user:
force_authenticate(request, user=user)
response = view(request, *view_args, **view_kwargs)
if middleware:
middleware.process_response(request, response)
return response
return rf
@pytest.fixture
def instance(settings):
return Instance.objects.create(uuid=settings.SYSTEM_UUID, primary=True, hostname="instance.example.org")
@pytest.fixture
def organization(instance):
return Organization.objects.create(name="test-org", description="test-org-desc")
@pytest.fixture
@mock.patch.object(Project, "update", lambda self, **kwargs: None)
def project(instance):
return Project.objects.create(name="test-proj",
description="test-proj-desc",
scm_type="git",
scm_url="https://github.com/jlaska/ansible-playbooks")

View File

@ -0,0 +1,124 @@
import mock
import pytest
from awx.main.models.notifications import Notification, Notifier
from awx.main.models.inventory import Inventory, Group
from awx.main.models.organization import Organization
from awx.main.models.projects import Project
from awx.main.models.jobs import JobTemplate
from django.core.urlresolvers import reverse
from django.core.mail.message import EmailMessage
@pytest.fixture
def notifier():
return Notifier.objects.create(name="test-notification",
notification_type="webhook",
notification_configuration=dict(url="http://localhost",
headers={"Test": "Header"}))
@pytest.mark.django_db
def test_get_notifier_list(get, user, notifier):
url = reverse('api:notifier_list')
response = get(url, user('admin', True))
assert response.status_code == 200
assert len(response.data['results']) == 1
@pytest.mark.django_db
def test_basic_parameterization(get, post, user, organization):
u = user('admin-poster', True)
url = reverse('api:notifier_list')
response = post(url,
dict(name="test-webhook",
description="test webhook",
organization=1,
notification_type="webhook",
notification_configuration=dict(url="http://localhost",
headers={"Test": "Header"})),
u)
assert response.status_code == 201
url = reverse('api:notifier_detail', args=(response.data['id'],))
response = get(url, u)
assert 'related' in response.data
assert 'organization' in response.data['related']
assert 'summary_fields' in response.data
assert 'organization' in response.data['summary_fields']
assert 'notifications' in response.data['related']
assert 'notification_configuration' in response.data
assert 'url' in response.data['notification_configuration']
assert 'headers' in response.data['notification_configuration']
@pytest.mark.django_db
def test_encrypted_subfields(get, post, user, organization):
def assert_send(self, messages):
assert self.account_token == "shouldhide"
return 1
u = user('admin-poster', True)
url = reverse('api:notifier_list')
response = post(url,
dict(name="test-twilio",
description="test twilio",
organization=1,
notification_type="twilio",
notification_configuration=dict(account_sid="dummy",
account_token="shouldhide",
from_number="+19999999999",
to_numbers=["9998887777"])),
u)
assert response.status_code == 201
notifier_actual = Notifier.objects.get(id=response.data['id'])
assert notifier_actual.notification_configuration['account_token'].startswith("$encrypted$")
url = reverse('api:notifier_detail', args=(response.data['id'],))
response = get(url, u)
assert response.data['notification_configuration']['account_token'] == "$encrypted$"
with mock.patch.object(notifier_actual.notification_class, "send_messages", assert_send):
notifier_actual.send("Test", {'body': "Test"})
@pytest.mark.django_db
def test_inherited_notifiers(get, post, user, organization, project):
u = user('admin-poster', True)
url = reverse('api:notifier_list')
notifiers = []
for nfiers in xrange(3):
response = post(url,
dict(name="test-webhook-{}".format(nfiers),
description="test webhook {}".format(nfiers),
organization=1,
notification_type="webhook",
notification_configuration=dict(url="http://localhost",
headers={"Test": "Header"})),
u)
assert response.status_code == 201
notifiers.append(response.data['id'])
o = Organization.objects.get(id=1)
p = Project.objects.get(id=1)
o.projects.add(p)
i = Inventory.objects.create(name='test', organization=o)
i.save()
g = Group.objects.create(name='test', inventory=i)
g.save()
jt = JobTemplate.objects.create(name='test', inventory=i, project=p, playbook='debug.yml')
jt.save()
url = reverse('api:organization_notifiers_any_list', args=(1,))
response = post(url, dict(id=notifiers[0]), u)
assert response.status_code == 204
url = reverse('api:project_notifiers_any_list', args=(1,))
response = post(url, dict(id=notifiers[1]), u)
assert response.status_code == 204
url = reverse('api:job_template_notifiers_any_list', args=(jt.id,))
response = post(url, dict(id=notifiers[2]), u)
assert response.status_code == 204
assert len(jt.notifiers['any']) == 3
assert len(p.notifiers['any']) == 2
assert len(g.inventory_source.notifiers['any']) == 1
@pytest.mark.django_db
def test_notifier_merging(get, post, user, organization, project, notifier):
u = user('admin-poster', True)
o = Organization.objects.get(id=1)
p = Project.objects.get(id=1)
n = Notifier.objects.get(id=1)
o.projects.add(p)
o.notifiers_any.add(n)
p.notifiers_any.add(n)
assert len(p.notifiers['any']) == 1