-
Notifications
You must be signed in to change notification settings - Fork 18
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feat/scaffold UI test user on demand #2286
Changes from 19 commits
9b3c5d1
7352db5
de7d5b9
5f5eb9f
cfe8719
00dc686
6d1acba
7704b89
0dd0721
7417e0e
9bb10ae
33e2ca7
490e666
ae3be90
17b4a2b
425e156
3be6c6c
f4c313a
74dd85d
0b1584a
9608861
b3f9d5d
09d027c
dd93318
e4ed50a
b38cb7a
0afb435
4e4027d
350649c
1cdb7ea
0a31d2c
c0f4456
6518911
56f8cb2
911b957
bbfdb84
baeb377
7dc741f
1bf62de
18458ad
a566d52
d3d877c
15d248a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,206 @@ | ||
""" | ||
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 re | ||
import uuid | ||
from datetime import datetime, timedelta | ||
|
||
from flask import Blueprint, current_app, jsonify | ||
|
||
from app import db | ||
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, | ||
) | ||
|
||
cypress_blueprint = Blueprint("cypress", __name__) | ||
register_errors(cypress_blueprint) | ||
|
||
EMAIL_PREFIX = "notify-ui-tests+ag_" | ||
|
||
|
||
@cypress_blueprint.route("/create_user/<email_name>", methods=["POST"]) | ||
def create_test_user(email_name): | ||
""" | ||
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 | ||
|
||
# Sanitize email_name to allow only alphanumeric characters | ||
if not re.match(r"^[a-z0-9]+$", email_name): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider using |
||
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(), | ||
"mobile_number": "9025555555", | ||
"state": "active", | ||
"blocked": False, | ||
} | ||
|
||
user = User(**user_regular) | ||
save_model_user(user) | ||
|
||
# Create the users | ||
user_admin = { | ||
"id": uuid.uuid4(), | ||
"name": "Notify UI testing account", | ||
"email_address": f"{EMAIL_PREFIX}{email_name}[email protected]", | ||
"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_reg = [] | ||
for p in [ | ||
"manage_users", | ||
"manage_templates", | ||
"manage_settings", | ||
"send_texts", | ||
"send_emails", | ||
"send_letters", | ||
"manage_api_keys", | ||
"view_activity", | ||
]: | ||
permissions_reg.append(Permission(permission=p)) | ||
|
||
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider logging the exception details here to help with debugging. |
||
|
||
users = {"regular": user.serialize(), "admin": user2.serialize()} | ||
|
||
return jsonify(users), 201 | ||
|
||
|
||
def _destroy_test_user(email_name): | ||
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") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The log message here references |
||
return | ||
|
||
try: | ||
# 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() | ||
LoginEvent.query.filter_by(user=user).delete() | ||
ServiceUser.query.filter_by(user_id=user.id).delete() | ||
VerifyCode.query.filter_by(user=user).delete() | ||
User.query.filter_by(email_address=f"{EMAIL_PREFIX}{email_name}@cds-snc.ca").delete() | ||
|
||
db.session.commit() | ||
|
||
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(): | ||
""" | ||
Method for cleaning up stale users. This method will only be used internally by the Cypress tests. | ||
|
||
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. | ||
|
||
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 | ||
raise Exception("") | ||
|
||
|
||
try: | ||
three_hours_ago = datetime.utcnow() - timedelta(hours=3) | ||
users = User.query.filter( | ||
User.email_address.like(f"%{EMAIL_PREFIX}%@cds-snc.ca%"), User.created_at < three_hours_ago | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The |
||
).all() | ||
|
||
# loop through users and call destroy_user on each one | ||
for user in users: | ||
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") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Consider logging the exception details here to help with debugging. |
||
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 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
"""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', '[email protected]', '{}', 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', '[email protected]', '{}', 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, '[email protected]', | ||
'{}', 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, '[email protected]', | ||
'{}', 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)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Love this addition + the Cypress auth, I'll be sure to move the utility method from that PR in here too!