Skip to content
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

Draft
wants to merge 21 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 12 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
9b3c5d1
initial commit
andrewleith Jul 18, 2024
7352db5
feat: create and destroy test users on demand
andrewleith Jul 22, 2024
de7d5b9
Merge branch 'main' into feat/scaffold-ui-test-user-on-demand
andrewleith Sep 13, 2024
5f5eb9f
chore(config): add some hardcoded UUIDs for cypress related items
andrewleith Sep 13, 2024
cfe8719
feat(cypress api): update create_test_user route:
andrewleith Sep 13, 2024
00dc686
feat(cypress api): update cleanup_stale_users route:
andrewleith Sep 13, 2024
6d1acba
chore: formatting
andrewleith Sep 13, 2024
7704b89
chore(format): formatting files I haven't touched :(
andrewleith Sep 13, 2024
0dd0721
chore: formatting
andrewleith Sep 13, 2024
7417e0e
chore: formatting
andrewleith Sep 13, 2024
9bb10ae
chore: formatting
andrewleith Sep 13, 2024
33e2ca7
chore: update docstrings; enhance exception handling
andrewleith Sep 16, 2024
490e666
fix(cypress api): use service id from config
andrewleith Sep 17, 2024
ae3be90
chore: add more cypress values to config
andrewleith Sep 17, 2024
17b4a2b
feat(cypress api): dont pass password around since its already a secr…
andrewleith Sep 23, 2024
425e156
feat(cypress data): migration to create cypress service, permissions,…
andrewleith Sep 23, 2024
3be6c6c
Merge branch 'main' into feat/scaffold-ui-test-user-on-demand
andrewleith Sep 27, 2024
f4c313a
feat(create_test_user): update delete logic to be more complete; para…
andrewleith Sep 28, 2024
74dd85d
chore: formatting
andrewleith Sep 28, 2024
0b1584a
Merge branch 'main' into feat/scaffold-ui-test-user-on-demand
andrewleith Oct 4, 2024
9608861
chore: remove unreachable code
andrewleith Oct 4, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -270,6 +271,8 @@ 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, cache_blueprint, requires_cache_clear_auth)


Expand Down
4 changes: 4 additions & 0 deletions app/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,10 @@ 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_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(",")]

Expand Down
Empty file added app/cypress/__init__.py
Empty file.
164 changes: 164 additions & 0 deletions app/cypress/rest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
"""
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

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, TemplateRedacted, User, VerifyCode

cypress_blueprint = Blueprint("cypress", __name__)
register_errors(cypress_blueprint)


@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

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(),
Fixed Show fixed Hide fixed
"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


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

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()

except Exception:
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.

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.

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

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)

db.session.commit()
except Exception:
return jsonify(message="Error cleaning up"), 500

return jsonify(message="Zeds dead, baby"), 201
2 changes: 1 addition & 1 deletion application.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion run_celery.py
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Loading