diff --git a/app/__init__.py b/app/__init__.py index 83e494de8..928da2966 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -19,7 +19,8 @@ from app.clients.sms.mmg import MMGClient from app.clients.statsd.statsd_client import StatsdClient from app.encryption import Encryption -DATETIME_FORMAT = "%Y-%m-%dT%H:%M:%S.%f" + +DATETIME_FORMAT = "%Y-%m-%dT%H:%M:%S.%fZ" DATE_FORMAT = "%Y-%m-%d" db = SQLAlchemy() @@ -99,8 +100,11 @@ def register_blueprint(application): def register_v2_blueprints(application): - from app.v2.notifications.post_notifications import notification_blueprint - application.register_blueprint(notification_blueprint) + from app.v2.notifications.post_notifications import notification_blueprint as post_notifications + from app.v2.notifications.get_notifications import notification_blueprint as get_notifications + + application.register_blueprint(post_notifications) + application.register_blueprint(get_notifications) def init_app(app): diff --git a/app/models.py b/app/models.py index bd805c400..eaebdfc03 100644 --- a/app/models.py +++ b/app/models.py @@ -1,11 +1,12 @@ import uuid import datetime +from flask import url_for from sqlalchemy.dialects.postgresql import ( UUID, JSON ) -from sqlalchemy import UniqueConstraint, and_ +from sqlalchemy import UniqueConstraint, and_, desc from sqlalchemy.orm import foreign, remote from notifications_utils.recipients import ( validate_email_address, @@ -22,7 +23,8 @@ from app.authentication.utils import get_secret from app import ( db, encryption, - DATETIME_FORMAT) + DATETIME_FORMAT +) from app.history_meta import Versioned @@ -281,6 +283,10 @@ class Template(db.Model): created_by = db.relationship('User') version = db.Column(db.Integer, default=1, nullable=False) + def get_link(self): + # TODO: use "/v2/" route once available + return url_for("template.get_template_by_id_and_service_id", service_id=self.service_id, template_id=self.id) + class TemplateHistory(db.Model): __tablename__ = 'templates_history' @@ -538,6 +544,64 @@ class Notification(db.Model): if personalisation: self._personalisation = encryption.encrypt(personalisation) + def cost(self): + if not self.sent_by or self.billable_units == 0: + return 0 + + provider_rate = db.session.query( + ProviderRates + ).join(ProviderDetails).filter( + ProviderDetails.identifier == self.sent_by, + ProviderRates.provider_id == ProviderDetails.id + ).order_by( + desc(ProviderRates.valid_from) + ).limit(1).one() + + return provider_rate.rate * self.billable_units + + def completed_at(self): + if self.status in [ + NOTIFICATION_DELIVERED, + NOTIFICATION_FAILED, + NOTIFICATION_TECHNICAL_FAILURE, + NOTIFICATION_TEMPORARY_FAILURE, + NOTIFICATION_PERMANENT_FAILURE + ]: + return self.updated_at.strftime(DATETIME_FORMAT) + + return None + + def serialize(self): + + template_dict = { + 'version': self.template.version, + 'id': self.template.id, + 'uri': self.template.get_link() + } + + serialized = { + "id": self.id, + "reference": self.client_reference, + "email_address": self.to if self.notification_type == EMAIL_TYPE else None, + "phone_number": self.to if self.notification_type == SMS_TYPE else None, + "line_1": None, + "line_2": None, + "line_3": None, + "line_4": None, + "line_5": None, + "line_6": None, + "postcode": None, + "cost": self.cost(), + "type": self.notification_type, + "status": self.status, + "template": template_dict, + "created_at": self.created_at.strftime(DATETIME_FORMAT), + "sent_at": self.sent_at.strftime(DATETIME_FORMAT) if self.sent_at else None, + "completed_at": self.completed_at() + } + + return serialized + class NotificationHistory(db.Model): __tablename__ = 'notification_history' diff --git a/app/notifications/process_notifications.py b/app/notifications/process_notifications.py index 9118117f6..1887eccc8 100644 --- a/app/notifications/process_notifications.py +++ b/app/notifications/process_notifications.py @@ -58,7 +58,7 @@ def persist_notification(template_id, notification_type=notification_type, api_key_id=api_key_id, key_type=key_type, - created_at=created_at or datetime.utcnow().strftime(DATETIME_FORMAT), + created_at=created_at or datetime.utcnow(), job_id=job_id, job_row_number=job_row_number, client_reference=reference diff --git a/app/schema_validation/__init__.py b/app/schema_validation/__init__.py index e3f267999..ee8f27943 100644 --- a/app/schema_validation/__init__.py +++ b/app/schema_validation/__init__.py @@ -10,12 +10,14 @@ def validate(json_to_validate, schema): @format_checker.checks('phone_number', raises=InvalidPhoneError) def validate_schema_phone_number(instance): - validate_phone_number(instance) + if instance is not None: + validate_phone_number(instance) return True @format_checker.checks('email_address', raises=InvalidEmailError) def validate_schema_email_address(instance): - validate_email_address(instance) + if instance is not None: + validate_email_address(instance) return True validator = Draft4Validator(schema, format_checker=format_checker) diff --git a/app/v2/notifications/get_notifications.py b/app/v2/notifications/get_notifications.py index b2fee08e0..405713dd4 100644 --- a/app/v2/notifications/get_notifications.py +++ b/app/v2/notifications/get_notifications.py @@ -1,9 +1,17 @@ +from flask import jsonify + +from app import api_user +from app.dao import notifications_dao from app.v2.notifications import notification_blueprint @notification_blueprint.route("/", methods=['GET']) def get_notification_by_id(id): - pass + notification = notifications_dao.get_notification_with_personalisation( + str(api_user.service_id), id, key_type=None + ) + + return jsonify(notification.serialize()), 200 @notification_blueprint.route("/", methods=['GET']) diff --git a/app/v2/notifications/notification_schemas.py b/app/v2/notifications/notification_schemas.py index d77a4d21a..4dfaee7b1 100644 --- a/app/v2/notifications/notification_schemas.py +++ b/app/v2/notifications/notification_schemas.py @@ -1,5 +1,79 @@ from app.schema_validation.definitions import (uuid, personalisation) +# this may belong in a templates module +template = { + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "template schema", + "type": "object", + "title": "notification content", + "properties": { + "id": uuid, + "version": {"type": "integer"}, + "uri": {"type": "string"} + }, + "required": ["id", "version", "uri"] +} + +get_notification_response = { + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "GET notification response schema", + "type": "object", + "title": "response v2/notification", + "oneOf": [ + {"properties": { + "email_address": {"type": "string", "format": "email_address"}, + "type": {"enum": ["email"]}, + + "phone_number": {"type": "null"}, + "line_1": {"type": "null"}, + "postcode": {"type": "null"} + }}, + {"properties": { + "phone_number": {"type": "string", "format": "phone_number"}, + "type": {"enum": ["sms"]}, + + "email_address": {"type": "null"}, + "line_1": {"type": "null"}, + "postcode": {"type": "null"} + }}, + {"properties": { + "line_1": {"type": "string", "minLength": 1}, + "postcode": {"type": "string", "minLength": 1}, + "type": {"enum": ["letter"]}, + + "email_address": {"type": "null"}, + "phone_number": {"type": "null"} + }} + ], + "properties": { + "id": uuid, + "reference": {"type": ["string", "null"]}, + "email_address": {"type": ["string", "null"]}, + "phone_number": {"type": ["string", "null"]}, + "line_1": {"type": ["string", "null"]}, + "line_2": {"type": ["string", "null"]}, + "line_3": {"type": ["string", "null"]}, + "line_4": {"type": ["string", "null"]}, + "line_5": {"type": ["string", "null"]}, + "line_6": {"type": ["string", "null"]}, + "postcode": {"type": ["string", "null"]}, + "cost": {"type": "number"}, + "type": {"enum": ["sms", "letter", "email"]}, + "status": {"type": "string"}, + "template": template, + "created_at": {"type": "string"}, + "sent_at": {"type": ["string", "null"]}, + "completed_at": {"type": ["string", "null"]} + }, + "required": [ + # technically, all keys are required since we always have all of them + "id", "reference", "email_address", "phone_number", + "line_1", "line_2", "line_3", "line_4", "line_5", "line_6", "postcode", + "cost", "type", "status", "template", + "created_at", "sent_at", "completed_at" + ] +} + post_sms_request = { "$schema": "http://json-schema.org/draft-04/schema#", "description": "POST sms notification schema", @@ -26,20 +100,6 @@ sms_content = { "required": ["body"] } -# this may belong in a templates module -template = { - "$schema": "http://json-schema.org/draft-04/schema#", - "description": "template schema", - "type": "object", - "title": "notification content", - "properties": { - "id": uuid, - "version": {"type": "integer"}, - "uri": {"type": "string"} - }, - "required": ["id", "version", "uri"] -} - post_sms_response = { "$schema": "http://json-schema.org/draft-04/schema#", "description": "POST sms notification response schema", @@ -47,7 +107,7 @@ post_sms_response = { "title": "response v2/notifications/sms", "properties": { "id": uuid, - "reference": {"type": "string"}, + "reference": {"type": ["string", "null"]}, "content": sms_content, "uri": {"type": "string"}, "template": template @@ -90,7 +150,7 @@ post_email_response = { "title": "response v2/notifications/email", "properties": { "id": uuid, - "reference": {"type": "string"}, + "reference": {"type": ["string", "null"]}, "content": email_content, "uri": {"type": "string"}, "template": template diff --git a/app/v2/notifications/post_notifications.py b/app/v2/notifications/post_notifications.py index 07127b730..df7f8edf1 100644 --- a/app/v2/notifications/post_notifications.py +++ b/app/v2/notifications/post_notifications.py @@ -38,7 +38,7 @@ def post_sms_notification(): notification_type=SMS_TYPE, api_key_id=api_user.id, key_type=api_user.key_type, - reference=form['reference']) + reference=form.get('reference')) send_notification_to_queue(notification, service.research_mode) resp = create_post_sms_response_from_notification(notification, @@ -65,7 +65,7 @@ def post_email_notification(): notification_type=EMAIL_TYPE, api_key_id=api_user.id, key_type=api_user.key_type, - reference=form['reference']) + reference=form.get('reference')) send_notification_to_queue(notification, service.research_mode) diff --git a/tests/app/celery/test_tasks.py b/tests/app/celery/test_tasks.py index b1ed36731..c17533296 100644 --- a/tests/app/celery/test_tasks.py +++ b/tests/app/celery/test_tasks.py @@ -76,7 +76,7 @@ def test_should_process_sms_job(sample_job, mocker): (str(sample_job.service_id), "uuid", "something_encrypted", - "2016-01-01T11:09:00.061258"), + "2016-01-01T11:09:00.061258Z"), queue="db-sms" ) job = jobs_dao.dao_get_job_by_id(sample_job.id) @@ -104,7 +104,7 @@ def test_should_process_sms_job_into_research_mode_queue_if_research_mode_servic (str(job.service_id), "uuid", "something_encrypted", - "2016-01-01T11:09:00.061258"), + "2016-01-01T11:09:00.061258Z"), queue="research-mode" ) @@ -133,7 +133,7 @@ def test_should_process_email_job_into_research_mode_queue_if_research_mode_serv (str(job.service_id), "uuid", "something_encrypted", - "2016-01-01T11:09:00.061258"), + "2016-01-01T11:09:00.061258Z"), queue="research-mode" ) @@ -252,7 +252,7 @@ def test_should_process_email_job_if_exactly_on_send_limits(notify_db, str(job.service_id), "uuid", "something_encrypted", - "2016-01-01T11:09:00.061258" + "2016-01-01T11:09:00.061258Z" ), queue="db-email" ) @@ -294,7 +294,7 @@ def test_should_process_email_job(sample_email_job, mocker): str(sample_email_job.service_id), "uuid", "something_encrypted", - "2016-01-01T11:09:00.061258" + "2016-01-01T11:09:00.061258Z" ), queue="db-email" ) diff --git a/tests/app/conftest.py b/tests/app/conftest.py index 566c888e1..76187e6e2 100644 --- a/tests/app/conftest.py +++ b/tests/app/conftest.py @@ -29,6 +29,7 @@ from app.dao.api_key_dao import save_model_api_key from app.dao.jobs_dao import dao_create_job from app.dao.notifications_dao import dao_create_notification from app.dao.invited_user_dao import save_invited_user +from app.dao.provider_rates_dao import create_provider_rates from app.clients.sms.firetext import FiretextClient @@ -409,7 +410,8 @@ def sample_notification(notify_db, create=True, personalisation=None, api_key_id=None, - key_type=KEY_TYPE_NORMAL): + key_type=KEY_TYPE_NORMAL, + sent_by=None): if created_at is None: created_at = datetime.utcnow() if service is None: @@ -441,7 +443,8 @@ def sample_notification(notify_db, 'personalisation': personalisation, 'notification_type': template.template_type, 'api_key_id': api_key_id, - 'key_type': key_type + 'key_type': key_type, + 'sent_by': sent_by } if job_row_number: data['job_row_number'] = job_row_number @@ -841,3 +844,12 @@ def sample_service_whitelist(notify_db, notify_db_session, service=None, email_a notify_db.session.add(whitelisted_user) notify_db.session.commit() return whitelisted_user + + +@pytest.fixture(scope='function') +def sample_provider_rate(notify_db, notify_db_session, valid_from=None, rate=None, provider_identifier=None): + create_provider_rates( + provider_identifier=provider_identifier if provider_identifier is not None else 'mmg', + valid_from=valid_from if valid_from is not None else datetime.utcnow(), + rate=rate if rate is not None else 1, + ) diff --git a/tests/app/invite/test_invite_rest.py b/tests/app/invite/test_invite_rest.py index 9cf9c2f13..f76296d8c 100644 --- a/tests/app/invite/test_invite_rest.py +++ b/tests/app/invite/test_invite_rest.py @@ -55,7 +55,7 @@ def test_create_invited_user(notify_api, sample_service, mocker, invitation_emai (str(current_app.config['NOTIFY_SERVICE_ID']), 'some_uuid', encryption.encrypt(message), - "2016-01-01T11:09:00.061258"), + "2016-01-01T11:09:00.061258Z"), queue="notify") diff --git a/tests/app/public_contracts/__init__.py b/tests/app/public_contracts/__init__.py index c3276dd83..c8dd707d3 100644 --- a/tests/app/public_contracts/__init__.py +++ b/tests/app/public_contracts/__init__.py @@ -2,15 +2,25 @@ import os from flask import json import jsonschema +from jsonschema import Draft4Validator -def validate(json_string, schema_filename): - schema_dir = os.path.join(os.path.dirname(__file__), 'schemas') +def return_json_from_response(response): + return json.loads(response.get_data(as_text=True)) + + +def validate_v0(json_to_validate, schema_filename): + schema_dir = os.path.join(os.path.dirname(__file__), 'schemas/v0') resolver = jsonschema.RefResolver('file://' + schema_dir + '/', None) with open(os.path.join(schema_dir, schema_filename)) as schema: jsonschema.validate( - json.loads(json_string), + json_to_validate, json.load(schema), format_checker=jsonschema.FormatChecker(), resolver=resolver ) + + +def validate(json_to_validate, schema): + validator = Draft4Validator(schema) + validator.validate(json_to_validate, schema) diff --git a/tests/app/public_contracts/schemas/GET_notification_return_email.json b/tests/app/public_contracts/schemas/v0/GET_notification_return_email.json similarity index 100% rename from tests/app/public_contracts/schemas/GET_notification_return_email.json rename to tests/app/public_contracts/schemas/v0/GET_notification_return_email.json diff --git a/tests/app/public_contracts/schemas/GET_notification_return_sms.json b/tests/app/public_contracts/schemas/v0/GET_notification_return_sms.json similarity index 100% rename from tests/app/public_contracts/schemas/GET_notification_return_sms.json rename to tests/app/public_contracts/schemas/v0/GET_notification_return_sms.json diff --git a/tests/app/public_contracts/schemas/GET_notifications_return.json b/tests/app/public_contracts/schemas/v0/GET_notifications_return.json similarity index 100% rename from tests/app/public_contracts/schemas/GET_notifications_return.json rename to tests/app/public_contracts/schemas/v0/GET_notifications_return.json diff --git a/tests/app/public_contracts/schemas/POST_notification_return_email.json b/tests/app/public_contracts/schemas/v0/POST_notification_return_email.json similarity index 100% rename from tests/app/public_contracts/schemas/POST_notification_return_email.json rename to tests/app/public_contracts/schemas/v0/POST_notification_return_email.json diff --git a/tests/app/public_contracts/schemas/POST_notification_return_sms.json b/tests/app/public_contracts/schemas/v0/POST_notification_return_sms.json similarity index 100% rename from tests/app/public_contracts/schemas/POST_notification_return_sms.json rename to tests/app/public_contracts/schemas/v0/POST_notification_return_sms.json diff --git a/tests/app/public_contracts/schemas/definitions.json b/tests/app/public_contracts/schemas/v0/definitions.json similarity index 100% rename from tests/app/public_contracts/schemas/definitions.json rename to tests/app/public_contracts/schemas/v0/definitions.json diff --git a/tests/app/public_contracts/schemas/email_notification.json b/tests/app/public_contracts/schemas/v0/email_notification.json similarity index 100% rename from tests/app/public_contracts/schemas/email_notification.json rename to tests/app/public_contracts/schemas/v0/email_notification.json diff --git a/tests/app/public_contracts/schemas/sms_notification.json b/tests/app/public_contracts/schemas/v0/sms_notification.json similarity index 100% rename from tests/app/public_contracts/schemas/sms_notification.json rename to tests/app/public_contracts/schemas/v0/sms_notification.json diff --git a/tests/app/public_contracts/test_GET_notification.py b/tests/app/public_contracts/test_GET_notification.py index fdc13e078..0a3adc74c 100644 --- a/tests/app/public_contracts/test_GET_notification.py +++ b/tests/app/public_contracts/test_GET_notification.py @@ -1,59 +1,65 @@ -from . import validate +from . import return_json_from_response, validate_v0, validate from app.models import ApiKey, KEY_TYPE_NORMAL -from app.dao.notifications_dao import dao_update_notification from app.dao.api_key_dao import save_model_api_key +from app.v2.notifications.notification_schemas import get_notification_response from tests import create_authorization_header -def test_get_api_sms_contract(client, sample_notification): - api_key = ApiKey(service=sample_notification.service, - name='api_key', - created_by=sample_notification.service.created_by, - key_type=KEY_TYPE_NORMAL) - save_model_api_key(api_key) - sample_notification.job = None - sample_notification.api_key = api_key - sample_notification.key_type = KEY_TYPE_NORMAL - dao_update_notification(sample_notification) - auth_header = create_authorization_header(service_id=sample_notification.service_id) - response = client.get('/notifications/{}'.format(sample_notification.id), headers=[auth_header]) +def _get_notification(client, notification, url): + save_model_api_key(ApiKey( + service=notification.service, + name='api_key', + created_by=notification.service.created_by, + key_type=KEY_TYPE_NORMAL + )) + auth_header = create_authorization_header(service_id=notification.service_id) + return client.get(url, headers=[auth_header]) - validate(response.get_data(as_text=True), 'GET_notification_return_sms.json') + +def test_get_v2_sms_contract(client, sample_notification): + response_json = return_json_from_response(_get_notification( + client, sample_notification, '/v2/notifications/{}'.format(sample_notification.id) + )) + validate(response_json, get_notification_response) + + +def test_get_v2_email_contract(client, sample_email_notification): + response_json = return_json_from_response(_get_notification( + client, sample_email_notification, '/v2/notifications/{}'.format(sample_email_notification.id) + )) + validate(response_json, get_notification_response) + + +def test_get_api_sms_contract(client, sample_notification): + response_json = return_json_from_response(_get_notification( + client, sample_notification, '/notifications/{}'.format(sample_notification.id) + )) + validate_v0(response_json, 'GET_notification_return_sms.json') def test_get_api_email_contract(client, sample_email_notification): - api_key = ApiKey(service=sample_email_notification.service, - name='api_key', - created_by=sample_email_notification.service.created_by, - key_type=KEY_TYPE_NORMAL) - save_model_api_key(api_key) - sample_email_notification.job = None - sample_email_notification.api_key = api_key - sample_email_notification.key_type = KEY_TYPE_NORMAL - dao_update_notification(sample_email_notification) - - auth_header = create_authorization_header(service_id=sample_email_notification.service_id) - response = client.get('/notifications/{}'.format(sample_email_notification.id), headers=[auth_header]) - - validate(response.get_data(as_text=True), 'GET_notification_return_email.json') + response_json = return_json_from_response(_get_notification( + client, sample_email_notification, '/notifications/{}'.format(sample_email_notification.id) + )) + validate_v0(response_json, 'GET_notification_return_email.json') def test_get_job_sms_contract(client, sample_notification): - auth_header = create_authorization_header(service_id=sample_notification.service_id) - response = client.get('/notifications/{}'.format(sample_notification.id), headers=[auth_header]) - - validate(response.get_data(as_text=True), 'GET_notification_return_sms.json') + response_json = return_json_from_response(_get_notification( + client, sample_notification, '/notifications/{}'.format(sample_notification.id) + )) + validate_v0(response_json, 'GET_notification_return_sms.json') def test_get_job_email_contract(client, sample_email_notification): - auth_header = create_authorization_header(service_id=sample_email_notification.service_id) - response = client.get('/notifications/{}'.format(sample_email_notification.id), headers=[auth_header]) - - validate(response.get_data(as_text=True), 'GET_notification_return_email.json') + response_json = return_json_from_response(_get_notification( + client, sample_email_notification, '/notifications/{}'.format(sample_email_notification.id) + )) + validate_v0(response_json, 'GET_notification_return_email.json') def test_get_notifications_contract(client, sample_notification, sample_email_notification): - auth_header = create_authorization_header(service_id=sample_notification.service_id) - response = client.get('/notifications', headers=[auth_header]) - - validate(response.get_data(as_text=True), 'GET_notifications_return.json') + response_json = return_json_from_response(_get_notification( + client, sample_notification, '/notifications' + )) + validate_v0(response_json, 'GET_notifications_return.json') diff --git a/tests/app/public_contracts/test_POST_notification.py b/tests/app/public_contracts/test_POST_notification.py index fe2468191..3acd5cf5d 100644 --- a/tests/app/public_contracts/test_POST_notification.py +++ b/tests/app/public_contracts/test_POST_notification.py @@ -1,44 +1,39 @@ from flask import json -from . import validate +from . import return_json_from_response, validate_v0 from tests import create_authorization_header +def _post_notification(client, template, url, to): + data = { + 'to': to, + 'template': str(template.id) + } + + auth_header = create_authorization_header(service_id=template.service_id) + + return client.post( + path=url, + data=json.dumps(data), + headers=[('Content-Type', 'application/json'), auth_header] + ) + + def test_post_sms_contract(client, mocker, sample_template): mocker.patch('app.celery.provider_tasks.deliver_sms.apply_async') mocker.patch('app.encryption.encrypt', return_value="something_encrypted") - data = { - 'to': '07700 900 855', - 'template': str(sample_template.id) - } - - auth_header = create_authorization_header(service_id=sample_template.service_id) - - response = client.post( - path='/notifications/sms', - data=json.dumps(data), - headers=[('Content-Type', 'application/json'), auth_header] - ) - - validate(response.get_data(as_text=True), 'POST_notification_return_sms.json') + response_json = return_json_from_response(_post_notification( + client, sample_template, url='/notifications/sms', to='07700 900 855' + )) + validate_v0(response_json, 'POST_notification_return_sms.json') def test_post_email_contract(client, mocker, sample_email_template): mocker.patch('app.celery.provider_tasks.deliver_email.apply_async') mocker.patch('app.encryption.encrypt', return_value="something_encrypted") - data = { - 'to': 'foo@bar.com', - 'template': str(sample_email_template.id) - } - - auth_header = create_authorization_header(service_id=sample_email_template.service_id) - - response = client.post( - path='/notifications/email', - data=json.dumps(data), - headers=[('Content-Type', 'application/json'), auth_header] - ) - - validate(response.get_data(as_text=True), 'POST_notification_return_email.json') + response_json = return_json_from_response(_post_notification( + client, sample_email_template, url='/notifications/email', to='foo@bar.com' + )) + validate_v0(response_json, 'POST_notification_return_email.json') diff --git a/tests/app/test_model.py b/tests/app/test_model.py index abbd167a5..1625d7119 100644 --- a/tests/app/test_model.py +++ b/tests/app/test_model.py @@ -1,9 +1,7 @@ -import uuid +import pytest from datetime import datetime -import pytest - -from app import DATETIME_FORMAT +from tests.app.conftest import sample_notification, sample_provider_rate from app.models import ( Notification, ServiceWhitelist, @@ -37,3 +35,29 @@ def test_should_build_service_whitelist_from_email_address(email_address): def test_should_not_build_service_whitelist_from_invalid_contact(recipient_type, contact): with pytest.raises(ValueError): ServiceWhitelist.from_string('service_id', recipient_type, contact) + + +@pytest.mark.parametrize('provider, billable_units, expected_cost', [ + ('mmg', 1, 3.5), + ('firetext', 2, 5), + ('ses', 0, 0) +]) +def test_calculate_cost_from_notification_billable_units( + notify_db, notify_db_session, provider, billable_units, expected_cost +): + provider_rates = [ + ('mmg', datetime(2016, 7, 1), 1.5), + ('firetext', datetime(2016, 7, 1), 2.5), + ('mmg', datetime.utcnow(), 3.5), + ] + for provider_identifier, valid_from, rate in provider_rates: + sample_provider_rate( + notify_db, + notify_db_session, + provider_identifier=provider_identifier, + valid_from=valid_from, + rate=rate + ) + + notification = sample_notification(notify_db, notify_db_session, billable_units=billable_units, sent_by=provider) + assert notification.cost() == expected_cost diff --git a/tests/app/user/test_rest.py b/tests/app/user/test_rest.py index 8c87893aa..a6513e0f9 100644 --- a/tests/app/user/test_rest.py +++ b/tests/app/user/test_rest.py @@ -445,7 +445,7 @@ def test_send_user_reset_password_should_send_reset_password_link(notify_api, [str(current_app.config['NOTIFY_SERVICE_ID']), 'some_uuid', app.encryption.encrypt(message), - "2016-01-01T11:09:00.061258"], + "2016-01-01T11:09:00.061258Z"], queue="notify") @@ -525,7 +525,7 @@ def test_send_already_registered_email(notify_api, sample_user, already_register (str(current_app.config['NOTIFY_SERVICE_ID']), 'some_uuid', app.encryption.encrypt(message), - "2016-01-01T11:09:00.061258"), + "2016-01-01T11:09:00.061258Z"), queue="notify") @@ -573,7 +573,7 @@ def test_send_user_confirm_new_email_returns_204(notify_api, sample_user, change str(current_app.config['NOTIFY_SERVICE_ID']), "some_uuid", app.encryption.encrypt(message), - "2016-01-01T11:09:00.061258"), queue="notify") + "2016-01-01T11:09:00.061258Z"), queue="notify") def test_send_user_confirm_new_email_returns_400_when_email_missing(notify_api, sample_user, mocker): diff --git a/tests/app/user/test_rest_verify.py b/tests/app/user/test_rest_verify.py index f38575b55..006247292 100644 --- a/tests/app/user/test_rest_verify.py +++ b/tests/app/user/test_rest_verify.py @@ -248,7 +248,7 @@ def test_send_user_sms_code(notify_api, ([current_app.config['NOTIFY_SERVICE_ID'], "some_uuid", encrypted, - "2016-01-01T11:09:00.061258"]), + "2016-01-01T11:09:00.061258Z"]), queue="notify" ) @@ -288,7 +288,7 @@ def test_send_user_code_for_sms_with_optional_to_field(notify_api, ([current_app.config['NOTIFY_SERVICE_ID'], "some_uuid", encrypted, - "2016-01-01T11:09:00.061258"]), + "2016-01-01T11:09:00.061258Z"]), queue="notify" ) @@ -343,7 +343,7 @@ def test_send_user_email_verification(notify_api, (str(current_app.config['NOTIFY_SERVICE_ID']), 'some_uuid', encryption.encrypt(message), - "2016-01-01T11:09:00.061258"), + "2016-01-01T11:09:00.061258Z"), queue="notify") diff --git a/tests/app/v2/notifications/test_get_notifications.py b/tests/app/v2/notifications/test_get_notifications.py new file mode 100644 index 000000000..a9c583b5b --- /dev/null +++ b/tests/app/v2/notifications/test_get_notifications.py @@ -0,0 +1,46 @@ +import json + +from app import DATETIME_FORMAT +from tests import create_authorization_header + + +def test_get_notification_by_id_returns_200(client, sample_notification): + auth_header = create_authorization_header(service_id=sample_notification.service_id) + + response = client.get( + path='/v2/notifications/{}'.format(sample_notification.id), + headers=[('Content-Type', 'application/json'), auth_header]) + + assert response.status_code == 200 + assert response.headers['Content-type'] == 'application/json' + + json_response = json.loads(response.get_data(as_text=True)) + + expected_template_response = { + 'id': '{}'.format(sample_notification.serialize()['template']['id']), + 'version': sample_notification.serialize()['template']['version'], + 'uri': sample_notification.serialize()['template']['uri'] + } + + expected_response = { + 'id': '{}'.format(sample_notification.id), + 'reference': None, + 'email_address': None, + 'phone_number': '{}'.format(sample_notification.to), + 'line_1': None, + 'line_2': None, + 'line_3': None, + 'line_4': None, + 'line_5': None, + 'line_6': None, + 'postcode': None, + 'cost': sample_notification.cost(), + 'type': '{}'.format(sample_notification.notification_type), + 'status': '{}'.format(sample_notification.status), + 'template': expected_template_response, + 'created_at': sample_notification.created_at.strftime(DATETIME_FORMAT), + 'sent_at': sample_notification.sent_at, + 'completed_at': sample_notification.completed_at() + } + + assert json_response == expected_response diff --git a/tests/app/v2/notifications/test_notification_schemas.py b/tests/app/v2/notifications/test_notification_schemas.py index 8b6620444..73afbb29e 100644 --- a/tests/app/v2/notifications/test_notification_schemas.py +++ b/tests/app/v2/notifications/test_notification_schemas.py @@ -3,10 +3,10 @@ import uuid import pytest from flask import json from jsonschema import ValidationError -from notifications_utils.recipients import InvalidPhoneError, InvalidEmailError -from app.v2.notifications.notification_schemas import post_sms_request, post_sms_response, post_email_request, \ - post_email_response +from app.v2.notifications.notification_schemas import ( + post_sms_request, post_sms_response, post_email_request, post_email_response +) from app.schema_validation import validate valid_json = {"phone_number": "07515111111", diff --git a/tests/app/v2/notifications/test_post_notifications.py b/tests/app/v2/notifications/test_post_notifications.py index a509d2a2f..a2d99fb64 100644 --- a/tests/app/v2/notifications/test_post_notifications.py +++ b/tests/app/v2/notifications/test_post_notifications.py @@ -6,15 +6,17 @@ from app.models import Notification from tests import create_authorization_header -def test_post_sms_notification_returns_201(notify_api, sample_template, mocker): +@pytest.mark.parametrize("reference", [None, "reference_from_client"]) +def test_post_sms_notification_returns_201(notify_api, sample_template, mocker, reference): with notify_api.test_request_context(): with notify_api.test_client() as client: mocked = mocker.patch('app.celery.provider_tasks.deliver_sms.apply_async') data = { 'phone_number': '+447700900855', - 'template_id': str(sample_template.id), - 'reference': 'reference_from_client' + 'template_id': str(sample_template.id) } + if reference: + data.update({"reference": reference}) auth_header = create_authorization_header(service_id=sample_template.service_id) response = client.post( @@ -27,8 +29,8 @@ def test_post_sms_notification_returns_201(notify_api, sample_template, mocker): notifications = Notification.query.all() assert len(notifications) == 1 notification_id = notifications[0].id - assert resp_json['id'] is not None - assert resp_json['reference'] == 'reference_from_client' + assert resp_json['id'] == str(notification_id) + assert resp_json['reference'] == reference assert resp_json['content']['body'] == sample_template.content assert resp_json['content']['from_number'] == sample_template.service.sms_sender assert 'v2/notifications/{}'.format(notification_id) in resp_json['uri'] @@ -105,13 +107,15 @@ def test_post_sms_notification_returns_400_and_for_schema_problems(notify_api, s }] -def test_post_email_notification_returns_201(client, sample_email_template, mocker): +@pytest.mark.parametrize("reference", [None, "reference_from_client"]) +def test_post_email_notification_returns_201(client, sample_email_template, mocker, reference): mocked = mocker.patch('app.celery.provider_tasks.deliver_email.apply_async') data = { - "reference": "reference from caller", "email_address": sample_email_template.service.users[0].email_address, "template_id": sample_email_template.id, } + if reference: + data.update({"reference": reference}) auth_header = create_authorization_header(service_id=sample_email_template.service_id) response = client.post( path="v2/notifications/email", @@ -121,7 +125,7 @@ def test_post_email_notification_returns_201(client, sample_email_template, mock resp_json = json.loads(response.get_data(as_text=True)) notification = Notification.query.first() assert resp_json['id'] == str(notification.id) - assert resp_json['reference'] == "reference from caller" + assert resp_json['reference'] == reference assert notification.reference is None assert resp_json['content']['body'] == sample_email_template.content assert resp_json['content']['subject'] == sample_email_template.subject