From f8eb72a5370fea05554a018cb2175f849fcd1d80 Mon Sep 17 00:00:00 2001 From: Rebecca Law Date: Thu, 24 Jan 2019 16:38:52 +0000 Subject: [PATCH] Adding rest endpoints for letter-branding --- app/__init__.py | 4 + app/dao/letter_branding_dao.py | 20 ++++- app/letter_branding/letter_branding_rest.py | 76 ++++++++++++++++++- app/letter_branding/letter_branding_schema.py | 11 +++ app/models.py | 9 +++ app/service/rest.py | 4 +- tests/app/dao/test_letter_branding_dao.py | 63 +++++++++++++-- tests/app/letter_branding/__init__.py | 0 .../test_letter_branding_rest.py | 14 ++++ 9 files changed, 187 insertions(+), 14 deletions(-) create mode 100644 app/letter_branding/letter_branding_schema.py create mode 100644 tests/app/letter_branding/__init__.py create mode 100644 tests/app/letter_branding/test_letter_branding_rest.py diff --git a/app/__init__.py b/app/__init__.py index 1f94c697e..e1e793454 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -128,6 +128,7 @@ def register_blueprint(application): from app.complaint.complaint_rest import complaint_blueprint from app.platform_stats.rest import platform_stats_blueprint from app.template_folder.rest import template_folder_blueprint + from app.letter_branding.letter_branding_rest import letter_branding_blueprint service_blueprint.before_request(requires_admin_auth) application.register_blueprint(service_blueprint, url_prefix='/service') @@ -213,6 +214,9 @@ def register_blueprint(application): template_folder_blueprint.before_request(requires_admin_auth) application.register_blueprint(template_folder_blueprint) + letter_branding_blueprint.before_request(requires_admin_auth()) + application.register_blueprint(letter_branding_blueprint) + def register_v2_blueprints(application): from app.v2.inbound_sms.get_inbound_sms import v2_inbound_sms_blueprint as get_inbound_sms diff --git a/app/dao/letter_branding_dao.py b/app/dao/letter_branding_dao.py index e80eeea35..da18eb03c 100644 --- a/app/dao/letter_branding_dao.py +++ b/app/dao/letter_branding_dao.py @@ -1,7 +1,9 @@ +from app import db +from app.dao.dao_utils import transactional from app.models import LetterBranding -def get_letter_branding_or_platform_default(domain=None): +def dao_get_letter_branding_or_platform_default(domain=None): letter_branding = None if domain: letter_branding = LetterBranding.query.filter( @@ -14,5 +16,19 @@ def get_letter_branding_or_platform_default(domain=None): return letter_branding -def get_all_letter_branding(): +def dao_get_all_letter_branding(): return LetterBranding.query.order_by(LetterBranding.name).all() + + +@transactional +def dao_create_letter_branding(letter_branding): + db.session.add(letter_branding) + + +@transactional +def dao_update_letter_branding(letter_branding_id, **kwargs): + letter_branding = LetterBranding.query.get(letter_branding_id) + for key, value in kwargs.items(): + setattr(letter_branding, key, value or None) + db.session.add(letter_branding) + return letter_branding diff --git a/app/letter_branding/letter_branding_rest.py b/app/letter_branding/letter_branding_rest.py index 3e6eb4a90..51e937951 100644 --- a/app/letter_branding/letter_branding_rest.py +++ b/app/letter_branding/letter_branding_rest.py @@ -1,6 +1,76 @@ -from flask import Blueprint +from celery import current_app +from sqlalchemy.exc import IntegrityError +from flask import Blueprint, jsonify, request + +from app.dao.letter_branding_dao import ( + dao_get_all_letter_branding, dao_create_letter_branding, + dao_update_letter_branding +) from app.errors import register_errors +from app.letter_branding.letter_branding_schema import post_letter_branding_schema +from app.models import LetterBranding +from app.schema_validation import validate -email_branding_blueprint = Blueprint('letter_branding', __name__, url_prefix='letter-branding') -register_errors(email_branding_blueprint) +letter_branding_blueprint = Blueprint('letter_branding', __name__, url_prefix='letter-branding') +register_errors(letter_branding_blueprint) + + +@letter_branding_blueprint.errorhandler(IntegrityError) +def handle_integrity_error(exc): + """ + Handle integrity errors caused by the unique constraint + """ + if 'domain' in str(exc): + return jsonify( + result='error', + message={'name': ["Duplicate domain '{}'".format( + exc.params.get('domain') + )]} + ), 400 + if 'name' in str(exc): + return jsonify( + result='error', + message={'name': ["Duplicate name '{}'".format( + exc.params.get('name') + )]} + ), 400 + if 'filename' in str(exc): + return jsonify( + result='error', + message={'name': ["Duplicate filename '{}'".format( + exc.params.get('fileaname') + )]} + ), 400 + current_app.logger.exception(exc) + return jsonify(result='error', message="Internal server error"), 500 + + +@letter_branding_blueprint.route('', methods=['GET']) +def get_all_letter_brands(): + letter_brands = dao_get_all_letter_branding() + + return jsonify([lb.serialize() for lb in letter_brands]) + + +@letter_branding_blueprint.route('', methods=['POST']) +def create_letter_brand(): + data = request.get_json() + + validate(data, post_letter_branding_schema) + + letter_branding = LetterBranding(**data) + dao_create_letter_branding(letter_branding) + + return jsonify(letter_branding.serialize()), 201 + + +@letter_branding_blueprint.route('/', methods=['POST']) +def create_letter_branding(letter_branding_id): + data = request.get_json() + + validate(data, post_letter_branding_schema) + + letter_branding = dao_update_letter_branding(letter_branding_id, ) + + return jsonify(letter_branding.serialize()), 201 diff --git a/app/letter_branding/letter_branding_schema.py b/app/letter_branding/letter_branding_schema.py new file mode 100644 index 000000000..5f91bf829 --- /dev/null +++ b/app/letter_branding/letter_branding_schema.py @@ -0,0 +1,11 @@ +post_letter_branding_schema = { + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "POST schema for creating or updating a letter brand", + "type": "object", + "properties": { + "name": {"type": ["string", "null"]}, + "filename": {"type": ["string", "null"]}, + "domain": {"type": ["string", "null"]}, + }, + "required": ("name", "filename", "domain") +} diff --git a/app/models.py b/app/models.py index 7b6346d8f..c4dc96053 100644 --- a/app/models.py +++ b/app/models.py @@ -257,6 +257,15 @@ class LetterBranding(db.Model): domain = db.Column(db.Text, unique=True, nullable=True) platform_default = db.Column(db.Boolean, nullable=False, default=False) + def serialize(self): + return { + "id": self.id, + "name": self.name, + "filename": self.filename, + "domain": self.domain, + "platform_default": self.platform_default + } + service_letter_branding = db.Table( 'service_letter_branding', diff --git a/app/service/rest.py b/app/service/rest.py index 2088d96ff..e22eb467b 100644 --- a/app/service/rest.py +++ b/app/service/rest.py @@ -27,7 +27,7 @@ from app.dao.fact_notification_status_dao import ( fetch_stats_for_all_services_by_date_range, fetch_monthly_template_usage_for_service ) from app.dao.inbound_numbers_dao import dao_allocate_number_for_service -from app.dao.letter_branding_dao import get_letter_branding_or_platform_default +from app.dao.letter_branding_dao import dao_get_letter_branding_or_platform_default from app.dao.organisation_dao import dao_get_organisation_by_service_id from app.dao.service_data_retention_dao import ( fetch_service_data_retention, @@ -192,7 +192,7 @@ def create_service(): # unpack valid json into service object valid_service = Service.from_json(data) - letter_branding = get_letter_branding_or_platform_default(domain) + letter_branding = dao_get_letter_branding_or_platform_default(domain) dao_create_service(valid_service, user, letter_branding=letter_branding) return jsonify(data=service_schema.dump(valid_service).data), 201 diff --git a/tests/app/dao/test_letter_branding_dao.py b/tests/app/dao/test_letter_branding_dao.py index d7cb0f772..4438f14ea 100644 --- a/tests/app/dao/test_letter_branding_dao.py +++ b/tests/app/dao/test_letter_branding_dao.py @@ -1,23 +1,72 @@ -from app.dao.letter_branding_dao import get_letter_branding_or_platform_default +from app.dao.letter_branding_dao import ( + dao_get_letter_branding_or_platform_default, + dao_get_all_letter_branding, + dao_create_letter_branding, + dao_update_letter_branding +) +from app.models import LetterBranding from tests.app.db import create_letter_branding -def test_get_letter_branding_or_platform_default_returns_platform_default_if_domain_is_none(notify_db_session): +def test_dao_get_letter_branding_or_platform_default_returns_platform_default_if_domain_is_none(notify_db_session): create_letter_branding() - result = get_letter_branding_or_platform_default(domain=None) + result = dao_get_letter_branding_or_platform_default(domain=None) assert result.filename == 'hm-government' -def test_get_letter_branding_or_platform_default_if_domain_is_not_associated_with_a_brand(notify_db_session): +def test_dao_get_letter_branding_or_platform_default_if_domain_is_not_associated_with_a_brand(notify_db_session): create_letter_branding() - result = get_letter_branding_or_platform_default(domain="foo.bar") + result = dao_get_letter_branding_or_platform_default(domain="foo.bar") assert result.filename == 'hm-government' -def test_get_letter_branding_or_platform_default_returns_correct_brand_for_domain(notify_db_session): +def test_dao_get_letter_branding_or_platform_default_returns_correct_brand_for_domain(notify_db_session): create_letter_branding() test_domain_branding = create_letter_branding( name='test domain', filename='test-domain', domain='test.domain', platform_default=False ) - result = get_letter_branding_or_platform_default(domain='test.domain') + result = dao_get_letter_branding_or_platform_default(domain='test.domain') result == test_domain_branding + + +def test_dao_get_all_letter_branding(notify_db_session): + platform_default = create_letter_branding() + test_domain = create_letter_branding( + name='test domain', filename='test-domain', domain='test.domain', platform_default=False + ) + + results = dao_get_all_letter_branding() + + assert platform_default in results + assert test_domain in results + assert len(results) == 2 + + +def test_dao_get_all_letter_branding_returns_empty_list_if_no_brands_exist(notify_db): + assert dao_get_all_letter_branding() == [] + + +def test_dao_create_letter_branding(notify_db_session): + data = { + 'name': 'test-logo', + 'domain': 'test.co.uk', + 'filename': 'test-logo' + } + assert LetterBranding.query.count() == 0 + dao_create_letter_branding(LetterBranding(**data)) + + assert LetterBranding.query.count() == 1 + + new_letter_branding = LetterBranding.query.first() + assert new_letter_branding.name == data['name'] + assert new_letter_branding.domain == data['domain'] + assert new_letter_branding.filename == data['name'] + assert not new_letter_branding.platform_default + + +def test_dao_update_letter_branding(notify_db_session): + create_letter_branding(name='original') + letter_branding = LetterBranding.query.first() + assert letter_branding.name == 'original' + dao_update_letter_branding(letter_branding.id, name='new name') + assert LetterBranding.query.first().name == 'new name' diff --git a/tests/app/letter_branding/__init__.py b/tests/app/letter_branding/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/app/letter_branding/test_letter_branding_rest.py b/tests/app/letter_branding/test_letter_branding_rest.py new file mode 100644 index 000000000..ee8133963 --- /dev/null +++ b/tests/app/letter_branding/test_letter_branding_rest.py @@ -0,0 +1,14 @@ +from tests import create_authorization_header +from tests.app.db import create_letter_branding + + +def test_get_letter_brandings(client, notify_db_session): + platform_default = create_letter_branding() + test_domain_branding = create_letter_branding( + name='test domain', filename='test-domain', domain='test.domain', platform_default=False + ) + response = client.get('/letter-branding', headers=[create_authorization_header()]) + assert response.status_code == 200 + json_response = response.get_data(as_text=True) + assert platform_default.serialize() in json_response + assert test_domain_branding.serialize() in json_response