mirror of
https://github.com/GSA/notifications-api.git
synced 2026-02-03 09:51:11 -05:00
Catch itegrity errors and return 400.
When creating or updating an organisation an itegrity error is raise if the name is already used. This change adds a new error handler for the organisation to catch the named unique index and return a 400 with a sensible message. We have an other error handler for unique service names which was caught in the error handler for all blueprints. A new error handler for the service_blueprint has been created for catch those specific unique constraints. This is a nice way to encapulate the specific errors for a specific blueprint.
This commit is contained in:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -72,3 +72,5 @@ celerybeat-schedule
|
|||||||
|
|
||||||
# CloudFoundry
|
# CloudFoundry
|
||||||
.cf
|
.cf
|
||||||
|
|
||||||
|
/scripts/run_my_tests.sh
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from flask import (
|
|||||||
current_app,
|
current_app,
|
||||||
json)
|
json)
|
||||||
from notifications_utils.recipients import InvalidEmailError
|
from notifications_utils.recipients import InvalidEmailError
|
||||||
from sqlalchemy.exc import SQLAlchemyError, DataError
|
from sqlalchemy.exc import DataError
|
||||||
from sqlalchemy.orm.exc import NoResultFound
|
from sqlalchemy.orm.exc import NoResultFound
|
||||||
from marshmallow import ValidationError
|
from marshmallow import ValidationError
|
||||||
from jsonschema import ValidationError as JsonSchemaValidationError
|
from jsonschema import ValidationError as JsonSchemaValidationError
|
||||||
@@ -95,20 +95,6 @@ def register_errors(blueprint):
|
|||||||
current_app.logger.info(e)
|
current_app.logger.info(e)
|
||||||
return jsonify(result='error', message="No result found"), 404
|
return jsonify(result='error', message="No result found"), 404
|
||||||
|
|
||||||
@blueprint.errorhandler(SQLAlchemyError)
|
|
||||||
def db_error(e):
|
|
||||||
if hasattr(e, 'orig') and hasattr(e.orig, 'pgerror') and e.orig.pgerror and \
|
|
||||||
('duplicate key value violates unique constraint "services_name_key"' in e.orig.pgerror or
|
|
||||||
'duplicate key value violates unique constraint "services_email_from_key"' in e.orig.pgerror):
|
|
||||||
return jsonify(
|
|
||||||
result='error',
|
|
||||||
message={'name': ["Duplicate service name '{}'".format(
|
|
||||||
e.params.get('name', e.params.get('email_from', ''))
|
|
||||||
)]}
|
|
||||||
), 400
|
|
||||||
current_app.logger.exception(e)
|
|
||||||
return jsonify(result='error', message="Internal server error"), 500
|
|
||||||
|
|
||||||
# this must be defined after all other error handlers since it catches the generic Exception object
|
# this must be defined after all other error handlers since it catches the generic Exception object
|
||||||
@blueprint.app_errorhandler(500)
|
@blueprint.app_errorhandler(500)
|
||||||
@blueprint.errorhandler(Exception)
|
@blueprint.errorhandler(Exception)
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from flask import Blueprint, jsonify, request
|
from flask import Blueprint, jsonify, request, current_app
|
||||||
from sqlalchemy.exc import IntegrityError
|
from sqlalchemy.exc import IntegrityError
|
||||||
|
|
||||||
from app.dao.organisation_dao import (
|
from app.dao.organisation_dao import (
|
||||||
@@ -26,12 +26,14 @@ register_errors(organisation_blueprint)
|
|||||||
@organisation_blueprint.errorhandler(IntegrityError)
|
@organisation_blueprint.errorhandler(IntegrityError)
|
||||||
def handle_integrity_error(exc):
|
def handle_integrity_error(exc):
|
||||||
"""
|
"""
|
||||||
Handle integrity errors caused by the unique contraint on ix_organisation_name
|
Handle integrity errors caused by the unique constraint on ix_organisation_name
|
||||||
"""
|
"""
|
||||||
if 'ix_organisation_name' in str(exc):
|
if 'ix_organisation_name' in str(exc):
|
||||||
raise InvalidRequest('Organisation name already exists', 400)
|
return jsonify(result="error",
|
||||||
|
message="Organisation name already exists"), 400
|
||||||
|
|
||||||
raise
|
current_app.logger.exception(exc)
|
||||||
|
return jsonify(result='error', message="Internal server error"), 500
|
||||||
|
|
||||||
|
|
||||||
@organisation_blueprint.route('', methods=['GET'])
|
@organisation_blueprint.route('', methods=['GET'])
|
||||||
@@ -57,7 +59,6 @@ def create_organisation():
|
|||||||
|
|
||||||
organisation = Organisation(**data)
|
organisation = Organisation(**data)
|
||||||
dao_create_organisation(organisation)
|
dao_create_organisation(organisation)
|
||||||
|
|
||||||
return jsonify(organisation.serialize()), 201
|
return jsonify(organisation.serialize()), 201
|
||||||
|
|
||||||
|
|
||||||
@@ -66,7 +67,6 @@ def update_organisation(organisation_id):
|
|||||||
data = request.get_json()
|
data = request.get_json()
|
||||||
validate(data, post_update_organisation_schema)
|
validate(data, post_update_organisation_schema)
|
||||||
result = dao_update_organisation(organisation_id, **data)
|
result = dao_update_organisation(organisation_id, **data)
|
||||||
|
|
||||||
if result:
|
if result:
|
||||||
return '', 204
|
return '', 204
|
||||||
else:
|
else:
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from flask import (
|
|||||||
current_app,
|
current_app,
|
||||||
Blueprint
|
Blueprint
|
||||||
)
|
)
|
||||||
|
from sqlalchemy.exc import IntegrityError
|
||||||
from sqlalchemy.orm.exc import NoResultFound
|
from sqlalchemy.orm.exc import NoResultFound
|
||||||
|
|
||||||
from app.dao import notifications_dao
|
from app.dao import notifications_dao
|
||||||
@@ -94,6 +95,22 @@ service_blueprint = Blueprint('service', __name__)
|
|||||||
register_errors(service_blueprint)
|
register_errors(service_blueprint)
|
||||||
|
|
||||||
|
|
||||||
|
@service_blueprint.errorhandler(IntegrityError)
|
||||||
|
def handle_integrity_error(exc):
|
||||||
|
"""
|
||||||
|
Handle integrity errors caused by the unique constraint on ix_organisation_name
|
||||||
|
"""
|
||||||
|
if 'services_name_key' or 'services_email_from_key' in str(exc):
|
||||||
|
return jsonify(
|
||||||
|
result='error',
|
||||||
|
message={'name': ["Duplicate service name '{}'".format(
|
||||||
|
exc.params.get('name', exc.params.get('email_from', ''))
|
||||||
|
)]}
|
||||||
|
), 400
|
||||||
|
current_app.logger.exception(exc)
|
||||||
|
return jsonify(result='error', message="Internal server error"), 500
|
||||||
|
|
||||||
|
|
||||||
@service_blueprint.route('/platform-stats', methods=['GET'])
|
@service_blueprint.route('/platform-stats', methods=['GET'])
|
||||||
def get_platform_stats():
|
def get_platform_stats():
|
||||||
include_from_test_key = request.args.get('include_from_test_key', 'True') != 'False'
|
include_from_test_key = request.args.get('include_from_test_key', 'True') != 'False'
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
import pytest
|
|
||||||
|
|
||||||
from app.errors import InvalidRequest
|
|
||||||
from app.models import Organisation
|
from app.models import Organisation
|
||||||
from app.dao.organisation_dao import dao_add_service_to_organisation
|
from app.dao.organisation_dao import dao_add_service_to_organisation
|
||||||
from tests.app.db import create_organisation, create_service
|
from tests.app.db import create_organisation, create_service
|
||||||
@@ -62,17 +59,16 @@ def test_post_create_organisation_existing_name_raises_400(admin_request, sample
|
|||||||
'active': True
|
'active': True
|
||||||
}
|
}
|
||||||
|
|
||||||
with pytest.raises(InvalidRequest) as e:
|
response = admin_request.post(
|
||||||
admin_request.post(
|
'organisation.create_organisation',
|
||||||
'organisation.create_organisation',
|
_data=data,
|
||||||
_data=data,
|
_expected_status=400
|
||||||
_expected_status=400
|
)
|
||||||
)
|
|
||||||
|
|
||||||
organisation = Organisation.query.all()
|
organisation = Organisation.query.all()
|
||||||
|
|
||||||
assert len(organisation) == 1
|
assert len(organisation) == 1
|
||||||
assert 'Organisation name already exists' in str(e.value)
|
assert response['message'] == 'Organisation name already exists'
|
||||||
|
|
||||||
|
|
||||||
def test_post_create_organisation_with_missing_name_gives_validation_error(admin_request, notify_db_session):
|
def test_post_create_organisation_with_missing_name_gives_validation_error(admin_request, notify_db_session):
|
||||||
@@ -114,22 +110,21 @@ def test_post_update_organisation_updates_fields(admin_request, notify_db_sessio
|
|||||||
|
|
||||||
|
|
||||||
def test_post_update_organisation_raises_400_on_existing_org_name(
|
def test_post_update_organisation_raises_400_on_existing_org_name(
|
||||||
admin_request, notify_db_session, sample_organisation):
|
admin_request, sample_organisation):
|
||||||
org = create_organisation()
|
org = create_organisation()
|
||||||
data = {
|
data = {
|
||||||
'name': sample_organisation.name,
|
'name': sample_organisation.name,
|
||||||
'active': False
|
'active': False
|
||||||
}
|
}
|
||||||
|
|
||||||
with pytest.raises(expected_exception=InvalidRequest) as e:
|
response = admin_request.post(
|
||||||
admin_request.post(
|
'organisation.update_organisation',
|
||||||
'organisation.update_organisation',
|
_data=data,
|
||||||
_data=data,
|
organisation_id=org.id,
|
||||||
organisation_id=org.id,
|
_expected_status=400
|
||||||
_expected_status=400
|
)
|
||||||
)
|
|
||||||
|
|
||||||
assert 'Organisation name already exists' in str(e.value)
|
assert response['message'] == 'Organisation name already exists'
|
||||||
|
|
||||||
|
|
||||||
def test_post_update_organisation_gives_404_status_if_org_does_not_exist(admin_request, notify_db_session):
|
def test_post_update_organisation_gives_404_status_if_org_does_not_exist(admin_request, notify_db_session):
|
||||||
|
|||||||
Reference in New Issue
Block a user