From fddb1653ac4bf2caa3a17822911d34c21dc2a2c1 Mon Sep 17 00:00:00 2001 From: Paul Craig Date: Fri, 18 Nov 2016 15:09:15 +0000 Subject: [PATCH 1/9] Add `.cost()` to notification model In the V2 API, the GET response for an individual notification returns a 'cost' value, which we can get by multiplying the billable units by the per-message rate of the supplier who sent the message. Any notifications with billable units > 0 but without a corresponding `ProviderRates` entry will blow up the application, so make sure you've got one. --- app/models.py | 17 +++++++++- tests/app/conftest.py | 16 ++++++++-- tests/app/test_model.py | 32 ++++++++++++++++--- .../test_notification_schemas.py | 1 - 4 files changed, 58 insertions(+), 8 deletions(-) diff --git a/app/models.py b/app/models.py index bd805c400..a1d7fa164 100644 --- a/app/models.py +++ b/app/models.py @@ -5,7 +5,7 @@ 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, @@ -538,6 +538,21 @@ 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 + class NotificationHistory(db.Model): __tablename__ = 'notification_history' 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/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/v2/notifications/test_notification_schemas.py b/tests/app/v2/notifications/test_notification_schemas.py index 8b6620444..5597ad377 100644 --- a/tests/app/v2/notifications/test_notification_schemas.py +++ b/tests/app/v2/notifications/test_notification_schemas.py @@ -3,7 +3,6 @@ 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 9758b96a2bf032bb52985bb0a3540d6946e7a9fe Mon Sep 17 00:00:00 2001 From: Paul Craig Date: Fri, 18 Nov 2016 17:36:11 +0000 Subject: [PATCH 2/9] Create 'v2' get notification route The new 'v2' API wants to return less data than the previous one, which was sending back tons of fields the clients never used. This new route returns only useful information, with the JSON response dict being built up in the model's `.serialize()` method. Note that writing the test for this was a bit painful because of having to treat loads of keys differently. Hopefully we think this is a good way to write this test, because if we don't, we should start thinking of a better way to check the values are what we expect. --- app/__init__.py | 7 ++- app/models.py | 51 ++++++++++++++++++- app/v2/notifications/get_notifications.py | 10 +++- .../notifications/test_get_notifications.py | 46 +++++++++++++++++ .../test_notification_schemas.py | 5 +- 5 files changed, 113 insertions(+), 6 deletions(-) create mode 100644 tests/app/v2/notifications/test_get_notifications.py diff --git a/app/__init__.py b/app/__init__.py index 165a8b884..e42bb7dc8 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -96,8 +96,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 a1d7fa164..eaebdfc03 100644 --- a/app/models.py +++ b/app/models.py @@ -1,5 +1,6 @@ import uuid import datetime +from flask import url_for from sqlalchemy.dialects.postgresql import ( UUID, @@ -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' @@ -553,6 +559,49 @@ class Notification(db.Model): 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/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/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 5597ad377..73afbb29e 100644 --- a/tests/app/v2/notifications/test_notification_schemas.py +++ b/tests/app/v2/notifications/test_notification_schemas.py @@ -4,8 +4,9 @@ import pytest from flask import json from jsonschema import ValidationError -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", From 82ba2cd22688a57cd7b71f8a0bfde3ab3a9bdac6 Mon Sep 17 00:00:00 2001 From: Paul Craig Date: Mon, 21 Nov 2016 15:42:17 +0000 Subject: [PATCH 3/9] Create new notification schema This is the schema that individual notifications will conform to when they are returned from this API. JSON logic enforces that the right keys are set depending on the `"type"`. (eg a schema with `"type": "sms"` must have a `"phone_number"` value and it cannot have an `"email_address"`) --- app/schema_validation/__init__.py | 6 +- app/v2/notifications/notification_schemas.py | 88 ++++++++++++++++---- 2 files changed, 78 insertions(+), 16 deletions(-) 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/notification_schemas.py b/app/v2/notifications/notification_schemas.py index d77a4d21a..ee1b11af7 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", From c1fa5e156a87276ca6aacb0341928bf2f95364bb Mon Sep 17 00:00:00 2001 From: Paul Craig Date: Mon, 21 Nov 2016 15:16:21 +0000 Subject: [PATCH 4/9] Append "Z" to DATETIME_FORMAT We're formally using the ISO 8601 UTC datetime format, and so the correct way to output the data is by appending the timezone. ("Z" in the case of UTC*). Unfortunately, Python's `datetime` formatting will just ignore the timezone part of the string on output, which means we just have to append the string "Z" to the end of all datetime strings we output. Should be fine, as we will only ever output UTC timestamps anyway. * https://en.wikipedia.org/wiki/ISO_8601#UTC --- app/__init__.py | 2 +- tests/app/celery/test_tasks.py | 10 +++++----- tests/app/invite/test_invite_rest.py | 2 +- tests/app/user/test_rest.py | 6 +++--- tests/app/user/test_rest_verify.py | 6 +++--- 5 files changed, 13 insertions(+), 13 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index e42bb7dc8..73082d14e 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -18,7 +18,7 @@ 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() diff --git a/tests/app/celery/test_tasks.py b/tests/app/celery/test_tasks.py index da4d5201c..f3654a8ef 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/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/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") From 46beece1583ccf6da6741f9526212d401d75f989 Mon Sep 17 00:00:00 2001 From: Rebecca Law Date: Mon, 21 Nov 2016 17:32:36 +0000 Subject: [PATCH 5/9] For the post_sms_response and post_email_response the reference property is always present but the value can be null. Added a test for an empty reference. Remove datetime format on the created_at attribute of a notification, it is not needed. --- app/notifications/process_notifications.py | 2 +- app/v2/notifications/notification_schemas.py | 4 ++-- app/v2/notifications/post_notifications.py | 4 ++-- .../notifications/test_post_notifications.py | 20 +++++++++++-------- 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/app/notifications/process_notifications.py b/app/notifications/process_notifications.py index b5ab8eb97..7e70c5abd 100644 --- a/app/notifications/process_notifications.py +++ b/app/notifications/process_notifications.py @@ -57,7 +57,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/v2/notifications/notification_schemas.py b/app/v2/notifications/notification_schemas.py index d77a4d21a..9f0155c02 100644 --- a/app/v2/notifications/notification_schemas.py +++ b/app/v2/notifications/notification_schemas.py @@ -47,7 +47,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 +90,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/v2/notifications/test_post_notifications.py b/tests/app/v2/notifications/test_post_notifications.py index a509d2a2f..e58e93dbf 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'] == 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 From fb50bb63256a2828840c843be52da2b2594fcd62 Mon Sep 17 00:00:00 2001 From: Paul Craig Date: Tue, 22 Nov 2016 11:03:57 +0000 Subject: [PATCH 6/9] Add 'v2' notification schema to contract tests Converted python-based schema in "app/v2/notifications/notification_schema.py" into a pure json schema and tested it with the new "v2/" API route to confirm that it validates. Also refactored some common code in the public contract GET tests that returns notifications. --- .../GET_notification_return_email_v2.json | 67 +++++++++++++++++++ .../public_contracts/test_GET_notification.py | 56 ++++++---------- 2 files changed, 88 insertions(+), 35 deletions(-) create mode 100644 tests/app/public_contracts/schemas/GET_notification_return_email_v2.json diff --git a/tests/app/public_contracts/schemas/GET_notification_return_email_v2.json b/tests/app/public_contracts/schemas/GET_notification_return_email_v2.json new file mode 100644 index 000000000..a6287c9ce --- /dev/null +++ b/tests/app/public_contracts/schemas/GET_notification_return_email_v2.json @@ -0,0 +1,67 @@ +{ + "$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": {"$ref": "definitions.json#/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": { + "type": "object", + "properties": { + "id": {"$ref": "definitions.json#/uuid"}, + "uri": {"type": "string"}, + "version": {"type": "number"} + }, + "additionalProperties": false, + "required": ["id", "uri", "version"] + }, + "created_at": {"type": "string"}, + "sent_at": {"type": ["string", "null"]}, + "completed_at": {"type": ["string", "null"]} + }, + "required": [ + "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" + ] +} \ No newline at end of file diff --git a/tests/app/public_contracts/test_GET_notification.py b/tests/app/public_contracts/test_GET_notification.py index fdc13e078..ae2a37371 100644 --- a/tests/app/public_contracts/test_GET_notification.py +++ b/tests/app/public_contracts/test_GET_notification.py @@ -1,59 +1,45 @@ from . import 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 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(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]) + +def test_get_v2_notification(client, sample_notification): + response = _get(client, sample_notification, '/v2/notifications/{}'.format(sample_notification.id)) + validate(response.get_data(as_text=True), 'GET_notification_return_email_v2.json') + + +def test_get_api_sms_contract(client, sample_notification): + response = _get(client, sample_notification, '/notifications/{}'.format(sample_notification.id)) validate(response.get_data(as_text=True), '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]) - + response = _get(client, sample_email_notification, '/notifications/{}'.format(sample_email_notification.id)) validate(response.get_data(as_text=True), '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]) - + response = _get(client, sample_notification, '/notifications/{}'.format(sample_notification.id)) validate(response.get_data(as_text=True), '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]) - + response = _get(client, sample_email_notification, '/notifications/{}'.format(sample_email_notification.id)) validate(response.get_data(as_text=True), '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]) - + response = _get(client, sample_notification, '/notifications') validate(response.get_data(as_text=True), 'GET_notifications_return.json') From fa422a85ef0e5e2fbdb3a994edc1db435bc6ef08 Mon Sep 17 00:00:00 2001 From: Rebecca Law Date: Tue, 22 Nov 2016 13:25:10 +0000 Subject: [PATCH 7/9] Fix unit test failure --- tests/app/v2/notifications/test_post_notifications.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/app/v2/notifications/test_post_notifications.py b/tests/app/v2/notifications/test_post_notifications.py index e58e93dbf..a2d99fb64 100644 --- a/tests/app/v2/notifications/test_post_notifications.py +++ b/tests/app/v2/notifications/test_post_notifications.py @@ -29,7 +29,7 @@ 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'] == notification_id + 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 From 7ae427c4ef84089551928b9bba809a467d729f49 Mon Sep 17 00:00:00 2001 From: Paul Craig Date: Tue, 22 Nov 2016 11:17:28 +0000 Subject: [PATCH 8/9] Use jsonschema validation + remove 'v2' schema Emualated the validation methods that exist [in the python-client](https://github.com/alphagov/notifications-python-client/blob/620e5f7014e4a431a7192cd617315e56b26c912f/integration_test/__init__.py). The `validate_v0` function loads json schemas from a local `/schemas` directory, whereas the new `validate` function (which we're going to use for our v2 API calls) uses the common `get_notification_response` python schema defined in "app/v2/notifications/notification_schemas.py". Removed the new `v2` schema from the last commit as it's no longer being used. Also, refactored common code in the GET and POST contract files so that making requests and converting responses to JSON are pulled out into common functions. --- tests/app/public_contracts/__init__.py | 16 ++++- .../GET_notification_return_email_v2.json | 67 ------------------- .../GET_notification_return_email.json | 0 .../{ => v0}/GET_notification_return_sms.json | 0 .../{ => v0}/GET_notifications_return.json | 0 .../POST_notification_return_email.json | 0 .../POST_notification_return_sms.json | 0 .../schemas/{ => v0}/definitions.json | 0 .../schemas/{ => v0}/email_notification.json | 0 .../schemas/{ => v0}/sms_notification.json | 0 .../public_contracts/test_GET_notification.py | 41 ++++++++---- .../test_POST_notification.py | 53 +++++++-------- 12 files changed, 64 insertions(+), 113 deletions(-) delete mode 100644 tests/app/public_contracts/schemas/GET_notification_return_email_v2.json rename tests/app/public_contracts/schemas/{ => v0}/GET_notification_return_email.json (100%) rename tests/app/public_contracts/schemas/{ => v0}/GET_notification_return_sms.json (100%) rename tests/app/public_contracts/schemas/{ => v0}/GET_notifications_return.json (100%) rename tests/app/public_contracts/schemas/{ => v0}/POST_notification_return_email.json (100%) rename tests/app/public_contracts/schemas/{ => v0}/POST_notification_return_sms.json (100%) rename tests/app/public_contracts/schemas/{ => v0}/definitions.json (100%) rename tests/app/public_contracts/schemas/{ => v0}/email_notification.json (100%) rename tests/app/public_contracts/schemas/{ => v0}/sms_notification.json (100%) 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_v2.json b/tests/app/public_contracts/schemas/GET_notification_return_email_v2.json deleted file mode 100644 index a6287c9ce..000000000 --- a/tests/app/public_contracts/schemas/GET_notification_return_email_v2.json +++ /dev/null @@ -1,67 +0,0 @@ -{ - "$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": {"$ref": "definitions.json#/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": { - "type": "object", - "properties": { - "id": {"$ref": "definitions.json#/uuid"}, - "uri": {"type": "string"}, - "version": {"type": "number"} - }, - "additionalProperties": false, - "required": ["id", "uri", "version"] - }, - "created_at": {"type": "string"}, - "sent_at": {"type": ["string", "null"]}, - "completed_at": {"type": ["string", "null"]} - }, - "required": [ - "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" - ] -} \ No newline at end of file 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 ae2a37371..738aea56a 100644 --- a/tests/app/public_contracts/test_GET_notification.py +++ b/tests/app/public_contracts/test_GET_notification.py @@ -1,10 +1,11 @@ -from . import validate +from . import return_json_from_response, validate_v0, validate from app.models import ApiKey, KEY_TYPE_NORMAL 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 _get(client, notification, url): +def _get_notification(client, notification, url): save_model_api_key(ApiKey( service=notification.service, name='api_key', @@ -16,30 +17,42 @@ def _get(client, notification, url): def test_get_v2_notification(client, sample_notification): - response = _get(client, sample_notification, '/v2/notifications/{}'.format(sample_notification.id)) - validate(response.get_data(as_text=True), 'GET_notification_return_email_v2.json') + 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_api_sms_contract(client, sample_notification): - response = _get(client, sample_notification, '/notifications/{}'.format(sample_notification.id)) - 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_api_email_contract(client, sample_email_notification): - response = _get(client, sample_email_notification, '/notifications/{}'.format(sample_email_notification.id)) - 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): - response = _get(client, sample_notification, '/notifications/{}'.format(sample_notification.id)) - 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): - response = _get(client, sample_email_notification, '/notifications/{}'.format(sample_email_notification.id)) - 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): - response = _get(client, sample_notification, '/notifications') - 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') From ebfac180c04d24ea8ff93583eac52e6c0bc8d553 Mon Sep 17 00:00:00 2001 From: Paul Craig Date: Tue, 22 Nov 2016 12:00:48 +0000 Subject: [PATCH 9/9] Add contract test for 'v2' email notification We don't have a way of creating letter notifications just yet, so this is all we can test. --- tests/app/public_contracts/test_GET_notification.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/tests/app/public_contracts/test_GET_notification.py b/tests/app/public_contracts/test_GET_notification.py index 738aea56a..0a3adc74c 100644 --- a/tests/app/public_contracts/test_GET_notification.py +++ b/tests/app/public_contracts/test_GET_notification.py @@ -16,13 +16,20 @@ def _get_notification(client, notification, url): return client.get(url, headers=[auth_header]) -def test_get_v2_notification(client, sample_notification): +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)