diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index fa3dac98..5a788907 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -23,7 +23,7 @@ jobs: with: python-version: 3.10.1 - name: install dependencies - run: python -m pip install --upgrade pip && python -m pip install -r requirements.txt + run: python -m pip install --upgrade pip && python -m pip install -r requirements-dev.txt - name: run unit tests run: python -m pip install pytest && python -m pytest . env: @@ -66,7 +66,7 @@ jobs: with: python-version: 3.10.1 - name: install dependencies - run: python -m pip install --upgrade pip && python -m pip install -r requirements.txt + run: python -m pip install --upgrade pip && python -m pip install -r requirements-dev.txt - name: Bandit run: bandit -r ./app - name: Safety diff --git a/README.md b/README.md index 092c6755..fd6612b1 100644 --- a/README.md +++ b/README.md @@ -32,10 +32,17 @@ Clone the repository .venv\Scripts\activate.bat ### Install dependencies - From the top-level directory enter the command to install pip and the dependencies of the project - python3 -m pip install --upgrade pip && pip install -r requirements.txt + python3 -m pip install --upgrade pip && pip install -r requirements-dev.txt + +NOTE: requirements-dev.txt and requirements.txt are updated using [pip-tools pip-compile](https://github.com/jazzband/pip-tools) +To update requirements please manually add the dependencies in the .in files (not the requirements.txt files) +Then run: + + pip-compile requirements.in + + pip-compile requirements-dev.in ## How to use 1. Set-up an API KEY that requires to connect with the govuk-notify-service @@ -48,12 +55,24 @@ From the top-level directory enter the command to install pip and the dependenci Note: For unit (integration) testing, you also need to set this in `pytest.ini` -1. Enter the virtual environment as described above, then: +2. Enter the virtual environment as described above, then: - flask run + `flask run` Note: This service is an internal service so it doesn't have the frontend. +# Run with Gunicorn + +In deployed environments the service is run with gunicorn. You can run the service locally with gunicorn to test + +First set the FLASK_ENV environment you wish to test eg: + + export FLASK_ENV=dev + +Then run gunicorn using the following command: + + gunicorn wsgi:app -c run/gunicorn/local.py + ## How to post data for notification service. Go to relevant service. See example diff --git a/app/create_app.py b/app/create_app.py index 8b0ef2e3..17f3c2c1 100644 --- a/app/create_app.py +++ b/app/create_app.py @@ -1,45 +1,19 @@ +from config import Config from flask import Flask -from flask_compress import Compress from flask_talisman import Talisman +from fsd_utils.logging import logging def create_app() -> Flask: - - # ---- SETUP STATIC URL PATH. - flask_app = Flask(__name__) + flask_app = Flask("Notification") flask_app.config.from_object("config.Config") - # ---- SETUP SECURITY CONFIGURATION & CSRF PROTECTION. - csp = { - "default-src": "'self'", - "script-src": [ - "'self'", - "'sha256-+6WnXIl4mbFTCARd8N3COQmT3bJJmo32N8q8ZSQAIcU='", - "'sha256-l1eTVSK8DTnK8+yloud7wZUqFrI0atVo6VlC6PJvYaQ='", - ], - "img-src": ["data:", "'self'"], - } - - hss = { - "Strict-Transport-Security": ( - "max-age=31536000; includeSubDomains; preload" - ), - "X-Content-Type-Options": "nosniff", - "X-Frame-Options": "SAMEORIGIN", - "X-XSS-Protection": "1; mode=block", - "Feature_Policy": ( - "microphone 'none'; camera 'none'; geolocation 'none'" - ), - } + # Initialise logging + logging.init_app(flask_app) - Compress(flask_app) - Talisman( - flask_app, - content_security_policy=csp, - strict_transport_security=hss, - force_https=False, - ) + # Configure application security with Talisman + Talisman(flask_app, **Config.TALISMAN_SETTINGS) # ---- SETUP GLOBAL CONSTANTS (to be accessed from the app). @flask_app.context_processor diff --git a/config/__init__.py b/config/__init__.py index ca76eb6d..a97141b1 100644 --- a/config/__init__.py +++ b/config/__init__.py @@ -1,22 +1,25 @@ # flake8: noqa -import os +from os import environ -FLASK_ENV = os.environ.get("FLASK_ENV") +FLASK_ENV = environ.get("FLASK_ENV") if not FLASK_ENV: raise KeyError("FLASK_ENV does not exist in environ") match FLASK_ENV: case "development": - from config.environments.development import ( - DevelopmentConfig as Config, - ) + from config.envs.development import DevelopmentConfig as Config + case "dev": + from config.envs.dev import DevConfig as Config + case "test": + from config.envs.test import TestConfig as Config case "unit_test": - from config.environments.unit_test import ( - UnitTestConfig as Config, - ) + from config.envs.unit_test import UnitTestConfig as Config + case "production": + from config.envs.production import ProductionConfig as Config case _: - from config.environments.default import DefaultConfig as Config + from config.envs.default import DefaultConfig as Config + try: Config.pretty_print() diff --git a/config/environments/default.py b/config/environments/default.py deleted file mode 100644 index 73b51a34..00000000 --- a/config/environments/default.py +++ /dev/null @@ -1,19 +0,0 @@ -import os - -from fsd_utils import configclass - - -@configclass -class DefaultConfig: - - GOV_NOTIFY_API_KEY = os.environ.get( - "GOV_NOTIFY_API_KEY", "gov_notify_api_key" - ) - - MAGIC_LINK_TEMPLATE_ID = os.environ.get( - "MAGIC_LINK_TEMPLATE_ID", "02a6d48a-f227-4b9a-9dd7-9e0cf203c8a2" - ) - APPLICATION_RECORD_TEMPLATE_ID = os.environ.get( - "APPLICATION_RECORD_TEMPLATE_ID", - "0ddadcb3-ebe7-44f9-90e6-80ff3b61e0cb", - ) diff --git a/config/environments/development.py b/config/environments/development.py deleted file mode 100644 index b951efe2..00000000 --- a/config/environments/development.py +++ /dev/null @@ -1,9 +0,0 @@ -from config.environments.default import DefaultConfig -from fsd_utils import configclass - - -@configclass -class DevelopmentConfig(DefaultConfig): - # Add any development specific config here - - pass diff --git a/config/environments/unit_test.py b/config/environments/unit_test.py deleted file mode 100644 index 0188ba69..00000000 --- a/config/environments/unit_test.py +++ /dev/null @@ -1,9 +0,0 @@ -from config.environments.default import DefaultConfig -from fsd_utils import configclass - - -@configclass -class UnitTestConfig(DefaultConfig): - # Add any unit test specific config here - - pass diff --git a/config/envs/default.py b/config/envs/default.py new file mode 100644 index 00000000..78d7d4e8 --- /dev/null +++ b/config/envs/default.py @@ -0,0 +1,78 @@ +import logging +import os + +from fsd_utils import configclass + + +@configclass +class DefaultConfig: + + GOV_NOTIFY_API_KEY = os.environ.get( + "GOV_NOTIFY_API_KEY", "gov_notify_api_key" + ) + + MAGIC_LINK_TEMPLATE_ID = os.environ.get( + "MAGIC_LINK_TEMPLATE_ID", "02a6d48a-f227-4b9a-9dd7-9e0cf203c8a2" + ) + APPLICATION_RECORD_TEMPLATE_ID = os.environ.get( + "APPLICATION_RECORD_TEMPLATE_ID", + "0ddadcb3-ebe7-44f9-90e6-80ff3b61e0cb", + ) + + # Logging + FSD_LOG_LEVEL = logging.WARNING + + # Talisman Config + FORCE_HTTPS = True + + # Content Security Policy + SECURE_CSP = { + "default-src": "'self'", + "script-src": [ + "'self'", + "'sha256-+6WnXIl4mbFTCARd8N3COQmT3bJJmo32N8q8ZSQAIcU='", + "'sha256-l1eTVSK8DTnK8+yloud7wZUqFrI0atVo6VlC6PJvYaQ='", + ], + "img-src": ["data:", "'self'"], + } + + # Security headers and other policies + FSD_REFERRER_POLICY = "strict-origin-when-cross-origin" + FSD_SESSION_COOKIE_SAMESITE = "Lax" + FSD_PERMISSIONS_POLICY = {"interest-cohort": "()"} + FSD_DOCUMENT_POLICY = {} + FSD_FEATURE_POLICY = { + "microphone": "'bob'", + "camera": "'none'", + "geolocation": "'none'", + } + + DENY = "DENY" + SAMEORIGIN = "SAMEORIGIN" + ALLOW_FROM = "ALLOW-FROM" + ONE_YEAR_IN_SECS = 31556926 + + TALISMAN_SETTINGS = { + "feature_policy": FSD_FEATURE_POLICY, + "permissions_policy": FSD_PERMISSIONS_POLICY, + "document_policy": FSD_DOCUMENT_POLICY, + "force_https": FORCE_HTTPS, + "force_https_permanent": False, + "force_file_save": False, + "frame_options": "SAMEORIGIN", + "frame_options_allow_from": None, + "strict_transport_security": True, + "strict_transport_security_preload": True, + "strict_transport_security_max_age": ONE_YEAR_IN_SECS, + "strict_transport_security_include_subdomains": True, + "content_security_policy": SECURE_CSP, + "content_security_policy_report_uri": None, + "content_security_policy_report_only": False, + "content_security_policy_nonce_in": None, + "referrer_policy": FSD_REFERRER_POLICY, + "session_cookie_secure": True, + "session_cookie_http_only": True, + "session_cookie_samesite": FSD_SESSION_COOKIE_SAMESITE, + "x_content_type_options": True, + "x_xss_protection": True, + } diff --git a/config/envs/dev.py b/config/envs/dev.py new file mode 100644 index 00000000..3c55bb94 --- /dev/null +++ b/config/envs/dev.py @@ -0,0 +1,15 @@ +"""Flask Dev Pipeline Environment Configuration.""" +import logging + +from config.envs.default import DefaultConfig as Config +from fsd_utils import configclass + + +@configclass +class DevConfig(Config): + # Application Config + SECRET_KEY = "dev" + SESSION_COOKIE_NAME = "session_cookie" + + # Logging + FSD_LOG_LEVEL = logging.INFO diff --git a/config/envs/development.py b/config/envs/development.py new file mode 100644 index 00000000..e4f5104c --- /dev/null +++ b/config/envs/development.py @@ -0,0 +1,15 @@ +import logging + +from config.envs.default import DefaultConfig +from fsd_utils import configclass + + +@configclass +class DevelopmentConfig(DefaultConfig): + # Application Config + SECRET_KEY = "dev" + SESSION_COOKIE_NAME = "session_cookie" + FLASK_ENV = "development" + + # Logging + FSD_LOG_LEVEL = logging.DEBUG diff --git a/config/envs/production.py b/config/envs/production.py new file mode 100644 index 00000000..e4d514fd --- /dev/null +++ b/config/envs/production.py @@ -0,0 +1,8 @@ +"""Flask Production Environment Configuration.""" +from config.envs.default import DefaultConfig as Config +from fsd_utils import configclass + + +@configclass +class ProductionConfig(Config): + pass diff --git a/config/envs/test.py b/config/envs/test.py new file mode 100644 index 00000000..48b314ae --- /dev/null +++ b/config/envs/test.py @@ -0,0 +1,11 @@ +"""Flask Test Environment Configuration.""" +from os import environ + +from config.envs.default import DefaultConfig as Config +from fsd_utils import configclass + + +@configclass +class TestConfig(Config): + + SECRET_KEY = environ.get("SECRET_KEY", "test") diff --git a/config/envs/unit_test.py b/config/envs/unit_test.py new file mode 100644 index 00000000..42d071cf --- /dev/null +++ b/config/envs/unit_test.py @@ -0,0 +1,15 @@ +import logging + +from config.envs.default import DefaultConfig +from fsd_utils import configclass + + +@configclass +class UnitTestConfig(DefaultConfig): + + # Application Config + SECRET_KEY = "dev" + SESSION_COOKIE_NAME = "session_cookie" + + # Logging + FSD_LOG_LEVEL = logging.DEBUG diff --git a/manifest.yml b/manifest.yml index 617b5cfe..8375982c 100644 --- a/manifest.yml +++ b/manifest.yml @@ -4,28 +4,31 @@ applications: memory: 64M buildpacks: - https://github.com/cloudfoundry/python-buildpack.git - command: flask run --host 0.0.0.0 --port 8080 + command: gunicorn wsgi:app -c run/gunicorn/devtest.py routes: - route: funding-service-design-notification-dev.apps.internal env: + FLASK_ENV: dev GOV_NOTIFY_API_KEY: ((GOV_NOTIFY_API_KEY)) - name: funding-service-design-notification-uat memory: 64M buildpacks: - https://github.com/cloudfoundry/python-buildpack.git - command: flask run --host 0.0.0.0 --port 8080 + command: gunicorn wsgi:app -c run/gunicorn/devtest.py routes: - route: funding-service-design-notification-uat.apps.internal env: + FLASK_ENV: test GOV_NOTIFY_API_KEY: ((GOV_NOTIFY_API_KEY)) - name: funding-service-design-notification-test memory: 64M buildpacks: - https://github.com/cloudfoundry/python-buildpack.git - command: flask run --host 0.0.0.0 --port 8080 + command: gunicorn wsgi:app -c run/gunicorn/devtest.py routes: - route: funding-service-design-notification-test.apps.internal env: + FLASK_ENV: test GOV_NOTIFY_API_KEY: ((GOV_NOTIFY_API_KEY)) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..60cf2c20 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,10 @@ +[package] +name = "funding-service-design-notification" +version = "0.1.1" +description = "The funding service design notification service for the DLUHC." +authors = ["DLUHC"] +license = "MIT License" + +[tool.black] +line-length = 79 +experimental-string-processing = 1 diff --git a/pytest.ini b/pytest.ini index 08be24b5..5b99b76f 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,6 +1,7 @@ [pytest] env = FLASK_ENV=unit_test + FLASK_DEBUG=1 # To make the unit tests work, add the following env var with an appropriate value: # GOV_NOTIFY_API_KEY=xxxx # Without this set in the env you may see the following error: diff --git a/requirements-dev.in b/requirements-dev.in new file mode 100644 index 00000000..0799b913 --- /dev/null +++ b/requirements-dev.in @@ -0,0 +1,16 @@ +-r requirements.txt +bandit +safety==1.10.3 +black +deepdiff +pre-commit +pytest +pytest-env +pytest-flask +pytest-mock==3.7.0 +pytest-selenium==2.0.1 +pip-tools +axe-selenium-python==2.1.6 +json2html==1.3.0 +selenium==4.2.0 +webdriver-manager==3.7.0 diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 00000000..52916a53 --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,345 @@ +# +# This file is autogenerated by pip-compile with python 3.10 +# To update, run: +# +# pip-compile requirements-dev.in +# +async-generator==1.10 + # via + # trio + # trio-websocket +attrs==21.4.0 + # via + # -r requirements.txt + # jsonschema + # outcome + # pytest + # trio +axe-selenium-python==2.1.6 + # via -r requirements-dev.in +bandit==1.7.4 + # via -r requirements-dev.in +black==22.6.0 + # via -r requirements-dev.in +build==0.8.0 + # via pip-tools +certifi==2022.6.15 + # via + # -r requirements.txt + # requests + # urllib3 +cffi==1.15.1 + # via cryptography +cfgv==3.3.1 + # via pre-commit +chardet==4.0.0 + # via + # -r requirements.txt + # prance +charset-normalizer==2.1.0 + # via + # -r requirements.txt + # requests +click==8.1.3 + # via + # -r requirements.txt + # black + # clickclick + # flask + # pip-tools + # safety +clickclick==20.10.2 + # via + # -r requirements.txt + # connexion +commonmark==0.9.1 + # via + # -r requirements.txt + # rich +connexion==2.14.0 + # via -r requirements.txt +cryptography==37.0.2 + # via + # pyopenssl + # urllib3 +deepdiff==5.8.1 + # via -r requirements-dev.in +distlib==0.3.4 + # via virtualenv +docopt==0.6.2 + # via + # -r requirements.txt + # notifications-python-client +dparse==0.5.1 + # via safety +filelock==3.7.1 + # via virtualenv +flask==2.1.1 + # via + # -r requirements.txt + # connexion + # funding-service-design-utils + # pytest-flask +flask-talisman==0.8.1 + # via -r requirements.txt +funding-service-design-utils @ git+https://github.com/communitiesuk/funding-service-design-utils.git@main + # via -r requirements.txt +gitdb==4.0.9 + # via gitpython +gitpython==3.1.27 + # via bandit +gunicorn==20.1.0 + # via + # -r requirements.txt + # funding-service-design-utils +h11==0.13.0 + # via wsproto +identify==2.5.1 + # via pre-commit +idna==3.3 + # via + # -r requirements.txt + # requests + # trio + # urllib3 +inflection==0.5.1 + # via + # -r requirements.txt + # connexion +iniconfig==1.1.1 + # via pytest +itsdangerous==2.1.2 + # via + # -r requirements.txt + # connexion + # flask +jinja2==3.1.2 + # via + # -r requirements.txt + # flask + # swagger-ui-bundle +json2html==1.3.0 + # via -r requirements-dev.in +jsonschema==4.6.1 + # via + # -r requirements.txt + # connexion + # openapi-schema-validator + # openapi-spec-validator +markupsafe==2.1.1 + # via + # -r requirements.txt + # jinja2 +mypy-extensions==0.4.3 + # via black +nodeenv==1.7.0 + # via pre-commit +notifications-python-client==6.3.0 + # via -r requirements.txt +openapi-schema-validator==0.2.3 + # via + # -r requirements.txt + # openapi-spec-validator +openapi-spec-validator==0.4.0 + # via -r requirements.txt +ordered-set==4.1.0 + # via deepdiff +outcome==1.2.0 + # via trio +packaging==21.3 + # via + # -r requirements.txt + # build + # connexion + # dparse + # pytest + # safety +pathspec==0.9.0 + # via black +pbr==5.9.0 + # via stevedore +pep517==0.12.0 + # via build +pip-tools==6.8.0 + # via -r requirements-dev.in +platformdirs==2.5.2 + # via + # black + # virtualenv +pluggy==1.0.0 + # via pytest +prance==0.21.8.0 + # via -r requirements.txt +pre-commit==2.19.0 + # via -r requirements-dev.in +py==1.11.0 + # via + # pytest + # pytest-forked +pycparser==2.21 + # via cffi +pygments==2.12.0 + # via + # -r requirements.txt + # rich +pyjwt==2.4.0 + # via + # -r requirements.txt + # notifications-python-client +pyopenssl==22.0.0 + # via urllib3 +pyparsing==3.0.9 + # via + # -r requirements.txt + # packaging +pyrsistent==0.18.1 + # via + # -r requirements.txt + # jsonschema +pysocks==1.7.1 + # via urllib3 +pytest==7.1.2 + # via + # -r requirements-dev.in + # axe-selenium-python + # pytest-base-url + # pytest-env + # pytest-flask + # pytest-forked + # pytest-html + # pytest-metadata + # pytest-mock + # pytest-selenium + # pytest-variables +pytest-base-url==2.0.0 + # via pytest-selenium +pytest-env==0.6.2 + # via -r requirements-dev.in +pytest-flask==1.2.0 + # via -r requirements-dev.in +pytest-forked==1.4.0 + # via -r requirements-dev.in +pytest-html==3.1.1 + # via pytest-selenium +pytest-metadata==2.0.1 + # via pytest-html +pytest-mock==3.7.0 + # via -r requirements-dev.in +pytest-selenium==2.0.1 + # via -r requirements-dev.in +pytest-variables==2.0.0 + # via pytest-selenium +python-dotenv==0.20.0 + # via + # -r requirements.txt + # funding-service-design-utils + # webdriver-manager +python-json-logger==2.0.2 + # via + # -r requirements.txt + # funding-service-design-utils +pytz==2022.1 + # via + # -r requirements.txt + # funding-service-design-utils +pyyaml==6.0 + # via + # -r requirements.txt + # bandit + # clickclick + # connexion + # dparse + # funding-service-design-utils + # openapi-spec-validator + # pre-commit +requests==2.28.1 + # via + # -r requirements.txt + # connexion + # notifications-python-client + # prance + # pytest-base-url + # pytest-selenium + # safety + # webdriver-manager +rich==12.4.4 + # via + # -r requirements.txt + # funding-service-design-utils +ruamel-yaml==0.17.21 + # via + # -r requirements.txt + # prance +ruamel-yaml-clib==0.2.6 + # via + # -r requirements.txt + # ruamel-yaml +safety==1.10.3 + # via -r requirements-dev.in +selenium==4.2.0 + # via + # -r requirements-dev.in + # axe-selenium-python + # pytest-selenium +semver==2.13.0 + # via + # -r requirements.txt + # prance +six==1.16.0 + # via + # -r requirements.txt + # flask-talisman + # prance + # tenacity + # virtualenv +smmap==5.0.0 + # via gitdb +sniffio==1.2.0 + # via trio +sortedcontainers==2.4.0 + # via trio +stevedore==3.5.0 + # via bandit +swagger-ui-bundle==0.0.9 + # via -r requirements.txt +tenacity==6.3.1 + # via pytest-selenium +toml==0.10.2 + # via + # dparse + # pre-commit +tomli==2.0.1 + # via + # black + # build + # pep517 + # pytest +trio==0.21.0 + # via + # selenium + # trio-websocket +trio-websocket==0.9.2 + # via selenium +urllib3[secure,socks]==1.26.9 + # via + # -r requirements.txt + # requests + # selenium +virtualenv==20.15.1 + # via pre-commit +webdriver-manager==3.7.0 + # via -r requirements-dev.in +werkzeug==2.1.2 + # via + # -r requirements.txt + # connexion + # flask + # pytest-flask +wheel==0.37.1 + # via pip-tools +wsproto==1.1.0 + # via trio-websocket + +# The following packages are considered to be unsafe in a requirements file: +# pip +# setuptools diff --git a/requirements.in b/requirements.in new file mode 100644 index 00000000..80a1314d --- /dev/null +++ b/requirements.in @@ -0,0 +1,28 @@ +#----------------------------------- +# FSD Utils +#----------------------------------- +git+https://github.com/communitiesuk/funding-service-design-utils.git@main +requests + +#----------------------------------- +# Flask Version +#----------------------------------- +Flask==2.1.1 + +#----------------------------------- +# Security +#----------------------------------- +flask-talisman==0.8.1 + +#----------------------------------- +# Notification API +#----------------------------------- +notifications_python_client==6.3.0 + +#----------------------------------- +# Connexion APIs +#----------------------------------- +connexion +swagger-ui-bundle +openapi-spec-validator +prance diff --git a/requirements.txt b/requirements.txt index 563eb460..a4456866 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,105 +1,115 @@ -aenum==3.1.8 -aniso8601==9.0.1 -async-generator==1.10 +# +# This file is autogenerated by pip-compile with python 3.10 +# To update, run: +# +# pip-compile requirements.in +# attrs==21.4.0 -axe-selenium-python==2.1.6 -Babel==2.9.1 -bandit==1.7.1 -black==22.3.0 -Brotli==1.0.9 -certifi==2021.10.8 -cffi==1.15.0 -cfgv==3.3.1 -charset-normalizer==2.0.11 -click==8.0.3 -colorama==0.4.4 -configparser==5.2.0 -crayons==0.4.0 -cryptography==36.0.1 -dill==0.3.4 -distlib==0.3.4 + # via jsonschema +certifi==2022.6.15 + # via requests +chardet==4.0.0 + # via prance +charset-normalizer==2.1.0 + # via requests +click==8.1.3 + # via + # clickclick + # flask +clickclick==20.10.2 + # via connexion +commonmark==0.9.1 + # via rich +connexion==2.14.0 + # via -r requirements.in docopt==0.6.2 -dparse==0.5.1 -execnet==1.9.0 -Faker==13.3.1 -filelock==3.4.2 -Flask==2.0.2 -Flask-Compress==1.10.1 -flask-restx==0.5.1 -Flask-SeaSurf==0.3.1 + # via notifications-python-client +flask==2.1.1 + # via + # -r requirements.in + # connexion + # funding-service-design-utils flask-talisman==0.8.1 -Flask-WTF==1.0.0 -git+https://github.com/communitiesuk/funding-service-design-utils@main -gitdb==4.0.9 -GitPython==3.1.26 -govuk-frontend-jinja==2.0.0 -h11==0.13.0 -identify==2.4.2 + # via -r requirements.in +funding-service-design-utils @ git+https://github.com/communitiesuk/funding-service-design-utils.git@main + # via -r requirements.in +gunicorn==20.1.0 + # via funding-service-design-utils idna==3.3 -iniconfig==1.1.1 -itsdangerous==2.0.1 -Jinja2==3.0.3 -json2html==1.3.0 -jsonschema==4.4.0 -MarkupSafe==2.0.1 -mypy-extensions==0.4.3 -nodeenv==1.6.0 + # via requests +inflection==0.5.1 + # via connexion +itsdangerous==2.1.2 + # via + # connexion + # flask +jinja2==3.1.2 + # via + # flask + # swagger-ui-bundle +jsonschema==4.6.1 + # via + # connexion + # openapi-schema-validator + # openapi-spec-validator +markupsafe==2.1.1 + # via jinja2 notifications-python-client==6.3.0 -outcome==1.1.0 + # via -r requirements.in +openapi-schema-validator==0.2.3 + # via openapi-spec-validator +openapi-spec-validator==0.4.0 + # via -r requirements.in packaging==21.3 -pathspec==0.9.0 -pbr==5.8.1 -platformdirs==2.4.1 -pluggy==1.0.0 -pre-commit==2.16.0 -py==1.11.0 -pycparser==2.21 -pydantic==1.9.0 -pyfiglet==0.8.post1 -PyJWT==2.4.0 -pyOpenSSL==22.0.0 -pyparsing==3.0.6 + # via connexion +prance==0.21.8.0 + # via -r requirements.in +pygments==2.12.0 + # via rich +pyjwt==2.4.0 + # via notifications-python-client +pyparsing==3.0.9 + # via packaging pyrsistent==0.18.1 -PySocks==1.7.1 -pytest==6.2.5 -pytest-base-url==1.4.2 -pytest-env==0.6.2 -pytest-flask==1.2.0 -pytest-forked==1.4.0 -pytest-html==3.1.1 -pytest-metadata==1.11.0 -pytest-parallel==0.1.1 -pytest-reportportal==5.0.12 -pytest-selenium==2.0.1 -pytest-sugar==0.9.4 -pytest-variables==1.9.0 -pytest-xdist==2.5.0 -python-dateutil==2.8.2 -python-slugify==6.0.1 -pytz==2021.3 -PyYAML==6.0 -reportportal-client==5.1.0 -requests==2.27.1 -safety==1.10.3 -selenium==4.1.3 + # via jsonschema +python-dotenv==0.20.0 + # via funding-service-design-utils +python-json-logger==2.0.2 + # via funding-service-design-utils +pytz==2022.1 + # via funding-service-design-utils +pyyaml==6.0 + # via + # clickclick + # connexion + # funding-service-design-utils + # openapi-spec-validator +requests==2.28.1 + # via + # -r requirements.in + # connexion + # notifications-python-client + # prance +rich==12.4.4 + # via funding-service-design-utils +ruamel-yaml==0.17.21 + # via prance +ruamel-yaml-clib==0.2.6 + # via ruamel-yaml +semver==2.13.0 + # via prance six==1.16.0 -smmap==5.0.0 -sniffio==1.2.0 -sortedcontainers==2.4.0 -stevedore==3.5.0 -tblib==1.7.0 -tenacity==6.3.1 -termcolor==1.1.0 -text-unidecode==1.3 -toml==0.10.2 -tomli==1.2.3 -trio==0.19.0 -trio-websocket==0.9.2 -typing_extensions==4.0.1 -urllib3==1.26.8 -virtualenv==20.13.0 -webdriver-manager==3.5.3 -Werkzeug==2.0.2 -wsproto==1.0.0 -WTForms==3.0.1 -pytest-mock==3.7.0 + # via + # flask-talisman + # prance +swagger-ui-bundle==0.0.9 + # via -r requirements.in +urllib3==1.26.9 + # via requests +werkzeug==2.1.2 + # via + # connexion + # flask + +# The following packages are considered to be unsafe in a requirements file: +# setuptools diff --git a/run/gunicorn/devtest.py b/run/gunicorn/devtest.py new file mode 100644 index 00000000..844e163c --- /dev/null +++ b/run/gunicorn/devtest.py @@ -0,0 +1,3 @@ +from fsd_utils.gunicorn.config.devtest import * # noqa + +# bind = "127.0.0.1:5000" diff --git a/run/gunicorn/local.py b/run/gunicorn/local.py new file mode 100644 index 00000000..714879d5 --- /dev/null +++ b/run/gunicorn/local.py @@ -0,0 +1,3 @@ +from fsd_utils.gunicorn.config.local import * # noqa + +# bind = "127.0.0.1:5000" diff --git a/tests/conftest.py b/tests/conftest.py index 77596f66..caca56f1 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,3 +1,6 @@ +import multiprocessing +import platform + import pytest from app.create_app import create_app from tests.test_application.application_data import expected_application_data @@ -7,6 +10,9 @@ from tests.test_magic_link.magic_link_data import expected_magic_link_data from tests.test_magic_link.magic_link_data import expected_magic_link_response +if platform.system() == "Darwin": + multiprocessing.set_start_method("fork") # Required on macOSX + @pytest.fixture() def flask_test_client(): diff --git a/tests/test_application/test_application.py b/tests/test_application/test_application.py index 7210e31e..d2221f48 100644 --- a/tests/test_application/test_application.py +++ b/tests/test_application/test_application.py @@ -40,8 +40,7 @@ def test_application_contents_with_unexpected_data(flask_test_client): ) assert ( b"Incorrect APPLICATION data, please check the contents of the" - b" APPLICATION data" - in response.data + b" APPLICATION data" in response.data ) @@ -59,8 +58,4 @@ def test_application_contents_with_none_contents(flask_test_client): follow_redirects=True, ) - assert ( - b"Bad request. No data has been received.Please check the contents of" - b" the notification data:" - in response.data - ) + assert response.status_code == 400 diff --git a/tests/test_magic_link/test_magic_link.py b/tests/test_magic_link/test_magic_link.py index 900a1350..65dd622a 100644 --- a/tests/test_magic_link/test_magic_link.py +++ b/tests/test_magic_link/test_magic_link.py @@ -36,8 +36,7 @@ def test_magic_link_contents_with_incorrect_content_key(flask_test_client): assert ( b"Incorrect MAGIC LINK data, please check the contents of the MAGIC" - b" LINK data." - in response.data + b" LINK data." in response.data ) @@ -55,8 +54,4 @@ def test_magic_link_contents_with_none_contents(flask_test_client): follow_redirects=True, ) - assert ( - b"Bad request. No data has been received.Please check the contents of" - b" the notification data:" - in response.data - ) + assert response.status_code == 400 diff --git a/tests/test_magic_link/test_mock_magic_link.py b/tests/test_magic_link/test_mock_magic_link.py index 2b871f82..b5342a22 100644 --- a/tests/test_magic_link/test_mock_magic_link.py +++ b/tests/test_magic_link/test_mock_magic_link.py @@ -111,20 +111,3 @@ def test_mocked_magic_link_incorrect_key( send_email_route_response.json["notify_response"] == mock_email_recipient_func.return_value ) - - -@pytest.mark.usefixtures("live_server") -def test_mocked_magic_link_None_data(): - """ - GIVEN: our service running on unittest mock library. - WHEN: we post "None" data to endpoint "/send". - THEN: we check route "send_email()" returns an expected error message - & check the status "Error" represent 400. - """ - send_email_route_response = send_email() - assert ( - send_email_route_response.json["message"] - == "Bad request. No data has been received.Please check the" - " contents of the notification data: None" - ) - assert "Error" in send_email_route_response.json["status"] diff --git a/wsgi.py b/wsgi.py new file mode 100644 index 00000000..6026b0fa --- /dev/null +++ b/wsgi.py @@ -0,0 +1,4 @@ +from app import app + +if __name__ == "__main__": + app.run()