From 9b3c5d10ca867e26d59bc97cdf5796efbc200b3a Mon Sep 17 00:00:00 2001 From: Andrew Leith Date: Thu, 18 Jul 2024 17:31:07 +0000 Subject: [PATCH 01/37] initial commit --- app/__init__.py | 3 ++- app/config.py | 3 +++ app/cypress/__init__.py | 0 app/cypress/rest.py | 44 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 app/cypress/__init__.py create mode 100644 app/cypress/rest.py diff --git a/app/__init__.py b/app/__init__.py index 78fae3406f..8ba0180166 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -207,7 +207,7 @@ def register_blueprint(application): template_statistics as template_statistics_blueprint, ) from app.user.rest import user_blueprint - + from app.cypress.rest import cypress_blueprint register_notify_blueprint(application, service_blueprint, requires_admin_auth, "/service") register_notify_blueprint(application, user_blueprint, requires_admin_auth, "/user") @@ -262,6 +262,7 @@ def register_blueprint(application): register_notify_blueprint(application, template_category_blueprint, requires_admin_auth) + register_notify_blueprint(application, cypress_blueprint, requires_no_auth, "/cypress") def register_v2_blueprints(application): from app.authentication.auth import requires_auth diff --git a/app/config.py b/app/config.py index fcb6aa4297..9112dd9a71 100644 --- a/app/config.py +++ b/app/config.py @@ -344,6 +344,9 @@ class Config(object): DEFAULT_TEMPLATE_CATEGORY_MEDIUM = "f75d6706-21b7-437e-b93a-2c0ab771e28e" DEFAULT_TEMPLATE_CATEGORY_HIGH = "c4f87d7c-a55b-4c0f-91fe-e56c65bb1871" + # UUIDs for Cypress tests + CYPRESS_SERVICE = "c6e8d4f1-6999-4b0f-8f3e-0e1f7b7b1f1e" + # Allowed service IDs able to send HTML through their templates. ALLOW_HTML_SERVICE_IDS: List[str] = [id.strip() for id in os.getenv("ALLOW_HTML_SERVICE_IDS", "").split(",")] diff --git a/app/cypress/__init__.py b/app/cypress/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/app/cypress/rest.py b/app/cypress/rest.py new file mode 100644 index 0000000000..26b45a01e3 --- /dev/null +++ b/app/cypress/rest.py @@ -0,0 +1,44 @@ +import uuid +from flask import Blueprint, jsonify, request + +from app.dao.users_dao import save_model_user +from app.errors import register_errors +from app.models import User + +cypress_blueprint = Blueprint("cypress", __name__) +register_errors(cypress_blueprint) + + +@cypress_blueprint.route("/start_test_suite/", methods=["GET"]) +def create_test_user(email_name): + # Create the user + data = { + "id": uuid.uuid4(), + "name": "Notify UI testing account", + "email_address": f"notify-ui-tests+{email_name}@cds-snc.ca", + "password": "", # TODO: move this to a secret! + "mobile_number": "9025555555", + "state": "active", + "blocked": False, + } + + user = User(**data) + save_model_user(user) + + # add user to cypress service w/ full permissions + # things to delete + # login_events + # verify_codes + + return jsonify(user.serialize()), 201 + +@cypress_blueprint.route("/end_test_suite/", methods=["GET"]) +def create_test_user(email_name): + # Delete the user + # Things to remove: + # login_events + # verify_codes + # user + + + return jsonify(message="yo"), 201 From 7352db50e533bdfb13bf0d9e49e24b4a663c4221 Mon Sep 17 00:00:00 2001 From: Andrew Leith Date: Mon, 22 Jul 2024 12:33:57 +0000 Subject: [PATCH 02/37] feat: create and destroy test users on demand --- app/__init__.py | 2 +- app/cypress/rest.py | 72 ++++++++++++++++++++++++++++++++++++++------- 2 files changed, 62 insertions(+), 12 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index 8ba0180166..c8ef10d090 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -262,7 +262,7 @@ def register_blueprint(application): register_notify_blueprint(application, template_category_blueprint, requires_admin_auth) - register_notify_blueprint(application, cypress_blueprint, requires_no_auth, "/cypress") + register_notify_blueprint(application, cypress_blueprint, requires_admin_auth, "/cypress") def register_v2_blueprints(application): from app.authentication.auth import requires_auth diff --git a/app/cypress/rest.py b/app/cypress/rest.py index 26b45a01e3..52c98fa5a7 100644 --- a/app/cypress/rest.py +++ b/app/cypress/rest.py @@ -1,22 +1,27 @@ import uuid from flask import Blueprint, jsonify, request +from app import db +from app.dao.services_dao import dao_add_user_to_service +from app.dao.templates_dao import dao_update_template from app.dao.users_dao import save_model_user from app.errors import register_errors -from app.models import User +from app.models import LoginEvent, Permission, Service, ServiceUser, Template, TemplateHistory, User, VerifyCode cypress_blueprint = Blueprint("cypress", __name__) register_errors(cypress_blueprint) +CYPRESS_SERVICE_ID = "5c8a0501-2aa8-433a-ba51-cefb8063ab93" +NOTIFY_TEST_USER_ID = "5e8fdc9b-4080-430d-962a-8065a1a17274" -@cypress_blueprint.route("/start_test_suite/", methods=["GET"]) +@cypress_blueprint.route("/create_user/", methods=["GET"]) def create_test_user(email_name): # Create the user data = { "id": uuid.uuid4(), "name": "Notify UI testing account", "email_address": f"notify-ui-tests+{email_name}@cds-snc.ca", - "password": "", # TODO: move this to a secret! + "password": "1a38490122e3455643d7b7d5f9f98c00765aed1a2b27227cb915f33737f2040f", # TODO: move this to a secret! "mobile_number": "9025555555", "state": "active", "blocked": False, @@ -26,19 +31,64 @@ def create_test_user(email_name): save_model_user(user) # add user to cypress service w/ full permissions + service = Service.query.filter_by(id="5c8a0501-2aa8-433a-ba51-cefb8063ab93").first() + permissions = [] + for p in ["manage_users", "manage_templates", "manage_settings", "send_texts", "send_emails", "send_letters", "manage_api_keys", "view_activity"]: + permissions.append(Permission(permission=p)) + + dao_add_user_to_service(service, user, permissions=permissions) + # things to delete # login_events # verify_codes return jsonify(user.serialize()), 201 -@cypress_blueprint.route("/end_test_suite/", methods=["GET"]) -def create_test_user(email_name): - # Delete the user - # Things to remove: - # login_events - # verify_codes - # user + +@cypress_blueprint.route("/destroy_user/", methods=["GET"]) +def destroy_user(email_name): + user = User.query.filter_by(email_address=f"notify-ui-tests+{email_name}@cds-snc.ca").first() + + if not user: + return + + # update the created_by field for each template to use id NOTIFY_TEST_USER_ID + templates = Template.query.filter_by(created_by=user).all() + for template in templates: + template.created_by_id = NOTIFY_TEST_USER_ID + dao_update_template(template) + + # update the created_by field for each template to use id NOTIFY_TEST_USER_ID + history_templates = TemplateHistory.query.filter_by(created_by=user).all() + for templateh in history_templates: + templateh.created_by_id = NOTIFY_TEST_USER_ID + db.session.add(templateh) + + # remove all the login events for this user + LoginEvent.query.filter_by(user=user).delete() + + # remove all permissions for this user + Permission.query.filter_by(user=user).delete() + + # remove user_to_service entries + ServiceUser.query.filter_by(user_id=user.id).delete() + + # remove verify codes + VerifyCode.query.filter_by(user=user).delete() + # remove the user + User.query.filter_by(email_address=f"notify-ui-tests+{email_name}@cds-snc.ca").delete() + + db.session.commit() + + return jsonify(message="Zeds dead, baby"), 201 + +@cypress_blueprint.route("/cleanup", methods=["GET"]) +def cleanup_users(): + users = User.query.filter(User.email_address.like(f"%notify-ui-tests+%@cds-snc.ca%")) - return jsonify(message="yo"), 201 + # loop through users and call destroy_user on each one + for user in users: + user_email = user.email_address.split("+")[1].split("@")[0] + print(user_email) + destroy_user(user_email) \ No newline at end of file From 5f5eb9fa9f02cb52b809eb1bb918dcf6f443060b Mon Sep 17 00:00:00 2001 From: Andrew Leith Date: Fri, 13 Sep 2024 18:14:10 +0000 Subject: [PATCH 03/37] chore(config): add some hardcoded UUIDs for cypress related items --- app/config.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/config.py b/app/config.py index 863a1a3dae..7a4642c369 100644 --- a/app/config.py +++ b/app/config.py @@ -348,7 +348,8 @@ class Config(object): DEFAULT_TEMPLATE_CATEGORY_HIGH = "c4f87d7c-a55b-4c0f-91fe-e56c65bb1871" # UUIDs for Cypress tests - CYPRESS_SERVICE = "c6e8d4f1-6999-4b0f-8f3e-0e1f7b7b1f1e" + CYPRESS_SERVICE_ID = "5c8a0501-2aa8-433a-ba51-cefb8063ab93" + CYPRESS_TEST_USER_ID = "5e8fdc9b-4080-430d-962a-8065a1a17274" # Allowed service IDs able to send HTML through their templates. ALLOW_HTML_SERVICE_IDS: List[str] = [id.strip() for id in os.getenv("ALLOW_HTML_SERVICE_IDS", "").split(",")] From cfe8719149ba5abdb96d0ef523c2007f4ebc914c Mon Sep 17 00:00:00 2001 From: Andrew Leith Date: Fri, 13 Sep 2024 18:15:19 +0000 Subject: [PATCH 04/37] feat(cypress api): update create_test_user route: - add safeguards to ensure this cant run in prod - hash the password on the fly instead of storing another secret --- app/cypress/rest.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/app/cypress/rest.py b/app/cypress/rest.py index 52c98fa5a7..9a53001991 100644 --- a/app/cypress/rest.py +++ b/app/cypress/rest.py @@ -11,17 +11,20 @@ cypress_blueprint = Blueprint("cypress", __name__) register_errors(cypress_blueprint) -CYPRESS_SERVICE_ID = "5c8a0501-2aa8-433a-ba51-cefb8063ab93" -NOTIFY_TEST_USER_ID = "5e8fdc9b-4080-430d-962a-8065a1a17274" - -@cypress_blueprint.route("/create_user/", methods=["GET"]) +@cypress_blueprint.route("/create_user/", methods=["POST"]) def create_test_user(email_name): + data = request.get_json() + password = data.get('password') + + if current_app.config["NOTIFY_ENVIRONMENT"] == "production": + return jsonify(message="Forbidden"), 403 + # Create the user data = { "id": uuid.uuid4(), "name": "Notify UI testing account", "email_address": f"notify-ui-tests+{email_name}@cds-snc.ca", - "password": "1a38490122e3455643d7b7d5f9f98c00765aed1a2b27227cb915f33737f2040f", # TODO: move this to a secret! + "password": hashlib.sha256((password + current_app.config["DANGEROUS_SALT"]).encode("utf-8")).hexdigest(), #"e01221bcb56f1fb931c0ca310e2a13e23390b267b4cd80dc36366b6c9ef8eb5d", # TODO: move this to a secret! "mobile_number": "9025555555", "state": "active", "blocked": False, From 00dc686c435452f1a26bba685ea9d4ce6a39624e Mon Sep 17 00:00:00 2001 From: Andrew Leith Date: Fri, 13 Sep 2024 18:16:01 +0000 Subject: [PATCH 05/37] feat(cypress api): update cleanup_stale_users route: - add safeguards to ensure this cant run in prod - remove/update related tables --- app/cypress/rest.py | 132 ++++++++++++++++++++++++++++++-------------- 1 file changed, 90 insertions(+), 42 deletions(-) diff --git a/app/cypress/rest.py b/app/cypress/rest.py index 9a53001991..714eb03012 100644 --- a/app/cypress/rest.py +++ b/app/cypress/rest.py @@ -1,12 +1,22 @@ +import hashlib +import logging import uuid -from flask import Blueprint, jsonify, request + +from datetime import datetime, timedelta +from flask import Blueprint, current_app, jsonify, request from app import db from app.dao.services_dao import dao_add_user_to_service from app.dao.templates_dao import dao_update_template from app.dao.users_dao import save_model_user from app.errors import register_errors -from app.models import LoginEvent, Permission, Service, ServiceUser, Template, TemplateHistory, User, VerifyCode +from app.models import LoginEvent, Permission, Service, ServiceUser, Template, TemplateHistory, TemplateRedacted, User, VerifyCode + +""" +This module will be used by the cypress tests to create users on the fly whenever a test suite is run. + +Additionally, this module will also be used to clean up test users periodically to keep the data footprint small. +""" cypress_blueprint = Blueprint("cypress", __name__) register_errors(cypress_blueprint) @@ -41,57 +51,95 @@ def create_test_user(email_name): dao_add_user_to_service(service, user, permissions=permissions) - # things to delete - # login_events - # verify_codes - return jsonify(user.serialize()), 201 -@cypress_blueprint.route("/destroy_user/", methods=["GET"]) -def destroy_user(email_name): +def _destroy_test_user(email_name): + CYPRESS_TEST_USER_ID = current_app.config["CYPRESS_TEST_USER_ID"] + user = User.query.filter_by(email_address=f"notify-ui-tests+{email_name}@cds-snc.ca").first() - + if not user: return - # update the created_by field for each template to use id NOTIFY_TEST_USER_ID - templates = Template.query.filter_by(created_by=user).all() - for template in templates: - template.created_by_id = NOTIFY_TEST_USER_ID - dao_update_template(template) - - # update the created_by field for each template to use id NOTIFY_TEST_USER_ID - history_templates = TemplateHistory.query.filter_by(created_by=user).all() - for templateh in history_templates: - templateh.created_by_id = NOTIFY_TEST_USER_ID - db.session.add(templateh) - - # remove all the login events for this user - LoginEvent.query.filter_by(user=user).delete() - - # remove all permissions for this user - Permission.query.filter_by(user=user).delete() - - # remove user_to_service entries - ServiceUser.query.filter_by(user_id=user.id).delete() - - # remove verify codes - VerifyCode.query.filter_by(user=user).delete() - - # remove the user - User.query.filter_by(email_address=f"notify-ui-tests+{email_name}@cds-snc.ca").delete() + try: + # update the created_by field for each template to use id CYPRESS_TEST_USER_ID + templates = Template.query.filter_by(created_by=user).all() + for template in templates: + template.created_by_id = CYPRESS_TEST_USER_ID + dao_update_template(template) + + # update the created_by field for each template to use id CYPRESS_TEST_USER_ID + history_templates = TemplateHistory.query.filter_by(created_by=user).all() + for templateh in history_templates: + templateh.created_by_id = CYPRESS_TEST_USER_ID + db.session.add(templateh) + + # update the created_by field for each template_redacted to use id CYPRESS_TEST_USER_ID + redacted_templates = TemplateRedacted.query.filter_by(updated_by=user).all() + for templater in redacted_templates: + templater.updated_by_id = CYPRESS_TEST_USER_ID + db.session.add(templater) + + # Update services create by this user to use CYPRESS_TEST_USER_ID + services = Service.query.filter_by(created_by=user).all() + for service in services: + service.created_by_id = CYPRESS_TEST_USER_ID + db.session.add(service) + + # remove all the login events for this user + LoginEvent.query.filter_by(user=user).delete() + + # remove all permissions for this user + Permission.query.filter_by(user=user).delete() + + # remove user_to_service entries + ServiceUser.query.filter_by(user_id=user.id).delete() + + # remove verify codes + VerifyCode.query.filter_by(user=user).delete() + + # remove the user + User.query.filter_by(email_address=f"notify-ui-tests+{email_name}@cds-snc.ca").delete() + + print("removal success: " + email_name) + except Exception as e: + print(f"Error cleaning up test user: {e}") + db.session.rollback() + + +""" +Endpoint for cleaning up stale users. This endpoint will only be used internally by the Cypress tests. + +This endpoint is responsible for removing stale testing users from the database. +Stale users are identified as users whose email addresses match the pattern "%notify-ui-tests+%@cds-snc.ca%" and whose creation time is older than three hours ago. + +Returns: + A JSON response with a success message if the cleanup is successful, or an error message if an exception occurs during the cleanup process. +""" +@cypress_blueprint.route("/cleanup", methods=["GET"]) +def cleanup_stale_users(): + if current_app.config["NOTIFY_ENVIRONMENT"] == "production": + return jsonify(message="Forbidden"), 403 - db.session.commit() + three_hours_ago = datetime.utcnow() - timedelta(hours=3) + users = User.query.filter(User.email_address.like(f"%notify-ui-tests+%@cds-snc.ca%"), + User.created_at < three_hours_ago).all() - return jsonify(message="Zeds dead, baby"), 201 + # get the list of email_address property from users + users_emails = [user for user in users] -@cypress_blueprint.route("/cleanup", methods=["GET"]) -def cleanup_users(): - users = User.query.filter(User.email_address.like(f"%notify-ui-tests+%@cds-snc.ca%")) + print('users to clean: ' + str(users_emails)) # loop through users and call destroy_user on each one for user in users: user_email = user.email_address.split("+")[1].split("@")[0] - print(user_email) - destroy_user(user_email) \ No newline at end of file + print("Trying to remove:" + user_email) + + try: + _destroy_test_user(user_email) + except Exception as e: + return jsonify(message="Error cleaning up"), 500 + + db.session.commit() + return jsonify(message="Zeds dead, baby"), 201 \ No newline at end of file From 6d1acba8bf17bdc903f9bc8842b58bdbd4df0f39 Mon Sep 17 00:00:00 2001 From: Andrew Leith Date: Fri, 13 Sep 2024 18:23:12 +0000 Subject: [PATCH 06/37] chore: formatting --- app/__init__.py | 3 ++- app/cypress/rest.py | 52 ++++++++++++++++++++++++++++----------------- 2 files changed, 34 insertions(+), 21 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index a26a81b881..2826b4a24e 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -189,6 +189,7 @@ def register_blueprint(application): from app.billing.rest import billing_blueprint from app.cache.rest import cache_blueprint from app.complaint.complaint_rest import complaint_blueprint + from app.cypress.rest import cypress_blueprint from app.email_branding.rest import email_branding_blueprint from app.events.rest import events as events_blueprint from app.inbound_number.rest import inbound_number_blueprint @@ -215,7 +216,7 @@ def register_blueprint(application): template_statistics as template_statistics_blueprint, ) from app.user.rest import user_blueprint - from app.cypress.rest import cypress_blueprint + register_notify_blueprint(application, service_blueprint, requires_admin_auth, "/service") register_notify_blueprint(application, user_blueprint, requires_admin_auth, "/user") diff --git a/app/cypress/rest.py b/app/cypress/rest.py index 714eb03012..e685438033 100644 --- a/app/cypress/rest.py +++ b/app/cypress/rest.py @@ -1,8 +1,7 @@ import hashlib -import logging import uuid - from datetime import datetime, timedelta + from flask import Blueprint, current_app, jsonify, request from app import db @@ -12,8 +11,8 @@ from app.errors import register_errors from app.models import LoginEvent, Permission, Service, ServiceUser, Template, TemplateHistory, TemplateRedacted, User, VerifyCode -""" -This module will be used by the cypress tests to create users on the fly whenever a test suite is run. +""" +This module will be used by the cypress tests to create users on the fly whenever a test suite is run. Additionally, this module will also be used to clean up test users periodically to keep the data footprint small. """ @@ -21,10 +20,11 @@ cypress_blueprint = Blueprint("cypress", __name__) register_errors(cypress_blueprint) + @cypress_blueprint.route("/create_user/", methods=["POST"]) def create_test_user(email_name): data = request.get_json() - password = data.get('password') + password = data.get("password") if current_app.config["NOTIFY_ENVIRONMENT"] == "production": return jsonify(message="Forbidden"), 403 @@ -34,21 +34,32 @@ def create_test_user(email_name): "id": uuid.uuid4(), "name": "Notify UI testing account", "email_address": f"notify-ui-tests+{email_name}@cds-snc.ca", - "password": hashlib.sha256((password + current_app.config["DANGEROUS_SALT"]).encode("utf-8")).hexdigest(), #"e01221bcb56f1fb931c0ca310e2a13e23390b267b4cd80dc36366b6c9ef8eb5d", # TODO: move this to a secret! + "password": hashlib.sha256( + (password + current_app.config["DANGEROUS_SALT"]).encode("utf-8") + ).hexdigest(), # "e01221bcb56f1fb931c0ca310e2a13e23390b267b4cd80dc36366b6c9ef8eb5d", # TODO: move this to a secret! "mobile_number": "9025555555", "state": "active", "blocked": False, } - + user = User(**data) save_model_user(user) # add user to cypress service w/ full permissions service = Service.query.filter_by(id="5c8a0501-2aa8-433a-ba51-cefb8063ab93").first() permissions = [] - for p in ["manage_users", "manage_templates", "manage_settings", "send_texts", "send_emails", "send_letters", "manage_api_keys", "view_activity"]: + for p in [ + "manage_users", + "manage_templates", + "manage_settings", + "send_texts", + "send_emails", + "send_letters", + "manage_api_keys", + "view_activity", + ]: permissions.append(Permission(permission=p)) - + dao_add_user_to_service(service, user, permissions=permissions) return jsonify(user.serialize()), 201 @@ -61,7 +72,7 @@ def _destroy_test_user(email_name): if not user: return - + try: # update the created_by field for each template to use id CYPRESS_TEST_USER_ID templates = Template.query.filter_by(created_by=user).all() @@ -86,19 +97,19 @@ def _destroy_test_user(email_name): for service in services: service.created_by_id = CYPRESS_TEST_USER_ID db.session.add(service) - + # remove all the login events for this user LoginEvent.query.filter_by(user=user).delete() # remove all permissions for this user Permission.query.filter_by(user=user).delete() - + # remove user_to_service entries ServiceUser.query.filter_by(user_id=user.id).delete() # remove verify codes VerifyCode.query.filter_by(user=user).delete() - + # remove the user User.query.filter_by(email_address=f"notify-ui-tests+{email_name}@cds-snc.ca").delete() @@ -106,30 +117,31 @@ def _destroy_test_user(email_name): except Exception as e: print(f"Error cleaning up test user: {e}") db.session.rollback() - + """ Endpoint for cleaning up stale users. This endpoint will only be used internally by the Cypress tests. -This endpoint is responsible for removing stale testing users from the database. +This endpoint is responsible for removing stale testing users from the database. Stale users are identified as users whose email addresses match the pattern "%notify-ui-tests+%@cds-snc.ca%" and whose creation time is older than three hours ago. Returns: A JSON response with a success message if the cleanup is successful, or an error message if an exception occurs during the cleanup process. """ + + @cypress_blueprint.route("/cleanup", methods=["GET"]) def cleanup_stale_users(): if current_app.config["NOTIFY_ENVIRONMENT"] == "production": return jsonify(message="Forbidden"), 403 three_hours_ago = datetime.utcnow() - timedelta(hours=3) - users = User.query.filter(User.email_address.like(f"%notify-ui-tests+%@cds-snc.ca%"), - User.created_at < three_hours_ago).all() + users = User.query.filter(User.email_address.like("%notify-ui-tests+%@cds-snc.ca%"), User.created_at < three_hours_ago).all() # get the list of email_address property from users users_emails = [user for user in users] - print('users to clean: ' + str(users_emails)) + print("users to clean: " + str(users_emails)) # loop through users and call destroy_user on each one for user in users: @@ -138,8 +150,8 @@ def cleanup_stale_users(): try: _destroy_test_user(user_email) - except Exception as e: + except Exception: return jsonify(message="Error cleaning up"), 500 db.session.commit() - return jsonify(message="Zeds dead, baby"), 201 \ No newline at end of file + return jsonify(message="Zeds dead, baby"), 201 From 7704b89aa1324c0a147c5fe98d7ddf2bb04de3b7 Mon Sep 17 00:00:00 2001 From: Andrew Leith Date: Fri, 13 Sep 2024 18:29:57 +0000 Subject: [PATCH 07/37] chore(format): formatting files I haven't touched :( --- application.py | 2 +- run_celery.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/application.py b/application.py index 7235426b1e..7f554ecd3f 100644 --- a/application.py +++ b/application.py @@ -25,7 +25,7 @@ app = create_app(application) -xray_recorder.configure(service='Notify-API', context=NotifyContext()) +xray_recorder.configure(service="Notify-API", context=NotifyContext()) XRayMiddleware(app, xray_recorder) apig_wsgi_handler = make_lambda_handler(app, binary_support=True) diff --git a/run_celery.py b/run_celery.py index c8381752d4..ce470fc06b 100644 --- a/run_celery.py +++ b/run_celery.py @@ -17,7 +17,7 @@ application = Flask("celery") create_app(application) -xray_recorder.configure(service='Notify', context=NotifyContext()) +xray_recorder.configure(service="Notify", context=NotifyContext()) XRayMiddleware(application, xray_recorder) application.app_context().push() From 0dd0721cd7c1f35c4022517ebe5744bfc8fde380 Mon Sep 17 00:00:00 2001 From: Andrew Leith Date: Fri, 13 Sep 2024 19:01:15 +0000 Subject: [PATCH 08/37] chore: formatting --- app/config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/config.py b/app/config.py index 7a4642c369..283d7c27bb 100644 --- a/app/config.py +++ b/app/config.py @@ -350,7 +350,7 @@ class Config(object): # UUIDs for Cypress tests CYPRESS_SERVICE_ID = "5c8a0501-2aa8-433a-ba51-cefb8063ab93" CYPRESS_TEST_USER_ID = "5e8fdc9b-4080-430d-962a-8065a1a17274" - + # Allowed service IDs able to send HTML through their templates. ALLOW_HTML_SERVICE_IDS: List[str] = [id.strip() for id in os.getenv("ALLOW_HTML_SERVICE_IDS", "").split(",")] From 7417e0eda317d1e7d08139374cb4c1d1dbaabf99 Mon Sep 17 00:00:00 2001 From: Andrew Leith Date: Fri, 13 Sep 2024 19:07:29 +0000 Subject: [PATCH 09/37] chore: formatting --- app/cypress/rest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/cypress/rest.py b/app/cypress/rest.py index e685438033..c7ff1fa2c5 100644 --- a/app/cypress/rest.py +++ b/app/cypress/rest.py @@ -36,7 +36,7 @@ def create_test_user(email_name): "email_address": f"notify-ui-tests+{email_name}@cds-snc.ca", "password": hashlib.sha256( (password + current_app.config["DANGEROUS_SALT"]).encode("utf-8") - ).hexdigest(), # "e01221bcb56f1fb931c0ca310e2a13e23390b267b4cd80dc36366b6c9ef8eb5d", # TODO: move this to a secret! + ).hexdigest(), "mobile_number": "9025555555", "state": "active", "blocked": False, From 9bb10ae87911b5b31336a194fce5342ea3b65796 Mon Sep 17 00:00:00 2001 From: Andrew Leith Date: Fri, 13 Sep 2024 19:10:12 +0000 Subject: [PATCH 10/37] chore: formatting --- app/cypress/rest.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/app/cypress/rest.py b/app/cypress/rest.py index c7ff1fa2c5..1f29434fa5 100644 --- a/app/cypress/rest.py +++ b/app/cypress/rest.py @@ -34,9 +34,7 @@ def create_test_user(email_name): "id": uuid.uuid4(), "name": "Notify UI testing account", "email_address": f"notify-ui-tests+{email_name}@cds-snc.ca", - "password": hashlib.sha256( - (password + current_app.config["DANGEROUS_SALT"]).encode("utf-8") - ).hexdigest(), + "password": hashlib.sha256((password + current_app.config["DANGEROUS_SALT"]).encode("utf-8")).hexdigest(), "mobile_number": "9025555555", "state": "active", "blocked": False, From 33e2ca701e755ed022e36897b7bf978a49ef1151 Mon Sep 17 00:00:00 2001 From: Andrew Leith Date: Mon, 16 Sep 2024 01:41:47 +0000 Subject: [PATCH 11/37] chore: update docstrings; enhance exception handling --- app/cypress/rest.py | 145 +++++++++++++++++++++++--------------------- 1 file changed, 77 insertions(+), 68 deletions(-) diff --git a/app/cypress/rest.py b/app/cypress/rest.py index 1f29434fa5..935bc37518 100644 --- a/app/cypress/rest.py +++ b/app/cypress/rest.py @@ -1,3 +1,8 @@ +""" +This module will be used by the cypress tests to create users on the fly whenever a test suite is run, and clean +them up periodically to keep the data footprint small. +""" + import hashlib import uuid from datetime import datetime, timedelta @@ -11,54 +16,64 @@ from app.errors import register_errors from app.models import LoginEvent, Permission, Service, ServiceUser, Template, TemplateHistory, TemplateRedacted, User, VerifyCode -""" -This module will be used by the cypress tests to create users on the fly whenever a test suite is run. - -Additionally, this module will also be used to clean up test users periodically to keep the data footprint small. -""" - cypress_blueprint = Blueprint("cypress", __name__) register_errors(cypress_blueprint) @cypress_blueprint.route("/create_user/", methods=["POST"]) def create_test_user(email_name): - data = request.get_json() - password = data.get("password") + """ + Create a test user for Notify UI testing. + + Args: + email_name (str): The name to be used in the email address of the test user. + Returns: + dict: A dictionary containing the serialized user information. + """ if current_app.config["NOTIFY_ENVIRONMENT"] == "production": return jsonify(message="Forbidden"), 403 - # Create the user - data = { - "id": uuid.uuid4(), - "name": "Notify UI testing account", - "email_address": f"notify-ui-tests+{email_name}@cds-snc.ca", - "password": hashlib.sha256((password + current_app.config["DANGEROUS_SALT"]).encode("utf-8")).hexdigest(), - "mobile_number": "9025555555", - "state": "active", - "blocked": False, - } - - user = User(**data) - save_model_user(user) - - # add user to cypress service w/ full permissions - service = Service.query.filter_by(id="5c8a0501-2aa8-433a-ba51-cefb8063ab93").first() - permissions = [] - for p in [ - "manage_users", - "manage_templates", - "manage_settings", - "send_texts", - "send_emails", - "send_letters", - "manage_api_keys", - "view_activity", - ]: - permissions.append(Permission(permission=p)) - - dao_add_user_to_service(service, user, permissions=permissions) + try: + data = request.get_json() + password = data.get("password") + except Exception: + return jsonify(message="Invalid JSON"), 400 + + try: + # Create the user + data = { + "id": uuid.uuid4(), + "name": "Notify UI testing account", + "email_address": f"notify-ui-tests+{email_name}@cds-snc.ca", + "password": hashlib.sha256((password + current_app.config["DANGEROUS_SALT"]).encode("utf-8")).hexdigest(), + "mobile_number": "9025555555", + "state": "active", + "blocked": False, + } + + user = User(**data) + save_model_user(user) + + # add user to cypress service w/ full permissions + service = Service.query.filter_by(id="5c8a0501-2aa8-433a-ba51-cefb8063ab93").first() + permissions = [] + for p in [ + "manage_users", + "manage_templates", + "manage_settings", + "send_texts", + "send_emails", + "send_letters", + "manage_api_keys", + "view_activity", + ]: + permissions.append(Permission(permission=p)) + + dao_add_user_to_service(service, user, permissions=permissions) + + except Exception: + return jsonify(message="Error creating user"), 400 return jsonify(user.serialize()), 201 @@ -111,45 +126,39 @@ def _destroy_test_user(email_name): # remove the user User.query.filter_by(email_address=f"notify-ui-tests+{email_name}@cds-snc.ca").delete() - print("removal success: " + email_name) - except Exception as e: - print(f"Error cleaning up test user: {e}") + except Exception: db.session.rollback() -""" -Endpoint for cleaning up stale users. This endpoint will only be used internally by the Cypress tests. - -This endpoint is responsible for removing stale testing users from the database. -Stale users are identified as users whose email addresses match the pattern "%notify-ui-tests+%@cds-snc.ca%" and whose creation time is older than three hours ago. - -Returns: - A JSON response with a success message if the cleanup is successful, or an error message if an exception occurs during the cleanup process. -""" - - @cypress_blueprint.route("/cleanup", methods=["GET"]) def cleanup_stale_users(): - if current_app.config["NOTIFY_ENVIRONMENT"] == "production": - return jsonify(message="Forbidden"), 403 - - three_hours_ago = datetime.utcnow() - timedelta(hours=3) - users = User.query.filter(User.email_address.like("%notify-ui-tests+%@cds-snc.ca%"), User.created_at < three_hours_ago).all() + """ + Endpoint for cleaning up stale users. This endpoint will only be used internally by the Cypress tests. - # get the list of email_address property from users - users_emails = [user for user in users] + This endpoint is responsible for removing stale testing users from the database. + Stale users are identified as users whose email addresses match the pattern "%notify-ui-tests+%@cds-snc.ca%" and whose creation time is older than three hours ago. - print("users to clean: " + str(users_emails)) + If this is accessed from production, it will return a 403 Forbidden response. - # loop through users and call destroy_user on each one - for user in users: - user_email = user.email_address.split("+")[1].split("@")[0] - print("Trying to remove:" + user_email) + Returns: + A JSON response with a success message if the cleanup is successful, or an error message if an exception occurs during the cleanup process. + """ + if current_app.config["NOTIFY_ENVIRONMENT"] == "production": + return jsonify(message="Forbidden"), 403 - try: + try: + three_hours_ago = datetime.utcnow() - timedelta(hours=3) + users = User.query.filter( + User.email_address.like("%notify-ui-tests+%@cds-snc.ca%"), User.created_at < three_hours_ago + ).all() + + # loop through users and call destroy_user on each one + for user in users: + user_email = user.email_address.split("+")[1].split("@")[0] _destroy_test_user(user_email) - except Exception: - return jsonify(message="Error cleaning up"), 500 - db.session.commit() + db.session.commit() + except Exception: + return jsonify(message="Error cleaning up"), 500 + return jsonify(message="Zeds dead, baby"), 201 From 490e666415f9b370bad3b7509af01438629b2941 Mon Sep 17 00:00:00 2001 From: Andrew Leith Date: Tue, 17 Sep 2024 12:25:40 +0000 Subject: [PATCH 12/37] fix(cypress api): use service id from config --- app/cypress/rest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/cypress/rest.py b/app/cypress/rest.py index 935bc37518..556456408a 100644 --- a/app/cypress/rest.py +++ b/app/cypress/rest.py @@ -56,7 +56,7 @@ def create_test_user(email_name): save_model_user(user) # add user to cypress service w/ full permissions - service = Service.query.filter_by(id="5c8a0501-2aa8-433a-ba51-cefb8063ab93").first() + service = Service.query.filter_by(id=current_app.config["CYPRESS_SERVICE_ID"]).first() permissions = [] for p in [ "manage_users", From ae3be906c77002bbf77061ed4ff742c53953e714 Mon Sep 17 00:00:00 2001 From: Andrew Leith Date: Tue, 17 Sep 2024 12:26:07 +0000 Subject: [PATCH 13/37] chore: add more cypress values to config --- app/config.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/config.py b/app/config.py index 283d7c27bb..e3619ddab5 100644 --- a/app/config.py +++ b/app/config.py @@ -348,8 +348,10 @@ class Config(object): DEFAULT_TEMPLATE_CATEGORY_HIGH = "c4f87d7c-a55b-4c0f-91fe-e56c65bb1871" # UUIDs for Cypress tests - CYPRESS_SERVICE_ID = "5c8a0501-2aa8-433a-ba51-cefb8063ab93" - CYPRESS_TEST_USER_ID = "5e8fdc9b-4080-430d-962a-8065a1a17274" + CYPRESS_SERVICE_ID = "d4e8a7f4-2b8a-4c9a-8b3f-9c2d4e8a7f4b" + CYPRESS_TEST_USER_ID = "e5f9d8c7-3a9b-4d8c-9b4f-8d3e5f9d8c7a" + CYPRESS_SMOKE_TEST_EMAIL_TEMPLATE_ID = "f47ac10b-58cc-4372-a567-0e02b2c3d479" + CYPRESS_USER_PW_SECRET = os.getenv("CYPRESS_USER_PW_SECRET") # Allowed service IDs able to send HTML through their templates. ALLOW_HTML_SERVICE_IDS: List[str] = [id.strip() for id in os.getenv("ALLOW_HTML_SERVICE_IDS", "").split(",")] From 17b4a2b6e9301d2bbacb14a57102fffe120b2318 Mon Sep 17 00:00:00 2001 From: Andrew Leith Date: Mon, 23 Sep 2024 11:40:39 +0000 Subject: [PATCH 14/37] feat(cypress api): dont pass password around since its already a secret in this repo; create an admin user at the same time as a regular user --- app/config.py | 2 ++ app/cypress/rest.py | 56 +++++++++++++++++++++++++++++++++------------ 2 files changed, 43 insertions(+), 15 deletions(-) diff --git a/app/config.py b/app/config.py index e3619ddab5..d41811460a 100644 --- a/app/config.py +++ b/app/config.py @@ -350,7 +350,9 @@ class Config(object): # UUIDs for Cypress tests CYPRESS_SERVICE_ID = "d4e8a7f4-2b8a-4c9a-8b3f-9c2d4e8a7f4b" CYPRESS_TEST_USER_ID = "e5f9d8c7-3a9b-4d8c-9b4f-8d3e5f9d8c7a" + CYPRESS_TEST_USER_ADMIN_ID = "4f8b8b1e-9c4f-4d8b-8b1e-4f8b8b1e9c4f" CYPRESS_SMOKE_TEST_EMAIL_TEMPLATE_ID = "f47ac10b-58cc-4372-a567-0e02b2c3d479" + CYPRESS_SMOKE_TEST_SMS_TEMPLATE_ID = "e4b8f8d0-6a3b-4b9e-8c2b-1f2d3e4a5b6c" CYPRESS_USER_PW_SECRET = os.getenv("CYPRESS_USER_PW_SECRET") # Allowed service IDs able to send HTML through their templates. diff --git a/app/cypress/rest.py b/app/cypress/rest.py index 556456408a..5d6d61dbc9 100644 --- a/app/cypress/rest.py +++ b/app/cypress/rest.py @@ -7,7 +7,7 @@ import uuid from datetime import datetime, timedelta -from flask import Blueprint, current_app, jsonify, request +from flask import Blueprint, current_app, jsonify from app import db from app.dao.services_dao import dao_add_user_to_service @@ -35,29 +35,38 @@ def create_test_user(email_name): return jsonify(message="Forbidden"), 403 try: - data = request.get_json() - password = data.get("password") - except Exception: - return jsonify(message="Invalid JSON"), 400 - - try: - # Create the user - data = { + # Create the users + user_regular = { "id": uuid.uuid4(), "name": "Notify UI testing account", "email_address": f"notify-ui-tests+{email_name}@cds-snc.ca", - "password": hashlib.sha256((password + current_app.config["DANGEROUS_SALT"]).encode("utf-8")).hexdigest(), + "password": hashlib.sha256((current_app.config["CYPRESS_USER_PW_SECRET"] + current_app.config["DANGEROUS_SALT"]).encode("utf-8")).hexdigest(), "mobile_number": "9025555555", "state": "active", "blocked": False, } - user = User(**data) + user = User(**user_regular) save_model_user(user) + # Create the users + user_admin = { + "id": uuid.uuid4(), + "name": "Notify UI testing account", + "email_address": f"notify-ui-tests+{email_name}_admin@cds-snc.ca", + "password": hashlib.sha256((current_app.config["CYPRESS_USER_PW_SECRET"] + current_app.config["DANGEROUS_SALT"]).encode("utf-8")).hexdigest(), + "mobile_number": "9025555555", + "state": "active", + "blocked": False, + "platform_admin": True, + } + + user2 = User(**user_admin) + save_model_user(user2) + # add user to cypress service w/ full permissions service = Service.query.filter_by(id=current_app.config["CYPRESS_SERVICE_ID"]).first() - permissions = [] + permissions_reg = [] for p in [ "manage_users", "manage_templates", @@ -68,14 +77,31 @@ def create_test_user(email_name): "manage_api_keys", "view_activity", ]: - permissions.append(Permission(permission=p)) + permissions_reg.append(Permission(permission=p)) - dao_add_user_to_service(service, user, permissions=permissions) + dao_add_user_to_service(service, user, permissions=permissions_reg) + permissions_admin = [] + for p in [ + "manage_users", + "manage_templates", + "manage_settings", + "send_texts", + "send_emails", + "send_letters", + "manage_api_keys", + "view_activity", + ]: + permissions_admin.append(Permission(permission=p)) + dao_add_user_to_service(service, user2, permissions=permissions_admin) + + current_app.logger.info(f"Created test user {user.email_address} and {user2.email_address}") except Exception: return jsonify(message="Error creating user"), 400 - return jsonify(user.serialize()), 201 + users = { "regular": user.serialize(), "admin": user2.serialize() } + + return jsonify(users), 201 def _destroy_test_user(email_name): From 425e156288c1e5d088cbbdd9be77cef0dbaf43f0 Mon Sep 17 00:00:00 2001 From: Andrew Leith Date: Mon, 23 Sep 2024 11:41:59 +0000 Subject: [PATCH 15/37] feat(cypress data): migration to create cypress service, permissions, users and templates --- migrations/versions/0461_add_cypress_data.py | 147 +++++++++++++++++++ 1 file changed, 147 insertions(+) create mode 100644 migrations/versions/0461_add_cypress_data.py diff --git a/migrations/versions/0461_add_cypress_data.py b/migrations/versions/0461_add_cypress_data.py new file mode 100644 index 0000000000..9295d816db --- /dev/null +++ b/migrations/versions/0461_add_cypress_data.py @@ -0,0 +1,147 @@ +"""empty message + +Revision ID: 0461_add_cypress_data +Revises: 0460_new_service_columns +Create Date: 2016-06-01 14:17:01.963181 + +""" + +import hashlib +import uuid + +# revision identifiers, used by Alembic. +from datetime import datetime + +from alembic import op +from flask import current_app + +from app.dao.date_util import get_current_financial_year_start_year +from app.encryption import hashpw +from app.models import PERMISSION_LIST + +revision = "0461_add_cypress_data" +down_revision = "0460_new_service_columns" + +user_id = current_app.config["CYPRESS_TEST_USER_ID"] +admin_user_id = current_app.config["CYPRESS_TEST_USER_ADMIN_ID"] +service_id = current_app.config["CYPRESS_SERVICE_ID"] +email_template_id = current_app.config["CYPRESS_SMOKE_TEST_EMAIL_TEMPLATE_ID"] +sms_template_id = current_app.config["CYPRESS_SMOKE_TEST_SMS_TEMPLATE_ID"] +default_category_id = current_app.config["DEFAULT_TEMPLATE_CATEGORY_LOW"] + +def upgrade(): + password = hashpw(hashlib.sha256((current_app.config["CYPRESS_USER_PW_SECRET"] + current_app.config["DANGEROUS_SALT"]).encode("utf-8")).hexdigest()) + current_year = get_current_financial_year_start_year() + default_limit = 250000 + + op.get_bind() + + # insert test user + user_insert = """INSERT INTO users (id, name, email_address, created_at, failed_login_count, _password, mobile_number, password_changed_at, state, platform_admin, auth_type) + VALUES ('{}', 'Notify UI test user', 'notify-ui-tests+regular_user@cds-snc.ca', '{}', 0,'{}', '+441234123412', '{}', 'active', False, 'email_auth') + """ + op.execute(user_insert.format(user_id, datetime.utcnow(), password, datetime.utcnow())) + # insert test user thats platform admin + user_insert = """INSERT INTO users (id, name, email_address, created_at, failed_login_count, _password, mobile_number, password_changed_at, state, platform_admin, auth_type) + VALUES ('{}', 'Notify UI test user', 'notify-ui-tests+platform_admin@cds-snc.ca', '{}', 0,'{}', '+441234123412', '{}', 'active', True, 'email_auth') + """ + op.execute(user_insert.format(admin_user_id, datetime.utcnow(), password, datetime.utcnow())) + + # insert test service + service_history_insert = """INSERT INTO services_history (id, name, created_at, active, message_limit, restricted, research_mode, email_from, created_by_id, sms_daily_limit, prefix_sms, organisation_type, version) + VALUES ('{}', 'Cypress UI Testing Service', '{}', True, 10000, False, False, 'notify@digital.cabinet-office.gov.uk', + '{}', 10000, True, 'central', 1) + """ + op.execute(service_history_insert.format(service_id, datetime.utcnow(), user_id)) + service_insert = """INSERT INTO services (id, name, created_at, active, message_limit, restricted, research_mode, email_from, created_by_id, sms_daily_limit, prefix_sms, organisation_type, version) + VALUES ('{}', 'Cypress UI Testing Service', '{}', True, 10000, False, False, 'notify@digital.cabinet-office.gov.uk', + '{}', 10000, True, 'central', 1) + """ + op.execute(service_insert.format(service_id, datetime.utcnow(), user_id)) + + for send_type in ('sms', 'email'): + service_perms_insert = """INSERT INTO service_permissions (service_id, permission, created_at) + VALUES ('{}', '{}', '{}')""" + op.execute(service_perms_insert.format(service_id, send_type, datetime.utcnow())) + + insert_row_if_not_exist = """INSERT INTO annual_billing (id, service_id, financial_year_start, free_sms_fragment_limit, created_at, updated_at) + VALUES ('{}', '{}', {}, {}, '{}', '{}') + """ + op.execute(insert_row_if_not_exist.format(uuid.uuid4(), service_id, current_year, default_limit, datetime.utcnow(), datetime.utcnow())) + + user_to_service_insert = """INSERT INTO user_to_service (user_id, service_id) VALUES ('{}', '{}')""" + op.execute(user_to_service_insert.format(user_id, service_id)) + + for permission in PERMISSION_LIST: + perms_insert = """INSERT INTO permissions (id, service_id, user_id, permission, created_at) VALUES ('{}', '{}', '{}', '{}', '{}')""" + op.execute(perms_insert.format(uuid.uuid4(), service_id, user_id, permission, datetime.utcnow())) + + # insert test email template + _insert_template(email_template_id, "SMOKE_TEST_EMAIL", "SMOKE_TEST_EMAIL", "email", "SMOKE_TEST_EMAIL", default_category_id) + + # insert test SMS template + _insert_template(sms_template_id, "SMOKE_TEST_SMS", "SMOKE_TEST_SMS", "sms", None, default_category_id) + + # insert 10 random email templates + for i in range(10): + _insert_template(uuid.uuid4(), "Template {}".format(i), "Template {}".format(i), "email", "Template {}".format(i), default_category_id) + + #insert 1 random sms template + _insert_template(uuid.uuid4(), "Template 11", "Template 11", "sms", "Template 11", 'b6c42a7e-2a26-4a07-802b-123a5c3198a9') + + +def downgrade(): + op.get_bind() + op.execute("delete from permissions where service_id = '{}'".format(service_id)) + op.execute("delete from annual_billing where service_id = '{}'".format(service_id)) + op.execute("delete from service_permissions where service_id = '{}'".format(service_id)) + op.execute("delete from login_events where user_id = '{}'".format(user_id)) + op.execute("delete from verify_codes where user_id = '{}'".format(user_id)) + op.execute("delete from login_events where user_id = '{}'".format(admin_user_id)) + op.execute("delete from verify_codes where user_id = '{}'".format(admin_user_id)) + op.execute("delete from templates where service_id = '{}'".format(service_id)) + op.execute("delete from templates_history where service_id = '{}'".format(service_id)) + op.execute("delete from user_to_service where service_id = '{}'".format(service_id)) + op.execute("delete from services_history where id = '{}'".format(service_id)) + op.execute("delete from services where id = '{}'".format(service_id)) + op.execute("delete from users where id = '{}'".format(user_id)) + op.execute("delete from users where id = '{}'".format(admin_user_id)) + + +def _insert_template(id, name, content, type, subject, category_id): + template_history_insert = """INSERT INTO templates_history (id, name, template_type, created_at, + content, archived, service_id, + subject, created_by_id, hidden, template_category_id, version) + VALUES ('{}', '{}', '{}', '{}', '{}', False, '{}', '{}', '{}', False, '{}', 1) + """ + template_insert = """INSERT INTO templates (id, name, template_type, created_at, + content, archived, service_id, subject, created_by_id, hidden, template_category_id, version) + VALUES ('{}', '{}', '{}', '{}', '{}', False, '{}', '{}', '{}', False, '{}', 1) + """ + + op.execute( + template_history_insert.format( + uuid.uuid4(), + name, + type, + datetime.utcnow(), + content, + service_id, + subject, + user_id, + category_id + ) + ) + op.execute( + template_insert.format( + id, + name, + type, + datetime.utcnow(), + content, + service_id, + subject, + user_id, + category_id + ) + ) \ No newline at end of file From f4c313addabdfc4ce4e91a0a5e408713b8297225 Mon Sep 17 00:00:00 2001 From: Andrew Leith Date: Sat, 28 Sep 2024 12:36:16 +0000 Subject: [PATCH 16/37] feat(create_test_user): update delete logic to be more complete; paramaterize email address; ensure passed in param is alphanumeric --- app/cypress/rest.py | 97 ++++++++++++++++++++++----------------------- 1 file changed, 48 insertions(+), 49 deletions(-) diff --git a/app/cypress/rest.py b/app/cypress/rest.py index 5d6d61dbc9..5336f231e9 100644 --- a/app/cypress/rest.py +++ b/app/cypress/rest.py @@ -4,6 +4,7 @@ """ import hashlib +import re import uuid from datetime import datetime, timedelta @@ -11,14 +12,14 @@ from app import db from app.dao.services_dao import dao_add_user_to_service -from app.dao.templates_dao import dao_update_template from app.dao.users_dao import save_model_user from app.errors import register_errors -from app.models import LoginEvent, Permission, Service, ServiceUser, Template, TemplateHistory, TemplateRedacted, User, VerifyCode +from app.models import AnnualBilling, LoginEvent, Permission, Service, ServicePermission, ServiceUser, Template, TemplateHistory, TemplateRedacted, User, VerifyCode cypress_blueprint = Blueprint("cypress", __name__) register_errors(cypress_blueprint) +EMAIL_PREFIX = "notify-ui-tests+ag_" @cypress_blueprint.route("/create_user/", methods=["POST"]) def create_test_user(email_name): @@ -34,12 +35,16 @@ def create_test_user(email_name): if current_app.config["NOTIFY_ENVIRONMENT"] == "production": return jsonify(message="Forbidden"), 403 + # Sanitize email_name to allow only alphanumeric characters + if not re.match(r'^[a-z0-9]+$', email_name): + return jsonify(message="Invalid email name"), 400 + try: # Create the users user_regular = { "id": uuid.uuid4(), "name": "Notify UI testing account", - "email_address": f"notify-ui-tests+{email_name}@cds-snc.ca", + "email_address": f"{EMAIL_PREFIX}{email_name}@cds-snc.ca", "password": hashlib.sha256((current_app.config["CYPRESS_USER_PW_SECRET"] + current_app.config["DANGEROUS_SALT"]).encode("utf-8")).hexdigest(), "mobile_number": "9025555555", "state": "active", @@ -53,7 +58,7 @@ def create_test_user(email_name): user_admin = { "id": uuid.uuid4(), "name": "Notify UI testing account", - "email_address": f"notify-ui-tests+{email_name}_admin@cds-snc.ca", + "email_address": f"{EMAIL_PREFIX}{email_name}_admin@cds-snc.ca", "password": hashlib.sha256((current_app.config["CYPRESS_USER_PW_SECRET"] + current_app.config["DANGEROUS_SALT"]).encode("utf-8")).hexdigest(), "mobile_number": "9025555555", "state": "active", @@ -105,64 +110,55 @@ def create_test_user(email_name): def _destroy_test_user(email_name): - CYPRESS_TEST_USER_ID = current_app.config["CYPRESS_TEST_USER_ID"] - - user = User.query.filter_by(email_address=f"notify-ui-tests+{email_name}@cds-snc.ca").first() + user = User.query.filter_by(email_address=f"{EMAIL_PREFIX}{email_name}@cds-snc.ca").first() if not user: + current_app.logger.error(f"Error destroying test user {user.email_address}: no user found") return try: - # update the created_by field for each template to use id CYPRESS_TEST_USER_ID - templates = Template.query.filter_by(created_by=user).all() - for template in templates: - template.created_by_id = CYPRESS_TEST_USER_ID - dao_update_template(template) - - # update the created_by field for each template to use id CYPRESS_TEST_USER_ID - history_templates = TemplateHistory.query.filter_by(created_by=user).all() - for templateh in history_templates: - templateh.created_by_id = CYPRESS_TEST_USER_ID - db.session.add(templateh) - - # update the created_by field for each template_redacted to use id CYPRESS_TEST_USER_ID - redacted_templates = TemplateRedacted.query.filter_by(updated_by=user).all() - for templater in redacted_templates: - templater.updated_by_id = CYPRESS_TEST_USER_ID - db.session.add(templater) - - # Update services create by this user to use CYPRESS_TEST_USER_ID - services = Service.query.filter_by(created_by=user).all() - for service in services: - service.created_by_id = CYPRESS_TEST_USER_ID - db.session.add(service) - - # remove all the login events for this user - LoginEvent.query.filter_by(user=user).delete() - - # remove all permissions for this user + # update the cypress service's created_by to be the main cypress user + # this value gets changed when updating branding (and possibly other updates to service) + # and is a bug + cypress_service = Service.query.filter_by(id=current_app.config["CYPRESS_SERVICE_ID"]).first() + cypress_service.created_by_id = current_app.config["CYPRESS_TEST_USER_ID"] + + # cycle through all the services created by this user, remove associated entities + services = Service.query.filter_by(created_by=user).filter(Service.id != current_app.config["CYPRESS_SERVICE_ID"]) + for service in services.all(): + TemplateHistory.query.filter_by(service_id=service.id).delete() + + Template.query.filter_by(service_id=service.id).delete() + AnnualBilling.query.filter_by(service_id=service.id).delete() + ServicePermission.query.filter_by(service_id=service.id).delete() + Permission.query.filter_by(service_id=service.id).delete() + + services.delete() + + # remove all entities related to the user itself + TemplateRedacted.query.filter_by(updated_by=user).delete() + TemplateHistory.query.filter_by(created_by=user).delete() + Template.query.filter_by(created_by=user).delete() Permission.query.filter_by(user=user).delete() - - # remove user_to_service entries + LoginEvent.query.filter_by(user=user).delete() ServiceUser.query.filter_by(user_id=user.id).delete() - - # remove verify codes VerifyCode.query.filter_by(user=user).delete() + User.query.filter_by(email_address=f"{EMAIL_PREFIX}{email_name}@cds-snc.ca").delete() - # remove the user - User.query.filter_by(email_address=f"notify-ui-tests+{email_name}@cds-snc.ca").delete() + db.session.commit() - except Exception: + except Exception as e: + current_app.logger.error(f"Error destroying test user {user.email_address}: {str(e)}") db.session.rollback() @cypress_blueprint.route("/cleanup", methods=["GET"]) def cleanup_stale_users(): """ - Endpoint for cleaning up stale users. This endpoint will only be used internally by the Cypress tests. + Method for cleaning up stale users. This method will only be used internally by the Cypress tests. - This endpoint is responsible for removing stale testing users from the database. - Stale users are identified as users whose email addresses match the pattern "%notify-ui-tests+%@cds-snc.ca%" and whose creation time is older than three hours ago. + This method is responsible for removing stale testing users from the database. + Stale users are identified as users whose email addresses match the pattern "%notify-ui-tests+ag_%@cds-snc.ca%" and whose creation time is older than three hours ago. If this is accessed from production, it will return a 403 Forbidden response. @@ -170,21 +166,24 @@ def cleanup_stale_users(): A JSON response with a success message if the cleanup is successful, or an error message if an exception occurs during the cleanup process. """ if current_app.config["NOTIFY_ENVIRONMENT"] == "production": - return jsonify(message="Forbidden"), 403 + return jsonify(message="Forbidden"), 403 + raise Exception("") try: three_hours_ago = datetime.utcnow() - timedelta(hours=3) users = User.query.filter( - User.email_address.like("%notify-ui-tests+%@cds-snc.ca%"), User.created_at < three_hours_ago + User.email_address.like(f"%{EMAIL_PREFIX}%@cds-snc.ca%"), User.created_at < three_hours_ago ).all() # loop through users and call destroy_user on each one for user in users: - user_email = user.email_address.split("+")[1].split("@")[0] + user_email = user.email_address.split("+ag_")[1].split("@")[0] _destroy_test_user(user_email) db.session.commit() except Exception: + current_app.logger.error("[cleanup_stale_users]: error cleaning up test users") return jsonify(message="Error cleaning up"), 500 - return jsonify(message="Zeds dead, baby"), 201 + current_app.logger.info("[cleanup_stale_users]: Cleaned up stale test users") + return jsonify(message="Clean up ccomplete"), 201 From 74dd85d0bf9ac136f3f346014636487cf39190cb Mon Sep 17 00:00:00 2001 From: Andrew Leith Date: Sat, 28 Sep 2024 12:49:26 +0000 Subject: [PATCH 17/37] chore: formatting --- app/cypress/rest.py | 43 ++++++++++----- migrations/versions/0461_add_cypress_data.py | 55 +++++++++----------- 2 files changed, 54 insertions(+), 44 deletions(-) diff --git a/app/cypress/rest.py b/app/cypress/rest.py index 5336f231e9..6c1893a2bd 100644 --- a/app/cypress/rest.py +++ b/app/cypress/rest.py @@ -14,13 +14,26 @@ from app.dao.services_dao import dao_add_user_to_service from app.dao.users_dao import save_model_user from app.errors import register_errors -from app.models import AnnualBilling, LoginEvent, Permission, Service, ServicePermission, ServiceUser, Template, TemplateHistory, TemplateRedacted, User, VerifyCode +from app.models import ( + AnnualBilling, + LoginEvent, + Permission, + Service, + ServicePermission, + ServiceUser, + Template, + TemplateHistory, + TemplateRedacted, + User, + VerifyCode, +) cypress_blueprint = Blueprint("cypress", __name__) register_errors(cypress_blueprint) EMAIL_PREFIX = "notify-ui-tests+ag_" + @cypress_blueprint.route("/create_user/", methods=["POST"]) def create_test_user(email_name): """ @@ -36,16 +49,18 @@ def create_test_user(email_name): return jsonify(message="Forbidden"), 403 # Sanitize email_name to allow only alphanumeric characters - if not re.match(r'^[a-z0-9]+$', email_name): + if not re.match(r"^[a-z0-9]+$", email_name): return jsonify(message="Invalid email name"), 400 - + try: # Create the users user_regular = { "id": uuid.uuid4(), "name": "Notify UI testing account", "email_address": f"{EMAIL_PREFIX}{email_name}@cds-snc.ca", - "password": hashlib.sha256((current_app.config["CYPRESS_USER_PW_SECRET"] + current_app.config["DANGEROUS_SALT"]).encode("utf-8")).hexdigest(), + "password": hashlib.sha256( + (current_app.config["CYPRESS_USER_PW_SECRET"] + current_app.config["DANGEROUS_SALT"]).encode("utf-8") + ).hexdigest(), "mobile_number": "9025555555", "state": "active", "blocked": False, @@ -59,7 +74,9 @@ def create_test_user(email_name): "id": uuid.uuid4(), "name": "Notify UI testing account", "email_address": f"{EMAIL_PREFIX}{email_name}_admin@cds-snc.ca", - "password": hashlib.sha256((current_app.config["CYPRESS_USER_PW_SECRET"] + current_app.config["DANGEROUS_SALT"]).encode("utf-8")).hexdigest(), + "password": hashlib.sha256( + (current_app.config["CYPRESS_USER_PW_SECRET"] + current_app.config["DANGEROUS_SALT"]).encode("utf-8") + ).hexdigest(), "mobile_number": "9025555555", "state": "active", "blocked": False, @@ -104,7 +121,7 @@ def create_test_user(email_name): except Exception: return jsonify(message="Error creating user"), 400 - users = { "regular": user.serialize(), "admin": user2.serialize() } + users = {"regular": user.serialize(), "admin": user2.serialize()} return jsonify(users), 201 @@ -121,24 +138,24 @@ def _destroy_test_user(email_name): # this value gets changed when updating branding (and possibly other updates to service) # and is a bug cypress_service = Service.query.filter_by(id=current_app.config["CYPRESS_SERVICE_ID"]).first() - cypress_service.created_by_id = current_app.config["CYPRESS_TEST_USER_ID"] + cypress_service.created_by_id = current_app.config["CYPRESS_TEST_USER_ID"] # cycle through all the services created by this user, remove associated entities services = Service.query.filter_by(created_by=user).filter(Service.id != current_app.config["CYPRESS_SERVICE_ID"]) for service in services.all(): TemplateHistory.query.filter_by(service_id=service.id).delete() - + Template.query.filter_by(service_id=service.id).delete() AnnualBilling.query.filter_by(service_id=service.id).delete() ServicePermission.query.filter_by(service_id=service.id).delete() Permission.query.filter_by(service_id=service.id).delete() - - services.delete() + + services.delete() # remove all entities related to the user itself TemplateRedacted.query.filter_by(updated_by=user).delete() TemplateHistory.query.filter_by(created_by=user).delete() - Template.query.filter_by(created_by=user).delete() + Template.query.filter_by(created_by=user).delete() Permission.query.filter_by(user=user).delete() LoginEvent.query.filter_by(user=user).delete() ServiceUser.query.filter_by(user_id=user.id).delete() @@ -166,8 +183,8 @@ def cleanup_stale_users(): A JSON response with a success message if the cleanup is successful, or an error message if an exception occurs during the cleanup process. """ if current_app.config["NOTIFY_ENVIRONMENT"] == "production": - return jsonify(message="Forbidden"), 403 - raise Exception("") + return jsonify(message="Forbidden"), 403 + raise Exception("") try: three_hours_ago = datetime.utcnow() - timedelta(hours=3) diff --git a/migrations/versions/0461_add_cypress_data.py b/migrations/versions/0461_add_cypress_data.py index 9295d816db..b0ade23cbb 100644 --- a/migrations/versions/0461_add_cypress_data.py +++ b/migrations/versions/0461_add_cypress_data.py @@ -29,8 +29,13 @@ sms_template_id = current_app.config["CYPRESS_SMOKE_TEST_SMS_TEMPLATE_ID"] default_category_id = current_app.config["DEFAULT_TEMPLATE_CATEGORY_LOW"] + def upgrade(): - password = hashpw(hashlib.sha256((current_app.config["CYPRESS_USER_PW_SECRET"] + current_app.config["DANGEROUS_SALT"]).encode("utf-8")).hexdigest()) + password = hashpw( + hashlib.sha256( + (current_app.config["CYPRESS_USER_PW_SECRET"] + current_app.config["DANGEROUS_SALT"]).encode("utf-8") + ).hexdigest() + ) current_year = get_current_financial_year_start_year() default_limit = 250000 @@ -59,7 +64,7 @@ def upgrade(): """ op.execute(service_insert.format(service_id, datetime.utcnow(), user_id)) - for send_type in ('sms', 'email'): + for send_type in ("sms", "email"): service_perms_insert = """INSERT INTO service_permissions (service_id, permission, created_at) VALUES ('{}', '{}', '{}')""" op.execute(service_perms_insert.format(service_id, send_type, datetime.utcnow())) @@ -67,13 +72,19 @@ def upgrade(): insert_row_if_not_exist = """INSERT INTO annual_billing (id, service_id, financial_year_start, free_sms_fragment_limit, created_at, updated_at) VALUES ('{}', '{}', {}, {}, '{}', '{}') """ - op.execute(insert_row_if_not_exist.format(uuid.uuid4(), service_id, current_year, default_limit, datetime.utcnow(), datetime.utcnow())) - + op.execute( + insert_row_if_not_exist.format( + uuid.uuid4(), service_id, current_year, default_limit, datetime.utcnow(), datetime.utcnow() + ) + ) + user_to_service_insert = """INSERT INTO user_to_service (user_id, service_id) VALUES ('{}', '{}')""" op.execute(user_to_service_insert.format(user_id, service_id)) for permission in PERMISSION_LIST: - perms_insert = """INSERT INTO permissions (id, service_id, user_id, permission, created_at) VALUES ('{}', '{}', '{}', '{}', '{}')""" + perms_insert = ( + """INSERT INTO permissions (id, service_id, user_id, permission, created_at) VALUES ('{}', '{}', '{}', '{}', '{}')""" + ) op.execute(perms_insert.format(uuid.uuid4(), service_id, user_id, permission, datetime.utcnow())) # insert test email template @@ -84,10 +95,12 @@ def upgrade(): # insert 10 random email templates for i in range(10): - _insert_template(uuid.uuid4(), "Template {}".format(i), "Template {}".format(i), "email", "Template {}".format(i), default_category_id) - - #insert 1 random sms template - _insert_template(uuid.uuid4(), "Template 11", "Template 11", "sms", "Template 11", 'b6c42a7e-2a26-4a07-802b-123a5c3198a9') + _insert_template( + uuid.uuid4(), "Template {}".format(i), "Template {}".format(i), "email", "Template {}".format(i), default_category_id + ) + + # insert 1 random sms template + _insert_template(uuid.uuid4(), "Template 11", "Template 11", "sms", "Template 11", "b6c42a7e-2a26-4a07-802b-123a5c3198a9") def downgrade(): @@ -121,27 +134,7 @@ def _insert_template(id, name, content, type, subject, category_id): op.execute( template_history_insert.format( - uuid.uuid4(), - name, - type, - datetime.utcnow(), - content, - service_id, - subject, - user_id, - category_id + uuid.uuid4(), name, type, datetime.utcnow(), content, service_id, subject, user_id, category_id ) ) - op.execute( - template_insert.format( - id, - name, - type, - datetime.utcnow(), - content, - service_id, - subject, - user_id, - category_id - ) - ) \ No newline at end of file + op.execute(template_insert.format(id, name, type, datetime.utcnow(), content, service_id, subject, user_id, category_id)) From 9608861d9dec9f1405ce24d1a18f3cfb9d3cf24a Mon Sep 17 00:00:00 2001 From: Andrew Leith Date: Fri, 4 Oct 2024 14:07:33 +0000 Subject: [PATCH 18/37] chore: remove unreachable code --- app/cypress/rest.py | 1 - 1 file changed, 1 deletion(-) diff --git a/app/cypress/rest.py b/app/cypress/rest.py index 6c1893a2bd..47566081ea 100644 --- a/app/cypress/rest.py +++ b/app/cypress/rest.py @@ -184,7 +184,6 @@ def cleanup_stale_users(): """ if current_app.config["NOTIFY_ENVIRONMENT"] == "production": return jsonify(message="Forbidden"), 403 - raise Exception("") try: three_hours_ago = datetime.utcnow() - timedelta(hours=3) From b3f9d5d28c2db51de89f751c8c5fd9e978205bc0 Mon Sep 17 00:00:00 2001 From: Andrew Leith Date: Fri, 25 Oct 2024 16:30:18 +0000 Subject: [PATCH 19/37] task: add config values for testing --- app/config.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/config.py b/app/config.py index 7b0b93f61b..68292deac4 100644 --- a/app/config.py +++ b/app/config.py @@ -581,6 +581,8 @@ class Config(object): # cache clear auth keys CACHE_CLEAR_USER_NAME = "CACHE_CLEAR_USER" CACHE_CLEAR_CLIENT_SECRET = os.getenv("CACHE_CLEAR_CLIENT_SECRET") + CYPRESS_AUTH_USER_NAME = "CYPRESS_AUTH_USER" + CYPRESS_AUTH_CLIENT_SECRET = os.getenv("CYPRESS_AUTH_CLIENT_SECRET") @classmethod def get_sensitive_config(cls) -> list[str]: From 09d027c50574f82defd2665c32b061ab9e70c9e4 Mon Sep 17 00:00:00 2001 From: Andrew Leith Date: Fri, 25 Oct 2024 16:30:39 +0000 Subject: [PATCH 20/37] feat(auth): add separate auth mechanism for ui testing --- app/__init__.py | 3 ++- app/authentication/auth.py | 23 ++++++++++++++++++++++- 2 files changed, 24 insertions(+), 2 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index 2826b4a24e..cd2ebc42e8 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -183,6 +183,7 @@ def register_blueprint(application): requires_admin_auth, requires_auth, requires_cache_clear_auth, + requires_cypress_auth, requires_no_auth, requires_sre_auth, ) @@ -271,7 +272,7 @@ def register_blueprint(application): register_notify_blueprint(application, template_category_blueprint, requires_admin_auth) - register_notify_blueprint(application, cypress_blueprint, requires_admin_auth, "/cypress") + register_notify_blueprint(application, cypress_blueprint, requires_cypress_auth, "/cypress") register_notify_blueprint(application, cache_blueprint, requires_cache_clear_auth) diff --git a/app/authentication/auth.py b/app/authentication/auth.py index 8b627f8919..be003ee7f4 100644 --- a/app/authentication/auth.py +++ b/app/authentication/auth.py @@ -21,6 +21,7 @@ JWT_AUTH_TYPE = "jwt" API_KEY_V1_AUTH_TYPE = "api_key_v1" CACHE_CLEAR_V1_AUTH_TYPE = "cache_clear_v1" +CYPRESS_V1_AUTH_TYPE = "cypress_v1" AUTH_TYPES = [ ( "Bearer", @@ -40,6 +41,11 @@ CACHE_CLEAR_V1_AUTH_TYPE, "This is used internally by GC Notify to clear the redis cache after a deployment.", ), + ( + "Cypress-v1", + CYPRESS_V1_AUTH_TYPE, + "This is used by the Cypress tests to create users on the fly in staging.", + ) ] @@ -75,7 +81,7 @@ def get_auth_token(req): raise AuthError( "Unauthorized, Authorization header is invalid. " "GC Notify supports the following authentication methods. " - + ", ".join([f"{auth_type[0]}: {auth_type[2]}" for auth_type in AUTH_TYPES]), + + ", ".join([f"{auth_type[0]}: {auth_type[2]}" for auth_type in AUTH_TYPES[:2]]), 401, ) @@ -129,6 +135,21 @@ def requires_cache_clear_auth(): raise AuthError("Unauthorized, cache clear authentication token required", 401) +def requires_cypress_auth(): + request_helper.check_proxy_header_before_request() + + auth_type, auth_token = get_auth_token(request) + if auth_type != JWT_AUTH_TYPE: + raise AuthError("Invalid scheme: can only use JWT for sre authentication", 401) + client = __get_token_issuer(auth_token) + + if client == current_app.config.get("CYPRESS_AUTH_USER_NAME"): + g.service_id = current_app.config.get("CYPRESS_AUTH_USER_NAME") + return handle_admin_key(auth_token, current_app.config.get("CYPRESS_AUTH_CLIENT_SECRET")) + else: + raise AuthError("Unauthorized, cache clear authentication token required", 401) + + def requires_auth(): request_helper.check_proxy_header_before_request() From dd93318d1bff4cfcf4ead5b162f40439875d38db Mon Sep 17 00:00:00 2001 From: Andrew Leith Date: Fri, 25 Oct 2024 17:36:22 +0000 Subject: [PATCH 21/37] chore: add tests --- app/cypress/rest.py | 2 +- tests/__init__.py | 8 +++ tests/app/conftest.py | 56 +++++++++++++++ tests/app/cypress/test_rest.py | 126 +++++++++++++++++++++++++++++++++ 4 files changed, 191 insertions(+), 1 deletion(-) create mode 100644 tests/app/cypress/test_rest.py diff --git a/app/cypress/rest.py b/app/cypress/rest.py index 47566081ea..956c684b31 100644 --- a/app/cypress/rest.py +++ b/app/cypress/rest.py @@ -202,4 +202,4 @@ def cleanup_stale_users(): return jsonify(message="Error cleaning up"), 500 current_app.logger.info("[cleanup_stale_users]: Cleaned up stale test users") - return jsonify(message="Clean up ccomplete"), 201 + return jsonify(message="Clean up complete"), 201 diff --git a/tests/__init__.py b/tests/__init__.py index af1cad00e9..3f6507fb24 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -50,6 +50,14 @@ def create_cache_clear_authorization_header(): return "Authorization", "Bearer {}".format(token) +def create_cypres_authorization_header(): + client_id = current_app.config["CYPRESS_AUTH_USER_NAME"] + secret = current_app.config["CYPRESS_AUTH_CLIENT_SECRET"] + + token = create_jwt_token(secret=secret, client_id=client_id) + return "Authorization", "Bearer {}".format(token) + + def unwrap_function(fn): """ Given a function, returns its undecorated original. diff --git a/tests/app/conftest.py b/tests/app/conftest.py index cb9f1a4601..d3cbcbffa4 100644 --- a/tests/app/conftest.py +++ b/tests/app/conftest.py @@ -124,6 +124,14 @@ def notify_user(notify_db_session): ) +@pytest.fixture(scope="function") +def cypress_user(notify_db_session): + return create_user( + email="cypress-service-user@digital.cabinet-office.gov.uk", + id_=current_app.config["CYPRESS_TEST_USER_ID"], + ) + + def create_code(notify_db, notify_db_session, code_type, usr=None, code=None): if code is None: code = create_secret_code() @@ -215,6 +223,54 @@ def sample_service( ) +@pytest.fixture(scope="function") +def sample_service_cypress( + notify_db, + notify_db_session, +): + user = create_user() + service = Service.query.get(current_app.config["CYPRESS_SERVICE_ID"]) + if not service: + service = Service( + name="Cypress Service", + message_limit=1000, + sms_daily_limit=1000, + restricted=False, + email_from="notify.service", + created_by=user, + prefix_sms=False, + ) + dao_create_service( + service=service, + service_id=current_app.config["CYPRESS_SERVICE_ID"], + user=user, + ) + + data = { + "service": service, + "email_address": "notify@gov.uk", + "is_default": True, + } + reply_to = ServiceEmailReplyTo(**data) + + db.session.add(reply_to) + db.session.commit() + + return service, user + + return create_sample_service( + notify_db, + notify_db_session, + service_name="CypressSample service", + user=None, + restricted=False, + limit=1000, + email_from=None, + permissions=None, + research_mode=None, + ) + + @pytest.fixture(scope="function", name="sample_service_full_permissions") def _sample_service_full_permissions(notify_db_session): service = create_service( diff --git a/tests/app/cypress/test_rest.py b/tests/app/cypress/test_rest.py new file mode 100644 index 0000000000..1092af74b6 --- /dev/null +++ b/tests/app/cypress/test_rest.py @@ -0,0 +1,126 @@ +import json +from datetime import datetime, timedelta + +from freezegun import freeze_time +from app.models import User, Service +from tests import create_cypres_authorization_header +from tests.conftest import notify_api, set_config_values + +EMAIL_PREFIX = "notify-ui-tests+ag_" + +def test_create_test_user(client, sample_service_cypress): + auth_header = create_cypres_authorization_header() + + resp = client.post( + "/cypress/create_user/{}".format("emailsuffix"), + headers=[auth_header], + content_type="application/json", + ) + + data = json.loads(resp.data) + + assert resp.status_code == 201 + assert 'regular' in data + assert 'admin' in data + assert data['regular']['email_address'] == f"{EMAIL_PREFIX}emailsuffix@cds-snc.ca" + assert data['admin']['email_address'] == f"{EMAIL_PREFIX}emailsuffix_admin@cds-snc.ca" + + # verify users were created in the DB + user = User.query.filter_by(email_address=f"{EMAIL_PREFIX}emailsuffix@cds-snc.ca").first() + assert user is not None + + user = User.query.filter_by(email_address=f"{EMAIL_PREFIX}emailsuffix_admin@cds-snc.ca").first() + assert user is not None + + +def test_create_test_user_fails_bad_chars(client, sample_service_cypress): + auth_header = create_cypres_authorization_header() + + resp = client.post( + "/cypress/create_user/{}".format("email-suffix"), + headers=[auth_header], + content_type="application/json", + ) + + data = json.loads(resp.data) + assert resp.status_code == 400 + + +def test_create_test_user_fails_in_prod(client, notify_api, sample_service_cypress): + with set_config_values(notify_api, {"NOTIFY_ENVIRONMENT": "production"}): + auth_header = create_cypres_authorization_header() + + resp = client.post( + "/cypress/create_user/{}".format("email-suffix"), + headers=[auth_header], + content_type="application/json", + ) + + assert resp.status_code == 403 + +def test_cleanup_stale_users(client, sample_service_cypress, cypress_user, notify_db): + + + auth_header = create_cypres_authorization_header() + resp = client.post( + "/cypress/create_user/{}".format("emailsuffix"), + headers=[auth_header], + content_type="application/json", + ) + data = json.loads(resp.data) + assert resp.status_code == 201 + # verify users were created in the DB + user = User.query.filter_by(email_address=f"{EMAIL_PREFIX}emailsuffix@cds-snc.ca").first() + assert user is not None + user2 = User.query.filter_by(email_address=f"{EMAIL_PREFIX}emailsuffix_admin@cds-snc.ca").first() + assert user2 is not None + # update created_at time so they can be cleaned up + user.created_at = datetime.utcnow() - timedelta(days=30) + user2.created_at = datetime.utcnow() - timedelta(days=30) + notify_db.session.add(user) + notify_db.session.add(user2) + notify_db.session.commit() + + # clean up users + auth_header = create_cypres_authorization_header() + resp = client.get( + "/cypress/cleanup", + headers=[auth_header], + content_type="application/json", + ) + data = json.loads(resp.data) + + assert resp.status_code == 201 + assert data['message'] == "Clean up complete" + + # Verify the stale user has been deleted + user = User.query.filter_by(email_address=f"{EMAIL_PREFIX}emailsuffix@cds-snc.ca").first() + assert user is None + + user = User.query.filter_by(email_address=f"{EMAIL_PREFIX}emailsuffix_admin@cds-snc.ca").first() + assert user is None + +# def test_cleanup_stale_users_no_stale_users(test_client): +# response = test_client.get('/cleanup') +# data = json.loads(response.data) + +# assert response.status_code == 201 +# assert data['message'] == "Clean up complete" + +# def test_destroy_test_user(test_client): +# # Create a test user +# test_user = User( +# email_address=f"{EMAIL_PREFIX}testuser@cds-snc.ca", +# password='password', +# mobile_number='1234567890', +# state='active', +# blocked=False +# ) +# db.session.add(test_user) +# db.session.commit() + +# _destroy_test_user('testuser') + +# # Verify the test user has been deleted +# user = User.query.filter_by(email_address=f"{EMAIL_PREFIX}testuser@cds-snc.ca").first() +# assert user is None \ No newline at end of file From e4ed50a6db019ce92b9f16b3812fb555faf42dd1 Mon Sep 17 00:00:00 2001 From: Andrew Leith Date: Fri, 25 Oct 2024 17:38:48 +0000 Subject: [PATCH 22/37] chore: formatting --- app/authentication/auth.py | 2 +- tests/app/cypress/test_rest.py | 35 +++++++++++++++++----------------- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/app/authentication/auth.py b/app/authentication/auth.py index be003ee7f4..418480e213 100644 --- a/app/authentication/auth.py +++ b/app/authentication/auth.py @@ -45,7 +45,7 @@ "Cypress-v1", CYPRESS_V1_AUTH_TYPE, "This is used by the Cypress tests to create users on the fly in staging.", - ) + ), ] diff --git a/tests/app/cypress/test_rest.py b/tests/app/cypress/test_rest.py index 1092af74b6..fe2fa9b4de 100644 --- a/tests/app/cypress/test_rest.py +++ b/tests/app/cypress/test_rest.py @@ -1,29 +1,29 @@ import json from datetime import datetime, timedelta -from freezegun import freeze_time -from app.models import User, Service +from app.models import User from tests import create_cypres_authorization_header -from tests.conftest import notify_api, set_config_values +from tests.conftest import set_config_values EMAIL_PREFIX = "notify-ui-tests+ag_" + def test_create_test_user(client, sample_service_cypress): auth_header = create_cypres_authorization_header() - + resp = client.post( "/cypress/create_user/{}".format("emailsuffix"), headers=[auth_header], content_type="application/json", ) - + data = json.loads(resp.data) assert resp.status_code == 201 - assert 'regular' in data - assert 'admin' in data - assert data['regular']['email_address'] == f"{EMAIL_PREFIX}emailsuffix@cds-snc.ca" - assert data['admin']['email_address'] == f"{EMAIL_PREFIX}emailsuffix_admin@cds-snc.ca" + assert "regular" in data + assert "admin" in data + assert data["regular"]["email_address"] == f"{EMAIL_PREFIX}emailsuffix@cds-snc.ca" + assert data["admin"]["email_address"] == f"{EMAIL_PREFIX}emailsuffix_admin@cds-snc.ca" # verify users were created in the DB user = User.query.filter_by(email_address=f"{EMAIL_PREFIX}emailsuffix@cds-snc.ca").first() @@ -35,21 +35,20 @@ def test_create_test_user(client, sample_service_cypress): def test_create_test_user_fails_bad_chars(client, sample_service_cypress): auth_header = create_cypres_authorization_header() - + resp = client.post( "/cypress/create_user/{}".format("email-suffix"), headers=[auth_header], content_type="application/json", ) - - data = json.loads(resp.data) + assert resp.status_code == 400 def test_create_test_user_fails_in_prod(client, notify_api, sample_service_cypress): with set_config_values(notify_api, {"NOTIFY_ENVIRONMENT": "production"}): auth_header = create_cypres_authorization_header() - + resp = client.post( "/cypress/create_user/{}".format("email-suffix"), headers=[auth_header], @@ -58,9 +57,8 @@ def test_create_test_user_fails_in_prod(client, notify_api, sample_service_cypre assert resp.status_code == 403 + def test_cleanup_stale_users(client, sample_service_cypress, cypress_user, notify_db): - - auth_header = create_cypres_authorization_header() resp = client.post( "/cypress/create_user/{}".format("emailsuffix"), @@ -78,7 +76,7 @@ def test_cleanup_stale_users(client, sample_service_cypress, cypress_user, notif user.created_at = datetime.utcnow() - timedelta(days=30) user2.created_at = datetime.utcnow() - timedelta(days=30) notify_db.session.add(user) - notify_db.session.add(user2) + notify_db.session.add(user2) notify_db.session.commit() # clean up users @@ -91,7 +89,7 @@ def test_cleanup_stale_users(client, sample_service_cypress, cypress_user, notif data = json.loads(resp.data) assert resp.status_code == 201 - assert data['message'] == "Clean up complete" + assert data["message"] == "Clean up complete" # Verify the stale user has been deleted user = User.query.filter_by(email_address=f"{EMAIL_PREFIX}emailsuffix@cds-snc.ca").first() @@ -100,6 +98,7 @@ def test_cleanup_stale_users(client, sample_service_cypress, cypress_user, notif user = User.query.filter_by(email_address=f"{EMAIL_PREFIX}emailsuffix_admin@cds-snc.ca").first() assert user is None + # def test_cleanup_stale_users_no_stale_users(test_client): # response = test_client.get('/cleanup') # data = json.loads(response.data) @@ -123,4 +122,4 @@ def test_cleanup_stale_users(client, sample_service_cypress, cypress_user, notif # # Verify the test user has been deleted # user = User.query.filter_by(email_address=f"{EMAIL_PREFIX}testuser@cds-snc.ca").first() -# assert user is None \ No newline at end of file +# assert user is None From b38cb7a96c2afcebb0e4d148e10814ffae395a3a Mon Sep 17 00:00:00 2001 From: Andrew Leith Date: Fri, 25 Oct 2024 18:10:27 +0000 Subject: [PATCH 23/37] chore: fix tests --- app/config.py | 1 + .../app/authentication/test_authentication.py | 1 - tests/app/cypress/test_rest.py | 26 ------------------- 3 files changed, 1 insertion(+), 27 deletions(-) diff --git a/app/config.py b/app/config.py index 68292deac4..17d891634b 100644 --- a/app/config.py +++ b/app/config.py @@ -634,6 +634,7 @@ class Development(Config): DANGEROUS_SALT = os.getenv("DANGEROUS_SALT", "dev-notify-salt ") SRE_CLIENT_SECRET = os.getenv("SRE_CLIENT_SECRET", "dev-notify-secret-key") CACHE_CLEAR_CLIENT_SECRET = os.getenv("CACHE_CLEAR_CLIENT_SECRET", "dev-notify-cache-client-secret") + CYPRESS_AUTH_CLIENT_SECRET = os.getenv("CYPRESS_AUTH_CLIENT_SECRET", "dev-notify-cypress-secret-key") NOTIFY_ENVIRONMENT = "development" NOTIFICATION_QUEUE_PREFIX = os.getenv("NOTIFICATION_QUEUE_PREFIX", "notification-canada-ca") diff --git a/tests/app/authentication/test_authentication.py b/tests/app/authentication/test_authentication.py index 1b2e83c3d2..913a7ef0f6 100644 --- a/tests/app/authentication/test_authentication.py +++ b/tests/app/authentication/test_authentication.py @@ -44,7 +44,6 @@ def test_should_not_allow_request_with_incorrect_header(client, auth_fn): + "GC Notify supports the following authentication methods. " + f"{AUTH_TYPES[0][0]}: {AUTH_TYPES[0][2]}" + f", {AUTH_TYPES[1][0]}: {AUTH_TYPES[1][2]}" - + f", {AUTH_TYPES[2][0]}: {AUTH_TYPES[2][2]}" ) diff --git a/tests/app/cypress/test_rest.py b/tests/app/cypress/test_rest.py index fe2fa9b4de..85860b0374 100644 --- a/tests/app/cypress/test_rest.py +++ b/tests/app/cypress/test_rest.py @@ -97,29 +97,3 @@ def test_cleanup_stale_users(client, sample_service_cypress, cypress_user, notif user = User.query.filter_by(email_address=f"{EMAIL_PREFIX}emailsuffix_admin@cds-snc.ca").first() assert user is None - - -# def test_cleanup_stale_users_no_stale_users(test_client): -# response = test_client.get('/cleanup') -# data = json.loads(response.data) - -# assert response.status_code == 201 -# assert data['message'] == "Clean up complete" - -# def test_destroy_test_user(test_client): -# # Create a test user -# test_user = User( -# email_address=f"{EMAIL_PREFIX}testuser@cds-snc.ca", -# password='password', -# mobile_number='1234567890', -# state='active', -# blocked=False -# ) -# db.session.add(test_user) -# db.session.commit() - -# _destroy_test_user('testuser') - -# # Verify the test user has been deleted -# user = User.query.filter_by(email_address=f"{EMAIL_PREFIX}testuser@cds-snc.ca").first() -# assert user is None From 0afb435c1053bfa44eddf3d2adc191734bf4b16a Mon Sep 17 00:00:00 2001 From: Andrew Leith Date: Fri, 25 Oct 2024 18:14:12 +0000 Subject: [PATCH 24/37] chore: remove unused code --- tests/app/conftest.py | 12 ------------ tests/app/cypress/test_rest.py | 2 +- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/tests/app/conftest.py b/tests/app/conftest.py index d3cbcbffa4..1b96acde4e 100644 --- a/tests/app/conftest.py +++ b/tests/app/conftest.py @@ -258,18 +258,6 @@ def sample_service_cypress( return service, user - return create_sample_service( - notify_db, - notify_db_session, - service_name="CypressSample service", - user=None, - restricted=False, - limit=1000, - email_from=None, - permissions=None, - research_mode=None, - ) - @pytest.fixture(scope="function", name="sample_service_full_permissions") def _sample_service_full_permissions(notify_db_session): diff --git a/tests/app/cypress/test_rest.py b/tests/app/cypress/test_rest.py index 85860b0374..bf54410914 100644 --- a/tests/app/cypress/test_rest.py +++ b/tests/app/cypress/test_rest.py @@ -65,7 +65,7 @@ def test_cleanup_stale_users(client, sample_service_cypress, cypress_user, notif headers=[auth_header], content_type="application/json", ) - data = json.loads(resp.data) + assert resp.status_code == 201 # verify users were created in the DB user = User.query.filter_by(email_address=f"{EMAIL_PREFIX}emailsuffix@cds-snc.ca").first() From 350649c3d7840f1c10fde431014b3ba2417414a8 Mon Sep 17 00:00:00 2001 From: Andrew Leith Date: Mon, 28 Oct 2024 12:25:36 +0000 Subject: [PATCH 25/37] chore: fix migration due to incoming migrations --- ...{0461_add_cypress_data.py => 0463_add_cypress_data.py} | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) rename migrations/versions/{0461_add_cypress_data.py => 0463_add_cypress_data.py} (98%) diff --git a/migrations/versions/0461_add_cypress_data.py b/migrations/versions/0463_add_cypress_data.py similarity index 98% rename from migrations/versions/0461_add_cypress_data.py rename to migrations/versions/0463_add_cypress_data.py index b0ade23cbb..dfba58f267 100644 --- a/migrations/versions/0461_add_cypress_data.py +++ b/migrations/versions/0463_add_cypress_data.py @@ -1,7 +1,7 @@ """empty message -Revision ID: 0461_add_cypress_data -Revises: 0460_new_service_columns +Revision ID: 0463_add_cypress_data +Revises: 0462_add_pinpoint_fields Create Date: 2016-06-01 14:17:01.963181 """ @@ -19,8 +19,8 @@ from app.encryption import hashpw from app.models import PERMISSION_LIST -revision = "0461_add_cypress_data" -down_revision = "0460_new_service_columns" +revision = "0463_add_cypress_data" +down_revision = "0462_add_pinpoint_fields" user_id = current_app.config["CYPRESS_TEST_USER_ID"] admin_user_id = current_app.config["CYPRESS_TEST_USER_ADMIN_ID"] From 0a31d2c401529f9ff79e038ba499cf9047d80e8d Mon Sep 17 00:00:00 2001 From: Andrew Leith Date: Fri, 8 Nov 2024 12:57:20 +0000 Subject: [PATCH 26/37] chore: re-number migrations due to merge --- ...{0463_add_cypress_data.py => 0466_add_cypress_data.py} | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) rename migrations/versions/{0463_add_cypress_data.py => 0466_add_cypress_data.py} (98%) diff --git a/migrations/versions/0463_add_cypress_data.py b/migrations/versions/0466_add_cypress_data.py similarity index 98% rename from migrations/versions/0463_add_cypress_data.py rename to migrations/versions/0466_add_cypress_data.py index dfba58f267..a5a3bb2749 100644 --- a/migrations/versions/0463_add_cypress_data.py +++ b/migrations/versions/0466_add_cypress_data.py @@ -1,7 +1,7 @@ """empty message -Revision ID: 0463_add_cypress_data -Revises: 0462_add_pinpoint_fields +Revision ID: 0466_add_cypress_data +Revises: 0465_add_constraints Create Date: 2016-06-01 14:17:01.963181 """ @@ -19,8 +19,8 @@ from app.encryption import hashpw from app.models import PERMISSION_LIST -revision = "0463_add_cypress_data" -down_revision = "0462_add_pinpoint_fields" +revision = "0466_add_cypress_data" +down_revision = "0465_add_constraints" user_id = current_app.config["CYPRESS_TEST_USER_ID"] admin_user_id = current_app.config["CYPRESS_TEST_USER_ADMIN_ID"] From c0f4456f0ab8b74234280db9c92bf860bff67ab4 Mon Sep 17 00:00:00 2001 From: Andrew Leith Date: Fri, 8 Nov 2024 13:19:51 +0000 Subject: [PATCH 27/37] task: pass new secret `CYPRESS_USER_PW_SECRET` into CI --- .github/workflows/test.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 1ca6b7d279..c6178df141 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -11,6 +11,7 @@ jobs: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres POSTGRES_DB: test_notification_api + CYPRESS_USER_PW_SECRET: ${{ secrets.CYPRESS_USER_PW_SECRET }} ports: - 5432:5432 options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 From 6518911d2b86e7e0289d2ff84f68f025c9cf7f82 Mon Sep 17 00:00:00 2001 From: Andrew Leith Date: Fri, 8 Nov 2024 13:31:40 +0000 Subject: [PATCH 28/37] chore: add some debugging to the workflow --- .github/workflows/test.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index c6178df141..d653a728b9 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -17,6 +17,10 @@ jobs: options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 steps: + - name: Debug Environment Variables + run: | + echo "CYPRESS_USER_PW_SECRET is set: ${{ env.CYPRESS_USER_PW_SECRET != '' }}" + echo "DANGEROUS_SALT is set: ${{ env.DANGEROUS_SALT != '' }}" - name: Install libcurl run: sudo apt-get update && sudo apt-get install libssl-dev libcurl4-openssl-dev - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 From 56f8cb20b4cc3ee313a33d79f926abf5b69e617c Mon Sep 17 00:00:00 2001 From: Andrew Leith Date: Fri, 8 Nov 2024 13:53:43 +0000 Subject: [PATCH 29/37] task: debug workflow --- .github/workflows/test.yaml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index d653a728b9..11e3948189 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -21,6 +21,10 @@ jobs: run: | echo "CYPRESS_USER_PW_SECRET is set: ${{ env.CYPRESS_USER_PW_SECRET != '' }}" echo "DANGEROUS_SALT is set: ${{ env.DANGEROUS_SALT != '' }}" + run: | + echo "CYPRESS_USER_PW_SECRET exists: ${{ secrets.CYPRESS_USER_PW_SECRET != '' }}" + echo "Using direct env var reference:" + if [ -n "${{ secrets.CYPRESS_USER_PW_SECRET }}" ]; then echo "Secret exists"; else echo "Secret missing"; fi - name: Install libcurl run: sudo apt-get update && sudo apt-get install libssl-dev libcurl4-openssl-dev - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 From 911b9576a7c932b0bc3008c6d8db9e1dd0dd9a4d Mon Sep 17 00:00:00 2001 From: Andrew Leith Date: Fri, 8 Nov 2024 13:54:56 +0000 Subject: [PATCH 30/37] fix(workflow): put the env statement in the right place --- .github/workflows/test.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 11e3948189..093bd8af99 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -3,6 +3,8 @@ on: name: Python tests jobs: test: + env: + CYPRESS_USER_PW_SECRET: ${{ secrets.CYPRESS_USER_PW_SECRET }} runs-on: ubuntu-latest services: postgres: From bbfdb842b0beff93a2ba49ce87003a1a05ad2ccf Mon Sep 17 00:00:00 2001 From: Andrew Leith Date: Fri, 8 Nov 2024 14:01:43 +0000 Subject: [PATCH 31/37] chore: fix workflow --- .github/workflows/test.yaml | 9 --------- 1 file changed, 9 deletions(-) diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index 093bd8af99..037f12dbb5 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -13,20 +13,11 @@ jobs: POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres POSTGRES_DB: test_notification_api - CYPRESS_USER_PW_SECRET: ${{ secrets.CYPRESS_USER_PW_SECRET }} ports: - 5432:5432 options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 steps: - - name: Debug Environment Variables - run: | - echo "CYPRESS_USER_PW_SECRET is set: ${{ env.CYPRESS_USER_PW_SECRET != '' }}" - echo "DANGEROUS_SALT is set: ${{ env.DANGEROUS_SALT != '' }}" - run: | - echo "CYPRESS_USER_PW_SECRET exists: ${{ secrets.CYPRESS_USER_PW_SECRET != '' }}" - echo "Using direct env var reference:" - if [ -n "${{ secrets.CYPRESS_USER_PW_SECRET }}" ]; then echo "Secret exists"; else echo "Secret missing"; fi - name: Install libcurl run: sudo apt-get update && sudo apt-get install libssl-dev libcurl4-openssl-dev - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 From baeb377a81261e6147ce05f06f992fb6208261dc Mon Sep 17 00:00:00 2001 From: Andrew Leith Date: Fri, 8 Nov 2024 14:11:41 +0000 Subject: [PATCH 32/37] chore: add __init__.py in `tests/app/cypress` folder to make tests work --- tests/app/cypress/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/app/cypress/__init__.py diff --git a/tests/app/cypress/__init__.py b/tests/app/cypress/__init__.py new file mode 100644 index 0000000000..e69de29bb2 From 7dc741f9d945fd7c9e2c5ca22a5614dcde8fa915 Mon Sep 17 00:00:00 2001 From: Andrew Leith Date: Fri, 8 Nov 2024 14:35:33 +0000 Subject: [PATCH 33/37] Plz [review] ! From 1bf62de3d5dd1651b751f602755b1c87cded9f13 Mon Sep 17 00:00:00 2001 From: Andrew Leith Date: Fri, 8 Nov 2024 14:44:03 +0000 Subject: [PATCH 34/37] chore: fix incorrect descriptions --- app/authentication/auth.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/authentication/auth.py b/app/authentication/auth.py index 418480e213..23e708691c 100644 --- a/app/authentication/auth.py +++ b/app/authentication/auth.py @@ -140,14 +140,14 @@ def requires_cypress_auth(): auth_type, auth_token = get_auth_token(request) if auth_type != JWT_AUTH_TYPE: - raise AuthError("Invalid scheme: can only use JWT for sre authentication", 401) + raise AuthError("Invalid scheme: can only use JWT for cypress authentication", 401) client = __get_token_issuer(auth_token) if client == current_app.config.get("CYPRESS_AUTH_USER_NAME"): g.service_id = current_app.config.get("CYPRESS_AUTH_USER_NAME") return handle_admin_key(auth_token, current_app.config.get("CYPRESS_AUTH_CLIENT_SECRET")) else: - raise AuthError("Unauthorized, cache clear authentication token required", 401) + raise AuthError("Unauthorized, cypress authentication token required", 401) def requires_auth(): From 18458ad2a7ef27cfdd613dde94221a92e8ba8c07 Mon Sep 17 00:00:00 2001 From: Andrew Leith Date: Fri, 8 Nov 2024 14:44:23 +0000 Subject: [PATCH 35/37] fix: correct typo in function name --- tests/__init__.py | 2 +- tests/app/cypress/test_rest.py | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tests/__init__.py b/tests/__init__.py index 3f6507fb24..457d133e2c 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -50,7 +50,7 @@ def create_cache_clear_authorization_header(): return "Authorization", "Bearer {}".format(token) -def create_cypres_authorization_header(): +def create_cypress_authorization_header(): client_id = current_app.config["CYPRESS_AUTH_USER_NAME"] secret = current_app.config["CYPRESS_AUTH_CLIENT_SECRET"] diff --git a/tests/app/cypress/test_rest.py b/tests/app/cypress/test_rest.py index bf54410914..079d1de05d 100644 --- a/tests/app/cypress/test_rest.py +++ b/tests/app/cypress/test_rest.py @@ -2,14 +2,14 @@ from datetime import datetime, timedelta from app.models import User -from tests import create_cypres_authorization_header +from tests import create_cypress_authorization_header from tests.conftest import set_config_values EMAIL_PREFIX = "notify-ui-tests+ag_" def test_create_test_user(client, sample_service_cypress): - auth_header = create_cypres_authorization_header() + auth_header = create_cypress_authorization_header() resp = client.post( "/cypress/create_user/{}".format("emailsuffix"), @@ -34,7 +34,7 @@ def test_create_test_user(client, sample_service_cypress): def test_create_test_user_fails_bad_chars(client, sample_service_cypress): - auth_header = create_cypres_authorization_header() + auth_header = create_cypress_authorization_header() resp = client.post( "/cypress/create_user/{}".format("email-suffix"), @@ -47,7 +47,7 @@ def test_create_test_user_fails_bad_chars(client, sample_service_cypress): def test_create_test_user_fails_in_prod(client, notify_api, sample_service_cypress): with set_config_values(notify_api, {"NOTIFY_ENVIRONMENT": "production"}): - auth_header = create_cypres_authorization_header() + auth_header = create_cypress_authorization_header() resp = client.post( "/cypress/create_user/{}".format("email-suffix"), @@ -59,7 +59,7 @@ def test_create_test_user_fails_in_prod(client, notify_api, sample_service_cypre def test_cleanup_stale_users(client, sample_service_cypress, cypress_user, notify_db): - auth_header = create_cypres_authorization_header() + auth_header = create_cypress_authorization_header() resp = client.post( "/cypress/create_user/{}".format("emailsuffix"), headers=[auth_header], @@ -80,7 +80,7 @@ def test_cleanup_stale_users(client, sample_service_cypress, cypress_user, notif notify_db.session.commit() # clean up users - auth_header = create_cypres_authorization_header() + auth_header = create_cypress_authorization_header() resp = client.get( "/cypress/cleanup", headers=[auth_header], From a566d525e6623aeef8d1142697b8a780dd1cf65b Mon Sep 17 00:00:00 2001 From: Andrew Leith Date: Fri, 8 Nov 2024 14:44:39 +0000 Subject: [PATCH 36/37] fix: use cds domain instead of gov.uk --- tests/app/conftest.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/app/conftest.py b/tests/app/conftest.py index 1b96acde4e..8dbfd4c5b7 100644 --- a/tests/app/conftest.py +++ b/tests/app/conftest.py @@ -127,7 +127,7 @@ def notify_user(notify_db_session): @pytest.fixture(scope="function") def cypress_user(notify_db_session): return create_user( - email="cypress-service-user@digital.cabinet-office.gov.uk", + email="cypress-service-user@cds-snc.ca", id_=current_app.config["CYPRESS_TEST_USER_ID"], ) From d3d877cbde287d93d6782225cc9051a1a92cc5b4 Mon Sep 17 00:00:00 2001 From: Andrew Leith Date: Fri, 8 Nov 2024 15:22:29 +0000 Subject: [PATCH 37/37] chore: fix failing test --- .../app/dao/test_fact_notification_status_dao.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/tests/app/dao/test_fact_notification_status_dao.py b/tests/app/dao/test_fact_notification_status_dao.py index fea995ca56..5c2135ffe7 100644 --- a/tests/app/dao/test_fact_notification_status_dao.py +++ b/tests/app/dao/test_fact_notification_status_dao.py @@ -1358,10 +1358,12 @@ def test_fetch_quarter_data(self, notify_db_session): create_ft_notification_status(date(2018, 3, 20), "sms", service_2, count=100) create_ft_notification_status(date(2018, 2, 1), "sms", service_2, count=1000) - assert fetch_quarter_data(date(2018, 1, 1), date(2018, 3, 27), service_ids=[service_1.id, service_2.id]) == [ - (service_1.id, "sms", 4), - (service_2.id, "sms", 1100), - ] - assert fetch_quarter_data(date(2018, 4, 1), date(2018, 6, 30), service_ids=[service_1.id, service_2.id]) == [ - (service_1.id, "sms", 10) - ] + assert set(fetch_quarter_data(date(2018, 1, 1), date(2018, 3, 27), service_ids=[service_1.id, service_2.id])) == set( + [ + (service_1.id, "sms", 4), + (service_2.id, "sms", 1100), + ] + ) + assert set(fetch_quarter_data(date(2018, 4, 1), date(2018, 6, 30), service_ids=[service_1.id, service_2.id])) == set( + [(service_1.id, "sms", 10)] + )