Merge pull request #651 from alphagov/contract-tests

Contract tests
This commit is contained in:
Leo Hemsted
2016-09-05 11:31:29 +01:00
committed by GitHub
17 changed files with 513 additions and 56 deletions

View File

@@ -3,7 +3,7 @@ from datetime import datetime
import pytest
from freezegun import freeze_time
from mock import ANY
from unittest.mock import ANY
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.orm.exc import NoResultFound

View File

@@ -1,8 +1,8 @@
import requests_mock
import pytest
import uuid
from datetime import (datetime, date)
import requests_mock
import pytest
from flask import current_app
from app import db
@@ -28,7 +28,6 @@ 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.clients.sms.firetext import FiretextClient
from app.clients.sms.mmg import MMGClient
@pytest.yield_fixture
@@ -276,9 +275,9 @@ def sample_job(notify_db,
@pytest.fixture(scope='function')
def sample_job_with_placeholdered_template(
notify_db,
notify_db_session,
service=None
notify_db,
notify_db_session,
service=None
):
return sample_job(
notify_db,
@@ -374,6 +373,41 @@ def sample_notification(notify_db,
return notification
@pytest.fixture(scope='function')
def sample_email_notification(notify_db, notify_db_session):
created_at = datetime.utcnow()
service = sample_service(notify_db, notify_db_session)
template = sample_email_template(notify_db, notify_db_session, service=service)
job = sample_job(notify_db, notify_db_session, service=service, template=template)
notification_id = uuid.uuid4()
to = 'foo@bar.com'
data = {
'id': notification_id,
'to': to,
'job_id': job.id,
'job': job,
'service_id': service.id,
'service': service,
'template': template,
'template_version': template.version,
'status': 'created',
'reference': None,
'created_at': created_at,
'billable_units': 0,
'personalisation': None,
'notification_type': template.template_type,
'api_key_id': None,
'key_type': KEY_TYPE_NORMAL,
'job_row_number': 1
}
notification = Notification(**data)
dao_create_notification(notification)
return notification
@pytest.fixture(scope='function')
def mock_statsd_inc(mocker):
return mocker.patch('app.statsd_client.incr')

View File

@@ -624,44 +624,6 @@ def test_get_notifications_for_service_returns_merged_template_content(notify_ap
}
def test_get_notification_public_api_format_is_not_changed(notify_api, sample_notification):
with notify_api.test_request_context(), notify_api.test_client() as client:
auth_header = create_authorization_header(service_id=sample_notification.service_id)
response = client.get(
'/notifications/{}'.format(sample_notification.id),
headers=[auth_header])
assert response.status_code == 200
notification = json.loads(response.get_data(as_text=True))['data']['notification']
# you should never remove things from this list!
assert set(notification.keys()) == {
# straight from db
'id',
'to',
'job_row_number',
'template_version',
'billable_units',
'notification_type',
'created_at',
'sent_at',
'sent_by',
'updated_at',
'status',
'reference',
# relationships
'template',
'service',
'job',
'api_key',
# other
'body',
'content_char_count'
}
def test_get_notification_selects_correct_template_for_personalisation(notify_api,
notify_db,
notify_db_session,

View File

@@ -0,0 +1,16 @@
import os
from flask import json
import jsonschema
def validate(json_string, schema_filename):
schema_dir = os.path.join(os.path.dirname(__file__), 'schemas')
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.load(schema),
format_checker=jsonschema.FormatChecker(),
resolver=resolver
)

View File

@@ -0,0 +1,17 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "GET notification return schema - for email notifications",
"type" : "object",
"properties": {
"data": {
"type": "object",
"properties": {
"notification": {"$ref": "email_notification.json"}
},
"additionalProperties": false,
"required": ["notification"]
}
},
"additionalProperties": false,
"required": ["data"]
}

View File

@@ -0,0 +1,17 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "GET notification return schema - for sms notifications",
"type" : "object",
"properties": {
"data": {
"type": "object",
"properties": {
"notification": {"$ref": "sms_notification.json"}
},
"additionalProperties": false,
"required": ["notification"]
}
},
"additionalProperties": false,
"required": ["data"]
}

View File

@@ -0,0 +1,26 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "GET notification return schema - for sms notifications",
"type" : "object",
"properties": {
"notifications": {
"type": "array",
"items": {
"oneOf": [
{"$ref": "sms_notification.json"},
{"$ref": "email_notification.json"}
]
}
},
"links": {
"type": "object",
"additionalProperties": false
},
"page_size": {"type": "number"},
"total": {"type": "number"}
},
"additionalProperties": false,
"required": [
"notifications", "links", "page_size", "total"
]
}

View File

@@ -0,0 +1,27 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "POST notification return schema - for email notifications",
"type" : "object",
"properties": {
"data": {
"type": "object",
"properties": {
"notification": {
"type": "object",
"properties": {
"id": {"$ref": "definitions.json#/uuid"}
},
"additionalProperties": false,
"required": ["id"]
},
"body": {"type": "string"},
"template_version": {"type": "number"},
"subject": {"type": "string"}
},
"additionalProperties": false,
"required": ["notification", "body", "template_version", "subject"]
}
},
"additionalProperties": false,
"required": ["data"]
}

View File

@@ -0,0 +1,26 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "POST notification return schema - for sms notifications",
"type" : "object",
"properties": {
"data": {
"type": "object",
"properties": {
"notification": {
"type": "object",
"properties": {
"id": {"$ref": "definitions.json#/uuid"}
},
"additionalProperties": false,
"required": ["id"]
},
"body": {"type": "string"},
"template_version": {"type": "number"}
},
"additionalProperties": false,
"required": ["notification", "body", "template_version"]
}
},
"additionalProperties": false,
"required": ["data"]
}

View File

@@ -0,0 +1,12 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "Common definitions - usage example: {'$ref': 'definitions.json#/uuid'} (swap quotes for double quotes)",
"uuid": {
"type": "string",
"pattern": "^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$"
},
"datetime": {
"type": "string",
"format": "date-time"
}
}

View File

@@ -0,0 +1,106 @@
{
"description": "Single email notification schema - as returned by GET /notification and GET /notification/{}",
"type": "object",
"properties": {
"id": {"$ref": "definitions.json#/uuid"},
"to": {"type": "string", "format": "email"},
"job_row_number": {"oneOf":[
{"type": "number"},
{"type": "null"}
]},
"template_version": {"type": "number"},
"billable_units": {"type": "number"},
"notification_type": {
"type": "string",
"enum": ["email"]
},
"created_at": {"$ref": "definitions.json#/datetime"},
"sent_at": {"oneOf":[
{"$ref": "definitions.json#/datetime"},
{"type": "null"}
]},
"sent_by": {"oneOf":[
{"type": "string"},
{"type": "null"}
]},
"updated_at": {"oneOf":[
{"$ref": "definitions.json#/datetime"},
{"type": "null"}
]},
"status": {
"type": "string",
"enum": [
"created",
"sending",
"delivered",
"pending",
"failed",
"technical-failure",
"temporary-failure",
"permanent-failure"
]
},
"reference": {"oneOf":[
{"type": "string"},
{"type": "null"}
]},
"template": {
"type": "object",
"properties": {
"id": {"$ref": "definitions.json#/uuid"},
"name": {"type": "string"},
"template_type": {
"type": "string",
"enum": ["email"]
},
"version": {"type": "number"}
},
"additionalProperties": false,
"required": ["id", "name", "template_type", "version"]
},
"service": {"$ref": "definitions.json#/uuid"},
"job": {
"oneOf": [
{
"type": "object",
"properties": {
"id": {"$ref": "definitions.json#/uuid"},
"original_file_name": {"type": "string"}
},
"additionalProperties": false,
"required": ["id", "original_file_name"]
},
{"type": "null"}
]
},
"api_key": {"oneOf":[
{"$ref": "definitions.json#/uuid"},
{"type": "null"}
]},
"body": {"type": "string"},
"content_char_count": {"type": "null"},
"subject": {"type": "string"}
},
"additionalProperties": false,
"required": [
"id",
"to",
"job_row_number",
"template_version",
"billable_units",
"notification_type",
"created_at",
"sent_at",
"sent_by",
"updated_at",
"status",
"reference",
"template",
"service",
"job",
"api_key",
"body",
"content_char_count",
"subject"
]
}

View File

@@ -0,0 +1,104 @@
{
"description": "Single sms notification schema - as returned by GET /notification and GET /notification/{}",
"type": "object",
"properties": {
"id": {"$ref": "definitions.json#/uuid"},
"to": {"type": "string"},
"job_row_number": {"oneOf":[
{"type": "number"},
{"type": "null"}
]},
"template_version": {"type": "number"},
"billable_units": {"type": "number"},
"notification_type": {
"type": "string",
"enum": ["sms"]
},
"created_at": {"$ref": "definitions.json#/datetime"},
"sent_at": {"oneOf":[
{"$ref": "definitions.json#/datetime"},
{"type": "null"}
]},
"sent_by": {"oneOf":[
{"type": "string"},
{"type": "null"}
]},
"updated_at": {"oneOf":[
{"$ref": "definitions.json#/datetime"},
{"type": "null"}
]},
"status": {
"type": "string",
"enum": [
"created",
"sending",
"delivered",
"pending",
"failed",
"technical-failure",
"temporary-failure",
"permanent-failure"
]
},
"reference": {"oneOf":[
{"type": "string"},
{"type": "null"}
]},
"template": {
"type": "object",
"properties": {
"id": {"$ref": "definitions.json#/uuid"},
"name": {"type": "string"},
"template_type": {
"type": "string",
"enum": ["sms"]
},
"version": {"type": "number"}
},
"additionalProperties": false,
"required": ["id", "name", "template_type", "version"]
},
"service": {"$ref": "definitions.json#/uuid"},
"job": {
"oneOf": [
{
"type": "object",
"properties": {
"id": {"$ref": "definitions.json#/uuid"},
"original_file_name": {"type": "string"}
},
"additionalProperties": false,
"required": ["id", "original_file_name"]
},
{"type": "null"}
]
},
"api_key": {"oneOf":[
{"$ref": "definitions.json#/uuid"},
{"type": "null"}
]},
"body": {"type": "string"},
"content_char_count": {"type": "number"}
},
"additionalProperties": false,
"required": [
"id",
"to",
"job_row_number",
"template_version",
"billable_units",
"notification_type",
"created_at",
"sent_at",
"sent_by",
"updated_at",
"status",
"reference",
"template",
"service",
"job",
"api_key",
"body",
"content_char_count"
]
}

View File

@@ -0,0 +1,59 @@
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])
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])
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])
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])
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])
validate(response.get_data(as_text=True), 'GET_notifications_return.json')

View File

@@ -0,0 +1,44 @@
from flask import json
from . import validate
from tests import create_authorization_header
def test_post_sms_contract(client, mocker, sample_template):
mocker.patch('app.celery.tasks.send_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')
def test_post_email_contract(client, mocker, sample_email_template):
mocker.patch('app.celery.tasks.send_sms.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')

View File

@@ -1,4 +1,4 @@
from mock import ANY
from unittest.mock import ANY
from app.statsd_decorators import statsd
import app

View File

@@ -1,7 +1,7 @@
import os
import boto3
import mock
from unittest import mock
import pytest
from alembic.command import upgrade
from alembic.config import Config
@@ -24,6 +24,12 @@ def notify_api(request):
return app
@pytest.fixture(scope='function')
def client(notify_api):
with notify_api.test_request_context(), notify_api.test_client() as client:
yield client
@pytest.fixture(scope='session')
def notify_db(notify_api, request):
Migrate(notify_api, db)