diff --git a/Makefile b/Makefile index 9de9b6492..e0d15ba2e 100644 --- a/Makefile +++ b/Makefile @@ -80,6 +80,9 @@ generate-version-file: ## Generates the app version file .PHONY: test test: py-lint py-test js-test ## Run tests +.PHONY: test-fast +test-fast: py-lint py-test-fast js-test ## Run tests quickly in parallel (testing locally first) + .PHONY: py-lint py-lint: ## Run python linting scanners and black poetry self add poetry-dotenv-plugin @@ -111,6 +114,11 @@ py-test: ## Run python unit tests poetry run coverage report --fail-under=93 poetry run coverage html -d .coverage_cache +.PHONY: py-test-fast +py-test-fast: export NEW_RELIC_ENVIRONMENT=test +py-test-fast: ## Run python unit tests in parallel (testing locally first) + poetry run pytest --maxfail=10 --ignore=tests/end_to_end tests/ -n auto + .PHONY: dead-code dead-code: ## 60% is our aspirational goal, but currently breaks the build poetry run vulture ./app ./notifications_utils --min-confidence=100 diff --git a/app/main/views/activity.py b/app/main/views/activity.py index 028e053ab..5de8deffd 100644 --- a/app/main/views/activity.py +++ b/app/main/views/activity.py @@ -1,9 +1,13 @@ +from concurrent.futures import ThreadPoolExecutor + from flask import abort, render_template, request, url_for from app import current_service, job_api_client from app.enums import NotificationStatus, ServicePermission from app.formatters import get_time_left from app.main import main +from app.s3_client import check_s3_file_exists +from app.s3_client.s3_csv_client import get_csv_upload from app.utils.pagination import ( generate_next_dict, generate_pagination_pages, @@ -14,9 +18,6 @@ from app.utils.user import user_has_permissions def get_report_info(service_id, report_name): - from app.s3_client import check_s3_file_exists - from app.s3_client.s3_csv_client import get_csv_upload - try: obj = get_csv_upload(service_id, report_name) if check_s3_file_exists(obj): @@ -34,16 +35,19 @@ def get_report_info(service_id, report_name): def get_download_availability(service_id): - report_1_day = get_report_info(service_id, "1-day-report") - report_3_day = get_report_info(service_id, "3-day-report") - report_5_day = get_report_info(service_id, "5-day-report") - report_7_day = get_report_info(service_id, "7-day-report") + report_names = ["1-day-report", "3-day-report", "5-day-report", "7-day-report"] + + with ThreadPoolExecutor(max_workers=4) as executor: + futures = [ + executor.submit(get_report_info, service_id, name) for name in report_names + ] + results = [future.result() for future in futures] return { - "report_1_day": report_1_day, - "report_3_day": report_3_day, - "report_5_day": report_5_day, - "report_7_day": report_7_day, + "report_1_day": results[0], + "report_3_day": results[1], + "report_5_day": results[2], + "report_7_day": results[3], } diff --git a/tests/app/main/views/test_accept_invite.py b/tests/app/main/views/test_accept_invite.py index 76ebe658f..eab603143 100644 --- a/tests/app/main/views/test_accept_invite.py +++ b/tests/app/main/views/test_accept_invite.py @@ -892,7 +892,6 @@ def test_existing_email_auth_user_with_phone_can_set_sms_auth( api_user_active, service_one, sample_invite, - mock_get_existing_user_by_email, mock_check_invite_token, mock_accept_invite, mock_update_user_attribute, @@ -903,6 +902,11 @@ def test_existing_email_auth_user_with_phone_can_set_sms_auth( service_one["permissions"].append(ServicePermission.EMAIL_AUTH) sample_invite["auth_type"] = "sms_auth" + # Mock get_user_by_email explicitly to avoid hanging + mock_get_existing_user_by_email = mocker.patch( + "app.user_api_client.get_user_by_email", return_value=api_user_active + ) + client_request.get( "main.accept_invite", token="thisisnotarealtoken", diff --git a/tests/app/main/views/test_jobs_activity.py b/tests/app/main/views/test_jobs_activity.py index 2d5a55f2e..8c3eb6226 100644 --- a/tests/app/main/views/test_jobs_activity.py +++ b/tests/app/main/views/test_jobs_activity.py @@ -52,6 +52,8 @@ def test_all_activity( "app.job_api_client.get_page_of_jobs", return_value=MOCK_JOBS ) mocker.patch("app.job_api_client.get_immediate_jobs", return_value=[]) + mocker.patch("app.s3_client.check_s3_file_exists", return_value=False) + mocker.patch("app.s3_client.s3_csv_client.get_csv_upload", return_value=None) response = client_request.get_response( "main.all_jobs_activity", @@ -138,6 +140,8 @@ def test_all_activity_no_jobs(client_request, mocker): }, ) mocker.patch("app.job_api_client.get_immediate_jobs", return_value=[]) + mocker.patch("app.s3_client.check_s3_file_exists", return_value=False) + mocker.patch("app.s3_client.s3_csv_client.get_csv_upload", return_value=None) response = client_request.get_response( "main.all_jobs_activity", service_id=SERVICE_ONE_ID, @@ -191,6 +195,8 @@ def test_all_activity_pagination(client_request, mocker): }, ) mocker.patch("app.job_api_client.get_immediate_jobs", return_value=[]) + mocker.patch("app.s3_client.check_s3_file_exists", return_value=False) + mocker.patch("app.s3_client.s3_csv_client.get_csv_upload", return_value=None) response = client_request.get_response( "main.all_jobs_activity", @@ -228,6 +234,8 @@ def test_all_activity_filters(client_request, mocker, filter_type, expected_limi "app.job_api_client.get_page_of_jobs", return_value=MOCK_JOBS ) mocker.patch("app.job_api_client.get_immediate_jobs", return_value=[]) + mocker.patch("app.s3_client.check_s3_file_exists", return_value=False) + mocker.patch("app.s3_client.s3_csv_client.get_csv_upload", return_value=None) kwargs = {"filter": filter_type} if filter_type else {} response = client_request.get_response(