diff --git a/.gitignore b/.gitignore index 3ba494941..6eb8a4e21 100644 --- a/.gitignore +++ b/.gitignore @@ -46,7 +46,6 @@ htmlcov/ .coveragerc .coverage.* .cache -nosetests.xml coverage.xml *,cover .hypothesis/ diff --git a/Makefile b/Makefile index 9ec1ebd8a..0d201b91e 100644 --- a/Makefile +++ b/Makefile @@ -24,7 +24,7 @@ clean: find . -name '*.pyc' -exec rm -f {} + find . -name '*.pyo' -exec rm -f {} + rm -rf .mypy_cache dist build *.egg-info - rm -f .coverage + coverage erase requirements: pip install pipenv>2021.11.15 @@ -56,24 +56,39 @@ flake: flake8 zappa --count --exit-zero --max-complexity=55 --max-line-length=127 --statistics --ignore F403,F405,E203,E231,E252,W503 test-docs: - nosetests tests/tests_docs.py --with-coverage --cover-package=zappa --with-timer + pytest tests/tests_docs.py --cov=zappa --durations=0 test-handler: - nosetests tests/test_handler.py --with-coverage --cover-package=zappa --with-timer + pytest tests/test_handler.py --cov=zappa --durations=0 test-middleware: - nosetests tests/tests_middleware.py --with-coverage --cover-package=zappa --with-timer + pytest tests/tests_middleware.py --cov=zappa --durations=0 test-placebo: - nosetests tests/tests_placebo.py --with-coverage --cover-package=zappa --with-timer + pytest tests/tests_placebo.py --cov=zappa --durations=0 test-async: - nosetests tests/tests_async.py --with-coverage --cover-package=zappa --with-timer + pytest tests/tests_async.py --cov=zappa --durations=0 test-general: - nosetests tests/tests.py --with-coverage --cover-package=zappa --with-timer + pytest tests/tests.py --cov=zappa --durations=0 test-utilities: - nosetests tests/tests_utilities.py --with-coverage --cover-package=zappa --with-timer + pytest tests/tests_utilities.py --cov=zappa --durations=0 + +coverage-report: + coverage report --include="*/zappa*" + +tests: + make clean + pytest \ + tests/tests_docs.py \ + tests/test_handler.py \ + tests/tests_middleware.py \ + tests/tests_placebo.py \ + tests/tests_async.py \ + tests/tests.py \ + tests/tests_utilities.py \ + --cov=zappa + --durations=0 -tests: clean test-docs test-handler test-middleware test-placebo test-async test-general test-utilities diff --git a/Pipfile b/Pipfile index a5c15c4d7..e1a35d0e1 100644 --- a/Pipfile +++ b/Pipfile @@ -14,10 +14,10 @@ Flask = "*" isort = "*" mock = "*" mypy = "*" -nose = "*" -nose-timer = "*" pipenv = ">2021.11.15" packaging = "*" +pytest = "*" +pytest-cov = "*" [packages] argcomplete = "*" diff --git a/setup.py b/setup.py index 88fd418c0..53960a826 100755 --- a/setup.py +++ b/setup.py @@ -25,7 +25,6 @@ install_requires=required, python_requires=">=3.7", tests_require=test_required, - test_suite="nose.collector", include_package_data=True, license="MIT License", description="Server-less Python Web Services for AWS Lambda and API Gateway", diff --git a/test.sh b/test.sh index ab770a498..b716c3d27 100755 --- a/test.sh +++ b/test.sh @@ -1,5 +1,5 @@ #! /bin/bash -nosetests --with-coverage --cover-package=zappa +pytest --cov=zappa # For a specific test: -# nosetests tests.tests:TestZappa.test_lets_encrypt_sanity -s +# pytest tests/tests.py::TestZappa::test_lets_encrypt_sanity diff --git a/tests/tests.py b/tests/tests.py index 8f000744a..586c953e7 100644 --- a/tests/tests.py +++ b/tests/tests.py @@ -2,6 +2,7 @@ import base64 import collections import hashlib +import io import json import os import random @@ -13,6 +14,7 @@ import unittest import uuid import zipfile +from contextlib import redirect_stdout from functools import partial from io import BytesIO from subprocess import check_output @@ -583,7 +585,7 @@ def test_logging(self): # pattern = Zappa.selection_pattern(code) # for url in test_urls: - # self.assertRegexpMatches(url, pattern) + # self.assertRegex(url, pattern) # def test_b64_pattern(self): # head = '\{"http_status": ' @@ -592,7 +594,7 @@ def test_logging(self): # pattern = Zappa.selection_pattern(code) # document = head + code + random_string(50) - # self.assertRegexpMatches(document, pattern) + # self.assertRegex(document, pattern) # for bad_code in ['200', '301', '302']: # document = base64.b64encode(head + bad_code + random_string(50)) @@ -1411,7 +1413,7 @@ def test_cli_save_python_settings_file(self): # error_msg = output.getvalue().strip() # expected = r".*This argument must be positive \(got -1\)$" - # self.assertRegexpMatches(error_msg, expected) + # self.assertRegex(error_msg, expected) # sys.stderr = old_stderr # @mock.patch('zappa.cli.ZappaCLI.dispatch_command') @@ -1787,142 +1789,143 @@ def test_certify_sanity_checks(self): * Writes errors when certificate settings haven't been specified. * Calls Zappa correctly for creates vs. updates. """ - old_stdout = sys.stderr + zappa_cli = ZappaCLI() + zappa_cli.domain = "test.example.com" + try: + zappa_cli.certify() + except AttributeError: + # Since zappa_cli.zappa isn't initialized, the certify() call + # fails when it tries to inspect what Zappa has deployed. + pass + + # Set up a core.Zappa mock and let us save some state about + # domains and lambdas + zappa_mock = mock.create_autospec(Zappa) + zappa_mock.function_versions = [] + zappa_mock.domain_names = {} + + def get_lambda_function_versions(_function_name, *_args, **_kwargs): + return zappa_mock.function_versions + + def get_domain_name(domain, *_args, **_kwargs): + return zappa_mock.domain_names.get(domain) + + zappa_mock.get_domain_name.side_effect = get_domain_name + zappa_mock.get_lambda_function_versions.side_effect = get_lambda_function_versions + + zappa_cli.zappa = zappa_mock + self.assertRaises(ClickException, zappa_cli.certify) + + # Make sure we get an error if we don't configure the domain. + zappa_cli.zappa.function_versions = ["$LATEST"] + zappa_cli.api_stage = "stage" + zappa_cli.zappa_settings = {"stage": {}} + zappa_cli.api_stage = "stage" + zappa_cli.domain = "test.example.com" try: - zappa_cli = ZappaCLI() - zappa_cli.domain = "test.example.com" - try: - zappa_cli.certify() - except AttributeError: - # Since zappa_cli.zappa isn't initialized, the certify() call - # fails when it tries to inspect what Zappa has deployed. - pass - - # Set up a core.Zappa mock and let us save some state about - # domains and lambdas - zappa_mock = mock.create_autospec(Zappa) - zappa_mock.function_versions = [] - zappa_mock.domain_names = {} - - def get_lambda_function_versions(_function_name, *_args, **_kwargs): - return zappa_mock.function_versions - - def get_domain_name(domain, *_args, **_kwargs): - return zappa_mock.domain_names.get(domain) - - zappa_mock.get_domain_name.side_effect = get_domain_name - zappa_mock.get_lambda_function_versions.side_effect = get_lambda_function_versions - - zappa_cli.zappa = zappa_mock - self.assertRaises(ClickException, zappa_cli.certify) - - # Make sure we get an error if we don't configure the domain. - zappa_cli.zappa.function_versions = ["$LATEST"] - zappa_cli.api_stage = "stage" - zappa_cli.zappa_settings = {"stage": {}} - zappa_cli.api_stage = "stage" - zappa_cli.domain = "test.example.com" - - try: - zappa_cli.certify() - except ClickException as e: - log_output = str(e) - self.assertIn("Can't certify a domain without", log_output) - self.assertIn("domain", log_output) - - # Without any LetsEncrypt settings, we should get a message about - # not having a lets_encrypt_key setting. - zappa_cli.zappa_settings["stage"]["domain"] = "test.example.com" - try: - zappa_cli.certify() - self.fail("Expected a ClickException") - except ClickException as e: - log_output = str(e) - self.assertIn("Can't certify a domain without", log_output) - self.assertIn("lets_encrypt_key", log_output) - - # With partial settings, we should get a message about not having - # certificate, certificate_key, and certificate_chain - zappa_cli.zappa_settings["stage"]["certificate"] = "foo" - try: - zappa_cli.certify() - self.fail("Expected a ClickException") - except ClickException as e: - log_output = str(e) - self.assertIn("Can't certify a domain without", log_output) - self.assertIn("certificate_key", log_output) - self.assertIn("certificate_chain", log_output) - - zappa_cli.zappa_settings["stage"]["certificate_key"] = "key" - try: - zappa_cli.certify() - self.fail("Expected a ClickException") - except ClickException as e: - log_output = str(e) - self.assertIn("Can't certify a domain without", log_output) - self.assertIn("certificate_key", log_output) - self.assertIn("certificate_chain", log_output) - - zappa_cli.zappa_settings["stage"]["certificate_chain"] = "chain" - del zappa_cli.zappa_settings["stage"]["certificate_key"] - try: - zappa_cli.certify() - self.fail("Expected a ClickException") - except ClickException as e: - log_output = str(e) - self.assertIn("Can't certify a domain without", log_output) - self.assertIn("certificate_key", log_output) - self.assertIn("certificate_chain", log_output) - - # With all certificate settings, make sure Zappa's domain calls - # are executed. - cert_file = tempfile.NamedTemporaryFile() - cert_file.write(b"Hello world") - cert_file.flush() - - zappa_cli.zappa_settings["stage"].update( - { - "certificate": cert_file.name, - "certificate_key": cert_file.name, - "certificate_chain": cert_file.name, - } - ) - sys.stdout.truncate(0) + zappa_cli.certify() + except ClickException as e: + log_output = str(e) + self.assertIn("Can't certify a domain without", log_output) + self.assertIn("domain", log_output) + + # Without any LetsEncrypt settings, we should get a message about + # not having a lets_encrypt_key setting. + zappa_cli.zappa_settings["stage"]["domain"] = "test.example.com" + try: + zappa_cli.certify() + self.fail("Expected a ClickException") + except ClickException as e: + log_output = str(e) + self.assertIn("Can't certify a domain without", log_output) + self.assertIn("lets_encrypt_key", log_output) + + # With partial settings, we should get a message about not having + # certificate, certificate_key, and certificate_chain + zappa_cli.zappa_settings["stage"]["certificate"] = "foo" + try: + zappa_cli.certify() + self.fail("Expected a ClickException") + except ClickException as e: + log_output = str(e) + self.assertIn("Can't certify a domain without", log_output) + self.assertIn("certificate_key", log_output) + self.assertIn("certificate_chain", log_output) + + zappa_cli.zappa_settings["stage"]["certificate_key"] = "key" + try: + zappa_cli.certify() + self.fail("Expected a ClickException") + except ClickException as e: + log_output = str(e) + self.assertIn("Can't certify a domain without", log_output) + self.assertIn("certificate_key", log_output) + self.assertIn("certificate_chain", log_output) + + zappa_cli.zappa_settings["stage"]["certificate_chain"] = "chain" + del zappa_cli.zappa_settings["stage"]["certificate_key"] + try: + zappa_cli.certify() + self.fail("Expected a ClickException") + except ClickException as e: + log_output = str(e) + self.assertIn("Can't certify a domain without", log_output) + self.assertIn("certificate_key", log_output) + self.assertIn("certificate_chain", log_output) + + # With all certificate settings, make sure Zappa's domain calls + # are executed. + cert_file = tempfile.NamedTemporaryFile() + cert_file.write(b"Hello world") + cert_file.flush() + + zappa_cli.zappa_settings["stage"].update( + { + "certificate": cert_file.name, + "certificate_key": cert_file.name, + "certificate_chain": cert_file.name, + } + ) + + f = io.StringIO() + with redirect_stdout(f): zappa_cli.certify() zappa_cli.zappa.create_domain_name.assert_called_once() zappa_cli.zappa.update_route53_records.assert_called_once() zappa_cli.zappa.update_domain_name.assert_not_called() - log_output = sys.stdout.getvalue() - self.assertIn("Created a new domain name", log_output) + log_output = f.getvalue() + self.assertIn("Created a new domain name", log_output) - zappa_cli.zappa.reset_mock() - zappa_cli.zappa.domain_names["test.example.com"] = "*.example.com" - sys.stdout.truncate(0) + zappa_cli.zappa.reset_mock() + zappa_cli.zappa.domain_names["test.example.com"] = "*.example.com" + + f = io.StringIO() + with redirect_stdout(f): zappa_cli.certify() zappa_cli.zappa.update_domain_name.assert_called_once() zappa_cli.zappa.update_route53_records.assert_not_called() zappa_cli.zappa.create_domain_name.assert_not_called() - log_output = sys.stdout.getvalue() - self.assertNotIn("Created a new domain name", log_output) - - # Test creating domain without Route53 - zappa_cli.zappa_settings["stage"].update( - { - "route53_enabled": False, - } - ) - zappa_cli.zappa.reset_mock() - zappa_cli.zappa.domain_names["test.example.com"] = "" - sys.stdout.truncate(0) + log_output = f.getvalue() + self.assertNotIn("Created a new domain name", log_output) + + # Test creating domain without Route53 + zappa_cli.zappa_settings["stage"].update( + { + "route53_enabled": False, + } + ) + zappa_cli.zappa.reset_mock() + zappa_cli.zappa.domain_names["test.example.com"] = "" + + f = io.StringIO() + with redirect_stdout(f): zappa_cli.certify() zappa_cli.zappa.create_domain_name.assert_called_once() zappa_cli.zappa.update_route53_records.assert_not_called() zappa_cli.zappa.update_domain_name.assert_not_called() - log_output = sys.stdout.getvalue() - self.assertIn("Created a new domain name", log_output) - finally: - sys.stdout = old_stdout + log_output = f.getvalue() + self.assertIn("Created a new domain name", log_output) @mock.patch("troposphere.Template") @mock.patch("botocore.client") diff --git a/tests/tests_docs.py b/tests/tests_docs.py index ac3fc1805..2b77c5b69 100644 --- a/tests/tests_docs.py +++ b/tests/tests_docs.py @@ -84,7 +84,7 @@ def test_readmetoc(self): else: msg = "You can set environ[ZAPPA_TEST_SAVE_README_NEW]=1 to generate\n" " README.test.md to manually compare." - self.assertEquals( + self.assertEqual( "".join(old_readme), new_readme, "README doesn't match after regenerating TOC\n\n" "You need to run doctoc after a heading change.\n{}".format(msg), diff --git a/tests/tests_placebo.py b/tests/tests_placebo.py index f71454746..ef714d020 100644 --- a/tests/tests_placebo.py +++ b/tests/tests_placebo.py @@ -443,7 +443,7 @@ def test_handler(self, session): # Ensure Zappa does return 401 if no function was defined. lh.settings.AUTHORIZER_FUNCTION = None - with self.assertRaisesRegexp(Exception, "Unauthorized"): + with self.assertRaisesRegex(Exception, "Unauthorized"): lh.handler(event, None) # Unhandled event diff --git a/tests/tests_utilities.py b/tests/tests_utilities.py index 4004ad0a6..07baa3473 100644 --- a/tests/tests_utilities.py +++ b/tests/tests_utilities.py @@ -360,7 +360,7 @@ def test_with_response_time__true(self): rt_us = 15 environ, expected = self._build_expected_format_string(status_code, {}, content_length, rt_us=15) actual = formatter(status_code, environ, content_length, rt_us=rt_us) - self.assertRegexpMatches(actual, self.datetime_regex) + self.assertRegex(actual, self.datetime_regex) # extract and remove matched datetime result = self.datetime_regex.search(actual) match_start, match_end = result.span() @@ -380,7 +380,7 @@ def test_with_response_time__true__with_querystring(self): additional_environ = {"QUERY_STRING": "name=hello&data=hello"} environ, expected = self._build_expected_format_string(status_code, additional_environ, content_length, rt_us=15) actual = formatter(status_code, environ, content_length, rt_us=rt_us) - self.assertRegexpMatches(actual, self.datetime_regex) + self.assertRegex(actual, self.datetime_regex) # extract and remove matched datetime result = self.datetime_regex.search(actual) match_start, match_end = result.span() @@ -401,7 +401,7 @@ def test_with_response_time__false(self): content_length = 10 environ, expected = self._build_expected_format_string(status_code, {}, content_length) actual = formatter(status_code, environ, content_length) - self.assertRegexpMatches(actual, self.datetime_regex) + self.assertRegex(actual, self.datetime_regex) # extract and remove matched datetime result = self.datetime_regex.search(actual) match_start, match_end = result.span() @@ -420,7 +420,7 @@ def test_with_response_time__false__with_querystring(self): additional_environ = {"QUERY_STRING": "name=hello&data=hello"} environ, expected = self._build_expected_format_string(status_code, additional_environ, content_length) actual = formatter(status_code, environ, content_length) - self.assertRegexpMatches(actual, self.datetime_regex) + self.assertRegex(actual, self.datetime_regex) # extract and remove matched datetime result = self.datetime_regex.search(actual) match_start, match_end = result.span() diff --git a/zappa/core.py b/zappa/core.py index 1cc2bc58e..6bee528ef 100644 --- a/zappa/core.py +++ b/zappa/core.py @@ -2248,7 +2248,7 @@ def create_stack_template( auth_type = "NONE" if iam_authorization and authorizer: - logger.warn( + logger.warning( "Both IAM Authorization and Authorizer are specified, this is not possible. " "Setting Auth method to IAM Authorization" ) diff --git a/zappa/wsgi.py b/zappa/wsgi.py index c1889c08f..ecbcec251 100644 --- a/zappa/wsgi.py +++ b/zappa/wsgi.py @@ -2,9 +2,7 @@ import logging import sys from io import BytesIO -from urllib.parse import urlencode - -from werkzeug import urls +from urllib.parse import unquote, urlencode from .utilities import ApacheNCSAFormatter, merge_headers, titlecase_keys @@ -43,7 +41,7 @@ def create_wsgi_request( else: query = event_info.get("queryStringParameters", {}) query_string = urlencode(query) if query else "" - query_string = urls.url_unquote(query_string) + query_string = unquote(query_string) if context_header_mappings: for key, value in context_header_mappings.items(): @@ -81,7 +79,7 @@ def create_wsgi_request( # https://github.com/Miserlou/Zappa/issues/1188 headers = titlecase_keys(headers) - path = urls.url_unquote(event_info["path"]) + path = unquote(event_info["path"]) if base_path: script_name = "/" + base_path