From 7530408a21d993d575974c7da005866dd7eaa38e Mon Sep 17 00:00:00 2001 From: Chris Hill-Scott Date: Mon, 18 Jan 2021 10:01:44 +0000 Subject: [PATCH] Validate broadcast against schema This commit adds a JSONSchema which can validate the fields in an API call to create a broadcast. It takes the CAP XML schema as a starting point. --- app/v2/broadcast/broadcast_schemas.py | 97 +++++++++++++++++++ app/v2/broadcast/post_broadcast.py | 17 +++- tests/app/v2/broadcast/test_post_broadcast.py | 20 +++- 3 files changed, 124 insertions(+), 10 deletions(-) create mode 100644 app/v2/broadcast/broadcast_schemas.py diff --git a/app/v2/broadcast/broadcast_schemas.py b/app/v2/broadcast/broadcast_schemas.py new file mode 100644 index 000000000..1ac2b437d --- /dev/null +++ b/app/v2/broadcast/broadcast_schemas.py @@ -0,0 +1,97 @@ +post_broadcast_schema = { + "$schema": "http://json-schema.org/draft-07/schema", + "type": "object", + "required": [ + "reference", + "category", + "content", + "areas", + ], + "additionalProperties": False, + "properties": { + "reference": { + "type": [ + "string", + "null", + ], + }, + "category": { + "type": "string", + "enum": [ + "Geo", + "Met", + "Safety", + "Security", + "Rescue", + "Fire", + "Health", + "Env", + "Transport", + "Infra", + "CBRNE", + "Other", + ], + }, + "expires": { + "type": "string", + "format": "date-time", + }, + "content": { + "type": "string", + "minLength": 1, + "maxLength": 1395, + }, + "web": { + "type": "string", + "format": "uri", + }, + "areas": { + "type": "array", + "minItems": 1, + "items": { + "$ref": "#/definitions/area", + }, + }, + }, + "definitions": { + "area": { + "type": "object", + "required": [ + "name", + "polygons", + ], + "additionalProperties": False, + "properties": { + "name": { + "type": "string", + }, + "polygons": { + "type": "array", + "minItems": 1, + "items": { + "oneOf": [ + { + "$ref": "#/definitions/polygon", + }, + ], + }, + }, + }, + }, + "polygon": { + "type": "array", + "minItems": 4, + "items": { + "$ref": "#/definitions/coordinatePair", + }, + }, + "coordinatePair": { + "type": "array", + "items": { + "type": "number" + }, + "minItems": 2, + "maxItems": 2, + }, + }, +} diff --git a/app/v2/broadcast/post_broadcast.py b/app/v2/broadcast/post_broadcast.py index a6d5adc2a..09eed0e81 100644 --- a/app/v2/broadcast/post_broadcast.py +++ b/app/v2/broadcast/post_broadcast.py @@ -1,9 +1,12 @@ +from itertools import chain from flask import jsonify, request from app import authenticated_service, api_user from app.dao.dao_utils import dao_save_object from app.notifications.validators import check_service_has_permission from app.models import BROADCAST_TYPE, BroadcastMessage, BroadcastStatusType +from app.schema_validation import validate from app.v2.broadcast import v2_broadcast_blueprint +from app.v2.broadcast.broadcast_schemas import post_broadcast_schema @v2_broadcast_blueprint.route("", methods=['POST']) @@ -14,15 +17,19 @@ def create_broadcast(): authenticated_service.permissions, ) - request_json = request.get_json() + broadcast_json = validate(request.get_json(), post_broadcast_schema) broadcast_message = BroadcastMessage( service_id=authenticated_service.id, - content=request_json['content'], - reference=request_json['reference'], + content=broadcast_json['content'], + reference=broadcast_json['reference'], areas={ - "areas": [], - "simple_polygons": request_json['polygons'], + 'areas': [ + area['name'] for area in broadcast_json['areas'] + ], + 'simple_polygons': list(chain.from_iterable(( + area['polygons'] for area in broadcast_json['areas'] + ))) }, status=BroadcastStatusType.PENDING_APPROVAL, api_key_id=api_user.id, diff --git a/tests/app/v2/broadcast/test_post_broadcast.py b/tests/app/v2/broadcast/test_post_broadcast.py index 439d749ab..944e58ed5 100644 --- a/tests/app/v2/broadcast/test_post_broadcast.py +++ b/tests/app/v2/broadcast/test_post_broadcast.py @@ -32,9 +32,15 @@ def test_valid_post_broadcast_returns_201( data=json.dumps({ 'content': 'This is a test', 'reference': 'abc123', - 'polygons': [[ - [1, 2], [3, 4], [5, 6], - ]], + 'category': 'Other', + 'areas': [ + { + 'name': 'Borchester Downs', + 'polygons': [[ + [1, 2], [3, 4], [5, 6], [1, 2], + ]], + }, + ], }), headers=[('Content-Type', 'application/json'), auth_header], ) @@ -45,7 +51,9 @@ def test_valid_post_broadcast_returns_201( assert response_json['approved_at'] is None assert response_json['approved_by_id'] == None - assert response_json['areas'] == [] + assert response_json['areas'] == [ + 'Borchester Downs' + ] assert response_json['cancelled_at'] == None assert response_json['cancelled_by_id'] == None assert response_json['content'] == 'This is a test' @@ -56,7 +64,9 @@ def test_valid_post_broadcast_returns_201( assert response_json['id'] == ANY assert response_json['personalisation'] is None assert response_json['service_id'] == str(sample_broadcast_service.id) - assert response_json['simple_polygons'] == [[[1, 2], [3, 4], [5, 6]]] + assert response_json['simple_polygons'] == [ + [[1, 2], [3, 4], [5, 6], [1, 2],] + ] assert response_json['starts_at'] is None assert response_json['status'] == 'pending-approval' assert response_json['template_id'] is None