"""Validator Tests. Testing generic validator methods """ import json from unittest.mock import patch from jsonschema import Draft3Validator from oto import response import pytest from werkzeug import datastructures from product_workflow.validation import json_schema from product_workflow.validation.schema import json_validators @pytest.fixture def schema(): """Test schema to validate against. Returns: dict: validation schema """ return { 'type': 'object', 'properties': { 'string-property': {'type': 'string'}, 'numeric-property': {'type': 'number'} } } @pytest.fixture def validator(schema): """Validator instance that uses our test schema. Returns: Draft3Validator: validator instance """ return Draft3Validator(schema) @pytest.fixture def valid_data(): """Data that will pass validation against the test schema. Returns: dict: valid data """ return { 'string-property': 'this is a string', 'numeric-property': 123 } @pytest.fixture def invalid_data(): """Data that will fail validation against the test schema. Returns: dict: invalid data """ return { 'string-property': 79.3, 'numeric-property': 'i am a number, i swear' } @pytest.fixture def invalid_data_response(): """The formatted validation response for the invalid data. Returns: dict: invalid data response """ return { 'string-property': { 'validator_value': 'string', 'validator': 'type', 'message': "79.3 is not of type 'string'" }, 'numeric-property': { 'validator_value': 'number', 'validator': 'type', 'message': "'i am a number, i swear' is not of type 'number'" } } @pytest.fixture def teapot_status(): """Non-success status code to use when testing validation failure. Returns: int: status code """ return 418 @pytest.fixture def mock_request_with_args(mocker, resource): """Fixture for request with args.""" args_dict = {'foo': 'bar', 'baz': 'boink'} return mocker.Mock( args=mocker.Mock(to_dict=mocker.Mock(return_value=args_dict)), url_rule=mocker.Mock(rule=resource), method='GET') @pytest.fixture def mock_request_with_body(mocker, resource): """Fixture for request with body.""" get_json = mocker.Mock(return_value={'int_prop': 1, 'string_prop': 'hi'}) return mocker.Mock( get_json=get_json, url_rule=mocker.Mock(rule=resource), method='GET') @pytest.fixture def mock_request_with_headers(mocker, resource): """Fixture for request with headers.""" headers = datastructures.EnvironHeaders({'HTTP_CORRELATION_ID': 'bar'}) return mocker.Mock( headers=headers, url_rule=mocker.Mock(rule=resource), method='GET') @pytest.fixture def resource(): """Return a test resource name.""" return '/test_route' def test_validate_success(valid_data, validator): """Test status and message when data is valid.""" response = json_schema.validate(valid_data, validator) assert response.status == 200 assert response.message == {'status': 'ok'} def test_validate_failure( invalid_data, invalid_data_response, validator, teapot_status): """Test status and errors when data is invalid.""" response = json_schema.validate(invalid_data, validator, teapot_status) message = response.errors.get('message') assert response.status == teapot_status assert message == invalid_data_response def test_validate_request_args(mocker, mock_request_with_args, resource): """Test to validate request args. Test that a query args validator is constructed and called with the args from the given request. """ validator = mocker.Mock() mocker.patch.object( json_validators, 'query_args_validator', return_value=validator) mocker.patch.object(json_schema, 'validate', return_value=response.Response()) @json_schema.validate_request_args def test_func(): pass with patch.object(json_schema, 'request', mock_request_with_args): test_func() json_validators.query_args_validator.assert_called_with(resource, 'get') json_schema.validate.assert_called_with( mock_request_with_args.args.to_dict(), validator) def test_validate_headers(mocker, mock_request_with_headers, resource): """Test to validate headers. Test that a headers validator is constructed and called with the headers from the given request. """ validator = mocker.Mock() mocker.patch.object( json_validators, 'headers_validator', return_value=validator) mocker.patch.object(json_schema, 'validate', return_value=response.Response()) @json_schema.validate_request_headers def test_func(): pass with patch.object(json_schema, 'request', mock_request_with_headers): test_func() json_validators.headers_validator.assert_called_with(resource, 'get') json_schema.validate.assert_called_with( dict(mock_request_with_headers.headers), validator) def test_validate_body(mocker, mock_request_with_body, resource): """Test to validate body. Test that a body validator is constructed and called with the body from the given request. """ validator = mocker.Mock() mocker.patch.object( json_validators, 'body_validator', return_value=validator) mocker.patch.object(json_schema, 'validate', return_value=response.Response()) @json_schema.validate_request_body def test_func(): pass with patch.object(json_schema, 'request', mock_request_with_body): test_func() json_validators.body_validator.assert_called_with(resource, 'get') json_schema.validate.assert_called_with( mock_request_with_body.get_json(), validator) def test_wrap_request_validation_success(mocker, resource): """Test that on validation success the wrapped function is called.""" request = mocker.Mock() validation_function = mocker.Mock(return_value=response.Response()) @json_schema._wrap_request_validation(validation_function) def wrapped_function(): return 'i fired!' with patch.object(json_schema, 'request', request): assert wrapped_function() == 'i fired!' def test_wrap_request_validation_failure(mocker): """Test validation failure. Test that on validation failure an error is returned and the wrapped function never gets called. """ request = mocker.Mock() errors = {'message': 'something bad happened here'} validation_function = mocker.Mock( return_value=response.Response(status=400, errors=errors)) call_me_maybe = mocker.Mock() @json_schema._wrap_request_validation(validation_function) def wrapped_function(ignored_function): ignored_function() with patch.object(json_schema, 'request', request): result = wrapped_function(call_me_maybe) result_json = json.loads(result.data.decode('utf-8')) assert result.status_code == 400 assert result_json == errors call_me_maybe.assert_not_called()