import pytest import mock from chalice.app import Chalice from chalice.config import Config from chalice import CORSConfig from chalice.constants import MIN_COMPRESSION_SIZE from chalice.constants import MAX_COMPRESSION_SIZE from chalice.deploy.validate import validate_configuration from chalice.deploy.validate import validate_routes from chalice.deploy.validate import validate_python_version from chalice.deploy.validate import validate_route_content_types from chalice.deploy.validate import validate_unique_function_names from chalice.deploy.validate import validate_feature_flags from chalice.deploy.validate import validate_endpoint_type from chalice.deploy.validate import validate_resource_policy from chalice.deploy.validate import ExperimentalFeatureError def test_trailing_slash_routes_result_in_error(): app = Chalice('appname') app.routes = {'/trailing-slash/': None} config = Config.create(chalice_app=app) with pytest.raises(ValueError): validate_configuration(config) def test_empty_route_results_in_error(): app = Chalice('appname') app.routes = {'': {}} config = Config.create(chalice_app=app) with pytest.raises(ValueError): validate_configuration(config) def test_validate_python_version_invalid(): config = mock.Mock(spec=Config) config.lambda_python_version = 'python1.0' with pytest.warns(UserWarning): validate_python_version(config) def test_python_version_invalid_from_real_config(): config = Config.create() with pytest.warns(UserWarning): validate_python_version(config, 'python1.0') def test_python_version_is_valid(): config = Config.create() with pytest.warns(None) as record: validate_python_version(config, config.lambda_python_version) assert len(record) == 0 def test_manage_iam_role_false_requires_role_arn(sample_app): config = Config.create(chalice_app=sample_app, manage_iam_role=False, iam_role_arn='arn:::foo') assert validate_configuration(config) is None def test_validation_error_if_no_role_provided_when_manage_false(sample_app): # We're indicating that we should not be managing the # IAM role, but we're not giving a role ARN to use. # This is a validation error. config = Config.create(chalice_app=sample_app, manage_iam_role=False) with pytest.raises(ValueError): validate_configuration(config) def test_validate_unique_lambda_function_names(sample_app): @sample_app.lambda_function() def foo(event, context): pass # This will cause a validation error because # 'foo' is already registered as a lambda function. @sample_app.lambda_function(name='foo') def bar(event, context): pass config = Config.create(chalice_app=sample_app, manage_iam_role=False) with pytest.raises(ValueError): validate_unique_function_names(config) def test_validate_names_across_function_types(sample_app): @sample_app.lambda_function() def foo(event, context): pass @sample_app.schedule('rate(1 hour)', name='foo') def bar(event): pass config = Config.create(chalice_app=sample_app, manage_iam_role=False) with pytest.raises(ValueError): validate_unique_function_names(config) def test_validate_names_using_name_kwarg(sample_app): @sample_app.authorizer(name='duplicate') def foo(auth_request): pass @sample_app.lambda_function(name='duplicate') def bar(event): pass config = Config.create(chalice_app=sample_app, manage_iam_role=False) with pytest.raises(ValueError): validate_unique_function_names(config) class TestValidateCORS(object): def test_cant_have_options_with_cors(self, sample_app): @sample_app.route('/badcors', methods=['GET', 'OPTIONS'], cors=True) def badview(): pass with pytest.raises(ValueError): validate_routes(sample_app.routes) def test_cant_have_differing_cors_configurations(self, sample_app): custom_cors = CORSConfig( allow_origin='https://foo.example.com', allow_headers=['X-Special-Header'], max_age=600, expose_headers=['X-Special-Header'], allow_credentials=True ) @sample_app.route('/cors', methods=['GET'], cors=True) def cors(): pass @sample_app.route('/cors', methods=['PUT'], cors=custom_cors) def different_cors(): pass with pytest.raises(ValueError): validate_routes(sample_app.routes) def test_can_have_same_cors_configurations(self, sample_app): @sample_app.route('/cors', methods=['GET'], cors=True) def cors(): pass @sample_app.route('/cors', methods=['PUT'], cors=True) def same_cors(): pass try: validate_routes(sample_app.routes) except ValueError: pytest.fail( 'A ValueError was unexpectedly thrown. Applications ' 'may have multiple view functions that share the same ' 'route and CORS configuration.' ) def test_can_have_same_custom_cors_configurations(self, sample_app): custom_cors = CORSConfig( allow_origin='https://foo.example.com', allow_headers=['X-Special-Header'], max_age=600, expose_headers=['X-Special-Header'], allow_credentials=True ) @sample_app.route('/cors', methods=['GET'], cors=custom_cors) def cors(): pass same_custom_cors = CORSConfig( allow_origin='https://foo.example.com', allow_headers=['X-Special-Header'], max_age=600, expose_headers=['X-Special-Header'], allow_credentials=True ) @sample_app.route('/cors', methods=['PUT'], cors=same_custom_cors) def same_cors(): pass try: validate_routes(sample_app.routes) except ValueError: pytest.fail( 'A ValueError was unexpectedly thrown. Applications ' 'may have multiple view functions that share the same ' 'route and CORS configuration.' ) def test_can_have_one_cors_configured_and_others_not(self, sample_app): @sample_app.route('/cors', methods=['GET'], cors=True) def cors(): pass @sample_app.route('/cors', methods=['PUT']) def no_cors(): pass try: validate_routes(sample_app.routes) except ValueError: pytest.fail( 'A ValueError was unexpectedly thrown. Applications ' 'may have multiple view functions that share the same ' 'route but only one is configured for CORS.' ) def test_cant_have_mixed_content_types(sample_app): @sample_app.route('/index', content_types=['application/octet-stream', 'text/plain']) def index(): return {'hello': 'world'} with pytest.raises(ValueError): validate_route_content_types(sample_app.routes, sample_app.api.binary_types) def test_can_validate_updated_custom_binary_types(sample_app): sample_app.api.binary_types.extend(['text/plain']) @sample_app.route('/index', content_types=['application/octet-stream', 'text/plain']) def index(): return {'hello': 'world'} assert validate_route_content_types(sample_app.routes, sample_app.api.binary_types) is None def test_can_validate_resource_policy(sample_app): config = Config.create( chalice_app=sample_app, api_gateway_endpoint_type='PRIVATE') with pytest.raises(ValueError): validate_resource_policy(config) config = Config.create( chalice_app=sample_app, api_gateway_endpoint_vpce='vpce-abc123', api_gateway_endpoint_type='PRIVATE') validate_resource_policy(config) config = Config.create( chalice_app=sample_app, api_gateway_endpoint_vpce='vpce-abc123', api_gateway_endpoint_type='REGIONAL') with pytest.raises(ValueError): validate_resource_policy(config) config = Config.create( chalice_app=sample_app, api_gateway_policy_file='xyz.json', api_gateway_endpoint_type='PRIVATE') validate_resource_policy(config) config = Config.create( chalice_app=sample_app, api_gateway_endpoint_vpce=['vpce-abc123', 'vpce-bdef'], api_gateway_policy_file='bar.json', api_gateway_endpoint_type='PRIVATE') with pytest.raises(ValueError): validate_resource_policy(config) def test_can_validate_endpoint_type(sample_app): config = Config.create( chalice_app=sample_app, api_gateway_endpoint_type='EDGE2') with pytest.raises(ValueError): validate_endpoint_type(config) config = Config.create( chalice_app=sample_app, api_gateway_endpoint_type='REGIONAL') validate_endpoint_type(config) def test_can_validate_feature_flags(sample_app): # The _features_used is marked internal because we don't want # chalice users to access it, but this attribute is intended to be # accessed by anything within the chalice codebase. sample_app._features_used.add('SOME_NEW_FEATURE') with pytest.raises(ExperimentalFeatureError): validate_feature_flags(sample_app) # Now if we opt in, validation is fine. sample_app.experimental_feature_flags.add('SOME_NEW_FEATURE') try: validate_feature_flags(sample_app) except ExperimentalFeatureError: raise AssertionError("App was not suppose to raise an error when " "opting in to features via a feature flag.") def test_validation_error_if_minimum_compression_size_not_int(sample_app): config = Config.create(chalice_app=sample_app, minimum_compression_size='not int') with pytest.raises(ValueError): validate_configuration(config) def test_validation_error_if_minimum_compression_size_invalid_int(sample_app): config = Config.create(chalice_app=sample_app, minimum_compression_size=MIN_COMPRESSION_SIZE-1) with pytest.raises(ValueError): validate_configuration(config) config = Config.create(chalice_app=sample_app, minimum_compression_size=MAX_COMPRESSION_SIZE+1) with pytest.raises(ValueError): validate_configuration(config) def test_valid_minimum_compression_size(sample_app): config = Config.create(chalice_app=sample_app, minimum_compression_size=1) assert validate_configuration(config) is None def test_validate_sqs_queue_name(sample_app): @sample_app.on_sqs_message( queue='https://sqs.us-west-2.amazonaws.com/12345/myqueue') def handler(event): pass config = Config.create(chalice_app=sample_app) with pytest.raises(ValueError): validate_configuration(config) def test_can_use_queue_arn(sample_app): @sample_app.on_sqs_message(queue_arn='arn:sqs:...:myqueue') def handler(event): pass config = Config.create(chalice_app=sample_app) assert validate_configuration(config) is None def test_queue_arn_must_be_arn(sample_app): @sample_app.on_sqs_message( queue_arn='https://sqs.us-west-2.amazonaws.com/12345/myqueue') def handler(event): pass config = Config.create(chalice_app=sample_app) with pytest.raises(ValueError): validate_configuration(config) def test_validate_environment_variables_value_type_not_str(sample_app): config = Config.create(chalice_app=sample_app, environment_variables={"ENV_KEY": 1}) with pytest.raises(ValueError): validate_configuration(config) def test_validate_unicode_is_valid_env_var(sample_app): config = Config.create(chalice_app=sample_app, environment_variables={"ENV_KEY": u'unicode-val'}) assert validate_configuration(config) is None def test_validate_env_var_is_string_for_lambda_functions(sample_app): @sample_app.lambda_function() def foo(event, context): pass config = Config( chalice_stage='dev', config_from_disk={ 'stages': { 'dev': { 'lambda_functions': { 'foo': {'environment_variables': {'BAR': 2}}} } } }, user_provided_params={'chalice_app': sample_app} ) with pytest.raises(ValueError): validate_configuration(config)