diff --git a/app/commands.py b/app/commands.py index 9e97ce28c..25afef91a 100644 --- a/app/commands.py +++ b/app/commands.py @@ -1,8 +1,11 @@ +import uuid from datetime import datetime from decimal import Decimal from flask.ext.script import Command, Manager, Option -from app.models import PROVIDERS +from app.models import (PROVIDERS, Service, User) +from app.dao.services_dao import delete_service_and_all_associated_db_objects from app.dao.provider_rates_dao import create_provider_rates +from app.dao.users_dao import (delete_model_user, delete_user_verify_codes) class CreateProviderRateCommand(Command): @@ -28,3 +31,36 @@ class CreateProviderRateCommand(Command): raise Exception("Invalid valid_from date. Use the format %Y-%m-%dT%H:%M:%S") create_provider_rates(provider_name, valid_from, cost) + + +class PurgeFunctionalTestDataCommand(Command): + + option_list = ( + Option('-n', '-service-name-prefix', dest="service_name_prefix", help='Functional service name prefix.'), + Option('-u', '-user-email-prefix', dest='user_email_prefix', help="Functional test user email prefix.") + ) + + def run(self, service_name_prefix=None, user_email_prefix=None): + if service_name_prefix: + services = Service.query.filter(Service.name.like("{}%".format(service_name_prefix))).all() + for service in services: + # Make sure the second part of the service name is a uuid. + # Just in case someone decides to create a service with that name included in it. + try: + uuid.UUID(service.name.split(service_name_prefix)[1]) + except ValueError: + print("Skipping {} as the service name doesn't contain a UUID.".format(service.name)) + else: + delete_service_and_all_associated_db_objects(service) + if user_email_prefix: + users = User.query.filter(User.email_address.like("{}%".format(user_email_prefix))).all() + for usr in users: + # Make sure the full email includes a uuid in it + # Just in case someone decides to use a similar email address. + try: + uuid.UUID(usr.email_address.split("@")[0].split('+')[1]) + except ValueError: + print("Skipping {} as the user email doesn't contain a UUID.".format(usr.email_address)) + else: + delete_user_verify_codes(usr) + delete_model_user(usr) diff --git a/app/dao/notifications_dao.py b/app/dao/notifications_dao.py index b7a22fe74..798b8197a 100644 --- a/app/dao/notifications_dao.py +++ b/app/dao/notifications_dao.py @@ -200,11 +200,10 @@ def get_notifications_for_job(service_id, job_id, filter_dict=None, page=1, page page_size = current_app.config['PAGE_SIZE'] query = Notification.query.filter_by(service_id=service_id, job_id=job_id) query = filter_query(query, filter_dict) - pagination = query.order_by(desc(Notification.created_at)).paginate( + return query.order_by(desc(Notification.created_at)).paginate( page=page, per_page=page_size ) - return pagination def get_notification(service_id, notification_id): @@ -215,7 +214,11 @@ def get_notification_by_id(notification_id): return Notification.query.filter_by(id=notification_id).first() -def get_notifications_for_service(service_id, filter_dict=None, page=1, page_size=None, limit_days=None): +def get_notifications_for_service(service_id, + filter_dict=None, + page=1, + page_size=None, + limit_days=None): if page_size is None: page_size = current_app.config['PAGE_SIZE'] filters = [Notification.service_id == service_id] @@ -226,11 +229,10 @@ def get_notifications_for_service(service_id, filter_dict=None, page=1, page_siz query = Notification.query.filter(*filters) query = filter_query(query, filter_dict) - pagination = query.order_by(desc(Notification.created_at)).paginate( + return query.order_by(desc(Notification.created_at)).paginate( page=page, per_page=page_size ) - return pagination def filter_query(query, filter_dict=None): diff --git a/app/dao/provider_statistics_dao.py b/app/dao/provider_statistics_dao.py index 00fe8650e..48489b75a 100644 --- a/app/dao/provider_statistics_dao.py +++ b/app/dao/provider_statistics_dao.py @@ -1,4 +1,5 @@ from sqlalchemy import func +from app import db from app.models import (ProviderStatistics, SMS_PROVIDERS, EMAIL_PROVIDERS) diff --git a/app/dao/services_dao.py b/app/dao/services_dao.py index 4f89b1b16..e59481e74 100644 --- a/app/dao/services_dao.py +++ b/app/dao/services_dao.py @@ -9,6 +9,21 @@ from app.dao.dao_utils import ( version_class ) +from app.models import ( + NotificationStatistics, + TemplateStatistics, + ProviderStatistics, + VerifyCode, + ApiKey, + Template, + Job, + Notification, + Permission, + User, + InvitedUser, + Service +) + def dao_fetch_all_services(): return Service.query.order_by(asc(Service.created_at)).all() @@ -66,3 +81,31 @@ def dao_remove_user_from_service(service, user): raise e else: db.session.commit() + + +def delete_service_and_all_associated_db_objects(service): + + def _delete_commit(query): + query.delete() + db.session.commit() + + _delete_commit(NotificationStatistics.query.filter_by(service=service)) + _delete_commit(TemplateStatistics.query.filter_by(service=service)) + _delete_commit(ProviderStatistics.query.filter_by(service=service)) + _delete_commit(InvitedUser.query.filter_by(service=service)) + _delete_commit(Permission.query.filter_by(service=service)) + _delete_commit(ApiKey.query.filter_by(service=service)) + _delete_commit(Notification.query.filter_by(service=service)) + _delete_commit(Job.query.filter_by(service=service)) + _delete_commit(Template.query.filter_by(service=service)) + + verify_codes = VerifyCode.query.join(User).filter(User.id.in_([x.id for x in service.users])) + list(map(db.session.delete, verify_codes)) + db.session.commit() + users = [x for x in service.users] + map(service.users.remove, users) + [service.users.remove(x) for x in users] + db.session.delete(service) + db.session.commit() + list(map(db.session.delete, users)) + db.session.commit() diff --git a/app/dao/users_dao.py b/app/dao/users_dao.py index c108b6211..7960ed101 100644 --- a/app/dao/users_dao.py +++ b/app/dao/users_dao.py @@ -67,6 +67,11 @@ def delete_model_user(user): db.session.commit() +def delete_user_verify_codes(user): + VerifyCode.query.filter_by(user=user).delete() + db.session.commit() + + def get_model_users(user_id=None): if user_id: return User.query.filter_by(id=user_id).one() diff --git a/application.py b/application.py index fea2b6264..0b940dc87 100644 --- a/application.py +++ b/application.py @@ -14,6 +14,7 @@ manager.add_command("runserver", Server(host='0.0.0.0', port=port)) migrate = Migrate(application, db) manager.add_command('db', MigrateCommand) manager.add_command('create_provider_rate', commands.CreateProviderRateCommand) +manager.add_command('purge_functional_test_data', commands.PurgeFunctionalTestDataCommand) @manager.command diff --git a/tests/app/conftest.py b/tests/app/conftest.py index ee173097c..bcfe45dd5 100644 --- a/tests/app/conftest.py +++ b/tests/app/conftest.py @@ -413,6 +413,8 @@ def sample_permission(notify_db, 'user': user, 'permission': permission } + if service is None: + service = sample_service(notify_db, notify_db_session) if service: data['service'] = service p_model = Permission.query.filter_by( diff --git a/tests/app/dao/test_services_dao.py b/tests/app/dao/test_services_dao.py index fb6d666ca..88931aca2 100644 --- a/tests/app/dao/test_services_dao.py +++ b/tests/app/dao/test_services_dao.py @@ -8,10 +8,24 @@ from app.dao.services_dao import ( dao_fetch_service_by_id, dao_fetch_all_services_by_user, dao_fetch_service_by_id_and_user, - dao_update_service + dao_update_service, + delete_service_and_all_associated_db_objects ) from app.dao.users_dao import save_model_user -from app.models import Service, User +from app.models import ( + NotificationStatistics, + TemplateStatistics, + ProviderStatistics, + VerifyCode, + ApiKey, + Template, + Job, + Notification, + Permission, + User, + InvitedUser, + Service +) from sqlalchemy.orm.exc import FlushError, NoResultFound from sqlalchemy.exc import IntegrityError @@ -287,3 +301,32 @@ def test_create_service_and_history_is_transactional(sample_user): assert 'column "name" violates not-null constraint' in str(excinfo.value) assert Service.query.count() == 0 assert Service.get_history_model().query.count() == 0 + + +def test_delete_service_and_associated_objects(notify_db, + notify_db_session, + sample_user, + sample_service, + sample_email_code, + sample_sms_code, + sample_template, + sample_email_template, + sample_api_key, + sample_job, + sample_notification, + sample_invited_user, + sample_permission, + sample_provider_statistics): + delete_service_and_all_associated_db_objects(sample_service) + assert NotificationStatistics.query.count() == 0 + assert TemplateStatistics.query.count() == 0 + assert ProviderStatistics.query.count() == 0 + assert VerifyCode.query.count() == 0 + assert ApiKey.query.count() == 0 + assert Template.query.count() == 0 + assert Job.query.count() == 0 + assert Notification.query.count() == 0 + assert Permission.query.count() == 0 + assert User.query.count() == 0 + assert InvitedUser.query.count() == 0 + assert Service.query.count() == 0 diff --git a/tests/app/permissions/test_rest.py b/tests/app/permissions/test_rest.py index bcc1d389f..941fea99f 100644 --- a/tests/app/permissions/test_rest.py +++ b/tests/app/permissions/test_rest.py @@ -17,12 +17,12 @@ def test_get_permission_list(notify_api, notify_db, notify_db_session, sample_pe headers=[header]) assert response.status_code == 200 json_resp = json.loads(response.get_data(as_text=True)) - assert len(json_resp['data']) == 1 + assert len(json_resp['data']) == 8 expected = { "permission": sample_permission.permission, "user": str(sample_permission.user.id), "id": str(sample_permission.id), - "service": None + "service": str(sample_permission.service.id) } assert expected in json_resp['data'] @@ -71,7 +71,7 @@ def test_get_permission(notify_api, notify_db, notify_db_session, sample_permiss "permission": sample_permission.permission, "user": str(sample_permission.user.id), "id": str(sample_permission.id), - "service": None + "service": str(sample_permission.service.id) } assert expected == json_resp['data']