mirror of
https://github.com/GSA/notifications-api.git
synced 2026-08-24 00:06:16 -04:00
Merge pull request #1447 from alphagov/vb-receipt-callback-dao
Email callback receipts rest end points
This commit is contained in:
@@ -82,6 +82,7 @@ def create_app(application):
|
|||||||
|
|
||||||
def register_blueprint(application):
|
def register_blueprint(application):
|
||||||
from app.service.rest import service_blueprint
|
from app.service.rest import service_blueprint
|
||||||
|
from app.service.callback_rest import service_callback_blueprint
|
||||||
from app.user.rest import user_blueprint
|
from app.user.rest import user_blueprint
|
||||||
from app.template.rest import template_blueprint
|
from app.template.rest import template_blueprint
|
||||||
from app.status.healthcheck import status as status_blueprint
|
from app.status.healthcheck import status as status_blueprint
|
||||||
@@ -171,6 +172,9 @@ def register_blueprint(application):
|
|||||||
billing_blueprint.before_request(requires_admin_auth)
|
billing_blueprint.before_request(requires_admin_auth)
|
||||||
application.register_blueprint(billing_blueprint)
|
application.register_blueprint(billing_blueprint)
|
||||||
|
|
||||||
|
service_callback_blueprint.before_request(requires_admin_auth)
|
||||||
|
application.register_blueprint(service_callback_blueprint)
|
||||||
|
|
||||||
|
|
||||||
def register_v2_blueprints(application):
|
def register_v2_blueprints(application):
|
||||||
from app.v2.inbound_sms.get_inbound_sms import v2_inbound_sms_blueprint as get_inbound_sms
|
from app.v2.inbound_sms.get_inbound_sms import v2_inbound_sms_blueprint as get_inbound_sms
|
||||||
|
|||||||
34
app/dao/service_callback_api_dao.py
Normal file
34
app/dao/service_callback_api_dao.py
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
from datetime import datetime
|
||||||
|
|
||||||
|
from app import db, create_uuid
|
||||||
|
from app.dao.dao_utils import transactional, version_class
|
||||||
|
from app.models import ServiceCallbackApi
|
||||||
|
|
||||||
|
|
||||||
|
@transactional
|
||||||
|
@version_class(ServiceCallbackApi)
|
||||||
|
def save_service_callback_api(service_callback_api):
|
||||||
|
service_callback_api.id = create_uuid()
|
||||||
|
service_callback_api.created_at = datetime.utcnow()
|
||||||
|
db.session.add(service_callback_api)
|
||||||
|
|
||||||
|
|
||||||
|
@transactional
|
||||||
|
@version_class(ServiceCallbackApi)
|
||||||
|
def reset_service_callback_api(service_callback_api, updated_by_id, url=None, bearer_token=None):
|
||||||
|
if url:
|
||||||
|
service_callback_api.url = url
|
||||||
|
if bearer_token:
|
||||||
|
service_callback_api.bearer_token = bearer_token
|
||||||
|
service_callback_api.updated_by_id = updated_by_id
|
||||||
|
service_callback_api.updated_at = datetime.utcnow()
|
||||||
|
|
||||||
|
db.session.add(service_callback_api)
|
||||||
|
|
||||||
|
|
||||||
|
def get_service_callback_api(service_callback_api_id, service_id):
|
||||||
|
return ServiceCallbackApi.query.filter_by(id=service_callback_api_id, service_id=service_id).first()
|
||||||
|
|
||||||
|
|
||||||
|
def get_service_callback_api_for_service(service_id):
|
||||||
|
return ServiceCallbackApi.query.filter_by(service_id=service_id).first()
|
||||||
@@ -9,8 +9,7 @@ from app.models import ServiceInboundApi
|
|||||||
@version_class(ServiceInboundApi)
|
@version_class(ServiceInboundApi)
|
||||||
def save_service_inbound_api(service_inbound_api):
|
def save_service_inbound_api(service_inbound_api):
|
||||||
service_inbound_api.id = create_uuid()
|
service_inbound_api.id = create_uuid()
|
||||||
service_inbound_api.created_at == datetime.utcnow()
|
service_inbound_api.created_at = datetime.utcnow()
|
||||||
service_inbound_api.bearer_token = service_inbound_api.bearer_token
|
|
||||||
db.session.add(service_inbound_api)
|
db.session.add(service_inbound_api)
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
120
app/service/callback_rest.py
Normal file
120
app/service/callback_rest.py
Normal file
@@ -0,0 +1,120 @@
|
|||||||
|
from flask import (
|
||||||
|
Blueprint,
|
||||||
|
jsonify,
|
||||||
|
request,
|
||||||
|
)
|
||||||
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
|
|
||||||
|
from app.errors import (
|
||||||
|
register_errors
|
||||||
|
)
|
||||||
|
from app.models import (
|
||||||
|
ServiceInboundApi,
|
||||||
|
ServiceCallbackApi
|
||||||
|
)
|
||||||
|
from app.schema_validation import validate
|
||||||
|
from app.service.service_callback_api_schema import (
|
||||||
|
create_service_callback_api_schema,
|
||||||
|
update_service_callback_api_schema
|
||||||
|
)
|
||||||
|
from app.dao.service_inbound_api_dao import (
|
||||||
|
save_service_inbound_api,
|
||||||
|
get_service_inbound_api,
|
||||||
|
reset_service_inbound_api
|
||||||
|
)
|
||||||
|
from app.dao.service_callback_api_dao import (
|
||||||
|
save_service_callback_api,
|
||||||
|
get_service_callback_api,
|
||||||
|
reset_service_callback_api
|
||||||
|
)
|
||||||
|
|
||||||
|
service_callback_blueprint = Blueprint('service_callback', __name__, url_prefix='/service/<uuid:service_id>')
|
||||||
|
|
||||||
|
register_errors(service_callback_blueprint)
|
||||||
|
|
||||||
|
|
||||||
|
@service_callback_blueprint.route('/inbound-api', methods=['POST'])
|
||||||
|
def create_service_inbound_api(service_id):
|
||||||
|
data = request.get_json()
|
||||||
|
validate(data, create_service_callback_api_schema)
|
||||||
|
data["service_id"] = service_id
|
||||||
|
inbound_api = ServiceInboundApi(**data)
|
||||||
|
try:
|
||||||
|
save_service_inbound_api(inbound_api)
|
||||||
|
except SQLAlchemyError as e:
|
||||||
|
return handle_sql_error(e, 'service_inbound_api')
|
||||||
|
|
||||||
|
return jsonify(data=inbound_api.serialize()), 201
|
||||||
|
|
||||||
|
|
||||||
|
@service_callback_blueprint.route('/inbound-api/<uuid:inbound_api_id>', methods=['POST'])
|
||||||
|
def update_service_inbound_api(service_id, inbound_api_id):
|
||||||
|
data = request.get_json()
|
||||||
|
validate(data, update_service_callback_api_schema)
|
||||||
|
|
||||||
|
to_update = get_service_inbound_api(inbound_api_id, service_id)
|
||||||
|
|
||||||
|
reset_service_inbound_api(service_inbound_api=to_update,
|
||||||
|
updated_by_id=data["updated_by_id"],
|
||||||
|
url=data.get("url", None),
|
||||||
|
bearer_token=data.get("bearer_token", None))
|
||||||
|
return jsonify(data=to_update.serialize()), 200
|
||||||
|
|
||||||
|
|
||||||
|
@service_callback_blueprint.route('/inbound-api/<uuid:inbound_api_id>', methods=["GET"])
|
||||||
|
def fetch_service_inbound_api(service_id, inbound_api_id):
|
||||||
|
inbound_api = get_service_inbound_api(inbound_api_id, service_id)
|
||||||
|
|
||||||
|
return jsonify(data=inbound_api.serialize()), 200
|
||||||
|
|
||||||
|
|
||||||
|
@service_callback_blueprint.route('/delivery-receipt-api', methods=['POST'])
|
||||||
|
def create_service_callback_api(service_id):
|
||||||
|
data = request.get_json()
|
||||||
|
validate(data, create_service_callback_api_schema)
|
||||||
|
data["service_id"] = service_id
|
||||||
|
callback_api = ServiceCallbackApi(**data)
|
||||||
|
try:
|
||||||
|
save_service_callback_api(callback_api)
|
||||||
|
except SQLAlchemyError as e:
|
||||||
|
return handle_sql_error(e, 'service_callback_api')
|
||||||
|
|
||||||
|
return jsonify(data=callback_api.serialize()), 201
|
||||||
|
|
||||||
|
|
||||||
|
@service_callback_blueprint.route('/delivery-receipt-api/<uuid:callback_api_id>', methods=['POST'])
|
||||||
|
def update_service_callback_api(service_id, callback_api_id):
|
||||||
|
data = request.get_json()
|
||||||
|
validate(data, update_service_callback_api_schema)
|
||||||
|
|
||||||
|
to_update = get_service_callback_api(callback_api_id, service_id)
|
||||||
|
|
||||||
|
reset_service_callback_api(service_callback_api=to_update,
|
||||||
|
updated_by_id=data["updated_by_id"],
|
||||||
|
url=data.get("url", None),
|
||||||
|
bearer_token=data.get("bearer_token", None))
|
||||||
|
return jsonify(data=to_update.serialize()), 200
|
||||||
|
|
||||||
|
|
||||||
|
@service_callback_blueprint.route('/delivery-receipt-api/<uuid:callback_api_id>', methods=["GET"])
|
||||||
|
def fetch_service_callback_api(service_id, callback_api_id):
|
||||||
|
callback_api = get_service_callback_api(callback_api_id, service_id)
|
||||||
|
|
||||||
|
return jsonify(data=callback_api.serialize()), 200
|
||||||
|
|
||||||
|
|
||||||
|
def handle_sql_error(e, table_name):
|
||||||
|
if hasattr(e, 'orig') and hasattr(e.orig, 'pgerror') and e.orig.pgerror \
|
||||||
|
and ('duplicate key value violates unique constraint "ix_{}_service_id"'.format(table_name)
|
||||||
|
in e.orig.pgerror):
|
||||||
|
return jsonify(
|
||||||
|
result='error',
|
||||||
|
message={'name': ["You can only have one URL and bearer token for your service."]}
|
||||||
|
), 400
|
||||||
|
elif hasattr(e, 'orig') and hasattr(e.orig, 'pgerror') and e.orig.pgerror \
|
||||||
|
and ('insert or update on table "{0}" violates '
|
||||||
|
'foreign key constraint "{0}_service_id_fkey"'.format(table_name)
|
||||||
|
in e.orig.pgerror):
|
||||||
|
return jsonify(result='error', message="No result found"), 404
|
||||||
|
else:
|
||||||
|
raise e
|
||||||
@@ -7,7 +7,6 @@ from flask import (
|
|||||||
current_app,
|
current_app,
|
||||||
Blueprint
|
Blueprint
|
||||||
)
|
)
|
||||||
from sqlalchemy.exc import SQLAlchemyError
|
|
||||||
from sqlalchemy.orm.exc import NoResultFound
|
from sqlalchemy.orm.exc import NoResultFound
|
||||||
|
|
||||||
from app.dao import notifications_dao
|
from app.dao import notifications_dao
|
||||||
@@ -18,11 +17,6 @@ from app.dao.api_key_dao import (
|
|||||||
get_unsigned_secret,
|
get_unsigned_secret,
|
||||||
expire_api_key)
|
expire_api_key)
|
||||||
from app.dao.inbound_numbers_dao import dao_allocate_number_for_service
|
from app.dao.inbound_numbers_dao import dao_allocate_number_for_service
|
||||||
from app.dao.service_inbound_api_dao import (
|
|
||||||
save_service_inbound_api,
|
|
||||||
reset_service_inbound_api,
|
|
||||||
get_service_inbound_api
|
|
||||||
)
|
|
||||||
from app.dao.service_sms_sender_dao import (
|
from app.dao.service_sms_sender_dao import (
|
||||||
dao_add_sms_sender_for_service,
|
dao_add_sms_sender_for_service,
|
||||||
dao_update_service_sms_sender,
|
dao_update_service_sms_sender,
|
||||||
@@ -72,17 +66,9 @@ from app.errors import (
|
|||||||
InvalidRequest,
|
InvalidRequest,
|
||||||
register_errors
|
register_errors
|
||||||
)
|
)
|
||||||
|
from app.models import Service
|
||||||
from app.models import (
|
|
||||||
Service,
|
|
||||||
ServiceInboundApi
|
|
||||||
)
|
|
||||||
from app.schema_validation import validate
|
from app.schema_validation import validate
|
||||||
from app.service import statistics
|
from app.service import statistics
|
||||||
from app.service.service_inbound_api_schema import (
|
|
||||||
service_inbound_api,
|
|
||||||
update_service_inbound_api_schema
|
|
||||||
)
|
|
||||||
from app.service.service_senders_schema import (
|
from app.service.service_senders_schema import (
|
||||||
add_service_email_reply_to_request,
|
add_service_email_reply_to_request,
|
||||||
add_service_letter_contact_block_request,
|
add_service_letter_contact_block_request,
|
||||||
@@ -540,58 +526,6 @@ def get_monthly_template_usage(service_id):
|
|||||||
raise InvalidRequest('Year must be a number', status_code=400)
|
raise InvalidRequest('Year must be a number', status_code=400)
|
||||||
|
|
||||||
|
|
||||||
@service_blueprint.route('/<uuid:service_id>/inbound-api', methods=['POST'])
|
|
||||||
def create_service_inbound_api(service_id):
|
|
||||||
data = request.get_json()
|
|
||||||
validate(data, service_inbound_api)
|
|
||||||
data["service_id"] = service_id
|
|
||||||
inbound_api = ServiceInboundApi(**data)
|
|
||||||
try:
|
|
||||||
save_service_inbound_api(inbound_api)
|
|
||||||
except SQLAlchemyError as e:
|
|
||||||
return handle_sql_errror(e)
|
|
||||||
|
|
||||||
return jsonify(data=inbound_api.serialize()), 201
|
|
||||||
|
|
||||||
|
|
||||||
@service_blueprint.route('/<uuid:service_id>/inbound-api/<uuid:inbound_api_id>', methods=['POST'])
|
|
||||||
def update_service_inbound_api(service_id, inbound_api_id):
|
|
||||||
data = request.get_json()
|
|
||||||
validate(data, update_service_inbound_api_schema)
|
|
||||||
|
|
||||||
to_update = get_service_inbound_api(inbound_api_id, service_id)
|
|
||||||
|
|
||||||
reset_service_inbound_api(service_inbound_api=to_update,
|
|
||||||
updated_by_id=data["updated_by_id"],
|
|
||||||
url=data.get("url", None),
|
|
||||||
bearer_token=data.get("bearer_token", None))
|
|
||||||
return jsonify(data=to_update.serialize()), 200
|
|
||||||
|
|
||||||
|
|
||||||
@service_blueprint.route('/<uuid:service_id>/inbound-api/<uuid:inbound_api_id>', methods=["GET"])
|
|
||||||
def fetch_service_inbound_api(service_id, inbound_api_id):
|
|
||||||
inbound_api = get_service_inbound_api(inbound_api_id, service_id)
|
|
||||||
|
|
||||||
return jsonify(data=inbound_api.serialize()), 200
|
|
||||||
|
|
||||||
|
|
||||||
def handle_sql_errror(e):
|
|
||||||
if hasattr(e, 'orig') and hasattr(e.orig, 'pgerror') and e.orig.pgerror \
|
|
||||||
and ('duplicate key value violates unique constraint "ix_service_inbound_api_service_id"'
|
|
||||||
in e.orig.pgerror):
|
|
||||||
return jsonify(
|
|
||||||
result='error',
|
|
||||||
message={'name': ["You can only have one URL and bearer token for your service."]}
|
|
||||||
), 400
|
|
||||||
elif hasattr(e, 'orig') and hasattr(e.orig, 'pgerror') and e.orig.pgerror \
|
|
||||||
and ('insert or update on table "service_inbound_api" violates '
|
|
||||||
'foreign key constraint "service_inbound_api_service_id_fkey"'
|
|
||||||
in e.orig.pgerror):
|
|
||||||
return jsonify(result='error', message="No result found"), 404
|
|
||||||
else:
|
|
||||||
raise e
|
|
||||||
|
|
||||||
|
|
||||||
@service_blueprint.route('/<uuid:service_id>/send-notification', methods=['POST'])
|
@service_blueprint.route('/<uuid:service_id>/send-notification', methods=['POST'])
|
||||||
def create_one_off_notification(service_id):
|
def create_one_off_notification(service_id):
|
||||||
resp = send_one_off_notification(service_id, request.get_json())
|
resp = send_one_off_notification(service_id, request.get_json())
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
from app.schema_validation.definitions import uuid, https_url
|
from app.schema_validation.definitions import uuid, https_url
|
||||||
|
|
||||||
service_inbound_api = {
|
create_service_callback_api_schema = {
|
||||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
"description": "POST service inbound api schema",
|
"description": "POST service callback/inbound api schema",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"title": "Create service inbound api",
|
"title": "Create service callback/inbound api",
|
||||||
"properties": {
|
"properties": {
|
||||||
"url": https_url,
|
"url": https_url,
|
||||||
"bearer_token": {"type": "string", "minLength": 10},
|
"bearer_token": {"type": "string", "minLength": 10},
|
||||||
@@ -13,11 +13,11 @@ service_inbound_api = {
|
|||||||
"required": ["url", "bearer_token", "updated_by_id"]
|
"required": ["url", "bearer_token", "updated_by_id"]
|
||||||
}
|
}
|
||||||
|
|
||||||
update_service_inbound_api_schema = {
|
update_service_callback_api_schema = {
|
||||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||||
"description": "POST service inbound api schema",
|
"description": "POST service callback/inbound api schema",
|
||||||
"type": "object",
|
"type": "object",
|
||||||
"title": "Create service inbound api",
|
"title": "Create service callback/inbound api",
|
||||||
"properties": {
|
"properties": {
|
||||||
"url": https_url,
|
"url": https_url,
|
||||||
"bearer_token": {"type": "string", "minLength": 10},
|
"bearer_token": {"type": "string", "minLength": 10},
|
||||||
129
tests/app/dao/test_service_callback_api_dao.py
Normal file
129
tests/app/dao/test_service_callback_api_dao.py
Normal file
@@ -0,0 +1,129 @@
|
|||||||
|
import uuid
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
from sqlalchemy.exc import SQLAlchemyError
|
||||||
|
|
||||||
|
from app import encryption
|
||||||
|
from app.dao.service_callback_api_dao import (
|
||||||
|
save_service_callback_api,
|
||||||
|
reset_service_callback_api,
|
||||||
|
get_service_callback_api,
|
||||||
|
get_service_callback_api_for_service)
|
||||||
|
from app.models import ServiceCallbackApi
|
||||||
|
from tests.app.db import create_service_callback_api
|
||||||
|
|
||||||
|
|
||||||
|
def test_save_service_callback_api(sample_service):
|
||||||
|
service_callback_api = ServiceCallbackApi(
|
||||||
|
service_id=sample_service.id,
|
||||||
|
url="https://some_service/callback_endpoint",
|
||||||
|
bearer_token="some_unique_string",
|
||||||
|
updated_by_id=sample_service.users[0].id
|
||||||
|
)
|
||||||
|
|
||||||
|
save_service_callback_api(service_callback_api)
|
||||||
|
|
||||||
|
results = ServiceCallbackApi.query.all()
|
||||||
|
assert len(results) == 1
|
||||||
|
callback_api = results[0]
|
||||||
|
assert callback_api.id is not None
|
||||||
|
assert callback_api.service_id == sample_service.id
|
||||||
|
assert callback_api.updated_by_id == sample_service.users[0].id
|
||||||
|
assert callback_api.url == "https://some_service/callback_endpoint"
|
||||||
|
assert callback_api.bearer_token == "some_unique_string"
|
||||||
|
assert callback_api._bearer_token != "some_unique_string"
|
||||||
|
assert callback_api.updated_at is None
|
||||||
|
|
||||||
|
versioned = ServiceCallbackApi.get_history_model().query.filter_by(id=callback_api.id).one()
|
||||||
|
assert versioned.id == callback_api.id
|
||||||
|
assert versioned.service_id == sample_service.id
|
||||||
|
assert versioned.updated_by_id == sample_service.users[0].id
|
||||||
|
assert versioned.url == "https://some_service/callback_endpoint"
|
||||||
|
assert encryption.decrypt(versioned._bearer_token) == "some_unique_string"
|
||||||
|
assert versioned.updated_at is None
|
||||||
|
assert versioned.version == 1
|
||||||
|
|
||||||
|
|
||||||
|
def test_save_service_callback_api_fails_if_service_does_not_exist(notify_db, notify_db_session):
|
||||||
|
service_callback_api = ServiceCallbackApi(
|
||||||
|
service_id=uuid.uuid4(),
|
||||||
|
url="https://some_service/callback_endpoint",
|
||||||
|
bearer_token="some_unique_string",
|
||||||
|
updated_by_id=uuid.uuid4()
|
||||||
|
)
|
||||||
|
|
||||||
|
with pytest.raises(SQLAlchemyError):
|
||||||
|
save_service_callback_api(service_callback_api)
|
||||||
|
|
||||||
|
|
||||||
|
def test_update_service_callback_api(sample_service):
|
||||||
|
service_callback_api = ServiceCallbackApi(
|
||||||
|
service_id=sample_service.id,
|
||||||
|
url="https://some_service/callback_endpoint",
|
||||||
|
bearer_token="some_unique_string",
|
||||||
|
updated_by_id=sample_service.users[0].id
|
||||||
|
)
|
||||||
|
|
||||||
|
save_service_callback_api(service_callback_api)
|
||||||
|
results = ServiceCallbackApi.query.all()
|
||||||
|
assert len(results) == 1
|
||||||
|
saved_callback_api = results[0]
|
||||||
|
|
||||||
|
reset_service_callback_api(saved_callback_api, updated_by_id=sample_service.users[0].id,
|
||||||
|
url="https://some_service/changed_url")
|
||||||
|
updated_results = ServiceCallbackApi.query.all()
|
||||||
|
assert len(updated_results) == 1
|
||||||
|
updated = updated_results[0]
|
||||||
|
assert updated.id is not None
|
||||||
|
assert updated.service_id == sample_service.id
|
||||||
|
assert updated.updated_by_id == sample_service.users[0].id
|
||||||
|
assert updated.url == "https://some_service/changed_url"
|
||||||
|
assert updated.bearer_token == "some_unique_string"
|
||||||
|
assert updated._bearer_token != "some_unique_string"
|
||||||
|
assert updated.updated_at is not None
|
||||||
|
|
||||||
|
versioned_results = ServiceCallbackApi.get_history_model().query.filter_by(id=saved_callback_api.id).all()
|
||||||
|
assert len(versioned_results) == 2
|
||||||
|
for x in versioned_results:
|
||||||
|
if x.version == 1:
|
||||||
|
assert x.url == "https://some_service/callback_endpoint"
|
||||||
|
assert not x.updated_at
|
||||||
|
elif x.version == 2:
|
||||||
|
assert x.url == "https://some_service/changed_url"
|
||||||
|
assert x.updated_at
|
||||||
|
else:
|
||||||
|
pytest.fail("version should not exist")
|
||||||
|
assert x.id is not None
|
||||||
|
assert x.service_id == sample_service.id
|
||||||
|
assert x.updated_by_id == sample_service.users[0].id
|
||||||
|
assert encryption.decrypt(x._bearer_token) == "some_unique_string"
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_service_callback_api(sample_service):
|
||||||
|
service_callback_api = ServiceCallbackApi(
|
||||||
|
service_id=sample_service.id,
|
||||||
|
url="https://some_service/callback_endpoint",
|
||||||
|
bearer_token="some_unique_string",
|
||||||
|
updated_by_id=sample_service.users[0].id
|
||||||
|
)
|
||||||
|
save_service_callback_api(service_callback_api)
|
||||||
|
|
||||||
|
callback_api = get_service_callback_api(service_callback_api.id, sample_service.id)
|
||||||
|
assert callback_api.id is not None
|
||||||
|
assert callback_api.service_id == sample_service.id
|
||||||
|
assert callback_api.updated_by_id == sample_service.users[0].id
|
||||||
|
assert callback_api.url == "https://some_service/callback_endpoint"
|
||||||
|
assert callback_api.bearer_token == "some_unique_string"
|
||||||
|
assert callback_api._bearer_token != "some_unique_string"
|
||||||
|
assert callback_api.updated_at is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_service_callback_api_for_service(sample_service):
|
||||||
|
service_callback_api = create_service_callback_api(service=sample_service)
|
||||||
|
result = get_service_callback_api_for_service(sample_service.id)
|
||||||
|
assert result.id == service_callback_api.id
|
||||||
|
assert result.url == service_callback_api.url
|
||||||
|
assert result.bearer_token == service_callback_api.bearer_token
|
||||||
|
assert result.created_at == service_callback_api.created_at
|
||||||
|
assert result.updated_at == service_callback_api.updated_at
|
||||||
|
assert result.updated_by_id == service_callback_api.updated_by_id
|
||||||
@@ -4,6 +4,7 @@ import uuid
|
|||||||
from app import db
|
from app import db
|
||||||
from app.dao.jobs_dao import dao_create_job
|
from app.dao.jobs_dao import dao_create_job
|
||||||
from app.dao.service_inbound_api_dao import save_service_inbound_api
|
from app.dao.service_inbound_api_dao import save_service_inbound_api
|
||||||
|
from app.dao.service_callback_api_dao import save_service_callback_api
|
||||||
from app.dao.service_sms_sender_dao import update_existing_sms_sender_with_inbound_number, dao_update_service_sms_sender
|
from app.dao.service_sms_sender_dao import update_existing_sms_sender_with_inbound_number, dao_update_service_sms_sender
|
||||||
from app.models import (
|
from app.models import (
|
||||||
ApiKey,
|
ApiKey,
|
||||||
@@ -17,6 +18,7 @@ from app.models import (
|
|||||||
Service,
|
Service,
|
||||||
ServiceEmailReplyTo,
|
ServiceEmailReplyTo,
|
||||||
ServiceInboundApi,
|
ServiceInboundApi,
|
||||||
|
ServiceCallbackApi,
|
||||||
ServiceLetterContact,
|
ServiceLetterContact,
|
||||||
ScheduledNotification,
|
ScheduledNotification,
|
||||||
ServicePermission,
|
ServicePermission,
|
||||||
@@ -296,6 +298,20 @@ def create_service_inbound_api(
|
|||||||
return service_inbound_api
|
return service_inbound_api
|
||||||
|
|
||||||
|
|
||||||
|
def create_service_callback_api(
|
||||||
|
service,
|
||||||
|
url="https://something.com",
|
||||||
|
bearer_token="some_super_secret",
|
||||||
|
):
|
||||||
|
service_callback_api = ServiceCallbackApi(service_id=service.id,
|
||||||
|
url=url,
|
||||||
|
bearer_token=bearer_token,
|
||||||
|
updated_by_id=service.users[0].id
|
||||||
|
)
|
||||||
|
save_service_callback_api(service_callback_api)
|
||||||
|
return service_callback_api
|
||||||
|
|
||||||
|
|
||||||
def create_organisation(colour='blue', logo='test_x2.png', name='test_org_1'):
|
def create_organisation(colour='blue', logo='test_x2.png', name='test_org_1'):
|
||||||
data = {
|
data = {
|
||||||
'colour': colour,
|
'colour': colour,
|
||||||
|
|||||||
165
tests/app/service/test_callback_rest.py
Normal file
165
tests/app/service/test_callback_rest.py
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
import json
|
||||||
|
import uuid
|
||||||
|
|
||||||
|
from tests import create_authorization_header
|
||||||
|
|
||||||
|
from tests.app.db import (
|
||||||
|
create_service_inbound_api,
|
||||||
|
create_service_callback_api
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_service_inbound_api(client, sample_service):
|
||||||
|
data = {
|
||||||
|
"url": "https://some_service/inbound-sms",
|
||||||
|
"bearer_token": "some-unique-string",
|
||||||
|
"updated_by_id": str(sample_service.users[0].id)
|
||||||
|
}
|
||||||
|
response = client.post(
|
||||||
|
'/service/{}/inbound-api'.format(sample_service.id),
|
||||||
|
data=json.dumps(data),
|
||||||
|
headers=[('Content-Type', 'application/json'), create_authorization_header()]
|
||||||
|
)
|
||||||
|
assert response.status_code == 201
|
||||||
|
|
||||||
|
resp_json = json.loads(response.get_data(as_text=True))["data"]
|
||||||
|
assert resp_json["id"]
|
||||||
|
assert resp_json["service_id"] == str(sample_service.id)
|
||||||
|
assert resp_json["url"] == "https://some_service/inbound-sms"
|
||||||
|
assert resp_json["updated_by_id"] == str(sample_service.users[0].id)
|
||||||
|
assert resp_json["created_at"]
|
||||||
|
assert not resp_json["updated_at"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_set_service_inbound_api_raises_404_when_service_does_not_exist(client):
|
||||||
|
data = {
|
||||||
|
"url": "https://some_service/inbound-sms",
|
||||||
|
"bearer_token": "some-unique-string",
|
||||||
|
"updated_by_id": str(uuid.uuid4())
|
||||||
|
}
|
||||||
|
response = client.post(
|
||||||
|
'/service/{}/inbound-api'.format(uuid.uuid4()),
|
||||||
|
data=json.dumps(data),
|
||||||
|
headers=[('Content-Type', 'application/json'), create_authorization_header()]
|
||||||
|
)
|
||||||
|
assert response.status_code == 404
|
||||||
|
assert json.loads(response.get_data(as_text=True))['message'] == 'No result found'
|
||||||
|
|
||||||
|
|
||||||
|
def test_update_service_inbound_api_updates_url(client, sample_service):
|
||||||
|
service_inbound_api = create_service_inbound_api(service=sample_service,
|
||||||
|
url="https://original_url.com")
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"url": "https://another_url.com",
|
||||||
|
"updated_by_id": str(sample_service.users[0].id)
|
||||||
|
}
|
||||||
|
response = client.post("/service/{}/inbound-api/{}".format(sample_service.id, service_inbound_api.id),
|
||||||
|
data=json.dumps(data),
|
||||||
|
headers=[('Content-Type', 'application/json'), create_authorization_header()])
|
||||||
|
assert response.status_code == 200
|
||||||
|
resp_json = json.loads(response.get_data(as_text=True))["data"]
|
||||||
|
assert resp_json["url"] == "https://another_url.com"
|
||||||
|
assert service_inbound_api.url == "https://another_url.com"
|
||||||
|
|
||||||
|
|
||||||
|
def test_update_service_inbound_api_updates_bearer_token(client, sample_service):
|
||||||
|
service_inbound_api = create_service_inbound_api(service=sample_service,
|
||||||
|
bearer_token="some_super_secret")
|
||||||
|
data = {
|
||||||
|
"bearer_token": "different_token",
|
||||||
|
"updated_by_id": str(sample_service.users[0].id)
|
||||||
|
}
|
||||||
|
response = client.post("/service/{}/inbound-api/{}".format(sample_service.id, service_inbound_api.id),
|
||||||
|
data=json.dumps(data),
|
||||||
|
headers=[('Content-Type', 'application/json'), create_authorization_header()])
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert service_inbound_api.bearer_token == "different_token"
|
||||||
|
|
||||||
|
|
||||||
|
def test_fetch_service_inbound_api(client, sample_service):
|
||||||
|
service_inbound_api = create_service_inbound_api(service=sample_service)
|
||||||
|
|
||||||
|
response = client.get("/service/{}/inbound-api/{}".format(sample_service.id, service_inbound_api.id),
|
||||||
|
headers=[create_authorization_header()])
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert json.loads(response.get_data(as_text=True))["data"] == service_inbound_api.serialize()
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_service_callback_api(client, sample_service):
|
||||||
|
data = {
|
||||||
|
"url": "https://some_service/delivery-receipt-endpoint",
|
||||||
|
"bearer_token": "some-unique-string",
|
||||||
|
"updated_by_id": str(sample_service.users[0].id)
|
||||||
|
}
|
||||||
|
response = client.post(
|
||||||
|
'/service/{}/delivery-receipt-api'.format(sample_service.id),
|
||||||
|
data=json.dumps(data),
|
||||||
|
headers=[('Content-Type', 'application/json'), create_authorization_header()]
|
||||||
|
)
|
||||||
|
assert response.status_code == 201
|
||||||
|
|
||||||
|
resp_json = json.loads(response.get_data(as_text=True))["data"]
|
||||||
|
assert resp_json["id"]
|
||||||
|
assert resp_json["service_id"] == str(sample_service.id)
|
||||||
|
assert resp_json["url"] == "https://some_service/delivery-receipt-endpoint"
|
||||||
|
assert resp_json["updated_by_id"] == str(sample_service.users[0].id)
|
||||||
|
assert resp_json["created_at"]
|
||||||
|
assert not resp_json["updated_at"]
|
||||||
|
|
||||||
|
|
||||||
|
def test_set_service_callback_api_raises_404_when_service_does_not_exist(client, notify_db_session):
|
||||||
|
data = {
|
||||||
|
"url": "https://some_service/delivery-receipt-endpoint",
|
||||||
|
"bearer_token": "some-unique-string",
|
||||||
|
"updated_by_id": str(uuid.uuid4())
|
||||||
|
}
|
||||||
|
response = client.post(
|
||||||
|
'/service/{}/delivery-receipt-api'.format(uuid.uuid4()),
|
||||||
|
data=json.dumps(data),
|
||||||
|
headers=[('Content-Type', 'application/json'), create_authorization_header()]
|
||||||
|
)
|
||||||
|
assert response.status_code == 404
|
||||||
|
assert json.loads(response.get_data(as_text=True))['message'] == 'No result found'
|
||||||
|
|
||||||
|
|
||||||
|
def test_update_service_callback_api_updates_url(client, sample_service):
|
||||||
|
service_callback_api = create_service_callback_api(service=sample_service,
|
||||||
|
url="https://original_url.com")
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"url": "https://another_url.com",
|
||||||
|
"updated_by_id": str(sample_service.users[0].id)
|
||||||
|
}
|
||||||
|
response = client.post("/service/{}/delivery-receipt-api/{}".format(sample_service.id, service_callback_api.id),
|
||||||
|
data=json.dumps(data),
|
||||||
|
headers=[('Content-Type', 'application/json'), create_authorization_header()])
|
||||||
|
assert response.status_code == 200
|
||||||
|
resp_json = json.loads(response.get_data(as_text=True))["data"]
|
||||||
|
assert resp_json["url"] == "https://another_url.com"
|
||||||
|
assert service_callback_api.url == "https://another_url.com"
|
||||||
|
|
||||||
|
|
||||||
|
def test_update_service_callback_api_updates_bearer_token(client, sample_service):
|
||||||
|
service_callback_api = create_service_callback_api(service=sample_service,
|
||||||
|
bearer_token="some_super_secret")
|
||||||
|
data = {
|
||||||
|
"bearer_token": "different_token",
|
||||||
|
"updated_by_id": str(sample_service.users[0].id)
|
||||||
|
}
|
||||||
|
response = client.post("/service/{}/delivery-receipt-api/{}".format(sample_service.id, service_callback_api.id),
|
||||||
|
data=json.dumps(data),
|
||||||
|
headers=[('Content-Type', 'application/json'), create_authorization_header()])
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert service_callback_api.bearer_token == "different_token"
|
||||||
|
|
||||||
|
|
||||||
|
def test_fetch_service_callback_api(client, sample_service):
|
||||||
|
service_callback_api = create_service_callback_api(service=sample_service)
|
||||||
|
|
||||||
|
response = client.get("/service/{}/delivery-receipt-api/{}".format(sample_service.id, service_callback_api.id),
|
||||||
|
headers=[create_authorization_header()])
|
||||||
|
|
||||||
|
assert response.status_code == 200
|
||||||
|
assert json.loads(response.get_data(as_text=True))["data"] == service_callback_api.serialize()
|
||||||
@@ -30,7 +30,6 @@ from tests.app.conftest import (
|
|||||||
from tests.app.db import (
|
from tests.app.db import (
|
||||||
create_service,
|
create_service,
|
||||||
create_template,
|
create_template,
|
||||||
create_service_inbound_api,
|
|
||||||
create_notification,
|
create_notification,
|
||||||
create_reply_to_email,
|
create_reply_to_email,
|
||||||
create_letter_contact,
|
create_letter_contact,
|
||||||
@@ -2164,84 +2163,6 @@ def test_search_for_notification_by_to_field_returns_content(
|
|||||||
assert notifications[0]['template']['content'] == 'Hello (( Name))\nYour thing is due soon'
|
assert notifications[0]['template']['content'] == 'Hello (( Name))\nYour thing is due soon'
|
||||||
|
|
||||||
|
|
||||||
def test_create_service_inbound_api(client, sample_service):
|
|
||||||
data = {
|
|
||||||
"url": "https://some_service/inbound-sms",
|
|
||||||
"bearer_token": "some-unique-string",
|
|
||||||
"updated_by_id": str(sample_service.users[0].id)
|
|
||||||
}
|
|
||||||
response = client.post(
|
|
||||||
'/service/{}/inbound-api'.format(sample_service.id),
|
|
||||||
data=json.dumps(data),
|
|
||||||
headers=[('Content-Type', 'application/json'), create_authorization_header()]
|
|
||||||
)
|
|
||||||
assert response.status_code == 201
|
|
||||||
|
|
||||||
resp_json = json.loads(response.get_data(as_text=True))["data"]
|
|
||||||
assert resp_json["id"]
|
|
||||||
assert resp_json["service_id"] == str(sample_service.id)
|
|
||||||
assert resp_json["url"] == "https://some_service/inbound-sms"
|
|
||||||
assert resp_json["updated_by_id"] == str(sample_service.users[0].id)
|
|
||||||
assert resp_json["created_at"]
|
|
||||||
assert not resp_json["updated_at"]
|
|
||||||
|
|
||||||
|
|
||||||
def test_set_service_inbound_api_raises_404_when_service_does_not_exist(client):
|
|
||||||
data = {
|
|
||||||
"url": "https://some_service/inbound-sms",
|
|
||||||
"bearer_token": "some-unique-string",
|
|
||||||
"updated_by_id": str(uuid.uuid4())
|
|
||||||
}
|
|
||||||
response = client.post(
|
|
||||||
'/service/{}/inbound-api'.format(uuid.uuid4()),
|
|
||||||
data=json.dumps(data),
|
|
||||||
headers=[('Content-Type', 'application/json'), create_authorization_header()]
|
|
||||||
)
|
|
||||||
assert response.status_code == 404
|
|
||||||
assert json.loads(response.get_data(as_text=True))['message'] == 'No result found'
|
|
||||||
|
|
||||||
|
|
||||||
def test_update_service_inbound_api_updates_url(client, sample_service):
|
|
||||||
service_inbound_api = create_service_inbound_api(service=sample_service,
|
|
||||||
url="https://original_url.com")
|
|
||||||
|
|
||||||
data = {
|
|
||||||
"url": "https://another_url.com",
|
|
||||||
"updated_by_id": str(sample_service.users[0].id)
|
|
||||||
}
|
|
||||||
response = client.post("/service/{}/inbound-api/{}".format(sample_service.id, service_inbound_api.id),
|
|
||||||
data=json.dumps(data),
|
|
||||||
headers=[('Content-Type', 'application/json'), create_authorization_header()])
|
|
||||||
assert response.status_code == 200
|
|
||||||
resp_json = json.loads(response.get_data(as_text=True))["data"]
|
|
||||||
assert resp_json["url"] == "https://another_url.com"
|
|
||||||
assert service_inbound_api.url == "https://another_url.com"
|
|
||||||
|
|
||||||
|
|
||||||
def test_update_service_inbound_api_updates_bearer_token(client, sample_service):
|
|
||||||
service_inbound_api = create_service_inbound_api(service=sample_service,
|
|
||||||
bearer_token="some_super_secret")
|
|
||||||
data = {
|
|
||||||
"bearer_token": "different_token",
|
|
||||||
"updated_by_id": str(sample_service.users[0].id)
|
|
||||||
}
|
|
||||||
response = client.post("/service/{}/inbound-api/{}".format(sample_service.id, service_inbound_api.id),
|
|
||||||
data=json.dumps(data),
|
|
||||||
headers=[('Content-Type', 'application/json'), create_authorization_header()])
|
|
||||||
assert response.status_code == 200
|
|
||||||
assert service_inbound_api.bearer_token == "different_token"
|
|
||||||
|
|
||||||
|
|
||||||
def test_fetch_service_inbound_api(client, sample_service):
|
|
||||||
service_inbound_api = create_service_inbound_api(service=sample_service)
|
|
||||||
|
|
||||||
response = client.get("/service/{}/inbound-api/{}".format(sample_service.id, service_inbound_api.id),
|
|
||||||
headers=[create_authorization_header()])
|
|
||||||
|
|
||||||
assert response.status_code == 200
|
|
||||||
assert json.loads(response.get_data(as_text=True))["data"] == service_inbound_api.serialize()
|
|
||||||
|
|
||||||
|
|
||||||
def test_send_one_off_notification(admin_request, mocker):
|
def test_send_one_off_notification(admin_request, mocker):
|
||||||
service = create_service()
|
service = create_service()
|
||||||
template = create_template(service=service)
|
template = create_template(service=service)
|
||||||
|
|||||||
@@ -5,41 +5,42 @@ import pytest
|
|||||||
from jsonschema import ValidationError
|
from jsonschema import ValidationError
|
||||||
|
|
||||||
from app.schema_validation import validate
|
from app.schema_validation import validate
|
||||||
from app.service.service_inbound_api_schema import service_inbound_api
|
from app.service.service_callback_api_schema import (
|
||||||
|
update_service_callback_api_schema)
|
||||||
|
|
||||||
|
|
||||||
def test_service_inbound_api_schema_validates():
|
def test_service_callback_api_schema_validates():
|
||||||
under_test = {"url": "https://some_url.for_service",
|
under_test = {"url": "https://some_url.for_service",
|
||||||
"bearer_token": "something_ten_chars",
|
"bearer_token": "something_ten_chars",
|
||||||
"updated_by_id": str(uuid.uuid4())
|
"updated_by_id": str(uuid.uuid4())
|
||||||
}
|
}
|
||||||
|
|
||||||
validated = validate(under_test, service_inbound_api)
|
validated = validate(under_test, update_service_callback_api_schema)
|
||||||
assert validated == under_test
|
assert validated == under_test
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("url", ["not a url", "https not a url", "http://valid.com"])
|
@pytest.mark.parametrize("url", ["not a url", "https not a url", "http://valid.com"])
|
||||||
def test_service_inbound_api_schema_errors_for_url_not_valid_url(url):
|
def test_service_callback_api_schema_errors_for_url_not_valid_url(url):
|
||||||
under_test = {"url": url,
|
under_test = {"url": url,
|
||||||
"bearer_token": "something_ten_chars",
|
"bearer_token": "something_ten_chars",
|
||||||
"updated_by_id": str(uuid.uuid4())
|
"updated_by_id": str(uuid.uuid4())
|
||||||
}
|
}
|
||||||
|
|
||||||
with pytest.raises(ValidationError) as e:
|
with pytest.raises(ValidationError) as e:
|
||||||
validate(under_test, service_inbound_api)
|
validate(under_test, update_service_callback_api_schema)
|
||||||
errors = json.loads(str(e.value)).get('errors')
|
errors = json.loads(str(e.value)).get('errors')
|
||||||
assert len(errors) == 1
|
assert len(errors) == 1
|
||||||
assert errors[0]['message'] == "url is not a valid https url"
|
assert errors[0]['message'] == "url is not a valid https url"
|
||||||
|
|
||||||
|
|
||||||
def test_service_inbound_api_schema_bearer_token_under_ten_char():
|
def test_service_callback_api_schema_bearer_token_under_ten_char():
|
||||||
under_test = {"url": "https://some_url.for_service",
|
under_test = {"url": "https://some_url.for_service",
|
||||||
"bearer_token": "shorty",
|
"bearer_token": "shorty",
|
||||||
"updated_by_id": str(uuid.uuid4())
|
"updated_by_id": str(uuid.uuid4())
|
||||||
}
|
}
|
||||||
|
|
||||||
with pytest.raises(ValidationError) as e:
|
with pytest.raises(ValidationError) as e:
|
||||||
validate(under_test, service_inbound_api)
|
validate(under_test, update_service_callback_api_schema)
|
||||||
errors = json.loads(str(e.value)).get('errors')
|
errors = json.loads(str(e.value)).get('errors')
|
||||||
assert len(errors) == 1
|
assert len(errors) == 1
|
||||||
assert errors[0]['message'] == "bearer_token shorty is too short"
|
assert errors[0]['message'] == "bearer_token shorty is too short"
|
||||||
|
|||||||
Reference in New Issue
Block a user