diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e258e57be..e02080571 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -4,6 +4,7 @@ on: workflow_call env: SECRET_KEY: "TEST_KEY" + ENVIRONMENT: CI jobs: @@ -23,12 +24,17 @@ jobs: run: | python -m pip install --upgrade pip python -m pip install -r requirements/generated/requirements-development.txt + - name: Unit test with pytest run: | # Remove this after we move from build assets in-app to esbuild npm install pip install pytest pytest-cov + # This is required because at the moment the assets are being built in the app + # When the app tries to access sass files in node_modules it will fail unless this is included + npm install + coverage run -m pytest tests/unit_tests coverage xml diff --git a/app/config/__init__.py b/app/config/__init__.py index 41546868c..85b5e5b61 100644 --- a/app/config/__init__.py +++ b/app/config/__init__.py @@ -20,3 +20,4 @@ class Config(object): SESSION_COOKIE_HTTPONLY = True SENTRY_DSN = os.environ.get("SENTRY_DSN") LANGUAGES = {"en": "English", "cy": "Welsh"} + SERVICE_UNAVAILABLE = os.environ.get("MAINTENANCE_MODE", "False").lower() == "true" diff --git a/app/main/routes.py b/app/main/routes.py index 87cab6bc9..043037a2f 100644 --- a/app/main/routes.py +++ b/app/main/routes.py @@ -8,6 +8,7 @@ render_template, request, current_app, + url_for, abort, ) from flask_wtf.csrf import CSRFError @@ -46,6 +47,18 @@ def set_locale(locale): return response +@bp.route("/status", methods=["GET"]) +def status(): + return "OK" + + +@bp.route("/service-unavailable", methods=["GET"]) +def service_unavailable_page(): + if not current_app.config["SERVICE_UNAVAILABLE"]: + return redirect(url_for("main.index")) + abort(503) + + @bp.route("/accessibility", methods=["GET"]) def accessibility(): return render_template("accessibility.html") @@ -100,3 +113,22 @@ def http_exception(error): def csrf_error(error): flash("The form you were submitting has expired. Please try again.") return redirect(request.full_path) + + +@bp.before_request +def service_unavailable_middleware(): + if not current_app.config["SERVICE_UNAVAILABLE"]: + return + + service_unavailable_url = url_for("main.service_unavailable_page") + exempt_urls = [ + service_unavailable_url, + url_for("main.status"), + url_for("main.cookies"), + url_for("main.accessibility"), + url_for("main.privacy"), + url_for("main.set_locale", locale="en"), + url_for("main.set_locale", locale="cy"), + ] + if request.path not in exempt_urls: + return redirect(service_unavailable_url) diff --git a/app/templates/main/503.html b/app/templates/main/503.html index a08854aad..c1a793a96 100644 --- a/app/templates/main/503.html +++ b/app/templates/main/503.html @@ -1,14 +1,34 @@ {% extends "base.html" %} -{% block pageTitle %}Sorry, the service is unavailable – {{config['SERVICE_NAME']}} – GOV.UK{% endblock %} +{% block pageTitle %}{% trans %}Sorry, the service is unavailable{% endtrans %} – {{ config.SERVICE_NAME }} – GOV.UK{% endblock %} {% set mainClasses = "govuk-main-wrapper--l" %} {% block content %}
-

Sorry, the service is unavailable

-

You will be able to use the service from 9am on Monday 19 November 2018.

+

{% trans %}Sorry, the service is unavailable{% endtrans%}

+

{% trans %}The service will be available again soon - try in the next hour.{% endtrans%}

+ +

{% trans %}If you already started using the service, your answers will not be saved.{% endtrans%}

+

{% trans %}If you need urgent help{% endtrans%}

+

{% trans %}Contact the Civil Legal Advice (CLA) helpline.{% endtrans%}

+

+ {% trans %}Telephone{% endtrans%}: 0345 345 4345
+ {% trans %}Relay UK{% endtrans%} {% trans %}(textphone and app): 18001 then 0345 345 4345{% endtrans%} +

+

+ {% trans %}Monday to Friday, 9am to 8pm{% endtrans%}
+ {% trans %}Saturday, 9am to 12:30pm{% endtrans%}
+ {% trans %}Find out about call charges{% endtrans%} +

+

+ {% trans %}If you’re worried about the cost of a call{% endtrans%}: +

+

-{% endblock %} \ No newline at end of file +{% endblock %} diff --git a/compose.yml b/compose.yml index ffb759a2d..972bf8bcb 100644 --- a/compose.yml +++ b/compose.yml @@ -10,6 +10,7 @@ services: restart: always environment: - SECRET_KEY=CHANGE_ME + - MAINTENANCE_MODE=FALSE - FLASK_RUN_PORT=8020 - SENTRY_DSN=${SENTRY_DSN:-} ports: diff --git a/helm_deploy/laa-access-civil-legal-aid/templates/deployment.yaml b/helm_deploy/laa-access-civil-legal-aid/templates/deployment.yaml index 833c0b305..81883dd42 100644 --- a/helm_deploy/laa-access-civil-legal-aid/templates/deployment.yaml +++ b/helm_deploy/laa-access-civil-legal-aid/templates/deployment.yaml @@ -44,9 +44,9 @@ spec: protocol: TCP livenessProbe: httpGet: - path: / + path: /status port: http readinessProbe: httpGet: - path: / + path: /status port: http diff --git a/helm_deploy/laa-access-civil-legal-aid/templates/ingress.yaml b/helm_deploy/laa-access-civil-legal-aid/templates/ingress.yaml index 82ec81bcc..259e4561b 100644 --- a/helm_deploy/laa-access-civil-legal-aid/templates/ingress.yaml +++ b/helm_deploy/laa-access-civil-legal-aid/templates/ingress.yaml @@ -13,6 +13,7 @@ metadata: labels: {{- include "laa-access-civil-legal-aid.labels" . | nindent 4 }} annotations: + nginx.ingress.kubernetes.io/custom-http-errors: "413,502,504" {{- if .Values.ingress.cluster.name }} external-dns.alpha.kubernetes.io/set-identifier: "{{ $fullName }}-{{ .Release.Namespace }}-{{- .Values.ingress.cluster.name -}}" external-dns.alpha.kubernetes.io/aws-weight: "{{- .Values.ingress.cluster.weight -}}" diff --git a/helm_deploy/laa-access-civil-legal-aid/values.yaml b/helm_deploy/laa-access-civil-legal-aid/values.yaml index b68824cf0..c793294f8 100644 --- a/helm_deploy/laa-access-civil-legal-aid/values.yaml +++ b/helm_deploy/laa-access-civil-legal-aid/values.yaml @@ -67,4 +67,9 @@ envVars: SENTRY_DSN: secret: name: sentry - key: dsn \ No newline at end of file + key: dsn + MAINTENANCE_MODE: + configmap: + name: maintenance-mode + key: enabled + optional: true \ No newline at end of file diff --git a/tests/functional_tests/test_accessibility.py b/tests/functional_tests/test_accessibility.py index e4104211d..e78d26e53 100644 --- a/tests/functional_tests/test_accessibility.py +++ b/tests/functional_tests/test_accessibility.py @@ -5,6 +5,8 @@ from axe_core_python.sync_playwright import Axe import json import os +import shutil + ACCESSIBILITY_STANDARDS = ["wcag2a", "wcag2aa"] @@ -45,7 +47,8 @@ def check_accessibility(page: Page): @pytest.mark.usefixtures("live_server") def test_all_page_accessibility(app, page: Page): - ignored_routes = ["static", "/", "main.set_locale"] + ignored_routes = ["static", "/", "main.status", "main.set_locale"] + shutil.rmtree("tests/functional_tests/accessibility_output", ignore_errors=True) routes = app.view_functions for route in routes: if route not in ignored_routes: diff --git a/tests/unit_tests/test_example.py b/tests/unit_tests/test_example.py deleted file mode 100644 index 02dbb2a90..000000000 --- a/tests/unit_tests/test_example.py +++ /dev/null @@ -1,4 +0,0 @@ -def test_coverage(): - # Test Pytest coverage - assert 1 + 1 == 2 - pass diff --git a/tests/unit_tests/test_pages.py b/tests/unit_tests/test_pages.py index a6d6eb9b5..48dafbf87 100644 --- a/tests/unit_tests/test_pages.py +++ b/tests/unit_tests/test_pages.py @@ -9,3 +9,20 @@ def test_set_locale(app, client): def test_set_locale_invalid(app, client): response = client.get("/locale/de", headers={"referer": "http://localhost/privacy"}) assert response.status_code == 404, f"Expecting 404 got {response.status_code}" + + +def test_service_unavailable_on(app, client): + app.config["SERVICE_UNAVAILABLE"] = True + response = client.get("/") + assert response.status_code == 302 + assert response.headers["location"] == "/service-unavailable" + response = client.get("/", follow_redirects=True) + assert response.status_code == 503 + assert response.request.path == "/service-unavailable" + + +def test_service_unavailable_off(app, client): + app.config["SERVICE_UNAVAILABLE"] = False + response = client.get("/service-unavailable", follow_redirects=True) + assert response.status_code == 200 + assert response.request.path == "/"