passerelle/tests/test_sms.py

376 lines
14 KiB
Python

import isodate
import json
import mock
import pytest
from requests import RequestException
from django.conf import settings
from django.contrib.contenttypes.models import ContentType
from django.core.urlresolvers import reverse
from passerelle.apps.ovh.models import OVHSMSGateway
from passerelle.base.models import ApiUser, AccessRight, Job
from passerelle.sms.models import SMSResource, SMSLog
from passerelle.utils.jsonresponse import APIError
from test_manager import login, admin_user
import utils
pytestmark = pytest.mark.django_db
klasses = SMSResource.__subclasses__()
def test_clean_numbers():
connector = OVHSMSGateway()
assert connector.clean_numbers(['+ 33 12']) == ['003312']
assert connector.clean_numbers(['0 0 33 12']) == ['003312']
assert connector.clean_numbers(['0 12']) == ['003312']
connector.default_country_code = '32'
connector.default_trunk_prefix = '1'
connector.save()
assert connector.clean_numbers(['+ 33 12']) == ['003312']
assert connector.clean_numbers(['0 0 33 12']) == ['003312']
assert connector.clean_numbers(['1 12']) == ['003212']
with pytest.raises(APIError, match='phone number %r is unsupported' % '0123'):
connector.clean_numbers(['0123'])
@pytest.fixture(params=klasses)
def connector(request, db):
klass = request.param
kwargs = getattr(klass, 'TEST_DEFAULTS', {}).get('create_kwargs', {})
kwargs.update({
'title': klass.__name__,
'slug': klass.__name__.lower(),
'description': klass.__name__,
})
c = klass.objects.create(**kwargs)
api = ApiUser.objects.create(username='apiuser', fullname='Api User', description='api')
obj_type = ContentType.objects.get_for_model(c)
# no access check
AccessRight.objects.create(codename='can_send_messages',
apiuser=api,
resource_type=obj_type,
resource_pk=c.pk)
return c
def test_connectors(app, connector, freezer):
path = '/%s/%s/send/' % (connector.get_connector_slug(), connector.slug)
result = app.post_json(path, params={}, status=400)
assert result.json['err'] == 1
assert result.json['err_desc'] == "'message' is a required property"
payload = {
'message': 'hello',
'from': '+33699999999',
'to': ['+33688888888', '+33677777777'],
}
test_vectors = getattr(connector, 'TEST_DEFAULTS', {}).get('test_vectors', [])
total = len(test_vectors)
nb_failed = 0
assert Job.objects.filter(method_name='send_job').count() == 0
for test_vector in test_vectors:
# register job
freezer.move_to('2019-01-01 00:00:00')
result = app.post_json(path, params=payload)
assert result.json['err'] == 0
job_id = Job.objects.get(method_name='send_job', status='registered').id
# perform job
freezer.move_to('2019-01-01 01:00:03')
with utils.mock_url(
connector.URL,
test_vector.get('response', ''),
test_vector.get('status_code', 200)):
connector.jobs()
job = Job.objects.get(id=job_id)
if job.status == 'failed':
assert len(job.status_details['error_summary']) > 0
assert test_vector['result']['err_desc'] in job.status_details['error_summary']
nb_failed += 1
else:
assert job.status == 'completed'
assert Job.objects.filter(method_name='send_job').count() == total
assert SMSLog.objects.count() == total - nb_failed
def test_manage_views(admin_user, app, connector):
url = '/%s/%s/' % (connector.get_connector_slug(), connector.slug)
resp = app.get(url)
assert 'Endpoints' in resp.text
assert not 'accessright/add' in resp.text
app = login(app)
resp = app.get(url)
description_fields = [
x.text.split(':')[0]
for x in resp.html.find('div', {'id': 'description'}).find_all('p')]
assert 'Default country code' in description_fields
assert 'Default trunk prefix' in description_fields
assert 'Maximum message length' in description_fields
assert 'Account' not in description_fields
assert 'Username' not in description_fields
assert 'Endpoints' in resp.text
assert 'accessright/add' in resp.text
@pytest.mark.parametrize('connector', [OVHSMSGateway], indirect=True)
def test_manage_views_ovh(app, connector):
connector.default_country_code = '44'
connector.account = 'secret'
connector.application_key = 'secret'
connector.application_secret = 'secret'
connector.consumer_key = 'secret'
connector.password = 'secret'
connector.username = 'secret'
connector.save()
url = '/%s/%s/' % (connector.get_connector_slug(), connector.slug)
resp = app.get(url)
description_fields = [
x.text for x in resp.html.find('div', {'id': 'description'}).find_all('p')]
assert any(x for x in description_fields if 'Default country code' in x)
assert any(x for x in description_fields if '44' in x)
assert not any(x for x in description_fields if 'secret' in x)
@pytest.mark.parametrize('connector', [OVHSMSGateway], indirect=True)
def test_sms_max_message_length(app, connector):
path = '/%s/%s/send/' % (connector.get_connector_slug(), connector.slug)
message_above_limit = 'a' * (connector.max_message_length + 1)
payload = {
'message': message_above_limit,
'from': '+33699999999',
'to': ['+33688888888'],
}
with mock.patch.object(OVHSMSGateway, 'send_msg') as send_function:
send_function.return_value = {}
result = app.post_json(path, params=payload)
connector.jobs()
assert send_function.call_args[1]['text'] == 'a' * connector.max_message_length
@pytest.mark.parametrize('connector', [OVHSMSGateway], indirect=True)
def test_sms_log(app, connector):
path = '/%s/%s/send/' % (connector.get_connector_slug(), connector.slug)
assert not SMSLog.objects.filter(appname=connector.get_connector_slug(), slug=connector.slug).exists()
payload = {
'message': 'plop',
'from': '+33699999999',
'to': ['+33688888888'],
}
with mock.patch.object(OVHSMSGateway, 'send_msg') as send_function:
send_function.return_value = {}
result = app.post_json(path, params=payload)
connector.jobs()
assert SMSLog.objects.filter(appname=connector.get_connector_slug(), slug=connector.slug).exists()
def test_sms_nostop_parameter(app, connector):
base_path = '/%s/%s/send/?nostop=1' % (connector.get_connector_slug(), connector.slug)
payload = {
'message': 'not a spam',
'from': '+33699999999',
'to': ['+33688888888'],
}
for path in (base_path, base_path + '?nostop=1', base_path + '?nostop=foo', base_path + '?nostop'):
with mock.patch.object(connector, 'send_msg') as send_function:
send_function.return_value = {}
result = app.post_json(base_path, params=payload)
connector.jobs()
assert send_function.call_args[1]['text'] == 'not a spam'
assert send_function.call_args[1]['stop'] == ('nostop' not in path)
def test_ovh_new_api(app, freezer):
connector = OVHSMSGateway.objects.create(
slug='ovh', account='sms-test42',
application_key='RHrTdU2oTsrVC0pu',
application_secret='CLjtS69tTcPgCKxedeoZlgMSoQGSiXMa',
consumer_key='iF0zi0MJrbjNcI3hvuvwkhNk8skrigxz'
)
api = ApiUser.objects.create(username='apiuser')
obj_type = ContentType.objects.get_for_model(connector)
# no access check
AccessRight.objects.create(codename='can_send_messages', apiuser=api, resource_type=obj_type,
resource_pk=connector.pk)
payload = {
'message': 'hello',
'from': '+33699999999',
'to': ['+33688888888', '+33677777777'],
}
# register job
freezer.move_to('2019-01-01 00:00:00')
path = '/%s/%s/send/' % (connector.get_connector_slug(), connector.slug)
result = app.post_json(path, params=payload)
assert result.json['err'] == 0
job_id = Job.objects.get(method_name='send_job', status='registered').id
# perform job
freezer.move_to('2019-01-01 01:00:03')
resp = {
'validReceivers': ['+33688888888', '+33677777777'],
'totalCreditsRemoved': 1,
'ids': [241615100],
'invalidReceivers': []
}
base_url = connector.API_URL % {'serviceName': 'sms-test42'}
url = base_url + 'jobs/'
with utils.mock_url(url, resp, 200) as mocked:
connector.jobs()
job = Job.objects.get(id=job_id)
assert job.status == 'completed'
request = mocked.handlers[0].call['requests'][0]
assert 'X-Ovh-Signature' in request.headers
@pytest.mark.parametrize('connector', [OVHSMSGateway], indirect=True)
def test_sms_test_send(admin_user, app, connector):
url = '/%s/%s/' % (connector.get_connector_slug(), connector.slug)
resp = app.get(url)
link = resp.html.find('div', {'id': 'endpoints'}).find_all('a')[-1]
assert 'Send a test message' not in link.text
app = login(app)
resp = app.get(url)
link = resp.html.find('div', {'id': 'endpoints'}).find_all('a')[-1]
assert 'Send a test message' in link.text
assert link['href'] == reverse('sms-test-send', kwargs={
'connector': connector.get_connector_slug(), 'slug': connector.slug})
resp = app.get(link['href'])
resp.form['number'] = '+33688888888'
resp.form['sender'] = '+33699999999'
resp.form['message'] = 'hello'
with mock.patch.object(OVHSMSGateway, 'send_msg') as send_function:
send_function.return_value = {}
resp = resp.form.submit()
assert send_function.call_args[1] == {
'text': 'hello', 'sender': '+33699999999', 'destinations': ['0033688888888'],
'stop': False,
}
assert resp.status_code == 302
assert resp.location == url
def test_ovh_new_api_credit(app, freezer):
connector = OVHSMSGateway.objects.create(
slug='ovh', account='sms-test42',
application_key='RHrTdU2oTsrVC0pu',
application_secret='CLjtS69tTcPgCKxedeoZlgMSoQGSiXMa',
consumer_key='iF0zi0MJrbjNcI3hvuvwkhNk8skrigxz'
)
manager_url = '/%s/%s/' % (connector.get_connector_slug(), connector.slug)
resp = app.get(manager_url)
assert 'no credit left' in resp.text
# a job to update credit was added on connector creation
resp = {
'creditsLeft': 123,
}
ovh_url = connector.API_URL % {'serviceName': 'sms-test42'}
with utils.mock_url(ovh_url, resp, 200) as mocked:
connector.jobs()
assert connector.credit_left == 123
resp = app.get(manager_url)
assert '123' in resp.text
# hourly update
resp = {
'creditsLeft': 456,
}
with utils.mock_url(ovh_url, resp, 200) as mocked:
connector.hourly()
assert connector.credit_left == 456
def test_ovh_alert_emails(app, freezer, mailoutbox):
connector = OVHSMSGateway.objects.create(
slug='test-ovh', title='Test OVH', account='sms-test42',
application_key='RHrTdU2oTsrVC0pu',
application_secret='CLjtS69tTcPgCKxedeoZlgMSoQGSiXMa',
consumer_key='iF0zi0MJrbjNcI3hvuvwkhNk8skrigxz',
credit_threshold_alert=100,
credit_left=102,
alert_emails=['test@entrouvert.org'],
)
api = ApiUser.objects.create(username='apiuser')
obj_type = ContentType.objects.get_for_model(connector)
AccessRight.objects.create(codename='can_send_messages', apiuser=api, resource_type=obj_type,
resource_pk=connector.pk)
freezer.move_to('2019-01-01 00:00:00')
resp = {'creditsLeft': 101}
ovh_url = connector.API_URL % {'serviceName': 'sms-test42'}
with utils.mock_url(ovh_url, resp, 200) as mocked:
connector.hourly()
assert len(mailoutbox) == 0
resp = {'creditsLeft': 99}
ovh_url = connector.API_URL % {'serviceName': 'sms-test42'}
with utils.mock_url(ovh_url, resp, 200) as mocked:
connector.hourly()
assert len(mailoutbox) == 1
mail = mailoutbox[0]
assert mail.recipients() == ['test@entrouvert.org']
assert mail.subject == 'OVH SMS alert: only 99 credits left'
for body in (mail.body, mail.alternatives[0][0]):
assert connector.account in body
assert connector.title in body
assert 'http://localhost/ovh/test-ovh/' in body
mailoutbox.clear()
# alert is sent again daily
freezer.move_to('2019-01-01 12:00:00')
resp = {'creditsLeft': 99}
ovh_url = connector.API_URL % {'serviceName': 'sms-test42'}
with utils.mock_url(ovh_url, resp, 200) as mocked:
connector.hourly()
assert len(mailoutbox) == 0
freezer.move_to('2019-01-02 01:00:07')
with utils.mock_url(ovh_url, resp, 200) as mocked:
connector.hourly()
assert len(mailoutbox) == 1
def test_ovh_token_request(admin_user, app):
connector = OVHSMSGateway.objects.create(
slug='test-ovh', title='Test OVH', account='sms-test42',
application_key='RHrTdU2oTsrVC0pu',
application_secret='CLjtS69tTcPgCKxedeoZlgMSoQGSiXMa',
)
app = login(app)
resp = app.get(connector.get_absolute_url())
assert 'not operational yet' in resp.text
ovh_request_token_url = 'https://eu.api.ovh.com/1.0/auth/credential'
ovh_response = {
'consumerKey': 'xyz',
'validationUrl': 'https://eu.api.ovh.com/auth/?credentialToken=iQ1joJE',
}
with utils.mock_url(ovh_request_token_url, ovh_response, 302) as mocked:
resp = resp.click('request access')
assert resp.url == 'https://eu.api.ovh.com/auth/?credentialToken=iQ1joJE'
request = mocked.handlers[0].call['requests'][0]
body = json.loads(request.body.decode())
assert 'accessRules' in body
redirect_url = body['redirection'][len('http://testserver'):]
resp = app.get(redirect_url).follow()
assert 'Successfuly completed connector configuration' in resp.text
connector.refresh_from_db()
assert connector.consumer_key == 'xyz'