From 84ec523c83cbd5c498bb11f940a260adeb7a1df9 Mon Sep 17 00:00:00 2001 From: Ray Chan Date: Wed, 8 May 2024 11:59:25 -0700 Subject: [PATCH] Add response_validation_exclude_routes support (#253) * RESTODART-130 Add response_validation_exclude_routes support * Remove print statement * Remove version bump --- docs/changelog.rst | 5 ++--- pyramid_swagger/tween.py | 12 +++++++++++- tests/acceptance/app/config.ini | 2 ++ tests/acceptance/config_ini_test.py | 1 + tests/acceptance/response20_test.py | 23 ++++++++++++++++++++++- tests/tween_test.py | 16 ++++++++++++++++ 6 files changed, 54 insertions(+), 5 deletions(-) diff --git a/docs/changelog.rst b/docs/changelog.rst index 8a38574..9afdf3b 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1,7 +1,6 @@ Changelog ========= - -2.8.0 (2024--5-03) +2.8.0 (2024-5-03) ++++++++++++++++++++++++++ * Ensure http 401 in case of missing security (see #239) * Fix pyramid swagger renderer if missing schema (see #242) @@ -9,7 +8,7 @@ Changelog * Minor fixes (see #243, #244 and #246) * Update tox to py38 and py310 and skip swagger 1.2 tests (see #249) -2.7.0 (2019--5-07) +2.7.0 (2019-5-07) ++++++++++++++++++++++++++ * Remove not needed deprecation warnings (see #238) * Make ``pyramid_swagger`` compatible with ``jsonschema>3`` (see #327) diff --git a/pyramid_swagger/tween.py b/pyramid_swagger/tween.py index aaacd92..a60287c 100644 --- a/pyramid_swagger/tween.py +++ b/pyramid_swagger/tween.py @@ -59,6 +59,7 @@ class Settings(namedtuple( 'exclude_paths', 'exclude_routes', 'prefer_20_routes', + 'response_validation_exclude_routes' ] )): @@ -79,6 +80,8 @@ class Settings(namedtuple( :param prefer_20_routes: list of route names that should be handled via v2.0 spec when `2.0` is in `swagger_versions`. All others will be handled via v1.2 spec. [i.e. Make v2.0 an opt-in feature] + :param response_validation_exclude_routes: list of route names that should be excluded from + response validation. """ @@ -195,7 +198,7 @@ def swagger_data(_): response = handler(request) - if settings.validate_response: + if settings.validate_response and not should_exclude_response_validation(settings, route_info): with validation_context(request, response=response): swagger_handler.handle_response(response, op_or_validators_map) @@ -389,6 +392,9 @@ def load_settings(registry): ) or [])), prefer_20_routes=set(aslist(registry.settings.get( 'pyramid_swagger.prefer_20_routes') or [])), + response_validation_exclude_routes=set(aslist(registry.settings.get( + 'pyramid_swagger.response_validation_exclude_routes', + ) or [])), ) @@ -462,6 +468,10 @@ def should_exclude_request(settings, request, route_info): ) +def should_exclude_response_validation(settings, route_info): + return should_exclude_route(settings.response_validation_exclude_routes, route_info) + + def should_exclude_path(exclude_path_regexes, path): # Skip validation for the specified endpoints return any(r.match(path) for r in exclude_path_regexes) diff --git a/tests/acceptance/app/config.ini b/tests/acceptance/app/config.ini index 409b584..023553a 100644 --- a/tests/acceptance/app/config.ini +++ b/tests/acceptance/app/config.ini @@ -8,3 +8,5 @@ pyramid_swagger.exclude_routes = /undefined/first pyramid_swagger.prefer_20_routes = /sample pyramid_swagger.schema_directory = tests/sample_schemas/good_app pyramid_swagger.swagger_versions = 1.2 2.0 +pyramid_swagger.response_validation_exclude_routes = /exclude_response/first + /exclude_response/second diff --git a/tests/acceptance/config_ini_test.py b/tests/acceptance/config_ini_test.py index a7ccb97..9dac319 100644 --- a/tests/acceptance/config_ini_test.py +++ b/tests/acceptance/config_ini_test.py @@ -31,6 +31,7 @@ def test_load_ini_settings(ini_app): assert settings.validate_path is True assert settings.exclude_routes == {'/undefined/first', '/undefined/second'} assert settings.prefer_20_routes == {'/sample'} + assert settings.response_validation_exclude_routes == {'/exclude_response/first', '/exclude_response/second'} def test_get_swagger_versions(ini_app): diff --git a/tests/acceptance/response20_test.py b/tests/acceptance/response20_test.py index bf2f44c..c07ee28 100644 --- a/tests/acceptance/response20_test.py +++ b/tests/acceptance/response20_test.py @@ -11,6 +11,7 @@ import simplejson from mock import Mock from mock import patch +from pyramid.interfaces import IRoute from pyramid.interfaces import IRoutesMapper from pyramid.response import Response from webtest.app import AppError @@ -58,7 +59,9 @@ def handler(request): # so that usages in the tween meet expectations. Holler if you know a # better way to do this! op = spec.get_op_for_request(request.method, path_pattern) - mock_route_info = {'match': request.matchdict, 'route': None} + mock_route = Mock(spec=IRoute) + mock_route.name = path_pattern + mock_route_info = {'match': request.matchdict, 'route': mock_route} mock_route_mapper = Mock(spec=IRoutesMapper, return_value=mock_route_info) with patch('pyramid_swagger.tween.get_op_for_request', return_value=op): with patch('pyramid.registry.Registry.queryUtility', @@ -183,6 +186,24 @@ def test_500_for_bad_validated_array_response(): str(excinfo.value) +def test_response_not_validated_if_route_in_response_validation_exclude_routes(): + request = EnhancedDummyRequest( + method='GET', + path='/sample_array_response', + ) + response = Response( + body='{}', + headers={'Content-Type': 'application/json; charset=UTF-8'}, + ) + + _validate_against_tween( + request, + response=response, + path_pattern='/sample_array_response', + # the route name is configured to be the same as the path_pattern in `_validate_against_tween` + **{'pyramid_swagger.response_validation_exclude_routes': {'/sample_array_response'}}) + + def test_200_for_good_validated_array_response(): request = EnhancedDummyRequest( method='GET', diff --git a/tests/tween_test.py b/tests/tween_test.py index ff582fd..332219c 100644 --- a/tests/tween_test.py +++ b/tests/tween_test.py @@ -31,6 +31,7 @@ from pyramid_swagger.tween import PyramidSwaggerResponse from pyramid_swagger.tween import Settings from pyramid_swagger.tween import should_exclude_path +from pyramid_swagger.tween import should_exclude_response_validation from pyramid_swagger.tween import should_exclude_route from pyramid_swagger.tween import SWAGGER_12 from pyramid_swagger.tween import SWAGGER_20 @@ -108,6 +109,21 @@ def test_should_exclude_route_no_route(): assert not should_exclude_route(set(['foo', 'two']), {'route': None}) +def test_should_exclude_response_validation(settings, mock_route_info): + settings.response_validation_exclude_routes = ['route-one', 'two'] + assert should_exclude_response_validation(settings, mock_route_info) + + +def test_should_exclude_response_validation_no_matched_route(settings, mock_route_info): + settings.response_validation_exclude_routes = ['foo', 'two'] + assert not should_exclude_response_validation(settings, mock_route_info) + + +def test_should_exclude_response_validation_no_route(): + settings.response_validation_exclude_routes = ['foo', 'two'] + assert not should_exclude_response_validation(settings, {'route': None}) + + def test_validation_skips_path_properly(): excluded_paths = [re.compile(r) for r in DEFAULT_EXCLUDED_PATHS] assert should_exclude_path(excluded_paths, '/static')