mirror of
https://github.com/GSA/notifications-api.git
synced 2026-09-05 21:28:25 -04:00
@@ -5,7 +5,7 @@ import uuid
|
|||||||
|
|
||||||
from flask import Flask, _request_ctx_stack
|
from flask import Flask, _request_ctx_stack
|
||||||
from flask import request, g, jsonify
|
from flask import request, g, jsonify
|
||||||
from flask.ext.sqlalchemy import SQLAlchemy
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
from flask_marshmallow import Marshmallow
|
from flask_marshmallow import Marshmallow
|
||||||
from monotonic import monotonic
|
from monotonic import monotonic
|
||||||
from notifications_utils.clients.statsd.statsd_client import StatsdClient
|
from notifications_utils.clients.statsd.statsd_client import StatsdClient
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import uuid
|
import uuid
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
from flask.ext.script import Command, Manager, Option
|
from flask_script import Command, Manager, Option
|
||||||
|
|
||||||
from app import db
|
from app import db
|
||||||
from app.dao.monthly_billing_dao import (
|
from app.dao.monthly_billing_dao import (
|
||||||
|
|||||||
@@ -270,27 +270,6 @@ def get_notifications_for_job(service_id, job_id, filter_dict=None, page=1, page
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@statsd(namespace="dao")
|
|
||||||
def get_notification_billable_unit_count_per_month(service_id, year):
|
|
||||||
month = get_london_month_from_utc_column(NotificationHistory.created_at)
|
|
||||||
|
|
||||||
start_date, end_date = get_financial_year(year)
|
|
||||||
notifications = db.session.query(
|
|
||||||
month,
|
|
||||||
func.sum(NotificationHistory.billable_units)
|
|
||||||
).filter(
|
|
||||||
NotificationHistory.billable_units != 0,
|
|
||||||
NotificationHistory.service_id == service_id,
|
|
||||||
NotificationHistory.created_at.between(start_date, end_date)
|
|
||||||
).group_by(
|
|
||||||
month
|
|
||||||
).order_by(
|
|
||||||
month
|
|
||||||
).all()
|
|
||||||
|
|
||||||
return [(datetime.strftime(x[0], "%B"), x[1]) for x in notifications]
|
|
||||||
|
|
||||||
|
|
||||||
@statsd(namespace="dao")
|
@statsd(namespace="dao")
|
||||||
def get_notification_with_personalisation(service_id, notification_id, key_type):
|
def get_notification_with_personalisation(service_id, notification_id, key_type):
|
||||||
filter_dict = {'service_id': service_id, 'id': notification_id}
|
filter_dict = {'service_id': service_id, 'id': notification_id}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from flask.ext.bcrypt import generate_password_hash, check_password_hash
|
from flask_bcrypt import generate_password_hash, check_password_hash
|
||||||
|
|
||||||
from itsdangerous import URLSafeSerializer
|
from itsdangerous import URLSafeSerializer
|
||||||
|
|
||||||
|
|||||||
@@ -450,16 +450,6 @@ def resume_service(service_id):
|
|||||||
return '', 204
|
return '', 204
|
||||||
|
|
||||||
|
|
||||||
@service_blueprint.route('/<uuid:service_id>/billable-units')
|
|
||||||
def get_billable_unit_count(service_id):
|
|
||||||
try:
|
|
||||||
return jsonify(notifications_dao.get_notification_billable_unit_count_per_month(
|
|
||||||
service_id, int(request.args.get('year'))
|
|
||||||
))
|
|
||||||
except TypeError:
|
|
||||||
return jsonify(result='error', message='No valid year provided'), 400
|
|
||||||
|
|
||||||
|
|
||||||
@service_blueprint.route('/<uuid:service_id>/notifications/templates/monthly', methods=['GET'])
|
@service_blueprint.route('/<uuid:service_id>/notifications/templates/monthly', methods=['GET'])
|
||||||
def get_monthly_template_stats(service_id):
|
def get_monthly_template_stats(service_id):
|
||||||
service = dao_fetch_service_by_id(service_id)
|
service = dao_fetch_service_by_id(service_id)
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
|
|
||||||
from __future__ import print_function
|
from __future__ import print_function
|
||||||
import os
|
import os
|
||||||
from flask.ext.script import Manager, Server
|
from flask_script import Manager, Server
|
||||||
from flask.ext.migrate import Migrate, MigrateCommand
|
from flask_migrate import Migrate, MigrateCommand
|
||||||
from app import (create_app, db, commands)
|
from app import (create_app, db, commands)
|
||||||
|
|
||||||
application = create_app()
|
application = create_app()
|
||||||
|
|||||||
2
db.py
2
db.py
@@ -1,4 +1,4 @@
|
|||||||
from flask.ext.script import Manager, Server
|
from flask_script import Manager, Server
|
||||||
from flask_migrate import Migrate, MigrateCommand
|
from flask_migrate import Migrate, MigrateCommand
|
||||||
|
|
||||||
from app import create_app, db
|
from app import create_app, db
|
||||||
|
|||||||
@@ -1,25 +1,24 @@
|
|||||||
apispec==0.25.0
|
apispec==0.25.0
|
||||||
Flask==0.10.1
|
boto3==1.4.6
|
||||||
Flask-Script==2.0.5
|
|
||||||
Flask-Migrate==2.1.0
|
|
||||||
Flask-SQLAlchemy==2.0
|
|
||||||
psycopg2==2.7.3
|
|
||||||
SQLAlchemy==1.0.15
|
|
||||||
SQLAlchemy-Utils==0.32.14
|
|
||||||
PyJWT==1.5.2
|
|
||||||
marshmallow==2.4.2
|
|
||||||
marshmallow-sqlalchemy==0.13.1
|
|
||||||
flask-marshmallow==0.6.2
|
|
||||||
Flask-Bcrypt==0.6.2
|
|
||||||
boto3==1.4.4
|
|
||||||
monotonic==1.3
|
|
||||||
statsd==3.2.1
|
|
||||||
jsonschema==2.6.0
|
|
||||||
gunicorn==19.7.1
|
|
||||||
docopt==0.6.2
|
|
||||||
six==1.10.0
|
|
||||||
iso8601==0.1.12
|
|
||||||
celery==3.1.25 # pyup: <4
|
celery==3.1.25 # pyup: <4
|
||||||
|
docopt==0.6.2
|
||||||
|
Flask-Bcrypt==0.6.2
|
||||||
|
Flask-Migrate==2.1.0
|
||||||
|
Flask-Script==2.0.5
|
||||||
|
Flask-SQLAlchemy==2.2
|
||||||
|
Flask==0.12.2
|
||||||
|
gunicorn==19.7.1
|
||||||
|
iso8601==0.1.12
|
||||||
|
jsonschema==2.6.0
|
||||||
|
marshmallow-sqlalchemy==0.13.1
|
||||||
|
marshmallow==2.13.6
|
||||||
|
monotonic==1.3
|
||||||
|
psycopg2==2.7.3
|
||||||
|
PyJWT==1.5.2
|
||||||
|
six==1.10.0
|
||||||
|
SQLAlchemy-Utils==0.32.14
|
||||||
|
SQLAlchemy==1.1.13
|
||||||
|
statsd==3.2.1
|
||||||
|
|
||||||
notifications-python-client==4.3.1
|
notifications-python-client==4.3.1
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from flask.ext.script import Manager, Server
|
from flask_script import Manager, Server
|
||||||
from flask_migrate import Migrate, MigrateCommand
|
from flask_migrate import Migrate, MigrateCommand
|
||||||
from app import (create_app, db, commands)
|
from app import (create_app, db, commands)
|
||||||
import os
|
import os
|
||||||
|
|||||||
@@ -1184,10 +1184,13 @@ def test_send_inbound_sms_to_service_retries_if_request_returns_500(notify_api,
|
|||||||
status_code=500)
|
status_code=500)
|
||||||
send_inbound_sms_to_service(inbound_sms.id, inbound_sms.service_id)
|
send_inbound_sms_to_service(inbound_sms.id, inbound_sms.service_id)
|
||||||
|
|
||||||
mocked.assert_called_with(
|
exc_msg = 'Unable to send_inbound_sms_to_service for service_id: {} and url: {url}'.format(
|
||||||
exc='Unable to send_inbound_sms_to_service for service_id: {} '
|
sample_service.id,
|
||||||
'and url: {}. \n500 Server Error: None'.format(sample_service.id, inbound_api.url),
|
url=inbound_api.url
|
||||||
queue="retry-tasks")
|
)
|
||||||
|
assert mocked.call_count == 1
|
||||||
|
assert mocked.call_args[1]['queue'] == 'retry-tasks'
|
||||||
|
assert exc_msg in mocked.call_args[1]['exc']
|
||||||
|
|
||||||
|
|
||||||
def test_send_inbound_sms_to_service_does_not_retries_if_request_returns_404(notify_api, sample_service, mocker):
|
def test_send_inbound_sms_to_service_does_not_retries_if_request_returns_404(notify_api, sample_service, mocker):
|
||||||
|
|||||||
@@ -31,7 +31,6 @@ from app.dao.notifications_dao import (
|
|||||||
delete_notifications_created_more_than_a_week_ago_by_type,
|
delete_notifications_created_more_than_a_week_ago_by_type,
|
||||||
get_notification_by_id,
|
get_notification_by_id,
|
||||||
get_notification_for_job,
|
get_notification_for_job,
|
||||||
get_notification_billable_unit_count_per_month,
|
|
||||||
get_notification_with_personalisation,
|
get_notification_with_personalisation,
|
||||||
get_notifications_for_job,
|
get_notifications_for_job,
|
||||||
get_notifications_for_service,
|
get_notifications_for_service,
|
||||||
@@ -914,38 +913,6 @@ def test_get_all_notifications_for_job_by_status(notify_db, notify_db_session, s
|
|||||||
assert len(notifications(filter_dict={'status': NOTIFICATION_STATUS_TYPES[:3]}).items) == 3
|
assert len(notifications(filter_dict={'status': NOTIFICATION_STATUS_TYPES[:3]}).items) == 3
|
||||||
|
|
||||||
|
|
||||||
def test_get_notification_billable_unit_count_per_month(notify_db, notify_db_session, sample_service):
|
|
||||||
|
|
||||||
for year, month, day, hour, minute, second in (
|
|
||||||
(2017, 1, 15, 23, 59, 59), # ↓ 2016 financial year
|
|
||||||
(2016, 9, 30, 23, 59, 59), # counts in October with BST conversion
|
|
||||||
(2016, 6, 30, 23, 50, 20),
|
|
||||||
(2016, 7, 15, 9, 20, 25),
|
|
||||||
(2016, 4, 1, 1, 1, 00),
|
|
||||||
(2016, 4, 1, 0, 0, 00),
|
|
||||||
(2016, 3, 31, 23, 00, 1), # counts in April with BST conversion
|
|
||||||
(2015, 4, 1, 13, 8, 59), # ↓ 2015 financial year
|
|
||||||
(2015, 11, 20, 22, 40, 45),
|
|
||||||
(2016, 1, 31, 23, 30, 40) # counts in January no BST conversion in winter
|
|
||||||
):
|
|
||||||
sample_notification(
|
|
||||||
notify_db, notify_db_session, service=sample_service,
|
|
||||||
created_at=datetime(
|
|
||||||
year, month, day, hour, minute, second, 0
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
for financial_year, months in (
|
|
||||||
(2017, []),
|
|
||||||
(2016, [('April', 3), ('July', 2), ('October', 1), ('January', 1)]),
|
|
||||||
(2015, [('April', 1), ('November', 1), ('January', 1)]),
|
|
||||||
(2014, [])
|
|
||||||
):
|
|
||||||
assert get_notification_billable_unit_count_per_month(
|
|
||||||
sample_service.id, financial_year
|
|
||||||
) == months
|
|
||||||
|
|
||||||
|
|
||||||
def test_update_notification_sets_status(sample_notification):
|
def test_update_notification_sets_status(sample_notification):
|
||||||
assert sample_notification.status == 'created'
|
assert sample_notification.status == 'created'
|
||||||
sample_notification.status = 'failed'
|
sample_notification.status = 'failed'
|
||||||
|
|||||||
@@ -41,56 +41,65 @@ def test_receive_notification_returns_received_to_mmg(client, mocker, sample_ser
|
|||||||
[str(inbound_sms_id), str(sample_service_full_permissions.id)], queue="notify-internal-tasks")
|
[str(inbound_sms_id), str(sample_service_full_permissions.id)], queue="notify-internal-tasks")
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('provider,headers,data,expected_response', [
|
|
||||||
(
|
|
||||||
'mmg',
|
|
||||||
[('Content-Type', 'application/json')],
|
|
||||||
json.dumps({
|
|
||||||
"ID": "1234",
|
|
||||||
"MSISDN": "447700900855",
|
|
||||||
"Message": "Some message to notify",
|
|
||||||
"Trigger": "Trigger?",
|
|
||||||
"Number": "testing",
|
|
||||||
"Channel": "SMS",
|
|
||||||
"DateRecieved": "2012-06-27 12:33:00"
|
|
||||||
}),
|
|
||||||
'RECEIVED'
|
|
||||||
),
|
|
||||||
(
|
|
||||||
'firetext',
|
|
||||||
None,
|
|
||||||
{
|
|
||||||
"Message": "Some message to notify",
|
|
||||||
"source": "Source",
|
|
||||||
"time": "2012-06-27 12:33:00",
|
|
||||||
"destination": "447700900855"
|
|
||||||
},
|
|
||||||
'{\n "status": "ok"\n}'
|
|
||||||
),
|
|
||||||
])
|
|
||||||
@pytest.mark.parametrize('permissions', [
|
@pytest.mark.parametrize('permissions', [
|
||||||
([SMS_TYPE]),
|
[SMS_TYPE],
|
||||||
([INBOUND_SMS_TYPE]),
|
[INBOUND_SMS_TYPE],
|
||||||
])
|
])
|
||||||
def test_receive_notification_without_permissions_does_not_create_inbound(
|
def test_receive_notification_from_mmg_without_permissions_does_not_persist(
|
||||||
client, mocker, notify_db, notify_db_session, permissions, provider, headers, data, expected_response):
|
client,
|
||||||
service = sample_service(notify_db, notify_db_session, permissions=permissions)
|
mocker,
|
||||||
mocker.patch("app.notifications.receive_notifications.dao_fetch_services_by_sms_sender",
|
notify_db_session,
|
||||||
return_value=[service])
|
permissions
|
||||||
mocked_send_inbound_sms = mocker.patch(
|
):
|
||||||
"app.notifications.receive_notifications.tasks.send_inbound_sms_to_service.apply_async")
|
mocked = mocker.patch("app.notifications.receive_notifications.tasks.send_inbound_sms_to_service.apply_async")
|
||||||
mocked_has_permissions = mocker.patch(
|
service = create_service(sms_sender='07111111111', service_permissions=permissions)
|
||||||
"app.notifications.receive_notifications.has_inbound_sms_permissions", return_value=False)
|
data = {
|
||||||
|
"ID": "1234",
|
||||||
response = client.post(path='/notifications/sms/receive/{}'.format(provider),
|
"MSISDN": "07111111111",
|
||||||
data=data,
|
"Message": "Some message to notify",
|
||||||
headers=headers)
|
"Trigger": "Trigger?",
|
||||||
|
"Number": "testing",
|
||||||
|
"Channel": "SMS",
|
||||||
|
"DateRecieved": "2012-06-27 12:33:00"
|
||||||
|
}
|
||||||
|
response = client.post(
|
||||||
|
path='/notifications/sms/receive/mmg',
|
||||||
|
data=json.dumps(data),
|
||||||
|
headers=[('Content-Type', 'application/json')]
|
||||||
|
)
|
||||||
|
|
||||||
assert response.status_code == 200
|
assert response.status_code == 200
|
||||||
assert response.get_data(as_text=True) == expected_response
|
assert response.get_data(as_text=True) == 'RECEIVED'
|
||||||
assert len(InboundSms.query.all()) == 0
|
assert InboundSms.query.count() == 0
|
||||||
assert mocked_has_permissions.called
|
assert mocked.called is False
|
||||||
mocked_send_inbound_sms.assert_not_called()
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('permissions', [
|
||||||
|
[SMS_TYPE],
|
||||||
|
[INBOUND_SMS_TYPE],
|
||||||
|
])
|
||||||
|
def test_receive_notification_from_firetext_without_permissions_does_not_persist(
|
||||||
|
client,
|
||||||
|
mocker,
|
||||||
|
notify_db_session,
|
||||||
|
permissions
|
||||||
|
):
|
||||||
|
service = create_service(sms_sender='07111111111', service_permissions=permissions)
|
||||||
|
mocked = mocker.patch("app.notifications.receive_notifications.tasks.send_inbound_sms_to_service.apply_async")
|
||||||
|
|
||||||
|
data = "source=07999999999&destination=07111111111&message=this is a message&time=2017-01-01 12:00:00"
|
||||||
|
response = client.post(
|
||||||
|
path='/notifications/sms/receive/firetext',
|
||||||
|
data=data,
|
||||||
|
headers=[('Content-Type', 'application/x-www-form-urlencoded')]
|
||||||
|
)
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
result = json.loads(response.get_data(as_text=True))
|
||||||
|
|
||||||
|
assert result['status'] == 'ok'
|
||||||
|
assert InboundSms.query.count() == 0
|
||||||
|
assert mocked.called is False
|
||||||
|
|
||||||
|
|
||||||
def test_receive_notification_without_permissions_does_not_create_inbound_even_with_inbound_number_set(
|
def test_receive_notification_without_permissions_does_not_create_inbound_even_with_inbound_number_set(
|
||||||
|
|||||||
@@ -1665,30 +1665,6 @@ def test_get_detailed_services_for_date_range(notify_db, notify_db_session, set_
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@freeze_time('2012-12-12T12:00:01')
|
|
||||||
def test_get_notification_billable_unit_count(client, notify_db, notify_db_session):
|
|
||||||
notification = create_sample_notification(notify_db, notify_db_session)
|
|
||||||
response = client.get(
|
|
||||||
'/service/{}/billable-units?year=2012'.format(notification.service_id),
|
|
||||||
headers=[create_authorization_header()]
|
|
||||||
)
|
|
||||||
assert response.status_code == 200
|
|
||||||
assert json.loads(response.get_data(as_text=True)) == {
|
|
||||||
'December': 1
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def test_get_notification_billable_unit_count_missing_year(client, sample_service):
|
|
||||||
response = client.get(
|
|
||||||
'/service/{}/billable-units'.format(sample_service.id),
|
|
||||||
headers=[create_authorization_header()]
|
|
||||||
)
|
|
||||||
assert response.status_code == 400
|
|
||||||
assert json.loads(response.get_data(as_text=True)) == {
|
|
||||||
'message': 'No valid year provided', 'result': 'error'
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('query_string, expected_status, expected_json', [
|
@pytest.mark.parametrize('query_string, expected_status, expected_json', [
|
||||||
('', 200, {'data': {'email_count': 0, 'sms_count': 0}}),
|
('', 200, {'data': {'email_count': 0, 'sms_count': 0}}),
|
||||||
('?year=2000', 200, {'data': {'email_count': 0, 'sms_count': 0}}),
|
('?year=2000', 200, {'data': {'email_count': 0, 'sms_count': 0}}),
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ import boto3
|
|||||||
import pytest
|
import pytest
|
||||||
from alembic.command import upgrade
|
from alembic.command import upgrade
|
||||||
from alembic.config import Config
|
from alembic.config import Config
|
||||||
from flask.ext.migrate import Migrate, MigrateCommand
|
from flask_migrate import Migrate, MigrateCommand
|
||||||
from flask.ext.script import Manager
|
from flask_script import Manager
|
||||||
|
|
||||||
from app import create_app, db
|
from app import create_app, db
|
||||||
|
|
||||||
@@ -18,16 +18,16 @@ def notify_api():
|
|||||||
# deattach server-error error handlers - error_handler_spec looks like:
|
# deattach server-error error handlers - error_handler_spec looks like:
|
||||||
# {'blueprint_name': {
|
# {'blueprint_name': {
|
||||||
# status_code: [error_handlers],
|
# status_code: [error_handlers],
|
||||||
# None: [ tuples of (exception, )]
|
# None: { ExceptionClass: error_handler }
|
||||||
# }}
|
# }}
|
||||||
for error_handlers in app.error_handler_spec.values():
|
for error_handlers in app.error_handler_spec.values():
|
||||||
error_handlers.pop(500, None)
|
error_handlers.pop(500, None)
|
||||||
if None in error_handlers:
|
if None in error_handlers:
|
||||||
error_handlers[None] = [
|
error_handlers[None] = {
|
||||||
exception_handler
|
exc_class: error_handler
|
||||||
for exception_handler in error_handlers[None]
|
for exc_class, error_handler in error_handlers[None].items()
|
||||||
if exception_handler[0] != Exception
|
if exc_class != Exception
|
||||||
]
|
}
|
||||||
if error_handlers[None] == []:
|
if error_handlers[None] == []:
|
||||||
error_handlers.pop(None)
|
error_handlers.pop(None)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user