From 82690d6e3d3ccb70f5176271685bd4ad237dbd2c Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Thu, 6 Jun 2024 09:34:49 -0700 Subject: [PATCH 01/16] add debug for user issue --- app/main/views/send.py | 35 ++++++++++++++++++++++++++++++++-- app/utils/csv.py | 18 +++++++++++++++++ notifications_utils/logging.py | 13 +++++++++++++ poetry.lock | 1 + 4 files changed, 65 insertions(+), 2 deletions(-) diff --git a/app/main/views/send.py b/app/main/views/send.py index aab2db104..8bb6ce24c 100644 --- a/app/main/views/send.py +++ b/app/main/views/send.py @@ -3,7 +3,16 @@ import uuid from string import ascii_uppercase from zipfile import BadZipFile -from flask import abort, flash, redirect, render_template, request, session, url_for +from flask import ( + abort, + current_app, + flash, + redirect, + render_template, + request, + session, + url_for, +) from flask_login import current_user from markupsafe import Markup from notifications_python_client.errors import HTTPError @@ -31,12 +40,18 @@ from app.s3_client.s3_csv_client import ( s3upload, set_metadata_on_csv_upload, ) -from app.utils import PermanentRedirect, should_skip_template_page, unicode_truncate +from app.utils import ( + PermanentRedirect, + hilite, + should_skip_template_page, + unicode_truncate, +) from app.utils.csv import Spreadsheet, get_errors_for_csv from app.utils.templates import get_template from app.utils.user import user_has_permissions from notifications_utils import SMS_CHAR_COUNT_LIMIT from notifications_utils.insensitive_dict import InsensitiveDict +from notifications_utils.logging import scrub from notifications_utils.recipients import RecipientCSV, first_column_headings from notifications_utils.sanitise_text import SanitiseASCII @@ -938,6 +953,9 @@ def send_notification(service_id, template_id): ) ) + current_app.logger.info( + hilite(scrub(f"Recipient for the one-off will be {recipient}")) + ) keys = [] values = [] for k, v in session["placeholders"].items(): @@ -971,6 +989,19 @@ def send_notification(service_id, template_id): valid="True", ) + # Here we are attempting to cleverly link the job id to the one-off recipient + # If we know the partial phone number of the recipient, we can search + # on that initially and find this, which will give us the job_id + # And once we know the job_id, we can search on that and it might tell us something + # about report generation. + current_app.logger.info( + hilite( + scrub( + f"Created job to send one-off, recipient is {recipient}, job_id is {upload_id}" + ) + ) + ) + session.pop("recipient") session.pop("placeholders") diff --git a/app/utils/csv.py b/app/utils/csv.py index c3c27ec18..a8d743adc 100644 --- a/app/utils/csv.py +++ b/app/utils/csv.py @@ -1,10 +1,13 @@ import datetime import pytz +from flask import current_app, json from flask_login import current_user from app.models.spreadsheet import Spreadsheet +from app.utils import hilite from app.utils.templates import get_sample_template +from notifications_utils.logging import scrub from notifications_utils.recipients import RecipientCSV @@ -71,7 +74,22 @@ def generate_notifications_csv(**kwargs): # This generates the "batch" csv report if kwargs.get("job_id"): + # The kwargs contain the job id, which is linked to the recipient's partial phone number in other debug + try: + current_app.logger.info( + hilite(f"Setting up report with kwargs {scrub(json.dumps(kwargs))}") + ) + except TypeError: + pass + original_file_contents = s3download(kwargs["service_id"], kwargs["job_id"]) + # This will verify that the user actually did successfully upload a csv for a one-off. Limit the size + # we display to 999 characters, because we don't want to show the contents for reports with thousands of rows. + current_app.logger.info( + hilite( + f"Original csv for job_id {kwargs['job_id']}: {scrub(original_file_contents[0:999])}" + ) + ) original_upload = RecipientCSV( original_file_contents, template=get_sample_template(kwargs["template_type"]), diff --git a/notifications_utils/logging.py b/notifications_utils/logging.py index 6a209cdd3..7c56a00ad 100644 --- a/notifications_utils/logging.py +++ b/notifications_utils/logging.py @@ -1,5 +1,6 @@ import logging import logging.handlers +import re import sys from itertools import product @@ -131,3 +132,15 @@ class JSONFormatter(BaseJSONFormatter): except (KeyError, IndexError) as e: logger.exception("failed to format log message: {} not found".format(e)) return log_record + + +def scrub(msg): + # Eventually we want to scrub all messages in all logs for phone numbers + # and email addresses, masking them. Ultimately this will probably get + # refactored into a 'SafeLogger' subclass or something, but let's start here + # with phones. + phones = re.findall("(?:\\+ *)?\\d[\\d\\- ]{7,}\\d", msg) + phones = [phone.replace("-", "").replace(" ", "") for phone in phones] + for phone in phones: + msg = msg.replace(phone, f"1XXXXX{phone[-5:]}") + return msg diff --git a/poetry.lock b/poetry.lock index 8cb15a59d..331962043 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1641,6 +1641,7 @@ files = [ {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fbb160554e319f7b22ecf530a80a3ff496d38e8e07ae763b9e82fadfe96f273"}, {file = "msgpack-1.0.8-cp39-cp39-win32.whl", hash = "sha256:f9af38a89b6a5c04b7d18c492c8ccf2aee7048aff1ce8437c4683bb5a1df893d"}, {file = "msgpack-1.0.8-cp39-cp39-win_amd64.whl", hash = "sha256:ed59dd52075f8fc91da6053b12e8c89e37aa043f8986efd89e61fae69dc1b011"}, + {file = "msgpack-1.0.8-py3-none-any.whl", hash = "sha256:24f727df1e20b9876fa6e95f840a2a2651e34c0ad147676356f4bf5fbb0206ca"}, {file = "msgpack-1.0.8.tar.gz", hash = "sha256:95c02b0e27e706e48d0e5426d1710ca78e0f0628d6e89d5b5a5b91a5f12274f3"}, ] From 83f5b6f0ab4ac6dda7532a5d1a7bbb84497f593f Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Thu, 6 Jun 2024 10:43:41 -0700 Subject: [PATCH 02/16] add comment --- app/utils/csv.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/utils/csv.py b/app/utils/csv.py index a8d743adc..5c5b794de 100644 --- a/app/utils/csv.py +++ b/app/utils/csv.py @@ -75,6 +75,8 @@ def generate_notifications_csv(**kwargs): # This generates the "batch" csv report if kwargs.get("job_id"): # The kwargs contain the job id, which is linked to the recipient's partial phone number in other debug + # Some unit tests are mocking the kwargs and turning them into a function instead of dict, + # hence the try/except. try: current_app.logger.info( hilite(f"Setting up report with kwargs {scrub(json.dumps(kwargs))}") From 934f8927c65e927d69419a97d473219c57801253 Mon Sep 17 00:00:00 2001 From: Jonathan Bobel Date: Thu, 6 Jun 2024 13:50:08 -0400 Subject: [PATCH 03/16] Updated content, removed test no longer needed --- app/templates/views/message-status.html | 40 +++++++++++++++++++------ app/templates/views/trial-mode.html | 2 +- tests/app/main/views/test_index.py | 20 ------------- 3 files changed, 32 insertions(+), 30 deletions(-) diff --git a/app/templates/views/message-status.html b/app/templates/views/message-status.html index 089a29f49..d9dae439f 100644 --- a/app/templates/views/message-status.html +++ b/app/templates/views/message-status.html @@ -9,9 +9,13 @@

Delivery status

-

Notify’s real-time dashboard lets you check the status of any message.

-

For security, this information is only available for seven days after a message has been sent. You can download a report, including a list of sent messages, for your own records.

-

This page describes the statuses you'll see when you're signed in to Notify.

+

Notify starts sending your text messages immediately. Each message is sent to its cell phone carrier, which attempts + delivery to the recipient. This process is often almost instantaneous, but can sometimes take a while if a phone is + unavailable. The carrier will continue to try to deliver the message for up to 72 hours.

+

The Notify dashboard provides a high-level view of the number of messages Sent, Pending, Delivered, and Failed. The + dashboard data starts to update about five minutes after a message is sent and will continue to update for four hours.

+

Delivery statuses reflect delivery to a specific cell phone number, not to a person. No status can confirm that a + specific person actually received or read the message.

Text messages

-
+
{% call mapping_table( caption='Message statuses – text messages', field_headings=['Status', 'Description'], @@ -53,12 +57,14 @@ caption_visible=False ) %} {% for message_status, description in [ - ('Total', 'The total number of messages that have been sent during the last seven days.'), - ('Pending', 'Notify has sent the message to the provider. The provider will try to deliver the message to the recipient for up to 72 hours. Notify is waiting for delivery information.'), + ('Scheduled', 'The total number of messages that have been scheduled to be sent at some future time.'), + ('Canceled', 'Messages that were created and scheduled, but canceled prior to Sending.'), + ('Total', 'The total number of messages that have been sent during the specified time.'), + ('Pending', 'Notify has sent the message to the provider. The provider will try to deliver the message to the recipient for up to 72 + hours. “Pending” indicates that Notify is waiting for delivery information.'), ('Delivered', 'The message was successfully delivered. Notify cannot tell you if a user has opened or read a message.'), - ('Failed', 'The provider could not deliver the message. This can happen if the phone number was wrong or if the network operator - rejects the message. If you’re sure that these phone numbers are correct, you should contact us. If not, you should remove them from your database. You’ll still be charged for text messages that - cannot be delivered.' | safe), + ('Failed', 'The message could not be delivered.'), + ('Process error / Delivery not attempted', 'If you receive a large number of process errors, please contact the Notify team.'), ] %} {% call row() %} {{ text_field(message_status) }} @@ -67,5 +73,21 @@ {% endfor %} {% endcall %}
+

About carrier statuses

+

Sometimes Notify receives more detailed information from the carriers on the status of messages, and these can be found + in the downloadable reports. Not all carriers provide the same level of detail regarding delivery and some delivery + statutes have a slight variation in word choice. Notify includes this information in the reports to provide you as much + detail as possible. Remember, for security purposes, detailed information is only available for seven days after a + message has been sent.

+ +

Opting out

+

A text recipient can opt out of receiving text messages from your phone number at any time by responding “STOP” or + “QUIT” or one of several other keywords. If they opt out,

+ +

Notify.gov does not yet have a way to pull opt-out status and make it available in the UI for agencies to download.

{% endblock %} diff --git a/app/templates/views/trial-mode.html b/app/templates/views/trial-mode.html index 7ca6d2b97..cb0dca0ec 100644 --- a/app/templates/views/trial-mode.html +++ b/app/templates/views/trial-mode.html @@ -21,7 +21,7 @@
  • A text message of 160-306 characters is two parts.
  • For more information on how message parts are calculated, see - Pricing.

    + Tracking usage.

    Before going Live

    Before you request to make your service live so you can send messages to clients:

    diff --git a/tests/app/main/views/test_index.py b/tests/app/main/views/test_index.py index 4ee5a817f..57bdfaa70 100644 --- a/tests/app/main/views/test_index.py +++ b/tests/app/main/views/test_index.py @@ -179,26 +179,6 @@ def test_old_static_pages_redirect(client_request, view, expected_view): ) -def test_message_status_page_contains_message_status_ids(client_request): - # The 'email-statuses' and 'sms-statuses' id are linked to when we display a message status, - # so this test ensures we don't accidentally remove them - page = client_request.get("main.message_status") - - # email-statuses is commented out in view - # assert page.find(id='email-statuses') - assert page.find(id="text-message-statuses") - - -def test_message_status_page_contains_link_to_support(client_request): - page = client_request.get("main.message_status") - sms_status_table = page.find(id="text-message-statuses").findNext("tbody") - - temp_fail_details_cell = sms_status_table.select_one( - "tr:nth-child(4) > td:nth-child(2)" - ) - assert temp_fail_details_cell.find("a").attrs["href"] == url_for("main.support") - - def test_old_using_notify_page(client_request): client_request.get("main.using_notify", _expected_status=410) From 63bd660403f60e81dad05fed8d41a68e17440fda Mon Sep 17 00:00:00 2001 From: Kenneth Kehl <@kkehl@flexion.us> Date: Fri, 7 Jun 2024 08:03:43 -0700 Subject: [PATCH 04/16] code review feedback --- tests/notifications_utils/test_logging.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/notifications_utils/test_logging.py b/tests/notifications_utils/test_logging.py index cc7f5ee34..858b9352b 100644 --- a/tests/notifications_utils/test_logging.py +++ b/tests/notifications_utils/test_logging.py @@ -49,3 +49,13 @@ def test_base_json_formatter_contains_service_id(): == "message to log" ) assert service_id_filter.filter(record).service_id == "no-service-id" + + +def test_scrub(): + result = logging.scrub( + "This is a message with 17775554324, and also 18884449323 and also 17775554324" + ) + assert ( + result + == "This is a message with 1XXXXX54324, and also 1XXXXX49323 and also 1XXXXX54324" + ) From ed0f5a7b9eb52863364e79e46f8ebacc3bd34825 Mon Sep 17 00:00:00 2001 From: Beverly Nguyen Date: Mon, 10 Jun 2024 16:05:59 -0700 Subject: [PATCH 05/16] removed old login announcement page and added new sign in button --- app/assets/images/logo-login.svg | 1 + .../uswds/_uswds-theme-custom-styles.scss | 13 +++++ app/main/views/index.py | 15 +++++- app/templates/views/signedout.html | 2 +- app/templates/views/signin.html | 53 ------------------- 5 files changed, 28 insertions(+), 56 deletions(-) create mode 100644 app/assets/images/logo-login.svg delete mode 100644 app/templates/views/signin.html diff --git a/app/assets/images/logo-login.svg b/app/assets/images/logo-login.svg new file mode 100644 index 000000000..019713377 --- /dev/null +++ b/app/assets/images/logo-login.svg @@ -0,0 +1 @@ + diff --git a/app/assets/sass/uswds/_uswds-theme-custom-styles.scss b/app/assets/sass/uswds/_uswds-theme-custom-styles.scss index d66e276bd..0451be565 100644 --- a/app/assets/sass/uswds/_uswds-theme-custom-styles.scss +++ b/app/assets/sass/uswds/_uswds-theme-custom-styles.scss @@ -157,6 +157,19 @@ td.table-empty-message { } } +.usa-button svg { + margin-left: .5rem; + height: 1rem; +} + +.login-button.login-button--primary, .login-button.login-button--primary:hover { + color: #112e51; + background-color: #fff; + border: 1px solid #767676; + display: inline-flex; + justify-content: center; +} + .user-list-edit-link:active:before, .user-list-edit-link:focus:before { box-shadow: none; diff --git a/app/main/views/index.py b/app/main/views/index.py index c68605b2e..7c6c6a68c 100644 --- a/app/main/views/index.py +++ b/app/main/views/index.py @@ -1,6 +1,6 @@ import os -from flask import abort, redirect, render_template, request, url_for +from flask import abort, current_app, redirect, render_template, request, url_for from flask_login import current_user from app import status_api_client @@ -9,6 +9,7 @@ from app.main import main from app.main.views.pricing import CURRENT_SMS_RATE from app.main.views.sub_navigation_dictionaries import features_nav, using_notify_nav from app.utils.user import user_is_logged_in +from notifications_utils.url_safe_token import generate_token login_dot_gov_url = os.getenv("LOGIN_DOT_GOV_INITIAL_SIGNIN_URL") @@ -17,12 +18,22 @@ login_dot_gov_url = os.getenv("LOGIN_DOT_GOV_INITIAL_SIGNIN_URL") def index(): if current_user and current_user.is_authenticated: return redirect(url_for("main.choose_account")) - + token = generate_token( + str(request.remote_addr), + current_app.config["SECRET_KEY"], + current_app.config["DANGEROUS_SALT"], + ) + url = os.getenv("LOGIN_DOT_GOV_INITIAL_SIGNIN_URL") + # handle unit tests + if url is not None: + url = url.replace("NONCE", token) + url = url.replace("STATE", token) return render_template( "views/signedout.html", sms_rate=CURRENT_SMS_RATE, counts=status_api_client.get_count_of_live_services_and_organizations(), login_dot_gov_url=login_dot_gov_url, + initial_signin_url=url, ) diff --git a/app/templates/views/signedout.html b/app/templates/views/signedout.html index a2aa554f0..9d19d8b9f 100644 --- a/app/templates/views/signedout.html +++ b/app/templates/views/signedout.html @@ -21,7 +21,7 @@ Notify.gov

    Reach people where they are with government-powered text messages

    Notify.gov is a text message service that helps federal, state, local, tribal and territorial governments more effectively communicate with the people they serve.

    - Sign in + if you are an existing pilot partner

    Currently we are only working with select pilot partners. If you are interested in using Notify.gov in the future, please contact
    tts-benefits-studio@gsa.gov to learn more.

    diff --git a/app/templates/views/signin.html b/app/templates/views/signin.html deleted file mode 100644 index f3b3006d7..000000000 --- a/app/templates/views/signin.html +++ /dev/null @@ -1,53 +0,0 @@ -{% extends "base.html" %} -{% from "components/page-footer.html" import page_footer %} -{% from "components/form.html" import form_wrapper %} - -{% block per_page_title %} - {% if again %} - You need to sign in again - {% else %} - Sign in - {% endif %} -{% endblock %} - -{% block maincolumn_content %} - -
    -
    - {% if again %} -

    You need to sign in again

    - {% if other_device %} -

    - We signed you out because you logged in to Notify on another device. -

    - {% else %} -

    - We signed you out because you have not used Notify for a while. -

    - {% endif %} - Sign in with Login.gov - {% else %} -

    Sign in

    -

    Access your Notify.gov account by signing in with Login.gov:

    - Sign in with Login.gov - {% endif %} -
    -
    -

    Effective April 16, 2024 Notify.gov requires you sign-in through Login.gov

    -

    Why are we doing this?

    -
      -
    • Enhanced security: Login.gov is really secure and trustworthy
    • -
    • One single source for signing in: You can use Login.gov for other services within the federal government
    • -
    • 2FA flexibility: Login.gov supports multiple methods for users to verify their identity.
    • -
    -

    What do I need to do?

    -
      -
    • If you have a Login.gov account, start using it to sign in to Notify today.
    • -
    • If you don’t have a Login.gov account, you must create one to continue to access Notify.
    • -
    -
    - Create Login.gov account -
    -
    - -{% endblock %} From 8407f5605b22830285f5ede41a7486ec46ffb1e2 Mon Sep 17 00:00:00 2001 From: Beverly Nguyen Date: Tue, 11 Jun 2024 11:06:13 -0700 Subject: [PATCH 06/16] add back signin.html and removed login banner --- app/templates/views/signin.html | 37 +++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 app/templates/views/signin.html diff --git a/app/templates/views/signin.html b/app/templates/views/signin.html new file mode 100644 index 000000000..c2c6ebe1d --- /dev/null +++ b/app/templates/views/signin.html @@ -0,0 +1,37 @@ +{% extends "base.html" %} +{% from "components/page-footer.html" import page_footer %} +{% from "components/form.html" import form_wrapper %} + +{% block per_page_title %} + {% if again %} + You need to sign in again + {% else %} + Sign in + {% endif %} +{% endblock %} + +{% block maincolumn_content %} + +
    +
    + {% if again %} +

    You need to sign in again

    + {% if other_device %} +

    + We signed you out because you logged in to Notify on another device. +

    + {% else %} +

    + We signed you out because you have not used Notify for a while. +

    + {% endif %} + Sign in with Login.gov + {% else %} +

    Sign in

    +

    Access your Notify.gov account by signing in with Login.gov:

    + Sign in with Login.gov + {% endif %} +
    +
    + +{% endblock %} From 89bd835cd8cb16a2d7765db55a4bdb64305ad871 Mon Sep 17 00:00:00 2001 From: Beverly Nguyen Date: Tue, 11 Jun 2024 12:35:59 -0700 Subject: [PATCH 07/16] update testing --- tests/app/main/views/test_dashboard.py | 28 +++++++++++--------------- tests/app/main/views/test_index.py | 6 +++--- 2 files changed, 15 insertions(+), 19 deletions(-) diff --git a/tests/app/main/views/test_dashboard.py b/tests/app/main/views/test_dashboard.py index 285444b1b..e83dacbae 100644 --- a/tests/app/main/views/test_dashboard.py +++ b/tests/app/main/views/test_dashboard.py @@ -1893,26 +1893,22 @@ def app_with_socketio(): ( SERVICE_ONE_ID, {"start_date": "2024-01-01", "days": 7}, - {"service_id": SERVICE_ONE_ID, "start_date": "2024-01-01", "days": 7} + {"service_id": SERVICE_ONE_ID, "start_date": "2024-01-01", "days": 7}, ), ( SERVICE_TWO_ID, {"start_date": "2023-06-01", "days": 7}, - {"service_id": SERVICE_TWO_ID, "start_date": "2023-06-01", "days": 7} + {"service_id": SERVICE_TWO_ID, "start_date": "2023-06-01", "days": 7}, ), - ] + ], ) def test_fetch_daily_stats( - app_with_socketio, mocker, - service_id, - date_range, - expected_call_args + app_with_socketio, mocker, service_id, date_range, expected_call_args ): app, socketio = app_with_socketio mocker.patch( - "app.main.views.dashboard.get_stats_date_range", - return_value=date_range + "app.main.views.dashboard.get_stats_date_range", return_value=date_range ) mock_service_api = mocker.patch( @@ -1920,9 +1916,9 @@ def test_fetch_daily_stats( return_value={ date_range["start_date"]: { "email": {"delivered": 0, "failure": 0, "requested": 0}, - "sms": {"delivered": 0, "failure": 1, "requested": 1} + "sms": {"delivered": 0, "failure": 1, "requested": 1}, }, - } + }, ) client = SocketIOTestClient(app, socketio) @@ -1930,22 +1926,22 @@ def test_fetch_daily_stats( connected = client.is_connected() assert connected, "Client should be connected" - client.emit('fetch_daily_stats', service_id) + client.emit("fetch_daily_stats", service_id) received = client.get_received() assert received, "Should receive a response message" - assert received[0]['name'] == 'daily_stats_update' - assert received[0]['args'][0] == { + assert received[0]["name"] == "daily_stats_update" + assert received[0]["args"][0] == { date_range["start_date"]: { "email": {"delivered": 0, "failure": 0, "requested": 0}, - "sms": {"delivered": 0, "failure": 1, "requested": 1} + "sms": {"delivered": 0, "failure": 1, "requested": 1}, }, } mock_service_api.assert_called_once_with( service_id, start_date=expected_call_args["start_date"], - days=expected_call_args["days"] + days=expected_call_args["days"], ) finally: client.disconnect() diff --git a/tests/app/main/views/test_index.py b/tests/app/main/views/test_index.py index 57bdfaa70..cc29c963c 100644 --- a/tests/app/main/views/test_index.py +++ b/tests/app/main/views/test_index.py @@ -19,9 +19,9 @@ def test_non_logged_in_user_can_see_homepage( "Reach people where they are with government-powered text messages" ) - assert page.select_one("a.usa-button.usa-button--big")["href"] == url_for( - "main.sign_in", - ) + assert page.select_one( + "a.usa-button.login-button.login-button--primary.margin-right-2" + )["href"] assert page.select_one("meta[name=description]") is not None # This area is hidden for the pilot From cfa31af6524a6c080e4482bbe3b0849291a621f4 Mon Sep 17 00:00:00 2001 From: Beverly Nguyen Date: Tue, 11 Jun 2024 13:38:26 -0700 Subject: [PATCH 08/16] fixed testing --- .ds.baseline | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.ds.baseline b/.ds.baseline index ec87d9c30..10d8f3ba6 100644 --- a/.ds.baseline +++ b/.ds.baseline @@ -591,7 +591,7 @@ "filename": "tests/app/main/views/test_sign_in.py", "hashed_secret": "1348b145fa1a555461c1b790a2f66614781091e9", "is_verified": false, - "line_number": 31, + "line_number": 30, "is_secret": false }, { @@ -599,7 +599,7 @@ "filename": "tests/app/main/views/test_sign_in.py", "hashed_secret": "8b8b69116ee882b5e987e330f55db81aba0636f9", "is_verified": false, - "line_number": 104, + "line_number": 103, "is_secret": false } ], @@ -710,5 +710,5 @@ } ] }, - "generated_at": "2024-06-05T22:01:56Z" + "generated_at": "2024-06-11T20:36:19Z" } From 7b8db705523a16b2d68595211747085863f184ab Mon Sep 17 00:00:00 2001 From: Beverly Nguyen Date: Tue, 11 Jun 2024 14:01:40 -0700 Subject: [PATCH 09/16] fixed testing --- .ds.baseline | 12 ++---------- tests/app/main/views/test_sign_in.py | 13 ------------- 2 files changed, 2 insertions(+), 23 deletions(-) diff --git a/.ds.baseline b/.ds.baseline index 10d8f3ba6..29ab6333e 100644 --- a/.ds.baseline +++ b/.ds.baseline @@ -586,20 +586,12 @@ } ], "tests/app/main/views/test_sign_in.py": [ - { - "type": "Private Key", - "filename": "tests/app/main/views/test_sign_in.py", - "hashed_secret": "1348b145fa1a555461c1b790a2f66614781091e9", - "is_verified": false, - "line_number": 30, - "is_secret": false - }, { "type": "Secret Keyword", "filename": "tests/app/main/views/test_sign_in.py", "hashed_secret": "8b8b69116ee882b5e987e330f55db81aba0636f9", "is_verified": false, - "line_number": 103, + "line_number": 91, "is_secret": false } ], @@ -710,5 +702,5 @@ } ] }, - "generated_at": "2024-06-11T20:36:19Z" + "generated_at": "2024-06-11T21:01:12Z" } diff --git a/tests/app/main/views/test_sign_in.py b/tests/app/main/views/test_sign_in.py index 135f4a5ba..81d32253a 100644 --- a/tests/app/main/views/test_sign_in.py +++ b/tests/app/main/views/test_sign_in.py @@ -20,25 +20,12 @@ def test_render_sign_in_template_for_new_user(client_request): # then these indices need to be 1 instead of 0. # Currently it's not enabled for the test or production environments. assert page.select("main a")[0].text == "Sign in with Login.gov" - assert page.select("main a")[1].text == "Create Login.gov account" # TODO: We'll have to adjust this depending on whether Login.gov is # enabled or not; fix this in the future. assert "Sign in again" not in normalize_spaces(page.text) -def test_reformat_keystring(): - orig = "-----BEGIN PRIVATE KEY----- blah blah blah -----END PRIVATE KEY-----" - expected = """-----BEGIN PRIVATE KEY----- -blah -blah -blah ------END PRIVATE KEY----- -""" - reformatted = _reformat_keystring(orig) - assert reformatted == expected - - def test_sign_in_explains_session_timeout(client_request): client_request.logout() page = client_request.get("main.sign_in", next="/foo") From 53c938ebc61d0b25d2a82db9bdea18d9f8dfd6f9 Mon Sep 17 00:00:00 2001 From: Beverly Nguyen Date: Tue, 11 Jun 2024 15:12:05 -0700 Subject: [PATCH 10/16] fixed css --- app/assets/sass/uswds/_uswds-theme-custom-styles.scss | 9 ++++----- app/templates/views/signedout.html | 3 ++- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/app/assets/sass/uswds/_uswds-theme-custom-styles.scss b/app/assets/sass/uswds/_uswds-theme-custom-styles.scss index 0451be565..ae9542349 100644 --- a/app/assets/sass/uswds/_uswds-theme-custom-styles.scss +++ b/app/assets/sass/uswds/_uswds-theme-custom-styles.scss @@ -157,15 +157,14 @@ td.table-empty-message { } } -.usa-button svg { +.usa-button img { margin-left: .5rem; height: 1rem; } -.login-button.login-button--primary, .login-button.login-button--primary:hover { - color: #112e51; - background-color: #fff; - border: 1px solid #767676; +.login-button.login-button--primary,.login-button.login-button--primary:hover{ + color:#112e51;background-color:#fff; + border:1px solid #767676; display: inline-flex; justify-content: center; } diff --git a/app/templates/views/signedout.html b/app/templates/views/signedout.html index 9d19d8b9f..fab9fbb30 100644 --- a/app/templates/views/signedout.html +++ b/app/templates/views/signedout.html @@ -21,7 +21,8 @@ Notify.gov

    Reach people where they are with government-powered text messages

    Notify.gov is a text message service that helps federal, state, local, tribal and territorial governments more effectively communicate with the people they serve.

    - + if you are an existing pilot partner

    Currently we are only working with select pilot partners. If you are interested in using Notify.gov in the future, please contact
    tts-benefits-studio@gsa.gov to learn more.

    From c8fb1b2f91952189da8ffceafa8d1437f3e04b58 Mon Sep 17 00:00:00 2001 From: Beverly Nguyen Date: Tue, 11 Jun 2024 15:28:46 -0700 Subject: [PATCH 11/16] fixed import errors --- .ds.baseline | 4 ++-- tests/app/main/views/test_sign_in.py | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.ds.baseline b/.ds.baseline index 29ab6333e..82ab59b8d 100644 --- a/.ds.baseline +++ b/.ds.baseline @@ -591,7 +591,7 @@ "filename": "tests/app/main/views/test_sign_in.py", "hashed_secret": "8b8b69116ee882b5e987e330f55db81aba0636f9", "is_verified": false, - "line_number": 91, + "line_number": 90, "is_secret": false } ], @@ -702,5 +702,5 @@ } ] }, - "generated_at": "2024-06-11T21:01:12Z" + "generated_at": "2024-06-11T22:26:18Z" } diff --git a/tests/app/main/views/test_sign_in.py b/tests/app/main/views/test_sign_in.py index 81d32253a..efa01deb7 100644 --- a/tests/app/main/views/test_sign_in.py +++ b/tests/app/main/views/test_sign_in.py @@ -3,7 +3,6 @@ import uuid import pytest from flask import url_for -from app.main.views.sign_in import _reformat_keystring from app.models.user import User from tests.conftest import SERVICE_ONE_ID, normalize_spaces From 5147072621a329056f008bf0ee25fa21811985e5 Mon Sep 17 00:00:00 2001 From: Beverly Nguyen Date: Tue, 11 Jun 2024 15:46:36 -0700 Subject: [PATCH 12/16] fixed e2e test --- tests/end_to_end/test_landing_and_sign_in_pages.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/end_to_end/test_landing_and_sign_in_pages.py b/tests/end_to_end/test_landing_and_sign_in_pages.py index a9148cb31..df4ec486c 100644 --- a/tests/end_to_end/test_landing_and_sign_in_pages.py +++ b/tests/end_to_end/test_landing_and_sign_in_pages.py @@ -22,7 +22,7 @@ def test_landing_page(end_to_end_context): "heading", name="Reach people where they are with government-powered text messages", ) - sign_in_button = page.get_by_role("link", name="Sign in") + sign_in_button = page.get_by_role("link", name="Sign in with") benefits_studio_email = page.get_by_role("link", name="tts-benefits-studio@gsa.gov") # Check to make sure the elements are visible. @@ -31,7 +31,7 @@ def test_landing_page(end_to_end_context): expect(benefits_studio_email).to_be_visible() # Check to make sure the sign-in button and email links are correct. - expect(sign_in_button).to_have_attribute("href", "/sign-in") + expect(sign_in_button).to_have_attribute("href") expect(benefits_studio_email).to_have_attribute( "href", "mailto:tts-benefits-studio@gsa.gov" ) From 6be4f878e484fc783a35561a32a30544ce10455f Mon Sep 17 00:00:00 2001 From: Beverly Nguyen Date: Tue, 11 Jun 2024 16:22:38 -0700 Subject: [PATCH 13/16] fixed end2end --- app/main/views/index.py | 3 --- tests/app/main/views/test_index.py | 3 +-- tests/end_to_end/test_landing_and_sign_in_pages.py | 4 +++- 3 files changed, 4 insertions(+), 6 deletions(-) diff --git a/app/main/views/index.py b/app/main/views/index.py index 7c6c6a68c..ec489d5ac 100644 --- a/app/main/views/index.py +++ b/app/main/views/index.py @@ -11,8 +11,6 @@ from app.main.views.sub_navigation_dictionaries import features_nav, using_notif from app.utils.user import user_is_logged_in from notifications_utils.url_safe_token import generate_token -login_dot_gov_url = os.getenv("LOGIN_DOT_GOV_INITIAL_SIGNIN_URL") - @main.route("/") def index(): @@ -32,7 +30,6 @@ def index(): "views/signedout.html", sms_rate=CURRENT_SMS_RATE, counts=status_api_client.get_count_of_live_services_and_organizations(), - login_dot_gov_url=login_dot_gov_url, initial_signin_url=url, ) diff --git a/tests/app/main/views/test_index.py b/tests/app/main/views/test_index.py index cc29c963c..1e08cd606 100644 --- a/tests/app/main/views/test_index.py +++ b/tests/app/main/views/test_index.py @@ -21,8 +21,7 @@ def test_non_logged_in_user_can_see_homepage( assert page.select_one( "a.usa-button.login-button.login-button--primary.margin-right-2" - )["href"] - + ).text == "Sign in with \n" assert page.select_one("meta[name=description]") is not None # This area is hidden for the pilot # assert normalize_spaces(page.select_one('#whos-using-notify').text) == ( diff --git a/tests/end_to_end/test_landing_and_sign_in_pages.py b/tests/end_to_end/test_landing_and_sign_in_pages.py index df4ec486c..4cdabe61c 100644 --- a/tests/end_to_end/test_landing_and_sign_in_pages.py +++ b/tests/end_to_end/test_landing_and_sign_in_pages.py @@ -31,7 +31,9 @@ def test_landing_page(end_to_end_context): expect(benefits_studio_email).to_be_visible() # Check to make sure the sign-in button and email links are correct. - expect(sign_in_button).to_have_attribute("href") + href_value = sign_in_button.get_attribute('href') + assert href_value is not None, "The sign-in button does not have an href attribute" + # expect(sign_in_button).to_have_attribute("href") expect(benefits_studio_email).to_have_attribute( "href", "mailto:tts-benefits-studio@gsa.gov" ) From d028beda4f5acd7fc37c96916e60c07d46a33b89 Mon Sep 17 00:00:00 2001 From: Beverly Nguyen Date: Tue, 11 Jun 2024 16:29:31 -0700 Subject: [PATCH 14/16] Removed comments --- tests/end_to_end/test_landing_and_sign_in_pages.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/end_to_end/test_landing_and_sign_in_pages.py b/tests/end_to_end/test_landing_and_sign_in_pages.py index 4cdabe61c..513269f51 100644 --- a/tests/end_to_end/test_landing_and_sign_in_pages.py +++ b/tests/end_to_end/test_landing_and_sign_in_pages.py @@ -33,7 +33,6 @@ def test_landing_page(end_to_end_context): # Check to make sure the sign-in button and email links are correct. href_value = sign_in_button.get_attribute('href') assert href_value is not None, "The sign-in button does not have an href attribute" - # expect(sign_in_button).to_have_attribute("href") expect(benefits_studio_email).to_have_attribute( "href", "mailto:tts-benefits-studio@gsa.gov" ) From b5623ca96bd2b7b66ba0e719cd72378763fa5615 Mon Sep 17 00:00:00 2001 From: Beverly Nguyen Date: Thu, 13 Jun 2024 12:10:22 -0700 Subject: [PATCH 15/16] updated css --- app/assets/sass/uswds/_uswds-theme-custom-styles.scss | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/assets/sass/uswds/_uswds-theme-custom-styles.scss b/app/assets/sass/uswds/_uswds-theme-custom-styles.scss index ae9542349..efe86c763 100644 --- a/app/assets/sass/uswds/_uswds-theme-custom-styles.scss +++ b/app/assets/sass/uswds/_uswds-theme-custom-styles.scss @@ -162,7 +162,7 @@ td.table-empty-message { height: 1rem; } -.login-button.login-button--primary,.login-button.login-button--primary:hover{ +.usa-button.login-button.login-button--primary,.login-button.login-button--primary:hover{ color:#112e51;background-color:#fff; border:1px solid #767676; display: inline-flex; From 79df8288df35893394318e351b773e1f8bbeb7e6 Mon Sep 17 00:00:00 2001 From: Carlo Costino Date: Fri, 14 Jun 2024 10:22:01 -0400 Subject: [PATCH 16/16] Updated dependencies - 6/14/2024 This changeset updates Python dependencies that Dependabot has flagged in addition to several others that were due for updates. It also reformats a test file via black. Signed-off-by: Carlo Costino --- poetry.lock | 241 ++++++++++++++++++------- pyproject.toml | 6 +- tests/app/main/views/test_dashboard.py | 28 ++- 3 files changed, 190 insertions(+), 85 deletions(-) diff --git a/poetry.lock b/poetry.lock index 0289c6df4..68e1e9836 100644 --- a/poetry.lock +++ b/poetry.lock @@ -42,13 +42,13 @@ tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "p [[package]] name = "bandit" -version = "1.7.8" +version = "1.7.9" description = "Security oriented static analyser for python code." optional = false python-versions = ">=3.8" files = [ - {file = "bandit-1.7.8-py3-none-any.whl", hash = "sha256:509f7af645bc0cd8fd4587abc1a038fc795636671ee8204d502b933aee44f381"}, - {file = "bandit-1.7.8.tar.gz", hash = "sha256:36de50f720856ab24a24dbaa5fee2c66050ed97c1477e0a1159deab1775eab6b"}, + {file = "bandit-1.7.9-py3-none-any.whl", hash = "sha256:52077cb339000f337fb25f7e045995c4ad01511e716e5daac37014b9752de8ec"}, + {file = "bandit-1.7.9.tar.gz", hash = "sha256:7c395a436743018f7be0a4cbb0a4ea9b902b6d87264ddecf8cfdc73b4f78ff61"}, ] [package.dependencies] @@ -85,6 +85,17 @@ charset-normalizer = ["charset-normalizer"] html5lib = ["html5lib"] lxml = ["lxml"] +[[package]] +name = "bidict" +version = "0.23.1" +description = "The bidirectional mapping library for Python." +optional = false +python-versions = ">=3.8" +files = [ + {file = "bidict-0.23.1-py3-none-any.whl", hash = "sha256:5dae8d4d79b552a71cbabc7deb25dfe8ce710b17ff41711e13010ead2abfc3e5"}, + {file = "bidict-0.23.1.tar.gz", hash = "sha256:03069d763bc387bbd20e7d49914e75fc4132a41937fa3405417e1a5a2d006d71"}, +] + [[package]] name = "black" version = "24.4.2" @@ -171,17 +182,17 @@ files = [ [[package]] name = "boto3" -version = "1.34.119" +version = "1.34.126" description = "The AWS SDK for Python" optional = false python-versions = ">=3.8" files = [ - {file = "boto3-1.34.119-py3-none-any.whl", hash = "sha256:8f9c43c54b3dfaa36c4a0d7b42c417227a515bc7a2e163e62802780000a5a3e2"}, - {file = "boto3-1.34.119.tar.gz", hash = "sha256:cea2365a25b2b83a97e77f24ac6f922ef62e20636b42f9f6ee9f97188f9c1c03"}, + {file = "boto3-1.34.126-py3-none-any.whl", hash = "sha256:7f676daef674fe74f34ce4063228eccc6e60c811f574720e31f230296c4bf29a"}, + {file = "boto3-1.34.126.tar.gz", hash = "sha256:7e8418b47dd43954a9088d504541bed8a42b6d06e712d02befba134c1c4d7c6d"}, ] [package.dependencies] -botocore = ">=1.34.119,<1.35.0" +botocore = ">=1.34.126,<1.35.0" jmespath = ">=0.7.1,<2.0.0" s3transfer = ">=0.10.0,<0.11.0" @@ -190,13 +201,13 @@ crt = ["botocore[crt] (>=1.21.0,<2.0a0)"] [[package]] name = "botocore" -version = "1.34.119" +version = "1.34.126" description = "Low-level, data-driven core of boto 3." optional = false python-versions = ">=3.8" files = [ - {file = "botocore-1.34.119-py3-none-any.whl", hash = "sha256:4bdf7926a1290b2650d62899ceba65073dd2693e61c35f5cdeb3a286a0aaa27b"}, - {file = "botocore-1.34.119.tar.gz", hash = "sha256:b253f15b24b87b070e176af48e8ef146516090429d30a7d8b136a4c079b28008"}, + {file = "botocore-1.34.126-py3-none-any.whl", hash = "sha256:7eff883c638fe30e0b036789df32d851e093d12544615a3b90062b42ac85bdbc"}, + {file = "botocore-1.34.126.tar.gz", hash = "sha256:7a8ccb6a7c02456757a984a3a44331b6f51c94cb8b9b287cd045122fd177a4b0"}, ] [package.dependencies] @@ -205,7 +216,7 @@ python-dateutil = ">=2.1,<3.0.0" urllib3 = {version = ">=1.25.4,<2.2.0 || >2.2.0,<3", markers = "python_version >= \"3.10\""} [package.extras] -crt = ["awscrt (==0.20.9)"] +crt = ["awscrt (==0.20.11)"] [[package]] name = "cachecontrol" @@ -580,13 +591,13 @@ test-randomorder = ["pytest-randomly"] [[package]] name = "cyclonedx-python-lib" -version = "7.4.0" +version = "7.4.1" description = "Python library for CycloneDX" optional = false python-versions = "<4.0,>=3.8" files = [ - {file = "cyclonedx_python_lib-7.4.0-py3-none-any.whl", hash = "sha256:fc423e7f46d772e5ded29a48cb0743233e692e5853c49b829efc0f59014efde1"}, - {file = "cyclonedx_python_lib-7.4.0.tar.gz", hash = "sha256:09b10736a7f440262578fa40f470b448de1ebf3c7a71e2ff0a4af0781d3a3b42"}, + {file = "cyclonedx_python_lib-7.4.1-py3-none-any.whl", hash = "sha256:73bf8d5c09ad10698c75d3ce3f123c84c9aff3959d67b8b5ca9e5a7c5da43abe"}, + {file = "cyclonedx_python_lib-7.4.1.tar.gz", hash = "sha256:23bf8196e008bb8e06c1040ad2ab69492891d8a581cb2aefa36a77f199790a37"}, ] [package.dependencies] @@ -730,18 +741,18 @@ testing = ["hatch", "pre-commit", "pytest", "tox"] [[package]] name = "filelock" -version = "3.14.0" +version = "3.15.1" description = "A platform independent file lock." optional = false python-versions = ">=3.8" files = [ - {file = "filelock-3.14.0-py3-none-any.whl", hash = "sha256:43339835842f110ca7ae60f1e1c160714c5a6afd15a2873419ab185334975c0f"}, - {file = "filelock-3.14.0.tar.gz", hash = "sha256:6ea72da3be9b8c82afd3edcf99f2fffbb5076335a5ae4d03248bb5b6c3eae78a"}, + {file = "filelock-3.15.1-py3-none-any.whl", hash = "sha256:71b3102950e91dfc1bb4209b64be4dc8854f40e5f534428d8684f953ac847fac"}, + {file = "filelock-3.15.1.tar.gz", hash = "sha256:58a2549afdf9e02e10720eaa4d4470f56386d7a6f72edd7d0596337af8ed7ad8"}, ] [package.extras] docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] -testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-asyncio (>=0.21)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"] typing = ["typing-extensions (>=4.8)"] [[package]] @@ -887,6 +898,24 @@ redis = ">=2.7.6" dev = ["coverage", "pre-commit", "pytest", "pytest-mock"] tests = ["coverage", "pytest", "pytest-mock"] +[[package]] +name = "flask-socketio" +version = "5.3.6" +description = "Socket.IO integration for Flask applications" +optional = false +python-versions = ">=3.6" +files = [ + {file = "Flask-SocketIO-5.3.6.tar.gz", hash = "sha256:bb8f9f9123ef47632f5ce57a33514b0c0023ec3696b2384457f0fcaa5b70501c"}, + {file = "Flask_SocketIO-5.3.6-py3-none-any.whl", hash = "sha256:9e62d2131842878ae6bfdd7067dfc3be397c1f2b117ab1dc74e6fe74aad7a579"}, +] + +[package.dependencies] +Flask = ">=0.9" +python-socketio = ">=5.0.2" + +[package.extras] +docs = ["sphinx"] + [[package]] name = "flask-talisman" version = "1.1.0" @@ -1049,6 +1078,17 @@ setproctitle = ["setproctitle"] testing = ["coverage", "eventlet", "gevent", "pytest", "pytest-cov"] tornado = ["tornado (>=0.2)"] +[[package]] +name = "h11" +version = "0.14.0" +description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" +optional = false +python-versions = ">=3.7" +files = [ + {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, + {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, +] + [[package]] name = "html5lib" version = "1.1" @@ -1641,7 +1681,6 @@ files = [ {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fbb160554e319f7b22ecf530a80a3ff496d38e8e07ae763b9e82fadfe96f273"}, {file = "msgpack-1.0.8-cp39-cp39-win32.whl", hash = "sha256:f9af38a89b6a5c04b7d18c492c8ccf2aee7048aff1ce8437c4683bb5a1df893d"}, {file = "msgpack-1.0.8-cp39-cp39-win_amd64.whl", hash = "sha256:ed59dd52075f8fc91da6053b12e8c89e37aa043f8986efd89e61fae69dc1b011"}, - {file = "msgpack-1.0.8-py3-none-any.whl", hash = "sha256:24f727df1e20b9876fa6e95f840a2a2651e34c0ad147676356f4bf5fbb0206ca"}, {file = "msgpack-1.0.8.tar.gz", hash = "sha256:95c02b0e27e706e48d0e5426d1710ca78e0f0628d6e89d5b5a5b91a5f12274f3"}, ] @@ -1658,40 +1697,40 @@ files = [ [[package]] name = "newrelic" -version = "9.10.0" +version = "9.11.0" description = "New Relic Python Agent" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ - {file = "newrelic-9.10.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:a4d4e5670082225ca7ef0ee986ef8e6588f4e530a05d43d66f9368459c0b1f18"}, - {file = "newrelic-9.10.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:f4605bc4feb114235e242dfe260b75ec85d0894f5400aa7f30e75fbbc0423b3f"}, - {file = "newrelic-9.10.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d3be6c97d007ceb142f908f5ab2444807b44dc600a0b7f3254dc685b5b03fd10"}, - {file = "newrelic-9.10.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:4e573d49c1543a488d6567906a9b2cb0c748cdbf80724c322b06874f8e47c789"}, - {file = "newrelic-9.10.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae0515f7ab19f1a5dd14e31506420d1b86014c5e1340c2a210833248bc765dae"}, - {file = "newrelic-9.10.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:acf5cdcafd2971933ad2f9e836284957f4a3eababe88f063cf53b1b1f67f1a16"}, - {file = "newrelic-9.10.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:5d18236bf4a80fca4eb1db03448ed72bf8e16b84b3a4ed5fcc29bb91c2d05d54"}, - {file = "newrelic-9.10.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:744c815f15ec06e441c11a6c57042d2eca8c41401c11de6f47b3e105d952b9bd"}, - {file = "newrelic-9.10.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:524ed5bfa09d330746b45e0087765da994ca34802cce032063041e404e58414c"}, - {file = "newrelic-9.10.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8ad9cd5459b8c620ab7a876bd5d920c3ef2943948d1262a42289d4f8d16dadab"}, - {file = "newrelic-9.10.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:4404c649b5e6165dcdd59091092c19b292a43cc96520d5ffd718b628fb866096"}, - {file = "newrelic-9.10.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e2576bbec0b640d9b76454dcfd5b2f03078e0bb062a7ea3952a8db7b9972c352"}, - {file = "newrelic-9.10.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77537a020ce84033f39210e46cc43bb3927cec3fb4b34b5c4df802e96fddaedf"}, - {file = "newrelic-9.10.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2236f70b8c6aa79635f2175e7315d032f3a80dfd65ad9c9ed12a921f5df4c655"}, - {file = "newrelic-9.10.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b8201a33caf7632b2e55e3f9687584ad6956aaf5751485cdb2bad7c428a9b400"}, - {file = "newrelic-9.10.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:6ed4bc2c9a44dfe59958eeecf1f327f0a0fb6324b5e609515bc511944d12db74"}, - {file = "newrelic-9.10.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1cc3ddb26c0615ba4e18f87453bca57f0688a43d2fcdd50e2771a77515cfc3ba"}, - {file = "newrelic-9.10.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:09912303e04bee6aa1fe1c671e87b4e8e55461081a96210895828798f5ba8c3f"}, - {file = "newrelic-9.10.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:40368dca0d423efe40b210686d7018787d4365a24ee1deca136b3b7c9d850325"}, - {file = "newrelic-9.10.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56f4c309a07a2c66243b12d18056c32aa704735469741495642c31be4a1c77fa"}, - {file = "newrelic-9.10.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4d68fc707d896dc7da8d6939bcc1f995bf9e463c2b911fc63250a10e1502a234"}, - {file = "newrelic-9.10.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7cd462804a6ede617fb3b4b126e9083b3ee8b4ed1250f7cc12299ebacb785432"}, - {file = "newrelic-9.10.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ceef4fef2a5cffb69e9e1742bd18a35625ca62c3856c7016c22be68ec876753d"}, - {file = "newrelic-9.10.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:1f11d9c17b50982fcc39de71f6592a61920ec5e5c29b9105edc9f8fb7f2480b9"}, - {file = "newrelic-9.10.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bf6757d422954e61082715dbba4208cae17bf3720006bc337c3f87f19ede2876"}, - {file = "newrelic-9.10.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ae84bacfdc60792bd04e681027cc5c58e6737a04c652e9be2eda84abe21f57f5"}, - {file = "newrelic-9.10.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:667722cf1f4ed9f6cd99f4fbe247fc2bdb941935528e14a93659ba2c651dc889"}, - {file = "newrelic-9.10.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d0c18210648889416da3de61aa282248e012cb507ba9841511407f922fff9a52"}, - {file = "newrelic-9.10.0.tar.gz", hash = "sha256:02db25b0fd2fc835efe4a7f1c92dbc5bbb95125341aba07152041aa6a5666cda"}, + {file = "newrelic-9.11.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:87670d872c3abc36203e10f93d266c8f36ad2bd06fb54e790001a409f9e2f40f"}, + {file = "newrelic-9.11.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:11653fd14f55999c5058b4dde8c721833076c0bd3efe668296725a622e9e7de8"}, + {file = "newrelic-9.11.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:72dd3eb190c62bb54aa59029f0d6ac1420c2050b3aaf88d947fc7f62ec58d97f"}, + {file = "newrelic-9.11.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:02eab15af4a08b870bcfdbc56390ecbb9dcacd144fe77f39a26d1be207bd30f0"}, + {file = "newrelic-9.11.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f477cdda9b998205084b822089b3ee4a8a2d9cd66b6f12487c9f9002566c5cb"}, + {file = "newrelic-9.11.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dcec4173cd0f83420e6f61f92955065f1d460075af5e5bf88a5fea746e3cc180"}, + {file = "newrelic-9.11.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8664e3b9e6ee0f78806b0cf7c90656a1a86d13232c2e0be18a1b1eb452f3f5d1"}, + {file = "newrelic-9.11.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:7f1e473eb0505cb91ab9a4155321eabe13a2f6b93fb3c41d6f10e5486276be60"}, + {file = "newrelic-9.11.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f95eb366ff714bce32476d256551b853247a72398ec46a89148ef5108509aa8"}, + {file = "newrelic-9.11.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:553674a66ef2c2206852b415b74e3c2fb7ed2b92e9800b68394d577f6aa1133e"}, + {file = "newrelic-9.11.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:21e7b52d5b214bba3534ced166e6ec991117772815020bec38b0571fdcecbaf4"}, + {file = "newrelic-9.11.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:10cb7f7a78c49580602b90f367f3378264e495f2f3706734f88ced7e7ca9b033"}, + {file = "newrelic-9.11.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:34b25d1beaf19825409f3d915a5bafa87b7b9230415821422be1e78e988750b7"}, + {file = "newrelic-9.11.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2b02139458aefba86a4572cb8214f91a942103d24d5502395f64d6d7a4ad3f25"}, + {file = "newrelic-9.11.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:3283885bcf31d9cbf8facb0004508a4eaa652a62471e0b724d26f9738a291979"}, + {file = "newrelic-9.11.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:0d43a0891bf71333f6a6253cf87dea2c9009e22699a2acfd93608125a33b1936"}, + {file = "newrelic-9.11.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b7903ba71ce5a4b2840f6d3c63ecd0fb3a018d2aceb915b48133c13c4a60185f"}, + {file = "newrelic-9.11.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d88fa17a515fb002eb14570800e4bfa69ac87ac27e6e2a96bc2bc9b60c80057a"}, + {file = "newrelic-9.11.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:6ceac1d8f13da38fa1b41c8202a91d3b4345e06adb655deaae0df08911fda56f"}, + {file = "newrelic-9.11.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ffc0d8d490de0f12df70db637481aaadb8a43fb6d71ba8866dc14242aa5edad4"}, + {file = "newrelic-9.11.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f6e1bb0df8ff2b54195baac41fddc0e15ea1bdf1deb6af49153487696355181"}, + {file = "newrelic-9.11.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b5d2d0814e1aa9de5bd55797ff8c426d98200ba46ca14dbca15557d0f17cfb4e"}, + {file = "newrelic-9.11.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b33539345c7cf349b65a176a30ab38e2998b071512a7450f5c5b89ac6c097006"}, + {file = "newrelic-9.11.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7c073f4c26539d6d74fbf4bac7f5046cac578975fb2cf77b156f802f1b39835e"}, + {file = "newrelic-9.11.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:76eb4cc599645a38a459b0002696d9c84844fecb02cf07bc18a4a91f737e438e"}, + {file = "newrelic-9.11.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35d08587e694f5c517e55fb7119f924c64569d2e7ec4968ef761fc1f7bd1f40c"}, + {file = "newrelic-9.11.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bc5c1b8a51946f64c34fc5fa29ce0221c4927a65c7f4435b3b8adeb29b9812d2"}, + {file = "newrelic-9.11.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:2010ed2793294a7e3c1057ec301d48997ed05dcef114d4c25120ac771f66bac1"}, + {file = "newrelic-9.11.0.tar.gz", hash = "sha256:94369792d61ccf21469c35cf66886c32350a180d8e782c0d28ec66411db29474"}, ] [package.extras] @@ -1798,13 +1837,13 @@ dev = ["black", "mypy", "pytest"] [[package]] name = "packageurl-python" -version = "0.15.0" +version = "0.15.1" description = "A purl aka. Package URL parser and builder" optional = false python-versions = ">=3.7" files = [ - {file = "packageurl-python-0.15.0.tar.gz", hash = "sha256:f219b2ce6348185a27bd6a72e6fdc9f984e6c9fa157effa7cb93e341c49cdcc2"}, - {file = "packageurl_python-0.15.0-py3-none-any.whl", hash = "sha256:cdc6bd42dc30c4fc7f8f0ccb721fc31f8c33985dbffccb6e6be4c72874de48ca"}, + {file = "packageurl_python-0.15.1-py3-none-any.whl", hash = "sha256:f7a44ddb9caaf6197b3b62b890ed0be5cb15e962accab2a51db36846d5174562"}, + {file = "packageurl_python-0.15.1.tar.gz", hash = "sha256:9a37b9a7cad9a2872b4612151ba3749fd9dec90485577c14d374b6e66b7edf03"}, ] [package.extras] @@ -1815,13 +1854,13 @@ test = ["pytest"] [[package]] name = "packaging" -version = "24.0" +version = "24.1" description = "Core utilities for Python packages" optional = false -python-versions = ">=3.7" +python-versions = ">=3.8" files = [ - {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, - {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, + {file = "packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124"}, + {file = "packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002"}, ] [[package]] @@ -2385,6 +2424,25 @@ files = [ [package.extras] cli = ["click (>=5.0)"] +[[package]] +name = "python-engineio" +version = "4.9.1" +description = "Engine.IO server and client for Python" +optional = false +python-versions = ">=3.6" +files = [ + {file = "python_engineio-4.9.1-py3-none-any.whl", hash = "sha256:f995e702b21f6b9ebde4e2000cd2ad0112ba0e5116ec8d22fe3515e76ba9dddd"}, + {file = "python_engineio-4.9.1.tar.gz", hash = "sha256:7631cf5563086076611e494c643b3fa93dd3a854634b5488be0bba0ef9b99709"}, +] + +[package.dependencies] +simple-websocket = ">=0.10.0" + +[package.extras] +asyncio-client = ["aiohttp (>=3.4)"] +client = ["requests (>=2.21.0)", "websocket-client (>=0.54.0)"] +docs = ["sphinx"] + [[package]] name = "python-json-logger" version = "2.0.7" @@ -2413,6 +2471,26 @@ text-unidecode = ">=1.3" [package.extras] unidecode = ["Unidecode (>=1.1.1)"] +[[package]] +name = "python-socketio" +version = "5.11.2" +description = "Socket.IO server and client for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "python-socketio-5.11.2.tar.gz", hash = "sha256:ae6a1de5c5209ca859dc574dccc8931c4be17ee003e74ce3b8d1306162bb4a37"}, + {file = "python_socketio-5.11.2-py3-none-any.whl", hash = "sha256:b9f22a8ff762d7a6e123d16a43ddb1a27d50f07c3c88ea999334f2f89b0ad52b"}, +] + +[package.dependencies] +bidict = ">=0.21.0" +python-engineio = ">=4.8.0" + +[package.extras] +asyncio-client = ["aiohttp (>=3.4)"] +client = ["requests (>=2.21.0)", "websocket-client (>=0.54.0)"] +docs = ["sphinx"] + [[package]] name = "pytz" version = "2024.1" @@ -2503,13 +2581,13 @@ toml = ["tomli (>=2.0.1)"] [[package]] name = "redis" -version = "5.0.4" +version = "5.0.6" description = "Python client for Redis database and key-value store" optional = false python-versions = ">=3.7" files = [ - {file = "redis-5.0.4-py3-none-any.whl", hash = "sha256:7adc2835c7a9b5033b7ad8f8918d09b7344188228809c98df07af226d39dec91"}, - {file = "redis-5.0.4.tar.gz", hash = "sha256:ec31f2ed9675cc54c21ba854cfe0462e6faf1d83c8ce5944709db8a4700b9c61"}, + {file = "redis-5.0.6-py3-none-any.whl", hash = "sha256:c0d6d990850c627bbf7be01c5c4cbaadf67b48593e913bb71c9819c30df37eee"}, + {file = "redis-5.0.6.tar.gz", hash = "sha256:38473cd7c6389ad3e44a91f4c3eaf6bcb8a9f746007f29bf4fb20824ff0b2197"}, ] [package.extras] @@ -2644,13 +2722,13 @@ fixture = ["fixtures"] [[package]] name = "responses" -version = "0.25.0" +version = "0.25.2" description = "A utility library for mocking out the `requests` Python library." optional = false python-versions = ">=3.8" files = [ - {file = "responses-0.25.0-py3-none-any.whl", hash = "sha256:2f0b9c2b6437db4b528619a77e5d565e4ec2a9532162ac1a131a83529db7be1a"}, - {file = "responses-0.25.0.tar.gz", hash = "sha256:01ae6a02b4f34e39bffceb0fc6786b67a25eae919c6368d05eabc8d9576c2a66"}, + {file = "responses-0.25.2-py3-none-any.whl", hash = "sha256:b59707ea25de536d324670791ab073fafd41f3a351cec9c51cb6147089a9a30a"}, + {file = "responses-0.25.2.tar.gz", hash = "sha256:77a61ad7e6016ed2ac00739b7efa5f35c42351d5b9b5d26bb1be87f197632487"}, ] [package.dependencies] @@ -2767,6 +2845,23 @@ numpy = ">=1.14,<3" docs = ["matplotlib", "numpydoc (==1.1.*)", "sphinx", "sphinx-book-theme", "sphinx-remove-toctrees"] test = ["pytest", "pytest-cov"] +[[package]] +name = "simple-websocket" +version = "1.0.0" +description = "Simple WebSocket server and client for Python" +optional = false +python-versions = ">=3.6" +files = [ + {file = "simple-websocket-1.0.0.tar.gz", hash = "sha256:17d2c72f4a2bd85174a97e3e4c88b01c40c3f81b7b648b0cc3ce1305968928c8"}, + {file = "simple_websocket-1.0.0-py3-none-any.whl", hash = "sha256:1d5bf585e415eaa2083e2bcf02a3ecf91f9712e7b3e6b9fa0b461ad04e0837bc"}, +] + +[package.dependencies] +wsproto = "*" + +[package.extras] +docs = ["sphinx"] + [[package]] name = "six" version = "1.16.0" @@ -2859,13 +2954,13 @@ files = [ [[package]] name = "typing-extensions" -version = "4.12.1" +version = "4.12.2" description = "Backported and Experimental Type Hints for Python 3.8+" optional = false python-versions = ">=3.8" files = [ - {file = "typing_extensions-4.12.1-py3-none-any.whl", hash = "sha256:6024b58b69089e5a89c347397254e35f1bf02a907728ec7fee9bf0fe837d203a"}, - {file = "typing_extensions-4.12.1.tar.gz", hash = "sha256:915f5e35ff76f56588223f15fdd5938f9a1cf9195c0de25130c627e4d597f6d1"}, + {file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"}, + {file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"}, ] [[package]] @@ -2944,6 +3039,20 @@ MarkupSafe = ">=2.1.1" [package.extras] watchdog = ["watchdog (>=2.3)"] +[[package]] +name = "wsproto" +version = "1.2.0" +description = "WebSockets state-machine based protocol implementation" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "wsproto-1.2.0-py3-none-any.whl", hash = "sha256:b9acddd652b585d75b20477888c56642fdade28bdfd3579aa24a4d2c037dd736"}, + {file = "wsproto-1.2.0.tar.gz", hash = "sha256:ad565f26ecb92588a3e43bc3d96164de84cd9902482b130d0ddbaa9664a85065"}, +] + +[package.dependencies] +h11 = ">=0.9.0,<1" + [[package]] name = "wtforms" version = "3.1.2" @@ -3002,4 +3111,4 @@ files = [ [metadata] lock-version = "2.0" python-versions = "^3.12.2" -content-hash = "e96b87c048826ecb23d826c45b516aee60532cd2677a22332dc86fef498a411d" +content-hash = "9636de2bab29446f6803efa5813b6ebd16ecff22ac5bc371196fa4b6a7d87a30" diff --git a/pyproject.toml b/pyproject.toml index 07c768f1c..42629374f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -39,8 +39,8 @@ wtforms = "~=3.1" markdown = "^3.5.2" async-timeout = "^4.0.3" bleach = "^6.1.0" -boto3 = "^1.34.119" -botocore = "^1.34.119" +boto3 = "^1.34.126" +botocore = "^1.34.126" cachetools = "^5.3.3" cffi = "^1.16.0" cryptography = "^42.0.8" @@ -53,7 +53,7 @@ ordered-set = "^4.1.0" phonenumbers = "^8.13.38" pycparser = "^2.22" python-json-logger = "^2.0.7" -redis = "^5.0.4" +redis = "^5.0.6" regex = "^2024.5.15" s3transfer = "^0.10.1" shapely = "^2.0.4" diff --git a/tests/app/main/views/test_dashboard.py b/tests/app/main/views/test_dashboard.py index 285444b1b..e83dacbae 100644 --- a/tests/app/main/views/test_dashboard.py +++ b/tests/app/main/views/test_dashboard.py @@ -1893,26 +1893,22 @@ def app_with_socketio(): ( SERVICE_ONE_ID, {"start_date": "2024-01-01", "days": 7}, - {"service_id": SERVICE_ONE_ID, "start_date": "2024-01-01", "days": 7} + {"service_id": SERVICE_ONE_ID, "start_date": "2024-01-01", "days": 7}, ), ( SERVICE_TWO_ID, {"start_date": "2023-06-01", "days": 7}, - {"service_id": SERVICE_TWO_ID, "start_date": "2023-06-01", "days": 7} + {"service_id": SERVICE_TWO_ID, "start_date": "2023-06-01", "days": 7}, ), - ] + ], ) def test_fetch_daily_stats( - app_with_socketio, mocker, - service_id, - date_range, - expected_call_args + app_with_socketio, mocker, service_id, date_range, expected_call_args ): app, socketio = app_with_socketio mocker.patch( - "app.main.views.dashboard.get_stats_date_range", - return_value=date_range + "app.main.views.dashboard.get_stats_date_range", return_value=date_range ) mock_service_api = mocker.patch( @@ -1920,9 +1916,9 @@ def test_fetch_daily_stats( return_value={ date_range["start_date"]: { "email": {"delivered": 0, "failure": 0, "requested": 0}, - "sms": {"delivered": 0, "failure": 1, "requested": 1} + "sms": {"delivered": 0, "failure": 1, "requested": 1}, }, - } + }, ) client = SocketIOTestClient(app, socketio) @@ -1930,22 +1926,22 @@ def test_fetch_daily_stats( connected = client.is_connected() assert connected, "Client should be connected" - client.emit('fetch_daily_stats', service_id) + client.emit("fetch_daily_stats", service_id) received = client.get_received() assert received, "Should receive a response message" - assert received[0]['name'] == 'daily_stats_update' - assert received[0]['args'][0] == { + assert received[0]["name"] == "daily_stats_update" + assert received[0]["args"][0] == { date_range["start_date"]: { "email": {"delivered": 0, "failure": 0, "requested": 0}, - "sms": {"delivered": 0, "failure": 1, "requested": 1} + "sms": {"delivered": 0, "failure": 1, "requested": 1}, }, } mock_service_api.assert_called_once_with( service_id, start_date=expected_call_args["start_date"], - days=expected_call_args["days"] + days=expected_call_args["days"], ) finally: client.disconnect()