-
+
Daily messages across all services
You have sent {{ global_message_limit - daily_global_messages_remaining }} of your {{ global_message_limit }} daily messages allowance.
You have {{ daily_global_messages_remaining }} messages remaining.
-
Text messages
+
Text message parts
You have sent
- {{ big_number(sms_sent, 'messages of your', smaller=True) }}
- {{ big_number(sms_free_allowance, 'free messages allowance.', smaller=True) }}
+ {{ big_number(sms_sent, 'text message parts of your', smaller=True) }}
+ {{ big_number(sms_free_allowance, 'free message parts allowance.', smaller=True) }}
You have
{% if sms_free_allowance > 0 %}
- {{ big_number(sms_allowance_remaining, 'messages remaining.', smaller=True) }}
+ {{ big_number(sms_allowance_remaining, 'message parts remaining.', smaller=True) }}
{% endif %}
{# {% for row in sms_breakdown %}
{% if row.charged_units > 0 %}
{{ big_number(
- row.charged_units,
+ row.charged_units,
'at {:.2f} pence per message'.format(row.rate * 100),
smaller=True
) }}
@@ -90,7 +90,7 @@
Emails
{{ item.month }}
{% endcall %}
{% for counts, template_type in [
- (item.sms_counts.0, 'sms'),
+ (item.sms_counts.0, 'parts'),
] %}
{% call field(align='left') %}
{{ big_number(
@@ -126,7 +126,7 @@ Emails
{% endif %}
{% endcall %}
-
+
{% endcall %}
{% endif %}
@@ -140,7 +140,7 @@
Emails
- What counts as 1 text message?
+ What counts as 1 text message part?
See pricing.
diff --git a/app/templates/views/user-profile.html b/app/templates/views/user-profile.html
index 876a142805..d3e6ff7c8a 100644
--- a/app/templates/views/user-profile.html
+++ b/app/templates/views/user-profile.html
@@ -1,4 +1,4 @@
-{% extends "withoutnav_template.html" %}
+{% extends "settings_template.html" %}
{% from "components/table.html" import list_table, row, field %}
{% from "components/table.html" import mapping_table, row, text_field, optional_text_field, edit_field, field, boolean_field with context %}
diff --git a/app/templates/views/using-notify.html b/app/templates/views/using-notify.html
index 7d27017779..3b11fa2710 100644
--- a/app/templates/views/using-notify.html
+++ b/app/templates/views/using-notify.html
@@ -21,7 +21,6 @@
Using Notify
diff --git a/app/url_converters.py b/app/url_converters.py
index 15d3f3b736..8c9500d36e 100644
--- a/app/url_converters.py
+++ b/app/url_converters.py
@@ -1,10 +1,5 @@
from werkzeug.routing import BaseConverter
-from app.models.feedback import (
- GENERAL_TICKET_TYPE,
- PROBLEM_TICKET_TYPE,
- QUESTION_TICKET_TYPE,
-)
from app.models.service import Service
@@ -12,9 +7,5 @@ class TemplateTypeConverter(BaseConverter):
regex = "(?:{})".format("|".join(Service.TEMPLATE_TYPES))
-class TicketTypeConverter(BaseConverter):
- regex = f"(?:{PROBLEM_TICKET_TYPE}|{QUESTION_TICKET_TYPE}|{GENERAL_TICKET_TYPE})"
-
-
class SimpleDateTypeConverter(BaseConverter):
regex = r"([12]\d{3}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01]))"
diff --git a/app/utils/branding.py b/app/utils/branding.py
deleted file mode 100644
index dd35fea30d..0000000000
--- a/app/utils/branding.py
+++ /dev/null
@@ -1,23 +0,0 @@
-from app.models.organization import Organization
-
-
-def get_email_choices(service):
- organization_branding_id = (
- service.organization.email_branding_id if service.organization else None
- )
-
- if (
- service.organization_type == Organization.TYPE_FEDERAL
- and service.email_branding_id is not None # GOV.UK is not current branding
- and organization_branding_id is None # no default to supersede it (GOV.UK)
- ):
- yield ("govuk", "GOV.UK")
-
- if (
- service.organization_type == Organization.TYPE_FEDERAL
- and service.organization
- and organization_branding_id is None # don't offer both if org has default
- and service.email_branding_name.lower()
- != f"GOV.UK and {service.organization.name}".lower()
- ):
- yield ("govuk_and_org", f"GOV.UK and {service.organization.name}")
diff --git a/app/utils/csv.py b/app/utils/csv.py
index 1c02ca8c5d..7cfcb89759 100644
--- a/app/utils/csv.py
+++ b/app/utils/csv.py
@@ -170,7 +170,7 @@ def convert_report_date_to_preferred_timezone(db_date_str_in_utc):
utc_date_obj = utc_date_obj.astimezone(pytz.utc)
preferred_timezone = pytz.timezone(get_user_preferred_timezone())
preferred_date_obj = utc_date_obj.astimezone(preferred_timezone)
- preferred_tz_created_at = preferred_date_obj.strftime("%Y-%m-%d %H:%M:%S")
+ preferred_tz_created_at = preferred_date_obj.strftime("%Y-%m-%d %I:%M:%S %p")
return f"{preferred_tz_created_at} {get_user_preferred_timezone()}"
diff --git a/deploy-config/egress_proxy/notify-admin-staging.allow.acl b/deploy-config/egress_proxy/notify-admin-staging.allow.acl
index d329f111da..65c7931d29 100644
--- a/deploy-config/egress_proxy/notify-admin-staging.allow.acl
+++ b/deploy-config/egress_proxy/notify-admin-staging.allow.acl
@@ -1,2 +1,4 @@
gov-collector.newrelic.com
egress-proxy-notify-admin-staging.apps.internal
+idp.int.identitysandbox.gov
+secure.login.gov
diff --git a/docs/FCC RND Best Practices.pdf b/docs/FCC RND Best Practices.pdf
new file mode 100644
index 0000000000..07cecf4b86
Binary files /dev/null and b/docs/FCC RND Best Practices.pdf differ
diff --git a/docs/compelling-texts-checklist.pdf b/docs/compelling-texts-checklist.pdf
new file mode 100644
index 0000000000..8e0680d08d
Binary files /dev/null and b/docs/compelling-texts-checklist.pdf differ
diff --git a/docs/sprint-goals.md b/docs/sprint-goals.md
index 598305e0e6..b779d45e78 100644
--- a/docs/sprint-goals.md
+++ b/docs/sprint-goals.md
@@ -1,8 +1,40 @@
# Notify Sprint Goals Log
+## Sprint: B (1/18/24)
+2024 Sprints will be named after mythical creatures
-## Sprint: Y (12/6/23)
-(Sprint X was canceled due to Thanksgiving)
+| | Goals | Impact | Milestone(s) |
+|-------------|-----------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------|--------------|
+| Engineering | Get `staging` stable and bugs fixed in order to do a prod push, finalize Login.gov implementation, complete epic to [remove phone numbers from db](https://github.com/GSA/notifications-api/issues/667), [calculate pre-send message parts](https://github.com/GSA/notifications-admin/issues/1027) to add to confirmation page, finish [E2E testing implementation](https://github.com/GSA/notifications-admin/issues/677), set up [opt-out lists by phone number](https://github.com/GSA/notifications-api/issues/312) | Install required auth, expand message send volume for partners, allow users to see how many parts a send will cost, improve app stability, partition opt-outs so that they aren't across the system | Building Notify.gov |
+| UX | [Tackle `Usage and Performance` epic](https://github.com/GSA/notifications-admin/issues/1108) as highest priority group of user stories, break out UX work into more user-stories and correlating epics, prioritize epic/story order, improve [pre-send confirmation page](https://github.com/GSA/notifications-admin/issues/815)| Improve UX | Building Notify.gov|
+| Security | Complete necessary control families | Aim to have package completed with enough time to allow for long assessment| Secure ATO |
+| Content | Get [refreshed content for `Using Notify` pages](https://github.com/GSA/notifications-admin/issues/449) in place, make content [more clear about limits](https://github.com/GSA/notifications-admin/issues/963), make content clearer by [adding `parts` where necessary](https://github.com/GSA/notifications-admin/issues/963) | Improve clarity of message parts for users, bring existing content into current state| Building Notify.gov |
+| Ops | Assist in Tech to Gov process, plan gap coverage for Tim's departure 2/9 |
+
+## Sprint: Akhlut (1/3/24)
+2024 Sprints will be named after mythical creatures
+
+| | Goals | Impact | Milestone(s) |
+|-------------|-----------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------|--------------|
+| Engineering | Continue [identifying](https://github.com/GSA/notifications-api/issues/656), [fixing](https://github.com/GSA/notifications-api/issues/645), and [displaying](https://github.com/GSA/notifications-admin/issues/964) various applicable message part limits, devise ways to [create better access to reports](https://github.com/GSA/notifications-admin/issues/855), implement Login.gov on staging environment, advance epic to [remove phone numbers from db](https://github.com/GSA/notifications-api/issues/667) | Better track annual and smart-retention limits, install required auth, make reports easier to find, expand message send volume for partners | Building Notify.gov |
+| UX | Spin up weekly [redesign syncs](https://github.com/GSA/notifications-admin/issues/1061), identify necessary dashboard metrics to visualize, improve [team member section](https://github.com/GSA/notifications-admin/issues/1031)| Improve UX | Building Notify.gov|
+| Security | Complete necessary control families | Aim to have package completed with enough time to allow for long assessment| Secure ATO |
+| Content | Finish making [editing content easier](https://github.com/GSA/notifications-admin/issues/976) with existing templates, finalize decision on terminology regarding [batch/campaign/job](https://github.com/GSA/notifications-admin/issues/1060), make content [more clear about limits](https://github.com/GSA/notifications-admin/issues/963), make content clearer by [adding `parts` where necessary](https://github.com/GSA/notifications-admin/issues/963) | Improve clarity of message parts for users, streamline content editing for the team| Building Notify.gov |
+| Ops | Assist in Tech to Gov process | |
+
+## Sprint: Zoe's Imperial Pigeon (12/21/23)
+Sprint velocity was very low due to holiday OOO
+
+| | Goals | Impact | Milestone(s) |
+|-------------|-----------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------|--------------|
+| Engineering | Spend time [identifying](https://github.com/GSA/notifications-api/issues/656), [fixing](https://github.com/GSA/notifications-api/issues/645), and [displaying](https://github.com/GSA/notifications-admin/issues/964) various applicable message part limits, devise ways to [create better access to reports](https://github.com/GSA/notifications-admin/issues/855), implement Login.gov on staging environment, advance epic to [remove phone numbers from db](https://github.com/GSA/notifications-api/issues/667) | Better track annual and smart-retention limits, install required auth, make reports easier to find, expand message send volume for partners | Building Notify.gov |
+| UX | [Re-think Notify IA](https://github.com/GSA/notifications-admin/issues/910), start implementing dashboard changes, improve [team member section](https://github.com/GSA/notifications-admin/issues/1031)| Improve UX | Building Notify.gov|
+| Security | Complete necessary control families | Aim to have package completed with enough time to allow for long assessment| Secure ATO |
+| Content | Make content [more clear about limits](https://github.com/GSA/notifications-admin/issues/963), make content clearer by [adding `parts` where necessary](https://github.com/GSA/notifications-admin/issues/963), test making [editing content easier](https://github.com/GSA/notifications-admin/issues/976) with existing templates | Improve clarity of message parts for users, streamline content editing for the team| Building Notify.gov |
+| Ops | Assist in Tech to Gov process | |
+
+## Sprint: Xingu Scale-Backed Antbird (12/6/23)
+(Sprint X was canceled due to Thanksgiving, so this is X & Y)
| | Goals | Impact | Milestone(s) |
|-------------|-----------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------|--------------|
diff --git a/get_zendesk_tickets.py b/get_zendesk_tickets.py
deleted file mode 100644
index 6d32d484d0..0000000000
--- a/get_zendesk_tickets.py
+++ /dev/null
@@ -1,152 +0,0 @@
-"""
-This script can be used to retrieve Zendesk tickets.
-This can be run locally if you set the ZENDESK_API_KEY. Or the script can be run from a flask shell from a ssh session.
-"""
-# flake8: noqa: T001 (print)
-
-import csv
-import os
-import urllib.parse
-
-import requests
-
-# Group: 3rd Line--Notify Support
-NOTIFY_GROUP_ID = 360000036529
-
-# Organization: GDS
-NOTIFY_ORG_ID = 21891972
-
-# the account used to authenticate with. If no requester is provided, the ticket will come from this account.
-NOTIFY_ZENDESK_EMAIL = "zd-api-notify@digital.cabinet-office.gov.uk"
-ZENDESK_API_KEY = os.environ.get("ZENDESK_API_KEY")
-
-
-def get_tickets():
- ZENDESK_TICKET_URL = "https://govuk.zendesk.com/api/v2/search.json?query={}"
- query_params = "type:ticket group:{}".format(NOTIFY_GROUP_ID)
- query_params = urllib.parse.quote(query_params)
-
- next_page = ZENDESK_TICKET_URL.format(query_params)
-
- with open("zendesk_ticket_data.csv", "w") as csvfile:
- fieldnames = [
- "Service id",
- "Ticket id",
- "Subject line",
- "Date ticket created",
- "Tags",
- ]
- writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
- writer.writeheader()
- while next_page:
- print(next_page)
- response = requests.get(
- next_page,
- headers={"Content-type": "application/json"},
- auth=("{}/token".format(NOTIFY_ZENDESK_EMAIL), ZENDESK_API_KEY),
- )
- data = response.json()
- print(data)
- for row in data["results"]:
- service_url = [
- x
- for x in row["description"].split("\n")
- if x.startswith(
- "https://www.notifications.service.gov.uk/services/"
- )
- ]
- service_url = service_url[0][50:] if len(service_url) > 0 else None
- if service_url:
- writer.writerow(
- {
- "Service id": service_url,
- "Ticket id": row["id"],
- "Subject line": row["subject"],
- "Date ticket created": row["created_at"],
- "Tags": row.get("tags", ""),
- }
- )
- next_page = data["next_page"]
-
-
-def get_tickets_without_service_id():
- ZENDESK_TICKET_URL = "https://govuk.zendesk.com/api/v2/search.json?query={}"
- query_params = "type:ticket group:{}".format(NOTIFY_GROUP_ID)
- query_params = urllib.parse.quote(query_params)
-
- next_page = ZENDESK_TICKET_URL.format(query_params)
- with open("zendesk_ticket_data_without_service.csv", "w") as csvfile:
- fieldnames = [
- "Ticket id",
- "Subject line",
- "Date ticket created",
- "Tags",
- ]
- writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
- writer.writeheader()
- while next_page:
- print(next_page)
- response = requests.get(
- next_page,
- headers={"Content-type": "application/json"},
- auth=("{}/token".format(NOTIFY_ZENDESK_EMAIL), ZENDESK_API_KEY),
- )
- data = response.json()
- print(data)
- for row in data["results"]:
- service_url = [
- x
- for x in row["description"].split("\n")
- if x.startswith(
- "https://www.notifications.service.gov.uk/services/"
- )
- ]
- service_url = service_url[0][50:] if len(service_url) > 0 else None
- if not service_url:
- writer.writerow(
- {
- "Ticket id": row["id"],
- "Subject line": row["subject"],
- "Date ticket created": row["created_at"],
- "Tags": row.get("tags", ""),
- }
- )
- next_page = data["next_page"]
-
-
-def get_tickets_with_description():
- ZENDESK_TICKET_URL = "https://govuk.zendesk.com/api/v2/search.json?query={}"
- query_params = "type:ticket group:{}, created>2019-07-01".format(NOTIFY_GROUP_ID)
- query_params = urllib.parse.quote(query_params)
-
- next_page = ZENDESK_TICKET_URL.format(query_params)
- with open("zendesk_ticket.csv", "w") as csvfile:
- fieldnames = [
- "Ticket id",
- "Subject line",
- "Description",
- "Date ticket created",
- "Tags",
- ]
- writer = csv.DictWriter(csvfile, fieldnames=fieldnames)
- writer.writeheader()
- while next_page:
- print(next_page)
- response = requests.get(
- next_page,
- headers={"Content-type": "application/json"},
- auth=("{}/token".format(NOTIFY_ZENDESK_EMAIL), ZENDESK_API_KEY),
- )
- data = response.json()
- print(data)
- for row in data["results"]:
- writer.writerow(
- {
- "Ticket id": row["id"],
- "Subject line": row["subject"],
- "Description": row["description"],
- "Date ticket created": row["created_at"],
- "Tags": row.get("tags", ""),
- }
- )
- next_page = data["next_page"]
diff --git a/gulpfile.js b/gulpfile.js
index 541c39cf4a..6828bcaf19 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -103,9 +103,6 @@ const javascripts = () => {
const local = src([
paths.toolkit + 'javascripts/govuk/modules.js',
paths.toolkit + 'javascripts/govuk/show-hide-content.js',
- paths.src + 'javascripts/govuk/cookie-functions.js',
- paths.src + 'javascripts/consent.js',
- paths.src + 'javascripts/cookieMessage.js',
paths.src + 'javascripts/copyToClipboard.js',
paths.src + 'javascripts/autofocus.js',
paths.src + 'javascripts/enhancedTextbox.js',
@@ -117,7 +114,6 @@ const javascripts = () => {
paths.src + 'javascripts/errorTracking.js',
paths.src + 'javascripts/preventDuplicateFormSubmissions.js',
paths.src + 'javascripts/fullscreenTable.js',
- paths.src + 'javascripts/previewPane.js',
paths.src + 'javascripts/colourPreview.js',
paths.src + 'javascripts/templateFolderForm.js',
paths.src + 'javascripts/collapsibleCheckboxes.js',
diff --git a/manifest.yml b/manifest.yml
index 1131e32f97..1c25fdf4af 100644
--- a/manifest.yml
+++ b/manifest.yml
@@ -46,3 +46,14 @@ applications:
REQUESTS_CA_BUNDLE: "/etc/ssl/certs/ca-certificates.crt"
NEW_RELIC_CA_BUNDLE_PATH: "/etc/ssl/certs/ca-certificates.crt"
+
+ # login.gov variables
+
+ LOGIN_PEM: ((LOGIN_PEM))
+ LOGIN_DOT_GOV_CLIENT_ID: ((LOGIN_DOT_GOV_CLIENT_ID))
+ LOGIN_DOT_GOV_USER_INFO_URL: ((LOGIN_DOT_GOV_USER_INFO_URL))
+ LOGIN_DOT_GOV_ACCESS_TOKEN_URL: ((LOGIN_DOT_GOV_ACCESS_TOKEN_URL))
+ LOGIN_DOT_GOV_LOGOUT_URL: ((LOGIN_DOT_GOV_LOGOUT_URL))
+ LOGIN_DOT_GOV_BASE_LOGOUT_URL: ((LOGIN_DOT_GOV_BASE_LOGOUT_URL))
+ LOGIN_DOT_GOV_SIGNOUT_REDIRECT: ((LOGIN_DOT_GOV_SIGNOUT_REDIRECT))
+ LOGIN_DOT_GOV_INITIAL_SIGNIN_URL: ((LOGIN_DOT_GOV_INITIAL_SIGNIN_URL))
diff --git a/paas-failwhale/static_503/stylesheets/main.css b/paas-failwhale/static_503/stylesheets/main.css
index 92ac258b85..50a467879c 100644
--- a/paas-failwhale/static_503/stylesheets/main.css
+++ b/paas-failwhale/static_503/stylesheets/main.css
@@ -8903,14 +8903,6 @@ only screen and (min-resolution: 2dppx) {
padding-bottom: 75%
}
-.branding-preview {
- width: 100%;
- box-sizing: border-box;
- border: solid 1px #bfc1c3;
- min-height: 200px;
- margin-bottom: 30px
-}
-
#logo-img {
background-color: #f8f8f8;
background-image: linear-gradient(45deg, #dee0e2 25%, transparent 25%), linear-gradient(-45deg, #dee0e2 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #dee0e2 75%), linear-gradient(-45deg, transparent 75%, #dee0e2 75%);
@@ -9444,56 +9436,6 @@ only screen and (min-resolution: 2dppx) {
position: relative
}
-.edit-template-link-letter-contact,
-.edit-template-link-letter-address,
-.edit-template-link-letter-body,
-.edit-template-link-letter-branding,
-.edit-template-link {
- font-family: "nta", Arial, sans-serif;
- font-weight: 400;
- text-transform: none;
- font-size: 16px;
- line-height: 1.25;
- position: absolute;
- background: #005ea5;
- color: #fff;
- padding: 10px 15px;
- z-index: 10000
-}
-
-@media (min-width: 641px) {
-
- .edit-template-link-letter-contact,
- .edit-template-link-letter-address,
- .edit-template-link-letter-body,
- .edit-template-link-letter-branding,
- .edit-template-link {
- font-size: 19px;
- line-height: 1.31579
- }
-}
-
-.edit-template-link-letter-contact:link,
-.edit-template-link-letter-address:link,
-.edit-template-link-letter-body:link,
-.edit-template-link-letter-branding:link,
-.edit-template-link-letter-contact:visited,
-.edit-template-link-letter-address:visited,
-.edit-template-link-letter-body:visited,
-.edit-template-link-letter-branding:visited,
-.edit-template-link:link,
-.edit-template-link:visited {
- color: #fff
-}
-
-.edit-template-link-letter-contact:hover,
-.edit-template-link-letter-address:hover,
-.edit-template-link-letter-body:hover,
-.edit-template-link-letter-branding:hover,
-.edit-template-link:hover {
- color: #d5e8f3
-}
-
.notification-status {
font-family: "nta", Arial, sans-serif;
font-weight: 400;
@@ -9817,4 +9759,4 @@ details .arrow {
.heading-upcoming-jobs {
margin-top: 15px
-}
\ No newline at end of file
+}
diff --git a/poetry.lock b/poetry.lock
index 4dcb8829f2..da354843e7 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -41,70 +41,77 @@ tests-no-zope = ["cloudpickle", "hypothesis", "mypy (>=1.1.1)", "pympler", "pyte
[[package]]
name = "bandit"
-version = "1.7.5"
+version = "1.7.7"
description = "Security oriented static analyser for python code."
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
files = [
- {file = "bandit-1.7.5-py3-none-any.whl", hash = "sha256:75665181dc1e0096369112541a056c59d1c5f66f9bb74a8d686c3c362b83f549"},
- {file = "bandit-1.7.5.tar.gz", hash = "sha256:bdfc739baa03b880c2d15d0431b31c658ffc348e907fe197e54e0389dd59e11e"},
+ {file = "bandit-1.7.7-py3-none-any.whl", hash = "sha256:17e60786a7ea3c9ec84569fd5aee09936d116cb0cb43151023258340dbffb7ed"},
+ {file = "bandit-1.7.7.tar.gz", hash = "sha256:527906bec6088cb499aae31bc962864b4e77569e9d529ee51df3a93b4b8ab28a"},
]
[package.dependencies]
colorama = {version = ">=0.3.9", markers = "platform_system == \"Windows\""}
-GitPython = ">=1.0.1"
PyYAML = ">=5.3.1"
rich = "*"
stevedore = ">=1.20.0"
[package.extras]
-test = ["beautifulsoup4 (>=4.8.0)", "coverage (>=4.5.4)", "fixtures (>=3.0.0)", "flake8 (>=4.0.0)", "pylint (==1.9.4)", "stestr (>=2.5.0)", "testscenarios (>=0.5.0)", "testtools (>=2.3.0)", "tomli (>=1.1.0)"]
+baseline = ["GitPython (>=3.1.30)"]
+test = ["beautifulsoup4 (>=4.8.0)", "coverage (>=4.5.4)", "fixtures (>=3.0.0)", "flake8 (>=4.0.0)", "pylint (==1.9.4)", "stestr (>=2.5.0)", "testscenarios (>=0.5.0)", "testtools (>=2.3.0)"]
toml = ["tomli (>=1.1.0)"]
yaml = ["PyYAML"]
[[package]]
name = "beautifulsoup4"
-version = "4.12.2"
+version = "4.12.3"
description = "Screen-scraping library"
optional = false
python-versions = ">=3.6.0"
files = [
- {file = "beautifulsoup4-4.12.2-py3-none-any.whl", hash = "sha256:bd2520ca0d9d7d12694a53d44ac482d181b4ec1888909b035a3dbf40d0f57d4a"},
- {file = "beautifulsoup4-4.12.2.tar.gz", hash = "sha256:492bbc69dca35d12daac71c4db1bfff0c876c00ef4a2ffacce226d4638eb72da"},
+ {file = "beautifulsoup4-4.12.3-py3-none-any.whl", hash = "sha256:b80878c9f40111313e55da8ba20bdba06d8fa3969fc68304167741bbf9e082ed"},
+ {file = "beautifulsoup4-4.12.3.tar.gz", hash = "sha256:74e3d1928edc070d21748185c46e3fb33490f22f52a3addee9aee0f4f7781051"},
]
[package.dependencies]
soupsieve = ">1.2"
[package.extras]
+cchardet = ["cchardet"]
+chardet = ["chardet"]
+charset-normalizer = ["charset-normalizer"]
html5lib = ["html5lib"]
lxml = ["lxml"]
[[package]]
name = "black"
-version = "23.11.0"
+version = "23.12.1"
description = "The uncompromising code formatter."
optional = false
python-versions = ">=3.8"
files = [
- {file = "black-23.11.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:dbea0bb8575c6b6303cc65017b46351dc5953eea5c0a59d7b7e3a2d2f433a911"},
- {file = "black-23.11.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:412f56bab20ac85927f3a959230331de5614aecda1ede14b373083f62ec24e6f"},
- {file = "black-23.11.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d136ef5b418c81660ad847efe0e55c58c8208b77a57a28a503a5f345ccf01394"},
- {file = "black-23.11.0-cp310-cp310-win_amd64.whl", hash = "sha256:6c1cac07e64433f646a9a838cdc00c9768b3c362805afc3fce341af0e6a9ae9f"},
- {file = "black-23.11.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cf57719e581cfd48c4efe28543fea3d139c6b6f1238b3f0102a9c73992cbb479"},
- {file = "black-23.11.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:698c1e0d5c43354ec5d6f4d914d0d553a9ada56c85415700b81dc90125aac244"},
- {file = "black-23.11.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:760415ccc20f9e8747084169110ef75d545f3b0932ee21368f63ac0fee86b221"},
- {file = "black-23.11.0-cp311-cp311-win_amd64.whl", hash = "sha256:58e5f4d08a205b11800332920e285bd25e1a75c54953e05502052738fe16b3b5"},
- {file = "black-23.11.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:45aa1d4675964946e53ab81aeec7a37613c1cb71647b5394779e6efb79d6d187"},
- {file = "black-23.11.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:4c44b7211a3a0570cc097e81135faa5f261264f4dfaa22bd5ee2875a4e773bd6"},
- {file = "black-23.11.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2a9acad1451632021ee0d146c8765782a0c3846e0e0ea46659d7c4f89d9b212b"},
- {file = "black-23.11.0-cp38-cp38-win_amd64.whl", hash = "sha256:fc7f6a44d52747e65a02558e1d807c82df1d66ffa80a601862040a43ec2e3142"},
- {file = "black-23.11.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7f622b6822f02bfaf2a5cd31fdb7cd86fcf33dab6ced5185c35f5db98260b055"},
- {file = "black-23.11.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:250d7e60f323fcfc8ea6c800d5eba12f7967400eb6c2d21ae85ad31c204fb1f4"},
- {file = "black-23.11.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5133f5507007ba08d8b7b263c7aa0f931af5ba88a29beacc4b2dc23fcefe9c06"},
- {file = "black-23.11.0-cp39-cp39-win_amd64.whl", hash = "sha256:421f3e44aa67138ab1b9bfbc22ee3780b22fa5b291e4db8ab7eee95200726b07"},
- {file = "black-23.11.0-py3-none-any.whl", hash = "sha256:54caaa703227c6e0c87b76326d0862184729a69b73d3b7305b6288e1d830067e"},
- {file = "black-23.11.0.tar.gz", hash = "sha256:4c68855825ff432d197229846f971bc4d6666ce90492e5b02013bcaca4d9ab05"},
+ {file = "black-23.12.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0aaf6041986767a5e0ce663c7a2f0e9eaf21e6ff87a5f95cbf3675bfd4c41d2"},
+ {file = "black-23.12.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:c88b3711d12905b74206227109272673edce0cb29f27e1385f33b0163c414bba"},
+ {file = "black-23.12.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a920b569dc6b3472513ba6ddea21f440d4b4c699494d2e972a1753cdc25df7b0"},
+ {file = "black-23.12.1-cp310-cp310-win_amd64.whl", hash = "sha256:3fa4be75ef2a6b96ea8d92b1587dd8cb3a35c7e3d51f0738ced0781c3aa3a5a3"},
+ {file = "black-23.12.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8d4df77958a622f9b5a4c96edb4b8c0034f8434032ab11077ec6c56ae9f384ba"},
+ {file = "black-23.12.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:602cfb1196dc692424c70b6507593a2b29aac0547c1be9a1d1365f0d964c353b"},
+ {file = "black-23.12.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c4352800f14be5b4864016882cdba10755bd50805c95f728011bcb47a4afd59"},
+ {file = "black-23.12.1-cp311-cp311-win_amd64.whl", hash = "sha256:0808494f2b2df923ffc5723ed3c7b096bd76341f6213989759287611e9837d50"},
+ {file = "black-23.12.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:25e57fd232a6d6ff3f4478a6fd0580838e47c93c83eaf1ccc92d4faf27112c4e"},
+ {file = "black-23.12.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:2d9e13db441c509a3763a7a3d9a49ccc1b4e974a47be4e08ade2a228876500ec"},
+ {file = "black-23.12.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6d1bd9c210f8b109b1762ec9fd36592fdd528485aadb3f5849b2740ef17e674e"},
+ {file = "black-23.12.1-cp312-cp312-win_amd64.whl", hash = "sha256:ae76c22bde5cbb6bfd211ec343ded2163bba7883c7bc77f6b756a1049436fbb9"},
+ {file = "black-23.12.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1fa88a0f74e50e4487477bc0bb900c6781dbddfdfa32691e780bf854c3b4a47f"},
+ {file = "black-23.12.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:a4d6a9668e45ad99d2f8ec70d5c8c04ef4f32f648ef39048d010b0689832ec6d"},
+ {file = "black-23.12.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b18fb2ae6c4bb63eebe5be6bd869ba2f14fd0259bda7d18a46b764d8fb86298a"},
+ {file = "black-23.12.1-cp38-cp38-win_amd64.whl", hash = "sha256:c04b6d9d20e9c13f43eee8ea87d44156b8505ca8a3c878773f68b4e4812a421e"},
+ {file = "black-23.12.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3e1b38b3135fd4c025c28c55ddfc236b05af657828a8a6abe5deec419a0b7055"},
+ {file = "black-23.12.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:4f0031eaa7b921db76decd73636ef3a12c942ed367d8c3841a0739412b260a54"},
+ {file = "black-23.12.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97e56155c6b737854e60a9ab1c598ff2533d57e7506d97af5481141671abf3ea"},
+ {file = "black-23.12.1-cp39-cp39-win_amd64.whl", hash = "sha256:dd15245c8b68fe2b6bd0f32c1556509d11bb33aec9b5d0866dd8e2ed3dba09c2"},
+ {file = "black-23.12.1-py3-none-any.whl", hash = "sha256:78baad24af0f033958cad29731e27363183e140962595def56423e626f4bee3e"},
+ {file = "black-23.12.1.tar.gz", hash = "sha256:4ce3ef14ebe8d9509188014d96af1c456a910d5b5cbf434a09fef7e024b3d0d5"},
]
[package.dependencies]
@@ -118,7 +125,7 @@ typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""}
[package.extras]
colorama = ["colorama (>=0.4.3)"]
-d = ["aiohttp (>=3.7.4)"]
+d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"]
jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"]
uvloop = ["uvloop (>=0.15.2)"]
@@ -458,63 +465,63 @@ files = [
[[package]]
name = "coverage"
-version = "7.3.2"
+version = "7.4.0"
description = "Code coverage measurement for Python"
optional = false
python-versions = ">=3.8"
files = [
- {file = "coverage-7.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d872145f3a3231a5f20fd48500274d7df222e291d90baa2026cc5152b7ce86bf"},
- {file = "coverage-7.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:310b3bb9c91ea66d59c53fa4989f57d2436e08f18fb2f421a1b0b6b8cc7fffda"},
- {file = "coverage-7.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f47d39359e2c3779c5331fc740cf4bce6d9d680a7b4b4ead97056a0ae07cb49a"},
- {file = "coverage-7.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:aa72dbaf2c2068404b9870d93436e6d23addd8bbe9295f49cbca83f6e278179c"},
- {file = "coverage-7.3.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:beaa5c1b4777f03fc63dfd2a6bd820f73f036bfb10e925fce067b00a340d0f3f"},
- {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:dbc1b46b92186cc8074fee9d9fbb97a9dd06c6cbbef391c2f59d80eabdf0faa6"},
- {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:315a989e861031334d7bee1f9113c8770472db2ac484e5b8c3173428360a9148"},
- {file = "coverage-7.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:d1bc430677773397f64a5c88cb522ea43175ff16f8bfcc89d467d974cb2274f9"},
- {file = "coverage-7.3.2-cp310-cp310-win32.whl", hash = "sha256:a889ae02f43aa45032afe364c8ae84ad3c54828c2faa44f3bfcafecb5c96b02f"},
- {file = "coverage-7.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:c0ba320de3fb8c6ec16e0be17ee1d3d69adcda99406c43c0409cb5c41788a611"},
- {file = "coverage-7.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:ac8c802fa29843a72d32ec56d0ca792ad15a302b28ca6203389afe21f8fa062c"},
- {file = "coverage-7.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:89a937174104339e3a3ffcf9f446c00e3a806c28b1841c63edb2b369310fd074"},
- {file = "coverage-7.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e267e9e2b574a176ddb983399dec325a80dbe161f1a32715c780b5d14b5f583a"},
- {file = "coverage-7.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2443cbda35df0d35dcfb9bf8f3c02c57c1d6111169e3c85fc1fcc05e0c9f39a3"},
- {file = "coverage-7.3.2-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4175e10cc8dda0265653e8714b3174430b07c1dca8957f4966cbd6c2b1b8065a"},
- {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0cbf38419fb1a347aaf63481c00f0bdc86889d9fbf3f25109cf96c26b403fda1"},
- {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:5c913b556a116b8d5f6ef834038ba983834d887d82187c8f73dec21049abd65c"},
- {file = "coverage-7.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:1981f785239e4e39e6444c63a98da3a1db8e971cb9ceb50a945ba6296b43f312"},
- {file = "coverage-7.3.2-cp311-cp311-win32.whl", hash = "sha256:43668cabd5ca8258f5954f27a3aaf78757e6acf13c17604d89648ecc0cc66640"},
- {file = "coverage-7.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10c39c0452bf6e694511c901426d6b5ac005acc0f78ff265dbe36bf81f808a2"},
- {file = "coverage-7.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4cbae1051ab791debecc4a5dcc4a1ff45fc27b91b9aee165c8a27514dd160836"},
- {file = "coverage-7.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:12d15ab5833a997716d76f2ac1e4b4d536814fc213c85ca72756c19e5a6b3d63"},
- {file = "coverage-7.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c7bba973ebee5e56fe9251300c00f1579652587a9f4a5ed8404b15a0471f216"},
- {file = "coverage-7.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:fe494faa90ce6381770746077243231e0b83ff3f17069d748f645617cefe19d4"},
- {file = "coverage-7.3.2-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6e9589bd04d0461a417562649522575d8752904d35c12907d8c9dfeba588faf"},
- {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d51ac2a26f71da1b57f2dc81d0e108b6ab177e7d30e774db90675467c847bbdf"},
- {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:99b89d9f76070237975b315b3d5f4d6956ae354a4c92ac2388a5695516e47c84"},
- {file = "coverage-7.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:fa28e909776dc69efb6ed975a63691bc8172b64ff357e663a1bb06ff3c9b589a"},
- {file = "coverage-7.3.2-cp312-cp312-win32.whl", hash = "sha256:289fe43bf45a575e3ab10b26d7b6f2ddb9ee2dba447499f5401cfb5ecb8196bb"},
- {file = "coverage-7.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:7dbc3ed60e8659bc59b6b304b43ff9c3ed858da2839c78b804973f613d3e92ed"},
- {file = "coverage-7.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f94b734214ea6a36fe16e96a70d941af80ff3bfd716c141300d95ebc85339738"},
- {file = "coverage-7.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:af3d828d2c1cbae52d34bdbb22fcd94d1ce715d95f1a012354a75e5913f1bda2"},
- {file = "coverage-7.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:630b13e3036e13c7adc480ca42fa7afc2a5d938081d28e20903cf7fd687872e2"},
- {file = "coverage-7.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c9eacf273e885b02a0273bb3a2170f30e2d53a6d53b72dbe02d6701b5296101c"},
- {file = "coverage-7.3.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d8f17966e861ff97305e0801134e69db33b143bbfb36436efb9cfff6ec7b2fd9"},
- {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b4275802d16882cf9c8b3d057a0839acb07ee9379fa2749eca54efbce1535b82"},
- {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:72c0cfa5250f483181e677ebc97133ea1ab3eb68645e494775deb6a7f6f83901"},
- {file = "coverage-7.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cb536f0dcd14149425996821a168f6e269d7dcd2c273a8bff8201e79f5104e76"},
- {file = "coverage-7.3.2-cp38-cp38-win32.whl", hash = "sha256:307adb8bd3abe389a471e649038a71b4eb13bfd6b7dd9a129fa856f5c695cf92"},
- {file = "coverage-7.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:88ed2c30a49ea81ea3b7f172e0269c182a44c236eb394718f976239892c0a27a"},
- {file = "coverage-7.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:b631c92dfe601adf8f5ebc7fc13ced6bb6e9609b19d9a8cd59fa47c4186ad1ce"},
- {file = "coverage-7.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d3d9df4051c4a7d13036524b66ecf7a7537d14c18a384043f30a303b146164e9"},
- {file = "coverage-7.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5f7363d3b6a1119ef05015959ca24a9afc0ea8a02c687fe7e2d557705375c01f"},
- {file = "coverage-7.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2f11cc3c967a09d3695d2a6f03fb3e6236622b93be7a4b5dc09166a861be6d25"},
- {file = "coverage-7.3.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:149de1d2401ae4655c436a3dced6dd153f4c3309f599c3d4bd97ab172eaf02d9"},
- {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3a4006916aa6fee7cd38db3bfc95aa9c54ebb4ffbfc47c677c8bba949ceba0a6"},
- {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9028a3871280110d6e1aa2df1afd5ef003bab5fb1ef421d6dc748ae1c8ef2ebc"},
- {file = "coverage-7.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:9f805d62aec8eb92bab5b61c0f07329275b6f41c97d80e847b03eb894f38d083"},
- {file = "coverage-7.3.2-cp39-cp39-win32.whl", hash = "sha256:d1c88ec1a7ff4ebca0219f5b1ef863451d828cccf889c173e1253aa84b1e07ce"},
- {file = "coverage-7.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b4767da59464bb593c07afceaddea61b154136300881844768037fd5e859353f"},
- {file = "coverage-7.3.2-pp38.pp39.pp310-none-any.whl", hash = "sha256:ae97af89f0fbf373400970c0a21eef5aa941ffeed90aee43650b81f7d7f47637"},
- {file = "coverage-7.3.2.tar.gz", hash = "sha256:be32ad29341b0170e795ca590e1c07e81fc061cb5b10c74ce7203491484404ef"},
+ {file = "coverage-7.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:36b0ea8ab20d6a7564e89cb6135920bc9188fb5f1f7152e94e8300b7b189441a"},
+ {file = "coverage-7.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0676cd0ba581e514b7f726495ea75aba3eb20899d824636c6f59b0ed2f88c471"},
+ {file = "coverage-7.4.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d0ca5c71a5a1765a0f8f88022c52b6b8be740e512980362f7fdbb03725a0d6b9"},
+ {file = "coverage-7.4.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a7c97726520f784239f6c62506bc70e48d01ae71e9da128259d61ca5e9788516"},
+ {file = "coverage-7.4.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:815ac2d0f3398a14286dc2cea223a6f338109f9ecf39a71160cd1628786bc6f5"},
+ {file = "coverage-7.4.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:80b5ee39b7f0131ebec7968baa9b2309eddb35b8403d1869e08f024efd883566"},
+ {file = "coverage-7.4.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:5b2ccb7548a0b65974860a78c9ffe1173cfb5877460e5a229238d985565574ae"},
+ {file = "coverage-7.4.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:995ea5c48c4ebfd898eacb098164b3cc826ba273b3049e4a889658548e321b43"},
+ {file = "coverage-7.4.0-cp310-cp310-win32.whl", hash = "sha256:79287fd95585ed36e83182794a57a46aeae0b64ca53929d1176db56aacc83451"},
+ {file = "coverage-7.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:5b14b4f8760006bfdb6e08667af7bc2d8d9bfdb648351915315ea17645347137"},
+ {file = "coverage-7.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:04387a4a6ecb330c1878907ce0dc04078ea72a869263e53c72a1ba5bbdf380ca"},
+ {file = "coverage-7.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ea81d8f9691bb53f4fb4db603203029643caffc82bf998ab5b59ca05560f4c06"},
+ {file = "coverage-7.4.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:74775198b702868ec2d058cb92720a3c5a9177296f75bd97317c787daf711505"},
+ {file = "coverage-7.4.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:76f03940f9973bfaee8cfba70ac991825611b9aac047e5c80d499a44079ec0bc"},
+ {file = "coverage-7.4.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:485e9f897cf4856a65a57c7f6ea3dc0d4e6c076c87311d4bc003f82cfe199d25"},
+ {file = "coverage-7.4.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:6ae8c9d301207e6856865867d762a4b6fd379c714fcc0607a84b92ee63feff70"},
+ {file = "coverage-7.4.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bf477c355274a72435ceb140dc42de0dc1e1e0bf6e97195be30487d8eaaf1a09"},
+ {file = "coverage-7.4.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:83c2dda2666fe32332f8e87481eed056c8b4d163fe18ecc690b02802d36a4d26"},
+ {file = "coverage-7.4.0-cp311-cp311-win32.whl", hash = "sha256:697d1317e5290a313ef0d369650cfee1a114abb6021fa239ca12b4849ebbd614"},
+ {file = "coverage-7.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:26776ff6c711d9d835557ee453082025d871e30b3fd6c27fcef14733f67f0590"},
+ {file = "coverage-7.4.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:13eaf476ec3e883fe3e5fe3707caeb88268a06284484a3daf8250259ef1ba143"},
+ {file = "coverage-7.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:846f52f46e212affb5bcf131c952fb4075b55aae6b61adc9856222df89cbe3e2"},
+ {file = "coverage-7.4.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26f66da8695719ccf90e794ed567a1549bb2644a706b41e9f6eae6816b398c4a"},
+ {file = "coverage-7.4.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:164fdcc3246c69a6526a59b744b62e303039a81e42cfbbdc171c91a8cc2f9446"},
+ {file = "coverage-7.4.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:316543f71025a6565677d84bc4df2114e9b6a615aa39fb165d697dba06a54af9"},
+ {file = "coverage-7.4.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:bb1de682da0b824411e00a0d4da5a784ec6496b6850fdf8c865c1d68c0e318dd"},
+ {file = "coverage-7.4.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:0e8d06778e8fbffccfe96331a3946237f87b1e1d359d7fbe8b06b96c95a5407a"},
+ {file = "coverage-7.4.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a56de34db7b7ff77056a37aedded01b2b98b508227d2d0979d373a9b5d353daa"},
+ {file = "coverage-7.4.0-cp312-cp312-win32.whl", hash = "sha256:51456e6fa099a8d9d91497202d9563a320513fcf59f33991b0661a4a6f2ad450"},
+ {file = "coverage-7.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:cd3c1e4cb2ff0083758f09be0f77402e1bdf704adb7f89108007300a6da587d0"},
+ {file = "coverage-7.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:e9d1bf53c4c8de58d22e0e956a79a5b37f754ed1ffdbf1a260d9dcfa2d8a325e"},
+ {file = "coverage-7.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:109f5985182b6b81fe33323ab4707011875198c41964f014579cf82cebf2bb85"},
+ {file = "coverage-7.4.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3cc9d4bc55de8003663ec94c2f215d12d42ceea128da8f0f4036235a119c88ac"},
+ {file = "coverage-7.4.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cc6d65b21c219ec2072c1293c505cf36e4e913a3f936d80028993dd73c7906b1"},
+ {file = "coverage-7.4.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5a10a4920def78bbfff4eff8a05c51be03e42f1c3735be42d851f199144897ba"},
+ {file = "coverage-7.4.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:b8e99f06160602bc64da35158bb76c73522a4010f0649be44a4e167ff8555952"},
+ {file = "coverage-7.4.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:7d360587e64d006402b7116623cebf9d48893329ef035278969fa3bbf75b697e"},
+ {file = "coverage-7.4.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:29f3abe810930311c0b5d1a7140f6395369c3db1be68345638c33eec07535105"},
+ {file = "coverage-7.4.0-cp38-cp38-win32.whl", hash = "sha256:5040148f4ec43644702e7b16ca864c5314ccb8ee0751ef617d49aa0e2d6bf4f2"},
+ {file = "coverage-7.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:9864463c1c2f9cb3b5db2cf1ff475eed2f0b4285c2aaf4d357b69959941aa555"},
+ {file = "coverage-7.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:936d38794044b26c99d3dd004d8af0035ac535b92090f7f2bb5aa9c8e2f5cd42"},
+ {file = "coverage-7.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:799c8f873794a08cdf216aa5d0531c6a3747793b70c53f70e98259720a6fe2d7"},
+ {file = "coverage-7.4.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e7defbb9737274023e2d7af02cac77043c86ce88a907c58f42b580a97d5bcca9"},
+ {file = "coverage-7.4.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a1526d265743fb49363974b7aa8d5899ff64ee07df47dd8d3e37dcc0818f09ed"},
+ {file = "coverage-7.4.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bf635a52fc1ea401baf88843ae8708591aa4adff875e5c23220de43b1ccf575c"},
+ {file = "coverage-7.4.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:756ded44f47f330666843b5781be126ab57bb57c22adbb07d83f6b519783b870"},
+ {file = "coverage-7.4.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:0eb3c2f32dabe3a4aaf6441dde94f35687224dfd7eb2a7f47f3fd9428e421058"},
+ {file = "coverage-7.4.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bfd5db349d15c08311702611f3dccbef4b4e2ec148fcc636cf8739519b4a5c0f"},
+ {file = "coverage-7.4.0-cp39-cp39-win32.whl", hash = "sha256:53d7d9158ee03956e0eadac38dfa1ec8068431ef8058fe6447043db1fb40d932"},
+ {file = "coverage-7.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:cfd2a8b6b0d8e66e944d47cdec2f47c48fef2ba2f2dff5a9a75757f64172857e"},
+ {file = "coverage-7.4.0-pp38.pp39.pp310-none-any.whl", hash = "sha256:c530833afc4707fe48524a44844493f36d8727f04dcce91fb978c414a8556cc6"},
+ {file = "coverage-7.4.0.tar.gz", hash = "sha256:707c0f58cb1712b8809ece32b68996ee1e609f71bd14615bd8f87a1293cb610e"},
]
[package.extras]
@@ -567,21 +574,26 @@ test-randomorder = ["pytest-randomly"]
[[package]]
name = "cyclonedx-python-lib"
-version = "4.2.3"
-description = "A library for producing CycloneDX SBOM (Software Bill of Materials) files."
+version = "6.3.0"
+description = "Python library for CycloneDX"
optional = false
-python-versions = ">=3.7,<4.0"
+python-versions = ">=3.8,<4.0"
files = [
- {file = "cyclonedx_python_lib-4.2.3-py3-none-any.whl", hash = "sha256:e9b923af525b6acf7bab917a35360b4b562b85dc15fde9eaa500828949adf73a"},
- {file = "cyclonedx_python_lib-4.2.3.tar.gz", hash = "sha256:904068b55d1665f0ea96f38307603cc14f95c3b421f1687fc2411326aefde3a6"},
+ {file = "cyclonedx_python_lib-6.3.0-py3-none-any.whl", hash = "sha256:0e73c1036c2f7fc67adc28aef807e6b44340ea70202aab197fb06b20ea165de8"},
+ {file = "cyclonedx_python_lib-6.3.0.tar.gz", hash = "sha256:82f2489de3c0cadad5af1ad7fa6b6a185f985746370245d38769699c734533c6"},
]
[package.dependencies]
license-expression = ">=30,<31"
packageurl-python = ">=0.11"
-py-serializable = ">=0.11.1,<0.12.0"
+py-serializable = ">=0.16,<0.18"
sortedcontainers = ">=2.4.0,<3.0.0"
+[package.extras]
+json-validation = ["jsonschema[format] (>=4.18,<5.0)"]
+validation = ["jsonschema[format] (>=4.18,<5.0)", "lxml (>=4,<6)"]
+xml-validation = ["lxml (>=4,<6)"]
+
[[package]]
name = "defusedxml"
version = "0.7.1"
@@ -722,13 +734,13 @@ pyflakes = ">=3.1.0,<3.2.0"
[[package]]
name = "flake8-bugbear"
-version = "23.12.2"
+version = "24.1.17"
description = "A plugin for flake8 finding likely bugs and design problems in your program. Contains warnings that don't belong in pyflakes and pycodestyle."
optional = false
python-versions = ">=3.8.1"
files = [
- {file = "flake8-bugbear-23.12.2.tar.gz", hash = "sha256:32b2903e22331ae04885dae25756a32a8c666c85142e933f43512a70f342052a"},
- {file = "flake8_bugbear-23.12.2-py3-none-any.whl", hash = "sha256:83324bad4d90fee4bf64dd69c61aff94debf8073fbd807c8b6a36eec7a2f0719"},
+ {file = "flake8-bugbear-24.1.17.tar.gz", hash = "sha256:bcb388a4f3b516258749b1e690ee394c082eff742f44595e3754cf5c7781c2c7"},
+ {file = "flake8_bugbear-24.1.17-py3-none-any.whl", hash = "sha256:46cc840ddaed26507cd0ada530d1526418b717ee76c9b5dfdbd238b5eab34139"},
]
[package.dependencies]
@@ -880,13 +892,13 @@ email = ["email-validator"]
[[package]]
name = "freezegun"
-version = "1.3.1"
+version = "1.4.0"
description = "Let your Python tests travel through time"
optional = false
python-versions = ">=3.7"
files = [
- {file = "freezegun-1.3.1-py3-none-any.whl", hash = "sha256:065e77a12624d05531afa87ade12a0b9bdb53495c4573893252a055b545ce3ea"},
- {file = "freezegun-1.3.1.tar.gz", hash = "sha256:48984397b3b58ef5dfc645d6a304b0060f612bcecfdaaf45ce8aff0077a6cb6a"},
+ {file = "freezegun-1.4.0-py3-none-any.whl", hash = "sha256:55e0fc3c84ebf0a96a5aa23ff8b53d70246479e9a68863f1fcac5a3e52f19dd6"},
+ {file = "freezegun-1.4.0.tar.gz", hash = "sha256:10939b0ba0ff5adaecf3b06a5c2f73071d9678e507c5eaedb23c761d56ac774b"},
]
[package.dependencies]
@@ -903,37 +915,6 @@ files = [
{file = "geojson-3.1.0.tar.gz", hash = "sha256:58a7fa40727ea058efc28b0e9ff0099eadf6d0965e04690830208d3ef571adac"},
]
-[[package]]
-name = "gitdb"
-version = "4.0.11"
-description = "Git Object Database"
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "gitdb-4.0.11-py3-none-any.whl", hash = "sha256:81a3407ddd2ee8df444cbacea00e2d038e40150acfa3001696fe0dcf1d3adfa4"},
- {file = "gitdb-4.0.11.tar.gz", hash = "sha256:bf5421126136d6d0af55bc1e7c1af1c397a34f5b7bd79e776cd3e89785c2b04b"},
-]
-
-[package.dependencies]
-smmap = ">=3.0.1,<6"
-
-[[package]]
-name = "gitpython"
-version = "3.1.40"
-description = "GitPython is a Python library used to interact with Git repositories"
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "GitPython-3.1.40-py3-none-any.whl", hash = "sha256:cf14627d5a8049ffbf49915732e5eddbe8134c3bdb9d476e6182b676fc573f8a"},
- {file = "GitPython-3.1.40.tar.gz", hash = "sha256:22b126e9ffb671fdd0c129796343a02bf67bf2994b35449ffc9321aa755e18a4"},
-]
-
-[package.dependencies]
-gitdb = ">=4.0.1,<5"
-
-[package.extras]
-test = ["black", "coverage[toml]", "ddt (>=1.1.1,!=1.4.3)", "mock", "mypy", "pre-commit", "pytest", "pytest-cov", "pytest-instafail", "pytest-subtests", "pytest-sugar"]
-
[[package]]
name = "govuk-bank-holidays"
version = "0.13"
@@ -1153,20 +1134,17 @@ files = [
[[package]]
name = "isort"
-version = "5.12.0"
+version = "5.13.2"
description = "A Python utility / library to sort Python imports."
optional = false
python-versions = ">=3.8.0"
files = [
- {file = "isort-5.12.0-py3-none-any.whl", hash = "sha256:f84c2818376e66cf843d497486ea8fed8700b340f308f076c6fb1229dff318b6"},
- {file = "isort-5.12.0.tar.gz", hash = "sha256:8bef7dde241278824a6d83f44a544709b065191b95b6e50894bdc722fcba0504"},
+ {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"},
+ {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"},
]
[package.extras]
-colors = ["colorama (>=0.4.3)"]
-pipfile-deprecated-finder = ["pip-shims (>=0.5.2)", "pipreqs", "requirementslib"]
-plugins = ["setuptools"]
-requirements-deprecated-finder = ["pip-api", "pipreqs"]
+colors = ["colorama (>=0.4.6)"]
[[package]]
name = "itsdangerous"
@@ -1181,13 +1159,13 @@ files = [
[[package]]
name = "jinja2"
-version = "3.1.2"
+version = "3.1.3"
description = "A very fast and expressive template engine."
optional = false
python-versions = ">=3.7"
files = [
- {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"},
- {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"},
+ {file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"},
+ {file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"},
]
[package.dependencies]
@@ -1381,6 +1359,24 @@ six = "*"
[package.extras]
restructuredtext = ["rst2ansi"]
+[[package]]
+name = "markdown"
+version = "3.5.2"
+description = "Python implementation of John Gruber's Markdown."
+optional = false
+python-versions = ">=3.8"
+files = [
+ {file = "Markdown-3.5.2-py3-none-any.whl", hash = "sha256:d43323865d89fc0cb9b20c75fc8ad313af307cc087e84b657d9eec768eddeadd"},
+ {file = "Markdown-3.5.2.tar.gz", hash = "sha256:e1ac7b3dc550ee80e602e71c1d168002f062e49f1b11e26a36264dafd4df2ef8"},
+]
+
+[package.dependencies]
+importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""}
+
+[package.extras]
+docs = ["mdx-gh-links (>=0.2)", "mkdocs (>=1.5)", "mkdocs-gen-files", "mkdocs-literate-nav", "mkdocs-nature (>=0.6)", "mkdocs-section-index", "mkdocstrings[python]"]
+testing = ["coverage", "pyyaml"]
+
[[package]]
name = "markdown-it-py"
version = "3.0.0"
@@ -1432,6 +1428,16 @@ files = [
{file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"},
{file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"},
{file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"},
+ {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:f698de3fd0c4e6972b92290a45bd9b1536bffe8c6759c62471efaa8acb4c37bc"},
+ {file = "MarkupSafe-2.1.3-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:aa57bd9cf8ae831a362185ee444e15a93ecb2e344c8e52e4d721ea3ab6ef1823"},
+ {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffcc3f7c66b5f5b7931a5aa68fc9cecc51e685ef90282f4a82f0f5e9b704ad11"},
+ {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:47d4f1c5f80fc62fdd7777d0d40a2e9dda0a05883ab11374334f6c4de38adffd"},
+ {file = "MarkupSafe-2.1.3-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1f67c7038d560d92149c060157d623c542173016c4babc0c1913cca0564b9939"},
+ {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:9aad3c1755095ce347e26488214ef77e0485a3c34a50c5a5e2471dff60b9dd9c"},
+ {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:14ff806850827afd6b07a5f32bd917fb7f45b046ba40c57abdb636674a8b559c"},
+ {file = "MarkupSafe-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8f9293864fe09b8149f0cc42ce56e3f0e54de883a9de90cd427f191c346eb2e1"},
+ {file = "MarkupSafe-2.1.3-cp312-cp312-win32.whl", hash = "sha256:715d3562f79d540f251b99ebd6d8baa547118974341db04f5ad06d5ea3eb8007"},
+ {file = "MarkupSafe-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:1b8dd8c3fd14349433c79fa8abeb573a55fc0fdd769133baac1f5e07abf54aeb"},
{file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"},
{file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"},
{file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"},
@@ -1499,13 +1505,13 @@ files = [
[[package]]
name = "moto"
-version = "4.2.11"
+version = "4.2.13"
description = ""
optional = false
python-versions = ">=3.7"
files = [
- {file = "moto-4.2.11-py2.py3-none-any.whl", hash = "sha256:58c12ab9ee69b6a5d1cddf83611ba4071508f07894317c57844b3ae6dc5bcd38"},
- {file = "moto-4.2.11.tar.gz", hash = "sha256:2da62d52eaa765dfe2762c920f0a88a58f3a09e04581c91db967d92faec848f1"},
+ {file = "moto-4.2.13-py2.py3-none-any.whl", hash = "sha256:93e0fd13b624bd79115494f833308c3641b2be0fc9f4f18aa9264aa01f6168e0"},
+ {file = "moto-4.2.13.tar.gz", hash = "sha256:01aef6a489a725c8d725bd3dc6f70ff1bedaee3e2641752e4b471ff0ede4b4d7"},
]
[package.dependencies]
@@ -1520,29 +1526,29 @@ werkzeug = ">=0.5,<2.2.0 || >2.2.0,<2.2.1 || >2.2.1"
xmltodict = "*"
[package.extras]
-all = ["PyYAML (>=5.1)", "aws-xray-sdk (>=0.93,!=0.96)", "cfn-lint (>=0.40.0)", "docker (>=3.0.0)", "ecdsa (!=0.15)", "graphql-core", "jsondiff (>=1.1.2)", "multipart", "openapi-spec-validator (>=0.5.0)", "py-partiql-parser (==0.4.2)", "pyparsing (>=3.0.7)", "python-jose[cryptography] (>=3.1.0,<4.0.0)", "setuptools", "sshpubkeys (>=3.1.0)"]
+all = ["PyYAML (>=5.1)", "aws-xray-sdk (>=0.93,!=0.96)", "cfn-lint (>=0.40.0)", "docker (>=3.0.0)", "ecdsa (!=0.15)", "graphql-core", "jsondiff (>=1.1.2)", "multipart", "openapi-spec-validator (>=0.5.0)", "py-partiql-parser (==0.5.0)", "pyparsing (>=3.0.7)", "python-jose[cryptography] (>=3.1.0,<4.0.0)", "setuptools", "sshpubkeys (>=3.1.0)"]
apigateway = ["PyYAML (>=5.1)", "ecdsa (!=0.15)", "openapi-spec-validator (>=0.5.0)", "python-jose[cryptography] (>=3.1.0,<4.0.0)"]
apigatewayv2 = ["PyYAML (>=5.1)"]
appsync = ["graphql-core"]
awslambda = ["docker (>=3.0.0)"]
batch = ["docker (>=3.0.0)"]
-cloudformation = ["PyYAML (>=5.1)", "aws-xray-sdk (>=0.93,!=0.96)", "cfn-lint (>=0.40.0)", "docker (>=3.0.0)", "ecdsa (!=0.15)", "graphql-core", "jsondiff (>=1.1.2)", "openapi-spec-validator (>=0.5.0)", "py-partiql-parser (==0.4.2)", "pyparsing (>=3.0.7)", "python-jose[cryptography] (>=3.1.0,<4.0.0)", "setuptools", "sshpubkeys (>=3.1.0)"]
+cloudformation = ["PyYAML (>=5.1)", "aws-xray-sdk (>=0.93,!=0.96)", "cfn-lint (>=0.40.0)", "docker (>=3.0.0)", "ecdsa (!=0.15)", "graphql-core", "jsondiff (>=1.1.2)", "openapi-spec-validator (>=0.5.0)", "py-partiql-parser (==0.5.0)", "pyparsing (>=3.0.7)", "python-jose[cryptography] (>=3.1.0,<4.0.0)", "setuptools", "sshpubkeys (>=3.1.0)"]
cognitoidp = ["ecdsa (!=0.15)", "python-jose[cryptography] (>=3.1.0,<4.0.0)"]
ds = ["sshpubkeys (>=3.1.0)"]
-dynamodb = ["docker (>=3.0.0)", "py-partiql-parser (==0.4.2)"]
-dynamodbstreams = ["docker (>=3.0.0)", "py-partiql-parser (==0.4.2)"]
+dynamodb = ["docker (>=3.0.0)", "py-partiql-parser (==0.5.0)"]
+dynamodbstreams = ["docker (>=3.0.0)", "py-partiql-parser (==0.5.0)"]
ebs = ["sshpubkeys (>=3.1.0)"]
ec2 = ["sshpubkeys (>=3.1.0)"]
efs = ["sshpubkeys (>=3.1.0)"]
eks = ["sshpubkeys (>=3.1.0)"]
glue = ["pyparsing (>=3.0.7)"]
iotdata = ["jsondiff (>=1.1.2)"]
-proxy = ["PyYAML (>=5.1)", "aws-xray-sdk (>=0.93,!=0.96)", "cfn-lint (>=0.40.0)", "docker (>=2.5.1)", "ecdsa (!=0.15)", "graphql-core", "jsondiff (>=1.1.2)", "multipart", "openapi-spec-validator (>=0.5.0)", "py-partiql-parser (==0.4.2)", "pyparsing (>=3.0.7)", "python-jose[cryptography] (>=3.1.0,<4.0.0)", "setuptools", "sshpubkeys (>=3.1.0)"]
-resourcegroupstaggingapi = ["PyYAML (>=5.1)", "cfn-lint (>=0.40.0)", "docker (>=3.0.0)", "ecdsa (!=0.15)", "graphql-core", "jsondiff (>=1.1.2)", "openapi-spec-validator (>=0.5.0)", "py-partiql-parser (==0.4.2)", "pyparsing (>=3.0.7)", "python-jose[cryptography] (>=3.1.0,<4.0.0)", "sshpubkeys (>=3.1.0)"]
+proxy = ["PyYAML (>=5.1)", "aws-xray-sdk (>=0.93,!=0.96)", "cfn-lint (>=0.40.0)", "docker (>=2.5.1)", "ecdsa (!=0.15)", "graphql-core", "jsondiff (>=1.1.2)", "multipart", "openapi-spec-validator (>=0.5.0)", "py-partiql-parser (==0.5.0)", "pyparsing (>=3.0.7)", "python-jose[cryptography] (>=3.1.0,<4.0.0)", "setuptools", "sshpubkeys (>=3.1.0)"]
+resourcegroupstaggingapi = ["PyYAML (>=5.1)", "cfn-lint (>=0.40.0)", "docker (>=3.0.0)", "ecdsa (!=0.15)", "graphql-core", "jsondiff (>=1.1.2)", "openapi-spec-validator (>=0.5.0)", "py-partiql-parser (==0.5.0)", "pyparsing (>=3.0.7)", "python-jose[cryptography] (>=3.1.0,<4.0.0)", "sshpubkeys (>=3.1.0)"]
route53resolver = ["sshpubkeys (>=3.1.0)"]
-s3 = ["PyYAML (>=5.1)", "py-partiql-parser (==0.4.2)"]
-s3crc32c = ["PyYAML (>=5.1)", "crc32c", "py-partiql-parser (==0.4.2)"]
-server = ["PyYAML (>=5.1)", "aws-xray-sdk (>=0.93,!=0.96)", "cfn-lint (>=0.40.0)", "docker (>=3.0.0)", "ecdsa (!=0.15)", "flask (!=2.2.0,!=2.2.1)", "flask-cors", "graphql-core", "jsondiff (>=1.1.2)", "openapi-spec-validator (>=0.5.0)", "py-partiql-parser (==0.4.2)", "pyparsing (>=3.0.7)", "python-jose[cryptography] (>=3.1.0,<4.0.0)", "setuptools", "sshpubkeys (>=3.1.0)"]
+s3 = ["PyYAML (>=5.1)", "py-partiql-parser (==0.5.0)"]
+s3crc32c = ["PyYAML (>=5.1)", "crc32c", "py-partiql-parser (==0.5.0)"]
+server = ["PyYAML (>=5.1)", "aws-xray-sdk (>=0.93,!=0.96)", "cfn-lint (>=0.40.0)", "docker (>=3.0.0)", "ecdsa (!=0.15)", "flask (!=2.2.0,!=2.2.1)", "flask-cors", "graphql-core", "jsondiff (>=1.1.2)", "openapi-spec-validator (>=0.5.0)", "py-partiql-parser (==0.5.0)", "pyparsing (>=3.0.7)", "python-jose[cryptography] (>=3.1.0,<4.0.0)", "setuptools", "sshpubkeys (>=3.1.0)"]
ssm = ["PyYAML (>=5.1)"]
xray = ["aws-xray-sdk (>=0.93,!=0.96)", "setuptools"]
@@ -1624,26 +1630,40 @@ files = [
[[package]]
name = "newrelic"
-version = "9.3.0"
+version = "9.6.0"
description = "New Relic Python Agent"
optional = false
python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*"
files = [
- {file = "newrelic-9.3.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:f25980a8c86bda75344b5b22edd5d6ad41777776e1ed8a495eb6e38e9813b02c"},
- {file = "newrelic-9.3.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:adefb6620c5a5d75b4bf3ec565cc4d91abcb5cc4e5569f5f82ab29fa3d5aa2d9"},
- {file = "newrelic-9.3.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:27056ab8a3cf39787fc1f93f55243749dd25786f65b15032b6fbb3e8534f4c2a"},
- {file = "newrelic-9.3.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:663fa1c074661f93abf681c8f6028de64744c67f004b722835de1372b6bc4d19"},
- {file = "newrelic-9.3.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:83346c8f0bcb8f07f74c88f6073e4d44a2e2b3eeec5b2ebe8c450ae695d02b88"},
- {file = "newrelic-9.3.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2187078d7b0054b30f39dbf891cb2caa71a7046f6d0258fb8c0fcfce70777774"},
- {file = "newrelic-9.3.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fe0f5edd4eba3d62742b3b0730bb4f826be015dd7fbb9c455b01c410421661a2"},
- {file = "newrelic-9.3.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8577a0f733174bee70a147f71aa061fb44a593a1be841feffe12dff480c6e02e"},
- {file = "newrelic-9.3.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:234594655ac0fbe938d34ce5d5d38549d0f5cc11d0552170903ad09574bb4499"},
- {file = "newrelic-9.3.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1ce38949404e974b566b21487394f5ea36a1fb80ba36cc4a6e8fb968d2e150ab"},
- {file = "newrelic-9.3.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a750ffed8aedacdafcb548b3d3f45630a96862db630c9f72520ebbfe91e4e9e0"},
- {file = "newrelic-9.3.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d01d0f0c22b1290dbd2756ef120cfbe154179aae35e1dfc579f8bfd574066105"},
- {file = "newrelic-9.3.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:53e860c6eacfdef879f23fbbf7d76d8bbb90b725a1c422f62439c6edfceebc21"},
- {file = "newrelic-9.3.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4caad3017cc978f3130fe2f3933f233c214a4850a595458b733282b3b7f7e886"},
- {file = "newrelic-9.3.0.tar.gz", hash = "sha256:c2dd685527433f6b6fbffe58f83852b46c24b9713ebb8ee7af647e04c2de3ee4"},
+ {file = "newrelic-9.6.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:09dad0db993402e166e37d99302c2ad5588b4ff1e5b814819540ca5ec2bd3cea"},
+ {file = "newrelic-9.6.0-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:aeadc0bd8140738af4d7d7ea9d0a3dfa7b6f37e02fe48c0a035ccb6cac881770"},
+ {file = "newrelic-9.6.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:16fb3bfde6dca0edab358c2b083386e2e764c463d85ffeb41cd6ce7a279bd7f6"},
+ {file = "newrelic-9.6.0-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:1ba2fc6ce0b6b9a399a1e5db7e480ba51b431cb3cc6ba136a4c0e76877cf972d"},
+ {file = "newrelic-9.6.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7fe927f79db2dd5432a362aedde98543e93599b26d5c2e065bc563991be7f347"},
+ {file = "newrelic-9.6.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3fefffff32e6605ddf26d51030ea1a41b9f7f606fccbb5f8d4b43c36d755acd1"},
+ {file = "newrelic-9.6.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:0a68dd85a7cf4c5039ea94c8975f86a7c516dd492d8bf08976ab0a459918da9b"},
+ {file = "newrelic-9.6.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:74a9a49ef596aa9f7560f6bb1480480b8f9b652c7e03de173ef06b0dc2355799"},
+ {file = "newrelic-9.6.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26255aec6b29bc17e5e089bb18fbf004f4c765386ba61d1289681aa7ba538207"},
+ {file = "newrelic-9.6.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c6f21f7a69294f64daa9be59c1c1e0eba889e85ed83afc67ed0a63c8efc9ff2b"},
+ {file = "newrelic-9.6.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:5ecd44ff5b39138469778b5224a1a373867d21a0ca2389d093d99939cb422947"},
+ {file = "newrelic-9.6.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c6c4aa8b91dcb5403862c3e5efa57751a8b4e778576916bb22cafbfc03d47807"},
+ {file = "newrelic-9.6.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6f02fb9166533f22fdb465f7a8d4b364178d94561b159cc9922f4d27491fd7d1"},
+ {file = "newrelic-9.6.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:703ee92dda983eb4dcbcc300750a815ff3a5401995c3cdd587ac785b6d747e0e"},
+ {file = "newrelic-9.6.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ce4646554246c6577f5aea358516ad5cf2c35fcef0cd16dbd01bbe89b19b4ccb"},
+ {file = "newrelic-9.6.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:5fae1f3addb422629c4d3e9d011de40629b4d078073ad0517ad42d4ddc7e8441"},
+ {file = "newrelic-9.6.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:333bd5aedb4f814447ac3aa1a0cc76910a64f13385aa6481a97f87585af52cbf"},
+ {file = "newrelic-9.6.0-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:51a5aa22ce7813294ca5dc89a0d86ce4774bb9201028305975efdcf93ef36e0e"},
+ {file = "newrelic-9.6.0-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:01c0eb630bb18261241a37aa0a70cb6f706079a1f58f59f2bb64f26fda54ffc5"},
+ {file = "newrelic-9.6.0-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:eb82f31754dadd27676fe6f681b25db7af75eb854e332b92f1ea17584efc322c"},
+ {file = "newrelic-9.6.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38256e67b2d295229240c29e9949cdae40c9ac987d51f569f57f52ed2aef027f"},
+ {file = "newrelic-9.6.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f681c005b1d44ec01b3fe1a7b4ec589572f681f27130ea367155ac0245e133d4"},
+ {file = "newrelic-9.6.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:23d54298bc0a936a17e25fdca52c2eefbc50966985738e31d62cfe12ea3f60ba"},
+ {file = "newrelic-9.6.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:f346aeb418c9da6cff4382cb4f50d314ae5126a6a6037f01224b0072eb30ff49"},
+ {file = "newrelic-9.6.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:26560dba7cd36c5c787e3dfd6c3c5d1e8e8605c82dc7ffee143880456d8650e5"},
+ {file = "newrelic-9.6.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d168349041922f206b72fa30a930c0752d8b76b4a2fd7774d4cb2843aa804e32"},
+ {file = "newrelic-9.6.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:72b378b4aa4a52d2ac14be962f0de747f573b9912365981c0b3fc16bd12b5133"},
+ {file = "newrelic-9.6.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5ab342ab7e3189e244e44238aa8563eeff3a5b5896b77d2d4dbf2c5d0bfe7d73"},
+ {file = "newrelic-9.6.0.tar.gz", hash = "sha256:3db642583cfee951f185696fe365b41d8aff733b23867e88de97de114e3cfcf8"},
]
[package.extras]
@@ -1665,12 +1685,12 @@ setuptools = "*"
[[package]]
name = "notifications-python-client"
-version = "8.1.0"
+version = "8.2.0"
description = "Python API client for GOV.UK Notify."
optional = false
python-versions = ">=3.7"
files = [
- {file = "notifications_python_client-8.1.0-py3-none-any.whl", hash = "sha256:8aec1f7a4ba592fd699eae899df8ccca9ccafb614f47826c07b318ca903c9c13"},
+ {file = "notifications_python_client-8.2.0-py3-none-any.whl", hash = "sha256:8cd8bd01ae603a972a5413c430ca42b16f6481f37d98406c3e9b68c1c192f59e"},
]
[package.dependencies]
@@ -1680,7 +1700,7 @@ requests = ">=2.0.0"
[[package]]
name = "notifications-utils"
-version = "0.2.4"
+version = "0.2.7"
description = ""
optional = false
python-versions = ">=3.9,<3.12"
@@ -1705,7 +1725,7 @@ geojson = "^3.0.1"
govuk-bank-holidays = "^0.13"
idna = "^3.4"
itsdangerous = "^2.1.2"
-jinja2 = "^3.1.2"
+jinja2 = "^3.1.3"
jmespath = "^1.0.1"
markupsafe = "^2.1.2"
mistune = "==0.8.4"
@@ -1732,7 +1752,7 @@ werkzeug = "^3.0.1"
type = "git"
url = "https://github.com/GSA/notifications-utils.git"
reference = "HEAD"
-resolved_reference = "bd604dc32ea80b5d8a1157b09653e4bd4a755a6e"
+resolved_reference = "b6cee72f45dbcd48b59447fa08bbac59e15a7b98"
[[package]]
name = "numpy"
@@ -1891,18 +1911,18 @@ pip = "*"
[[package]]
name = "pip-audit"
-version = "2.6.1"
+version = "2.7.0"
description = "A tool for scanning Python environments for known vulnerabilities"
optional = false
-python-versions = ">=3.7"
+python-versions = ">=3.8"
files = [
- {file = "pip_audit-2.6.1-py3-none-any.whl", hash = "sha256:8a32bb67dca6a76c244bbccebed562c0f6957b1fc9d34d59a9ec0fbff0672ae0"},
- {file = "pip_audit-2.6.1.tar.gz", hash = "sha256:55c9bd18b0fe3959f73397db08d257c6012ad1826825e3d74cb6c3f79e95c245"},
+ {file = "pip_audit-2.7.0-py3-none-any.whl", hash = "sha256:83e039740653eb9ef1a78b1540ed441600cd88a560588ba2c0a169180685a522"},
+ {file = "pip_audit-2.7.0.tar.gz", hash = "sha256:67740c5b1d5d967a258c3dfefc46f9713a2819c48062505ddf4b29de101c2b75"},
]
[package.dependencies]
CacheControl = {version = ">=0.13.0", extras = ["filecache"]}
-cyclonedx-python-lib = ">=4.0,<5.0"
+cyclonedx-python-lib = ">=5,<7"
html5lib = ">=1.1"
packaging = ">=23.0.0"
pip-api = ">=0.0.28"
@@ -1914,8 +1934,8 @@ toml = ">=0.10"
[package.extras]
dev = ["build", "bump (>=1.3.2)", "pip-audit[doc,lint,test]"]
doc = ["pdoc"]
-lint = ["black (>=22.3.0)", "interrogate", "isort", "mypy", "ruff (<0.0.281)", "types-html5lib", "types-requests", "types-toml"]
-test = ["coverage[toml]", "pretend", "pytest", "pytest-cov"]
+lint = ["interrogate", "mypy", "ruff (<0.1.12)", "types-html5lib", "types-requests", "types-toml"]
+test = ["coverage[toml] (>=7.0,!=7.3.3,<8.0)", "pretend", "pytest", "pytest-cov"]
[[package]]
name = "pip-requirements-parser"
@@ -1988,13 +2008,13 @@ testing = ["pytest", "pytest-benchmark"]
[[package]]
name = "pre-commit"
-version = "3.5.0"
+version = "3.6.0"
description = "A framework for managing and maintaining multi-language pre-commit hooks."
optional = false
-python-versions = ">=3.8"
+python-versions = ">=3.9"
files = [
- {file = "pre_commit-3.5.0-py2.py3-none-any.whl", hash = "sha256:841dc9aef25daba9a0238cd27984041fa0467b4199fc4852e27950664919f660"},
- {file = "pre_commit-3.5.0.tar.gz", hash = "sha256:5804465c675b659b0862f07907f96295d490822a450c4c40e747d0b1c6ebcb32"},
+ {file = "pre_commit-3.6.0-py2.py3-none-any.whl", hash = "sha256:c255039ef399049a5544b6ce13d135caba8f2c28c3b4033277a788f434308376"},
+ {file = "pre_commit-3.6.0.tar.gz", hash = "sha256:d30bad9abf165f7785c15a21a1f46da7d0677cb00ee7ff4c579fd38922efe15d"},
]
[package.dependencies]
@@ -2006,13 +2026,13 @@ virtualenv = ">=20.10.0"
[[package]]
name = "py-serializable"
-version = "0.11.1"
+version = "0.17.1"
description = "Library for serializing and deserializing Python Objects to and from JSON and XML."
optional = false
python-versions = ">=3.7,<4.0"
files = [
- {file = "py-serializable-0.11.1.tar.gz", hash = "sha256:ba0e1287b9e4f645a5334f1913abd8e647e7250209f84f55dce3909498a6f586"},
- {file = "py_serializable-0.11.1-py3-none-any.whl", hash = "sha256:79e21f0672822e6200b15f45ce9f636e8126466f62dbd7d488c67313c72b5c3e"},
+ {file = "py-serializable-0.17.1.tar.gz", hash = "sha256:875bb9c01df77f563dfcd1e75bb4244b5596083d3aad4ccd3fb63e1f5a9d3e5f"},
+ {file = "py_serializable-0.17.1-py3-none-any.whl", hash = "sha256:389c2254d912bec3a44acdac667c947d73c59325050d5ae66386e1ed7108a45a"},
]
[package.dependencies]
@@ -2257,13 +2277,13 @@ certifi = "*"
[[package]]
name = "pytest"
-version = "7.4.3"
+version = "7.4.4"
description = "pytest: simple powerful testing with Python"
optional = false
python-versions = ">=3.7"
files = [
- {file = "pytest-7.4.3-py3-none-any.whl", hash = "sha256:0d009c083ea859a71b76adf7c1d502e4bc170b80a8ef002da5806527b9591fac"},
- {file = "pytest-7.4.3.tar.gz", hash = "sha256:d989d136982de4e3b29dabcc838ad581c64e8ed52c11fbe86ddebd9da0818cd5"},
+ {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"},
+ {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"},
]
[package.dependencies]
@@ -2380,13 +2400,13 @@ six = ">=1.5"
[[package]]
name = "python-dotenv"
-version = "1.0.0"
+version = "1.0.1"
description = "Read key-value pairs from a .env file and set them as environment variables"
optional = false
python-versions = ">=3.8"
files = [
- {file = "python-dotenv-1.0.0.tar.gz", hash = "sha256:a8df96034aae6d2d50a4ebe8216326c61c3eb64836776504fcca410e5937a3ba"},
- {file = "python_dotenv-1.0.0-py3-none-any.whl", hash = "sha256:f5971a9226b701070a4bf2c38c89e5a3f0d64de8debda981d1db98583009122a"},
+ {file = "python-dotenv-1.0.1.tar.gz", hash = "sha256:e324ee90a023d808f1959c46bcbc04446a10ced277783dc6ee09987c37ec10ca"},
+ {file = "python_dotenv-1.0.1-py3-none-any.whl", hash = "sha256:f7b63ef50f1b690dddf550d03497b66d609393b40b564ed0d674909a68ebf16a"},
]
[package.extras]
@@ -2456,6 +2476,7 @@ files = [
{file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"},
{file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"},
{file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"},
+ {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"},
{file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"},
{file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"},
{file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"},
@@ -2825,17 +2846,6 @@ files = [
{file = "smartypants-2.0.1-py2.py3-none-any.whl", hash = "sha256:8db97f7cbdf08d15b158a86037cd9e116b4cf37703d24e0419a0d64ca5808f0d"},
]
-[[package]]
-name = "smmap"
-version = "5.0.1"
-description = "A pure Python implementation of a sliding window memory map manager"
-optional = false
-python-versions = ">=3.7"
-files = [
- {file = "smmap-5.0.1-py3-none-any.whl", hash = "sha256:e6d8668fa5f93e706934a62d7b4db19c8d9eb8cf2adbb75ef1b675aa332b69da"},
- {file = "smmap-5.0.1.tar.gz", hash = "sha256:dceeb6c0028fdb6734471eb07c0cd2aae706ccaecab45965ee83f11c8d3b1f62"},
-]
-
[[package]]
name = "sortedcontainers"
version = "2.4.0"
@@ -3007,13 +3017,13 @@ watchdog = ["watchdog (>=2.3)"]
[[package]]
name = "wtforms"
-version = "3.1.1"
+version = "3.1.2"
description = "Form validation and rendering for Python web development."
optional = false
python-versions = ">=3.8"
files = [
- {file = "wtforms-3.1.1-py3-none-any.whl", hash = "sha256:ae7c54b29806c70f7bce8eb9f24afceb10ca5c32af3d9f04f74d2f66ccc5c7e0"},
- {file = "wtforms-3.1.1.tar.gz", hash = "sha256:5e51df8af9a60f6beead75efa10975e97768825a82146a65c7cbf5b915990620"},
+ {file = "wtforms-3.1.2-py3-none-any.whl", hash = "sha256:bf831c042829c8cdbad74c27575098d541d039b1faa74c771545ecac916f2c07"},
+ {file = "wtforms-3.1.2.tar.gz", hash = "sha256:f8d76180d7239c94c6322f7990ae1216dae3659b7aa1cee94b6318bdffb474b9"},
]
[package.dependencies]
@@ -3078,4 +3088,4 @@ testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "p
[metadata]
lock-version = "2.0"
python-versions = ">=3.9,<3.12"
-content-hash = "cbc1f1bd362257b864862976a30c1c80c8e2117378074593c46cdb96c32eae4c"
+content-hash = "94e6fe2e143f12eaa31f01b5a9206012a6a48908d98daa9fa2f531e986e4a6d3"
diff --git a/pyproject.toml b/pyproject.toml
index 37c8b2d5ca..2aed42337e 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -8,6 +8,7 @@ readme = "README.md"
[tool.poetry.dependencies]
python = ">=3.9,<3.12"
ago = "~=0.0.95"
+beautifulsoup4 = "^4.12.3"
blinker = "~=1.7"
exceptiongroup = "==1.2.0"
flask = "~=2.3"
@@ -22,7 +23,7 @@ humanize = "~=4.9"
itsdangerous = "~=2.1"
jinja2 = "~=3.1"
newrelic = "*"
-notifications-python-client = "==8.1.0"
+notifications-python-client = "==8.2.0"
notifications-utils = {git = "https://github.com/GSA/notifications-utils.git"}
pyexcel = "==0.7.0"
pyexcel-io = "==0.6.6"
@@ -31,29 +32,29 @@ pyexcel-xls = "==0.7.0"
pyexcel-xlsx = "==0.6.0"
openpyxl = "==3.0.10"
pyproj = "==3.6.1"
-python-dotenv = "==1.0.0"
+python-dotenv = "==1.0.1"
pytz = "==2023.3.post1"
rtreelib = "==0.2.0"
werkzeug = "^3.0.1"
wtforms = "~=3.1"
+markdown = "^3.5.2"
[tool.poetry.group.dev.dependencies]
bandit = "*"
-beautifulsoup4 = "^4.12.2"
-black = "^23.11.0"
+black = "^23.12.1"
coverage = "*"
-freezegun = "^1.3.1"
+freezegun = "^1.4.0"
flake8 = "^6.1.0"
-flake8-bugbear = "^23.12.2"
+flake8-bugbear = "^24.1.17"
flake8-print = "^5.0.0"
flake8-pytest-style = "^1.7.2"
-isort = "^5.12.0"
+isort = "^5.13.2"
jinja2-cli = {version = "==0.8.2", extras = ["yaml"]}
moto = "^4.2"
pip-audit = "*"
-pre-commit = "^3.5.0"
-pytest = "^7.4.3"
+pre-commit = "^3.6.0"
+pytest = "^7.4.4"
pytest-env = "^1.1.3"
pytest-mock = "^3.12.0"
pytest-playwright = "^0.4.3"
diff --git a/sample.env b/sample.env
index 84913967fd..bd667f3b5c 100644
--- a/sample.env
+++ b/sample.env
@@ -42,3 +42,4 @@ LOGIN_DOT_GOV_ACCESS_TOKEN_URL="https://idp.int.identitysandbox.gov/api/openid_c
LOGIN_DOT_GOV_LOGOUT_URL="https://idp.int.identitysandbox.gov/openid_connect/logout?client_id=urn:gov:gsa:openidconnect.profiles:sp:sso:gsa:test_notify_gov&post_logout_redirect_uri=http://localhost:6012/sign-out"
LOGIN_DOT_GOV_BASE_LOGOUT_URL="https://idp.int.identitysandbox.gov/openid_connect/logout?"
LOGIN_DOT_GOV_SIGNOUT_REDIRECT="http://localhost:6012/sign-out"
+LOGIN_DOT_GOV_INITIAL_SIGNIN_URL="https://idp.int.identitysandbox.gov/openid_connect/authorize?acr_values=http%3A%2F%2Fidmanagement.gov%2Fns%2Fassurance%2Fial%2F1&client_id=urn:gov:gsa:openidconnect.profiles:sp:sso:gsa:test_notify_gov&nonce=01234567890123456789012345&prompt=select_account&redirect_uri=http://localhost:6012/sign-in&response_type=code&scope=openid+email&state=abcdefghijklmnopabcdefghijklmnop"
diff --git a/tests/__init__.py b/tests/__init__.py
index bf65271476..0f28275118 100644
--- a/tests/__init__.py
+++ b/tests/__init__.py
@@ -222,7 +222,6 @@ def organization_json(
agreement_signed_on_behalf_of_name=None,
agreement_signed_on_behalf_of_email_address=None,
organization_type="federal",
- request_to_go_live_notes=None,
notes=None,
billing_contact_email_addresses=None,
billing_contact_names=None,
@@ -248,7 +247,6 @@ def organization_json(
"agreement_signed_on_behalf_of_name": agreement_signed_on_behalf_of_name,
"agreement_signed_on_behalf_of_email_address": agreement_signed_on_behalf_of_email_address,
"domains": domains or [],
- "request_to_go_live_notes": request_to_go_live_notes,
"count_of_live_services": len(services),
"notes": notes,
"billing_contact_email_addresses": billing_contact_email_addresses,
diff --git a/tests/app/main/test_formatters.py b/tests/app/main/test_formatters.py
index 46e76da0da..b9fd7f975b 100644
--- a/tests/app/main/test_formatters.py
+++ b/tests/app/main/test_formatters.py
@@ -2,10 +2,12 @@
from functools import partial
import pytest
-from flask import url_for
+from flask import Flask, url_for
from freezegun import freeze_time
from app.formatters import (
+ apply_html_class,
+ convert_markdown_template,
email_safe,
format_datetime_relative,
format_delta,
@@ -166,3 +168,29 @@ def test_email_safe_return_dot_separated_email_domain(service_name, safe_email):
def test_format_delta():
naive_now_utc = datetime.utcnow().isoformat()
assert format_delta(naive_now_utc) == "just now"
+
+
+@pytest.mark.usefixtures("fake_jinja_template")
+def test_jinja_format(fake_jinja_template):
+ app = Flask("app")
+ with app.app_context():
+ assert (
+ convert_markdown_template(fake_jinja_template, test=True) == "
True
"
+ )
+
+
+@pytest.mark.usefixtures("fake_markdown_file")
+def test_markdown_format(fake_markdown_file):
+ app = Flask("app")
+ with app.app_context():
+ assert (
+ convert_markdown_template(fake_markdown_file, test=True) == "
Test
"
+ )
+
+
+@pytest.mark.usefixtures("fake_soup_template")
+def test_soup_format(fake_soup_template):
+ assert (
+ apply_html_class([["h1", "testClass"]], fake_soup_template)
+ == '
Test
'
+ )
diff --git a/tests/app/main/test_permissions.py b/tests/app/main/test_permissions.py
index fea2c2fb56..3ad521ae2e 100644
--- a/tests/app/main/test_permissions.py
+++ b/tests/app/main/test_permissions.py
@@ -125,7 +125,6 @@ def test_service_navigation_for_org_user(
(
"Send messages",
"Sent messages",
- "Team members",
),
403,
),
@@ -134,7 +133,6 @@ def test_service_navigation_for_org_user(
(
"Send messages",
"Sent messages",
- "Team members",
"Usage",
),
200,
diff --git a/tests/app/main/views/accounts/test_choose_accounts.py b/tests/app/main/views/accounts/test_choose_accounts.py
index 8b94ebe64e..dae5c712ad 100644
--- a/tests/app/main/views/accounts/test_choose_accounts.py
+++ b/tests/app/main/views/accounts/test_choose_accounts.py
@@ -363,7 +363,7 @@ def test_should_show_back_to_service_if_user_belongs_to_service(
):
mock_get_service.return_value = service_one
expected_page_text = (
- "Test Service Switch service " "Send messages " "Dashboard " "Team members"
+ "Test Service Switch service " "Send messages " "Dashboard " "Usage"
) # TODO: set sidebar variables in common test module
page = client_request.get(
diff --git a/tests/app/main/views/organizations/test_organizations.py b/tests/app/main/views/organizations/test_organizations.py
index 0b330b5a9a..7679e14293 100644
--- a/tests/app/main/views/organizations/test_organizations.py
+++ b/tests/app/main/views/organizations/test_organizations.py
@@ -928,10 +928,8 @@ def test_organization_settings_for_platform_admin(
"Label Value Action",
"Name Test organization Change organization name",
"Sector Federal government Change sector for the organization",
- "Request to go live notes None Change go live notes for the organization",
"Billing details None Change billing details for the organization",
"Notes None Change the notes for the organization",
- "Default email branding GOV.UK Change default email branding for the organization",
"Known email domains None Change known email domains for the organization",
]
@@ -1347,48 +1345,6 @@ def test_update_organization_with_non_unique_name(
)
-def test_get_edit_organization_go_live_notes_page(
- client_request,
- platform_admin_user,
- mock_get_organization,
- organization_one,
-):
- client_request.login(platform_admin_user)
- page = client_request.get(
- ".edit_organization_go_live_notes",
- org_id=organization_one["id"],
- )
- assert page.find("textarea", id="request_to_go_live_notes")
-
-
-@pytest.mark.parametrize(
- ("input_note", "saved_note"),
- [("Needs permission", "Needs permission"), (" ", None)],
-)
-def test_post_edit_organization_go_live_notes_updates_go_live_notes(
- client_request,
- platform_admin_user,
- mock_get_organization,
- mock_update_organization,
- organization_one,
- input_note,
- saved_note,
-):
- client_request.login(platform_admin_user)
- client_request.post(
- ".edit_organization_go_live_notes",
- org_id=organization_one["id"],
- _data={"request_to_go_live_notes": input_note},
- _expected_redirect=url_for(
- ".organization_settings",
- org_id=organization_one["id"],
- ),
- )
- mock_update_organization.assert_called_once_with(
- organization_one["id"], request_to_go_live_notes=saved_note
- )
-
-
def test_organization_settings_links_to_edit_organization_notes_page(
mocker,
mock_get_organization,
diff --git a/tests/app/main/views/service_settings/test_email_branding_requests.py b/tests/app/main/views/service_settings/test_email_branding_requests.py
deleted file mode 100644
index ccab8a87a6..0000000000
--- a/tests/app/main/views/service_settings/test_email_branding_requests.py
+++ /dev/null
@@ -1,519 +0,0 @@
-from unittest.mock import ANY, PropertyMock
-
-import pytest
-from flask import url_for
-from notifications_utils.clients.zendesk.zendesk_client import NotifySupportTicket
-
-from tests import sample_uuid
-from tests.conftest import ORGANISATION_ID, SERVICE_ONE_ID, normalize_spaces
-
-
-@pytest.mark.parametrize(
- ("organization_type", "expected_options"),
- [
- (
- "other",
- [
- ("something_else", "Something else"),
- ],
- ),
- ],
-)
-def test_email_branding_request_page_when_no_branding_is_set(
- service_one,
- client_request,
- mocker,
- mock_get_email_branding,
- organization_type,
- expected_options,
-):
- service_one["email_branding"] = None
- service_one["organization_type"] = organization_type
-
- mocker.patch(
- "app.models.service.Service.email_branding_id",
- new_callable=PropertyMock,
- return_value=None,
- )
-
- page = client_request.get(".email_branding_request", service_id=SERVICE_ONE_ID)
-
- assert mock_get_email_branding.called is False
- assert page.find_all("iframe")[1]["src"] == url_for(
- "main.email_template", branding_style="__NONE__"
- )
-
- button_text = normalize_spaces(page.select_one(".page-footer button").text)
-
- assert [
- (
- radio["value"],
- page.select_one("label[for={}]".format(radio["id"])).text.strip(),
- )
- for radio in page.select("input[type=radio]")
- ] == expected_options
-
- assert button_text == "Continue"
-
-
-def test_email_branding_request_page_shows_branding_if_set(
- mocker,
- service_one,
- client_request,
- mock_get_email_branding,
- mock_get_service_organization,
-):
- mocker.patch(
- "app.models.service.Service.email_branding_id",
- new_callable=PropertyMock,
- return_value="some-random-branding",
- )
-
- page = client_request.get(".email_branding_request", service_id=SERVICE_ONE_ID)
- assert page.find_all("iframe")[1]["src"] == url_for(
- "main.email_template", branding_style="some-random-branding"
- )
-
-
-def test_email_branding_request_page_back_link(
- client_request,
-):
- page = client_request.get(".email_branding_request", service_id=SERVICE_ONE_ID)
-
- back_link = page.select_one("a.usa-back-link")
- assert len(back_link) > 0, "No back link found on the page"
- assert back_link["href"] == url_for(".service_settings", service_id=SERVICE_ONE_ID)
-
-
-@pytest.mark.parametrize(
- ("data", "org_type", "endpoint"),
- [
- (
- {
- "options": "govuk",
- },
- "federal",
- "main.email_branding_govuk",
- ),
- (
- {
- "options": "govuk_and_org",
- },
- "federal",
- "main.email_branding_govuk_and_org",
- ),
- (
- {
- "options": "something_else",
- },
- "federal",
- "main.email_branding_something_else",
- ),
- ],
-)
-def test_email_branding_request_submit(
- client_request,
- service_one,
- mocker,
- mock_get_email_branding,
- organization_one,
- data,
- org_type,
- endpoint,
-):
- organization_one["organization_type"] = org_type
- service_one["email_branding"] = sample_uuid()
- service_one["organization"] = organization_one
-
- mocker.patch(
- "app.organizations_client.get_organization",
- return_value=organization_one,
- )
-
- client_request.post(
- ".email_branding_request",
- service_id=SERVICE_ONE_ID,
- _data=data,
- _expected_status=302,
- _expected_redirect=url_for(
- endpoint,
- service_id=SERVICE_ONE_ID,
- ),
- )
-
-
-def test_email_branding_request_submit_when_no_radio_button_is_selected(
- client_request,
- service_one,
- mock_get_email_branding,
-):
- service_one["email_branding"] = sample_uuid()
-
- page = client_request.post(
- ".email_branding_request",
- service_id=SERVICE_ONE_ID,
- _data={"options": ""},
- _follow_redirects=True,
- )
- assert page.h1.text == "Change email branding"
- assert (
- normalize_spaces(page.select_one(".error-message").text) == "Select an option"
- )
-
-
-@pytest.mark.parametrize(
- ("endpoint", "expected_heading"),
- [
- ("main.email_branding_govuk_and_org", "Before you request new branding"),
- ],
-)
-def test_email_branding_description_pages_for_org_branding(
- client_request,
- mocker,
- service_one,
- organization_one,
- mock_get_email_branding,
- endpoint,
- expected_heading,
-):
- service_one["email_branding"] = sample_uuid()
- service_one["organization"] = organization_one
-
- mocker.patch(
- "app.organizations_client.get_organization",
- return_value=organization_one,
- )
-
- page = client_request.get(
- endpoint,
- service_id=SERVICE_ONE_ID,
- )
- assert page.h1.text == expected_heading
- assert (
- normalize_spaces(page.select_one(".page-footer button").text)
- == "Request new branding"
- )
-
-
-@pytest.mark.parametrize(
- ("endpoint", "service_org_type", "branding_preview_id"),
- [("main.email_branding_govuk", "central", "__NONE__")],
-)
-@pytest.mark.skip(reason="Update for TTS")
-def test_email_branding_govuk_and_nhs_pages(
- client_request,
- mocker,
- service_one,
- organization_one,
- mock_get_email_branding,
- endpoint,
- service_org_type,
- branding_preview_id,
-):
- organization_one["organization_type"] = service_org_type
- service_one["email_branding"] = sample_uuid()
- service_one["organization"] = organization_one
-
- mocker.patch(
- "app.organizations_client.get_organization",
- return_value=organization_one,
- )
-
- page = client_request.get(
- endpoint,
- service_id=SERVICE_ONE_ID,
- )
- assert page.h1.text == "Check your new branding"
- assert "Emails from service one will look like this" in normalize_spaces(page.text)
- assert page.find("iframe")["src"] == url_for(
- "main.email_template", branding_style=branding_preview_id
- )
- assert (
- normalize_spaces(page.select_one(".page-footer button").text)
- == "Use this branding"
- )
-
-
-@pytest.mark.skip(reason="Update for TTS")
-def test_email_branding_something_else_page(client_request, service_one):
- # expect to have a "NHS" option as well as the
- # fallback, so back button goes to choices page
- service_one["organization_type"] = "nhs_central"
-
- page = client_request.get(
- "main.email_branding_something_else",
- service_id=SERVICE_ONE_ID,
- )
- assert normalize_spaces(page.h1.text) == "Describe the branding you want"
- assert page.select_one("textarea")["name"] == ("something_else")
- assert (
- normalize_spaces(page.select_one(".page-footer button").text)
- == "Request new branding"
- )
- assert page.select_one(".usa-back-link")["href"] == url_for(
- "main.email_branding_request",
- service_id=SERVICE_ONE_ID,
- )
-
-
-def test_get_email_branding_something_else_page_is_only_option(
- client_request, service_one
-):
- # should only have a "something else" option
- # so back button goes back to settings page
- service_one["organization_type"] = "other"
-
- page = client_request.get(
- "main.email_branding_something_else",
- service_id=SERVICE_ONE_ID,
- )
- assert page.select_one(".usa-back-link")["href"] == url_for(
- "main.service_settings",
- service_id=SERVICE_ONE_ID,
- )
-
-
-@pytest.mark.parametrize(
- "endpoint",
- [
- ("main.email_branding_govuk"),
- ("main.email_branding_govuk_and_org"),
- ("main.email_branding_organization"),
- ],
-)
-def test_email_branding_pages_give_404_if_selected_branding_not_allowed(
- client_request,
- endpoint,
-):
- # The only email branding allowed is 'something_else', so trying to visit any of the other
- # endpoints gives a 404 status code.
- client_request.get(endpoint, service_id=SERVICE_ONE_ID, _expected_status=404)
-
-
-def test_email_branding_govuk_submit(
- mocker,
- client_request,
- service_one,
- organization_one,
- no_reply_to_email_addresses,
- mock_get_email_branding,
- single_sms_sender,
- mock_update_service,
-):
- mocker.patch(
- "app.organizations_client.get_organization",
- return_value=organization_one,
- )
- mocker.patch(
- "app.models.service.Service.organization_id",
- new_callable=PropertyMock,
- return_value=ORGANISATION_ID,
- )
- service_one["email_branding"] = sample_uuid()
-
- page = client_request.post(
- ".email_branding_govuk",
- service_id=SERVICE_ONE_ID,
- _follow_redirects=True,
- )
-
- mock_update_service.assert_called_once_with(
- SERVICE_ONE_ID,
- email_branding=None,
- )
- assert page.h1.text == "Settings"
- assert (
- normalize_spaces(page.select_one(".banner-default").text)
- == "You’ve updated your email branding"
- )
-
-
-@pytest.mark.skip(reason="Update for TTS")
-def test_email_branding_govuk_and_org_submit(
- mocker,
- client_request,
- service_one,
- organization_one,
- no_reply_to_email_addresses,
- mock_get_email_branding,
- single_sms_sender,
-):
- mocker.patch(
- "app.organizations_client.get_organization",
- return_value=organization_one,
- )
- mocker.patch(
- "app.models.service.Service.organization_id",
- new_callable=PropertyMock,
- return_value=ORGANISATION_ID,
- )
- service_one["email_branding"] = sample_uuid()
-
- mock_create_ticket = mocker.spy(NotifySupportTicket, "__init__")
- mock_send_ticket_to_zendesk = mocker.patch(
- "app.main.views.service_settings.zendesk_client.send_ticket_to_zendesk",
- autospec=True,
- )
-
- page = client_request.post(
- ".email_branding_govuk_and_org",
- service_id=SERVICE_ONE_ID,
- _follow_redirects=True,
- )
-
- mock_create_ticket.assert_called_once_with(
- ANY,
- message="\n".join(
- [
- "Organization: organization one",
- "Service: service one",
- "http://localhost/services/596364a0-858e-42c8-9062-a8fe822260eb",
- "",
- "---",
- "Current branding: Organization name",
- "Branding requested: GOV.UK and organization one\n",
- ]
- ),
- subject="Email branding request - service one",
- ticket_type="question",
- user_name="Test User",
- user_email="test@user.gsa.gov",
- org_id=ORGANISATION_ID,
- org_type="central",
- service_id=SERVICE_ONE_ID,
- )
- mock_send_ticket_to_zendesk.assert_called_once()
- assert normalize_spaces(page.select_one(".banner-default").text) == (
- "Thanks for your branding request. We’ll get back to you "
- "within one working day."
- )
-
-
-@pytest.mark.skip(reason="Update for TTS")
-def test_email_branding_organization_submit(
- mocker,
- client_request,
- service_one,
- organization_one,
- no_reply_to_email_addresses,
- mock_get_email_branding,
- single_sms_sender,
-):
- mocker.patch(
- "app.organizations_client.get_organization",
- return_value=organization_one,
- )
- mocker.patch(
- "app.models.service.Service.organization_id",
- new_callable=PropertyMock,
- return_value=ORGANISATION_ID,
- )
- service_one["email_branding"] = sample_uuid()
-
- mock_create_ticket = mocker.spy(NotifySupportTicket, "__init__")
- mock_send_ticket_to_zendesk = mocker.patch(
- "app.main.views.service_settings.zendesk_client.send_ticket_to_zendesk",
- autospec=True,
- )
-
- page = client_request.post(
- ".email_branding_organization",
- service_id=SERVICE_ONE_ID,
- _follow_redirects=True,
- )
-
- mock_create_ticket.assert_called_once_with(
- ANY,
- message="\n".join(
- [
- "Organization: organization one",
- "Service: service one",
- "http://localhost/services/596364a0-858e-42c8-9062-a8fe822260eb",
- "",
- "---",
- "Current branding: Organization name",
- "Branding requested: organization one\n",
- ]
- ),
- subject="Email branding request - service one",
- ticket_type="question",
- user_name="Test User",
- user_email="test@user.gsa.gov",
- org_id=ORGANISATION_ID,
- org_type="central",
- service_id=SERVICE_ONE_ID,
- )
- mock_send_ticket_to_zendesk.assert_called_once()
- assert normalize_spaces(page.select_one(".banner-default").text) == (
- "Thanks for your branding request. We’ll get back to you "
- "within one working day."
- )
-
-
-def test_email_branding_something_else_submit(
- client_request,
- mocker,
- service_one,
- no_reply_to_email_addresses,
- mock_get_email_branding,
- single_sms_sender,
-):
- service_one["email_branding"] = sample_uuid()
- service_one["organization_type"] = "nhs_local"
-
- mock_create_ticket = mocker.spy(NotifySupportTicket, "__init__")
- mock_send_ticket_to_zendesk = mocker.patch(
- "app.main.views.service_settings.zendesk_client.send_ticket_to_zendesk",
- autospec=True,
- )
-
- page = client_request.post(
- ".email_branding_something_else",
- service_id=SERVICE_ONE_ID,
- _data={"something_else": "Homer Simpson"},
- _follow_redirects=True,
- )
-
- mock_create_ticket.assert_called_once_with(
- ANY,
- message="\n".join(
- [
- "Organization: Can’t tell (domain is user.gsa.gov)",
- "Service: service one",
- "http://localhost/services/596364a0-858e-42c8-9062-a8fe822260eb",
- "",
- "---",
- "Current branding: Organization name",
- "Branding requested: Something else\n",
- "Homer Simpson\n",
- ]
- ),
- subject="Email branding request - service one",
- ticket_type="question",
- user_name="Test User",
- user_email="test@user.gsa.gov",
- org_id=None,
- org_type="nhs_local",
- service_id=SERVICE_ONE_ID,
- )
- mock_send_ticket_to_zendesk.assert_called_once()
- assert normalize_spaces(page.select_one(".banner-default").text) == (
- "Thanks for your branding request. We’ll get back to you "
- "within one working day."
- )
-
-
-def test_email_branding_something_else_submit_shows_error_if_textbox_is_empty(
- client_request,
-):
- page = client_request.post(
- ".email_branding_something_else",
- service_id=SERVICE_ONE_ID,
- _data={"something_else": ""},
- _follow_redirects=True,
- )
- assert normalize_spaces(page.h1.text) == "Describe the branding you want"
- assert (
- normalize_spaces(page.select_one(".usa-error-message").text)
- == "Error: Cannot be empty"
- )
diff --git a/tests/app/main/views/service_settings/test_service_settings.py b/tests/app/main/views/service_settings/test_service_settings.py
index ede9f1e924..a36d2c0ffe 100644
--- a/tests/app/main/views/service_settings/test_service_settings.py
+++ b/tests/app/main/views/service_settings/test_service_settings.py
@@ -1,19 +1,15 @@
-from datetime import datetime
from functools import partial
-from unittest.mock import ANY, Mock, PropertyMock, call
-from urllib.parse import parse_qs, urlparse
+from unittest.mock import Mock, PropertyMock, call
from uuid import uuid4
import pytest
from flask import url_for
from freezegun import freeze_time
from notifications_python_client.errors import HTTPError
-from notifications_utils.clients.zendesk.zendesk_client import NotifySupportTicket
import app
from tests import (
find_element_by_tag_and_partial_text,
- invite_json,
organization_json,
sample_uuid,
service_json,
@@ -31,7 +27,6 @@
create_platform_admin_user,
create_reply_to_email_address,
create_sms_sender,
- create_template,
normalize_spaces,
)
@@ -83,7 +78,6 @@ def _mock_get_service_settings_page_common(
"Rate limit 3,000 per minute Change rate limit",
"Message batch limit 1,000 per send Change message batch limit",
"Free text message allowance 250,000 per year Change free text message allowance",
- "Email branding GOV.UK Change email branding (admin view)",
"Custom data retention Email – 7 days Change data retention",
"Receive inbound SMS Off Change your settings for Receive inbound SMS",
"Email authentication Off Change your settings for Email authentication",
@@ -121,39 +115,6 @@ def test_should_show_overview(
app.service_api_client.get_service.assert_called_with(SERVICE_ONE_ID)
-@pytest.mark.usefixtures("_mock_get_service_settings_page_common")
-def test_no_go_live_link_for_service_without_organization(
- client_request,
- mocker,
- no_reply_to_email_addresses,
- single_sms_sender,
- platform_admin_user,
-):
- mocker.patch("app.organizations_client.get_organization", return_value=None)
- client_request.login(platform_admin_user)
- page = client_request.get("main.service_settings", service_id=SERVICE_ONE_ID)
-
- assert page.find("h1").text == "Settings"
-
- is_live = find_element_by_tag_and_partial_text(page, tag="td", string="Live")
- assert (
- normalize_spaces(is_live.find_next_sibling().text)
- == "No (organization must be set first)"
- )
-
- organization = find_element_by_tag_and_partial_text(
- page, tag="td", string="Organization"
- )
- assert (
- normalize_spaces(organization.find_next_siblings()[0].text)
- == "Not set Federal government"
- )
- assert (
- normalize_spaces(organization.find_next_siblings()[1].text)
- == "Change organization for service"
- )
-
-
@pytest.mark.usefixtures("_mock_get_service_settings_page_common")
def test_organization_name_links_to_org_dashboard(
client_request,
@@ -256,13 +217,11 @@ def test_should_show_overview_for_service_with_more_things_set(
service_one,
single_reply_to_email_address,
single_sms_sender,
- mock_get_email_branding,
permissions,
expected_rows,
):
client_request.login(active_user_with_permissions)
service_one["permissions"] = permissions
- service_one["email_branding"] = uuid4()
page = client_request.get("main.service_settings", service_id=service_one["id"])
for index, row in enumerate(expected_rows):
assert row == " ".join(page.find_all("tr")[index + 1].text.split())
@@ -551,1146 +510,40 @@ def test_switch_service_to_count_as_live(
)
-def test_should_not_allow_duplicate_service_names(
- client_request,
- mock_update_service_raise_httperror_duplicate_name,
- service_one,
-):
- page = client_request.post(
- "main.service_name_change",
- service_id=SERVICE_ONE_ID,
- _data={"name": "SErvICE TWO"},
- _expected_status=200,
- )
-
- assert "This service name is already in use" in page.text
-
-
-def test_should_redirect_after_service_name_change(
- client_request,
- mock_update_service,
-):
- client_request.post(
- "main.service_name_change",
- service_id=SERVICE_ONE_ID,
- _data={"name": "New Name"},
- _expected_status=302,
- _expected_redirect=url_for(
- "main.service_settings",
- service_id=SERVICE_ONE_ID,
- ),
- )
-
- mock_update_service.assert_called_once_with(
- SERVICE_ONE_ID,
- name="New Name",
- email_from="new.name",
- )
-
-
-@pytest.mark.parametrize(
- ("volumes", "consent_to_research", "expected_estimated_volumes_item"),
- [
- ((0, 0), None, "Tell us how many messages you expect to send Not completed"),
- ((1, 0), None, "Tell us how many messages you expect to send Not completed"),
- ((1, 0), False, "Tell us how many messages you expect to send Completed"),
- ((1, 0), True, "Tell us how many messages you expect to send Completed"),
- ((9, 99), True, "Tell us how many messages you expect to send Completed"),
- ],
-)
-def test_should_check_if_estimated_volumes_provided(
- client_request,
- mocker,
- single_sms_sender,
- single_reply_to_email_address,
- mock_get_service_templates,
- mock_get_users_by_service,
- mock_get_organization,
- mock_get_invites_for_service,
- volumes,
- consent_to_research,
- expected_estimated_volumes_item,
-):
- for volume, channel in zip(volumes, ("sms", "email")):
- mocker.patch(
- "app.models.service.Service.volume_{}".format(channel),
- create=True,
- new_callable=PropertyMock,
- return_value=volume,
- )
-
- mocker.patch(
- "app.models.service.Service.consent_to_research",
- create=True,
- new_callable=PropertyMock,
- return_value=consent_to_research,
- )
-
- page = client_request.get("main.request_to_go_live", service_id=SERVICE_ONE_ID)
- assert page.h1.text == "Before you request to go live"
-
- assert normalize_spaces(page.select_one(".task-list .task-list-item").text) == (
- expected_estimated_volumes_item
- )
-
-
-@pytest.mark.parametrize(
- (
- "volume_email",
- "count_of_email_templates",
- "reply_to_email_addresses",
- "expected_reply_to_checklist_item",
- ),
- [
- (None, 1, [], "Add a reply-to email address Not completed"),
- (None, 1, [{}], "Add a reply-to email address Completed"),
- (1, 1, [], "Add a reply-to email address Not completed"),
- (1, 1, [{}], "Add a reply-to email address Completed"),
- (1, 0, [], "Add a reply-to email address Not completed"),
- (1, 0, [{}], "Add a reply-to email address Completed"),
- ],
-)
-def test_should_check_for_reply_to_on_go_live(
- client_request,
- mocker,
- service_one,
- fake_uuid,
- single_sms_sender,
- volume_email,
- count_of_email_templates,
- reply_to_email_addresses,
- expected_reply_to_checklist_item,
- mock_get_invites_for_service,
- mock_get_users_by_service,
-):
- mocker.patch(
- "app.service_api_client.get_service_templates",
- return_value={
- "data": [
- create_template(template_type="email")
- for _ in range(0, count_of_email_templates)
- ]
- },
- )
-
- mock_get_reply_to_email_addresses = mocker.patch(
- "app.main.views.service_settings.service_api_client.get_reply_to_email_addresses",
- return_value=reply_to_email_addresses,
- )
-
- for channel, volume in (("email", volume_email), ("sms", 0)):
- mocker.patch(
- "app.models.service.Service.volume_{}".format(channel),
- create=True,
- new_callable=PropertyMock,
- return_value=volume,
- )
-
- page = client_request.get("main.request_to_go_live", service_id=SERVICE_ONE_ID)
- assert page.h1.text == "Before you request to go live"
-
- checklist_items = page.select(".task-list .task-list-item")
- assert normalize_spaces(checklist_items[3].text) == expected_reply_to_checklist_item
-
- if count_of_email_templates:
- mock_get_reply_to_email_addresses.assert_called_once_with(SERVICE_ONE_ID)
-
-
-@pytest.mark.parametrize(
- (
- "volume_email",
- "count_of_email_templates",
- "reply_to_email_addresses",
- "expected_reply_to_checklist_item",
- ),
- [
- (None, 0, [], ""),
- (0, 0, [], ""),
- ],
-)
-def test_should_check_for_reply_to_on_go_live_index_error(
- client_request,
- mocker,
- service_one,
- fake_uuid,
- single_sms_sender,
- volume_email,
- count_of_email_templates,
- reply_to_email_addresses,
- expected_reply_to_checklist_item,
- mock_get_invites_for_service,
- mock_get_users_by_service,
-):
- mocker.patch(
- "app.service_api_client.get_service_templates",
- return_value={
- "data": [
- create_template(template_type="email")
- for _ in range(0, count_of_email_templates)
- ]
- },
- )
-
- mocker.patch(
- "app.main.views.service_settings.service_api_client.get_reply_to_email_addresses",
- return_value=reply_to_email_addresses,
- )
-
- for channel, volume in (("email", volume_email), ("sms", 0)):
- mocker.patch(
- "app.models.service.Service.volume_{}".format(channel),
- create=True,
- new_callable=PropertyMock,
- return_value=volume,
- )
-
- page = client_request.get("main.request_to_go_live", service_id=SERVICE_ONE_ID)
- assert page.h1.text == "Before you request to go live"
- checklist_items = page.select(".task-list .task-list-item")
-
- with pytest.raises(expected_exception=IndexError):
- assert (
- normalize_spaces(checklist_items[3].text)
- == expected_reply_to_checklist_item
- )
-
-
-@pytest.mark.parametrize(
- (
- "count_of_users_with_manage_service",
- "count_of_invites_with_manage_service",
- "expected_user_checklist_item",
- ),
- [
- (
- 1,
- 0,
- "Add a team member who can manage settings, team and usage Not completed",
- ),
- (2, 0, "Add a team member who can manage settings, team and usage Completed"),
- (1, 1, "Add a team member who can manage settings, team and usage Completed"),
- ],
-)
-@pytest.mark.parametrize(
- ("count_of_templates", "expected_templates_checklist_item"),
- [
- (
- 0,
- "Add templates with examples of the content you plan to send Not completed",
- ),
- (1, "Add templates with examples of the content you plan to send Completed"),
- (2, "Add templates with examples of the content you plan to send Completed"),
- ],
-)
-def test_should_check_for_sending_things_right(
- client_request,
- mocker,
- service_one,
- fake_uuid,
- single_sms_sender,
- count_of_users_with_manage_service,
- count_of_invites_with_manage_service,
- expected_user_checklist_item,
- count_of_templates,
- expected_templates_checklist_item,
- active_user_with_permissions,
- active_user_no_settings_permission,
- single_reply_to_email_address,
-):
- mocker.patch(
- "app.service_api_client.get_service_templates",
- return_value={
- "data": [
- create_template(template_type="sms")
- for _ in range(0, count_of_templates)
- ]
- },
- )
-
- mock_get_users = mocker.patch(
- "app.models.user.Users.client_method",
- return_value=(
- [active_user_with_permissions] * count_of_users_with_manage_service
- + [active_user_no_settings_permission]
- ),
- )
- invite_one = invite_json(
- id_=uuid4(),
- from_user=service_one["users"][0],
- service_id=service_one["id"],
- email_address="invited_user@test.gsa.gov",
- permissions="view_activity,send_messages,manage_service,manage_api_keys",
- created_at=datetime.utcnow(),
- status="pending",
- auth_type="sms_auth",
- folder_permissions=[],
- )
-
- invite_two = invite_one.copy()
- invite_two["permissions"] = "view_activity"
-
- mock_get_invites = mocker.patch(
- "app.models.user.InvitedUsers.client_method",
- return_value=(
- ([invite_one] * count_of_invites_with_manage_service) + [invite_two]
- ),
- )
-
- page = client_request.get("main.request_to_go_live", service_id=SERVICE_ONE_ID)
- assert page.h1.text == "Before you request to go live"
-
- checklist_items = page.select(".task-list .task-list-item")
- assert normalize_spaces(checklist_items[1].text) == expected_user_checklist_item
- assert (
- normalize_spaces(checklist_items[2].text) == expected_templates_checklist_item
- )
-
- mock_get_users.assert_called_once_with(SERVICE_ONE_ID)
- mock_get_invites.assert_called_once_with(SERVICE_ONE_ID)
-
-
-@pytest.mark.parametrize(
- ("checklist_completed", "expected_button"),
- [
- (True, True),
- (False, False),
- ],
-)
-def test_should_not_show_go_live_button_if_checklist_not_complete(
- client_request,
- mocker,
- mock_get_service_templates,
- mock_get_users_by_service,
- mock_get_service_organization,
- mock_get_invites_for_service,
- single_sms_sender,
- checklist_completed,
- expected_button,
-):
- mocker.patch(
- "app.models.service.Service.go_live_checklist_completed",
- new_callable=PropertyMock,
- return_value=checklist_completed,
- )
-
- for channel in ("email", "sms"):
- mocker.patch(
- "app.models.service.Service.volume_{}".format(channel),
- create=True,
- new_callable=PropertyMock,
- return_value=0,
- )
-
- page = client_request.get("main.request_to_go_live", service_id=SERVICE_ONE_ID)
- assert page.h1.text == "Before you request to go live"
-
- if expected_button:
- assert page.select_one("form")["method"] == "post"
- assert "action" not in page.select_one("form")
- assert normalize_spaces(page.select("main p")[0].text) == (
- "When we receive your request we’ll get back to you within one working day."
- )
- assert normalize_spaces(page.select("main p")[1].text) == (
- "By requesting to go live you’re agreeing to our terms of use."
- )
- assert page.select_one("main [type=submit]").text.strip() == (
- "Request to go live"
- )
- else:
- assert not page.select("form")
- assert not page.select("main [type=submit]")
- assert len(page.select("main p")) == 1
- assert normalize_spaces(page.select_one("main p").text) == (
- "You must complete these steps before you can request to go live."
- )
-
-
-@pytest.mark.parametrize(
- ("go_live_at", "message"),
- [
- (None, "‘service one’ is already live."),
- ("2020-10-09 13:55:20", "‘service one’ went live on 9 October 2020."),
- ],
-)
-def test_request_to_go_live_redirects_if_service_already_live(
- client_request,
- service_one,
- go_live_at,
- message,
-):
- service_one["restricted"] = False
- service_one["go_live_at"] = go_live_at
-
- page = client_request.get(
- "main.request_to_go_live",
- service_id=SERVICE_ONE_ID,
- )
-
- assert page.h1.text == "Your service is already live"
- assert normalize_spaces(page.select_one("main p").text) == message
-
-
-@pytest.mark.parametrize(
- (
- "estimated_sms_volume",
- "organization_type",
- "count_of_sms_templates",
- "sms_senders",
- "expected_sms_sender_checklist_item",
- ),
- [
- (
- 0,
- "state",
- 0,
- [],
- "",
- ),
- (
- None,
- "state",
- 0,
- [{"is_default": True, "sms_sender": "GOVUK"}],
- "",
- ),
- (
- None,
- "federal",
- 99,
- [{"is_default": True, "sms_sender": "GOVUK"}],
- "",
- ),
- (
- 1,
- "federal",
- 99,
- [{"is_default": True, "sms_sender": "GOVUK"}],
- "",
- ),
- (
- 1,
- "state",
- 1,
- [],
- "Change your text message sender name Not completed",
- ),
- (
- 1,
- "state",
- 1,
- [
- {"is_default": False, "sms_sender": "GOVUK"},
- {"is_default": True, "sms_sender": "KUVOG"},
- ],
- "Change your text message sender name Completed",
- ),
- ],
-)
-def test_should_check_for_sms_sender_on_go_live(
- client_request,
- service_one,
- mocker,
- mock_get_organization,
- mock_get_invites_for_service,
- organization_type,
- count_of_sms_templates,
- sms_senders,
- expected_sms_sender_checklist_item,
- estimated_sms_volume,
-):
- service_one["organization_type"] = organization_type
-
- mocker.patch(
- "app.service_api_client.get_service_templates",
- return_value={
- "data": [
- create_template(template_type="sms")
- for _ in range(0, count_of_sms_templates)
- ]
- },
- )
-
- mocker.patch(
- "app.models.service.Service.has_team_members",
- return_value=True,
- )
-
- mock_get_sms_senders = mocker.patch(
- "app.main.views.service_settings.service_api_client.get_sms_senders",
- return_value=sms_senders,
- )
-
- for channel, volume in (("email", 0), ("sms", estimated_sms_volume)):
- mocker.patch(
- "app.models.service.Service.volume_{}".format(channel),
- create=True,
- new_callable=PropertyMock,
- return_value=volume,
- )
-
- with pytest.raises(expected_exception=IndexError):
- simple_statement_for_test_should_check_for_sms_sender_on_go_live(
- client_request, expected_sms_sender_checklist_item, mock_get_sms_senders
- )
-
-
-def simple_statement_for_test_should_check_for_sms_sender_on_go_live(
- client_request, expected_sms_sender_checklist_item, mock_get_sms_senders
-):
- page = client_request.get("main.request_to_go_live", service_id=SERVICE_ONE_ID)
- assert page.h1.text == "Before you request to go live"
- checklist_items = page.select(".task-list .task-list-item")
-
- assert (
- normalize_spaces(checklist_items[3].text) == expected_sms_sender_checklist_item
- )
-
- mock_get_sms_senders.assert_called_once_with(SERVICE_ONE_ID)
-
-
-def test_non_gov_user_is_told_they_cant_go_live(
- client_request,
- api_nongov_user_active,
- mock_get_invites_for_service,
- mocker,
- mock_get_organizations,
- mock_get_organization,
-):
- mocker.patch(
- "app.models.service.Service.has_team_members",
- return_value=False,
- )
- mocker.patch(
- "app.models.service.Service.all_templates",
- new_callable=PropertyMock,
- return_value=[],
- )
- mocker.patch(
- "app.main.views.service_settings.service_api_client.get_sms_senders",
- return_value=[],
- )
- mocker.patch(
- "app.main.views.service_settings.service_api_client.get_reply_to_email_addresses",
- return_value=[],
- )
- client_request.login(api_nongov_user_active)
- page = client_request.get("main.request_to_go_live", service_id=SERVICE_ONE_ID)
- assert normalize_spaces(page.select_one("main p").text) == (
- "Only team members with a government email address can request to go live."
- )
- assert len(page.select("main form")) == 0
- assert len(page.select("main button")) == 0
-
-
-@pytest.mark.parametrize(
- ("consent_to_research", "displayed_consent"),
- [
- (None, None),
- (True, "yes"),
- (False, "no"),
- ],
-)
-@pytest.mark.parametrize(
- ("volumes", "displayed_volumes"),
- [
- (
- (("email", None), ("sms", None)),
- (None, None),
- ),
- (
- (("email", 1234), ("sms", 0)),
- ("1,234", "0"),
- ),
- ],
-)
-def test_should_show_estimate_volumes(
- mocker,
- client_request,
- volumes,
- displayed_volumes,
- consent_to_research,
- displayed_consent,
-):
- for channel, volume in volumes:
- mocker.patch(
- "app.models.service.Service.volume_{}".format(channel),
- create=True,
- new_callable=PropertyMock,
- return_value=volume,
- )
- mocker.patch(
- "app.models.service.Service.consent_to_research",
- create=True,
- new_callable=PropertyMock,
- return_value=consent_to_research,
- )
- page = client_request.get("main.estimate_usage", service_id=SERVICE_ONE_ID)
- assert page.h1.text == "Tell us how many messages you expect to send"
- for channel, label, hint, value in (
- (
- "email",
- "How many emails do you expect to send in the next year?",
- "For example, 50,000",
- displayed_volumes[0],
- ),
- (
- "sms",
- "How many text messages do you expect to send in the next year?",
- "For example, 50,000",
- displayed_volumes[1],
- ),
- ):
- assert (
- normalize_spaces(
- page.select_one("label[for=volume_{}]".format(channel)).text
- )
- == label
- )
- assert (
- normalize_spaces(page.select_one("#volume_{}-hint".format(channel)).text)
- == hint
- )
- assert page.select_one("#volume_{}".format(channel)).get("value") == value
-
- assert len(page.select("input[type=radio]")) == 2
-
- if displayed_consent is None:
- assert len(page.select("input[checked]")) == 0
- else:
- assert len(page.select("input[checked]")) == 1
- assert page.select_one("input[checked]")["value"] == displayed_consent
-
-
-@pytest.mark.parametrize(
- ("consent_to_research", "expected_persisted_consent_to_research"),
- [
- ("yes", True),
- ("no", False),
- ],
-)
-def test_should_show_persist_estimated_volumes(
- client_request,
- mock_update_service,
- consent_to_research,
- expected_persisted_consent_to_research,
-):
- client_request.post(
- "main.estimate_usage",
- service_id=SERVICE_ONE_ID,
- _data={
- "volume_email": "1,234,567",
- "volume_sms": "",
- "consent_to_research": consent_to_research,
- },
- _expected_status=302,
- _expected_redirect=url_for(
- "main.request_to_go_live",
- service_id=SERVICE_ONE_ID,
- ),
- )
- mock_update_service.assert_called_once_with(
- SERVICE_ONE_ID,
- volume_email=1234567,
- volume_sms=0,
- consent_to_research=expected_persisted_consent_to_research,
- )
-
-
-@pytest.mark.parametrize(
- ("data", "error_selector", "expected_error_message"),
- [
- (
- {
- "volume_email": "1234",
- "volume_sms": "2000000001",
- "consent_to_research": "yes",
- },
- "#volume_sms-error",
- "Number of text messages must be 2,000,000,000 or less",
- ),
- (
- {
- "volume_email": "1 234",
- "volume_sms": "0",
- "consent_to_research": "",
- },
- '[data-error-label="consent_to_research"]',
- "Select yes or no",
- ),
- ],
-)
-def test_should_error_if_bad_estimations_given(
- client_request,
- mock_update_service,
- data,
- error_selector,
- expected_error_message,
-):
- page = client_request.post(
- "main.estimate_usage",
- service_id=SERVICE_ONE_ID,
- _data=data,
- _expected_status=200,
- )
- assert expected_error_message in page.select_one(error_selector).text
- assert mock_update_service.called is False
-
-
-def test_should_error_if_all_volumes_zero(
- client_request,
- mock_update_service,
-):
- page = client_request.post(
- "main.estimate_usage",
- service_id=SERVICE_ONE_ID,
- _data={
- "volume_email": "",
- "volume_sms": "0",
- "consent_to_research": "yes",
- },
- _expected_status=200,
- )
- assert page.select("input[type=text]")[0].get("value") is None
- assert page.select("input[type=text]")[1]["value"] == "0"
- assert normalize_spaces(page.select_one(".banner-dangerous").text) == (
- "Enter the number of messages you expect to send in the next year"
- )
- assert mock_update_service.called is False
-
-
-def test_should_not_default_to_zero_if_some_fields_dont_validate(
- client_request,
- mock_update_service,
-):
- page = client_request.post(
- "main.estimate_usage",
- service_id=SERVICE_ONE_ID,
- _data={
- "volume_email": "aaaaaaaaaaaaa",
- "volume_sms": "",
- "consent_to_research": "yes",
- },
- _expected_status=200,
- )
- assert page.select("input[type=text]")[0]["value"] == "aaaaaaaaaaaaa"
- assert page.select("input[type=text]")[1].get("value") is None
- assert (
- normalize_spaces(page.select_one("#volume_email-error").text)
- == "Error: Enter the number of emails you expect to send"
- )
- assert mock_update_service.called is False
-
-
-def test_non_gov_users_cant_request_to_go_live(
- client_request,
- api_nongov_user_active,
- mock_get_organizations,
-):
- client_request.login(api_nongov_user_active)
- client_request.post(
- "main.request_to_go_live",
- service_id=SERVICE_ONE_ID,
- _expected_status=403,
- )
-
-
-@pytest.mark.usefixtures("_mock_get_service_settings_page_common")
-@pytest.mark.parametrize(
- ("volumes", "displayed_volumes", "formatted_displayed_volumes"),
- [
- (
- (("email", None), ("sms", None)),
- ", ",
- ("Emails in next year: \n" "Text messages in next year: \n"),
- ),
- (
- (("email", 1234), ("sms", 0)),
- "0, 1234", # This is a different order to match the spreadsheet
- ("Emails in next year: 1,234\n" "Text messages in next year: 0\n"),
- ),
- ],
-)
-@freeze_time("2012-12-21 13:12:12.12354")
-def test_should_redirect_after_request_to_go_live(
- client_request,
- mocker,
- active_user_with_permissions,
- single_reply_to_email_address,
- mock_get_organizations_and_services_for_user,
- single_sms_sender,
- mock_get_service_templates,
- mock_get_users_by_service,
- mock_update_service,
- mock_get_invites_without_manage_permission,
- volumes,
- displayed_volumes,
- formatted_displayed_volumes,
-):
- for channel, volume in volumes:
- mocker.patch(
- "app.models.service.Service.volume_{}".format(channel),
- create=True,
- new_callable=PropertyMock,
- return_value=volume,
- )
- mock_create_ticket = mocker.spy(NotifySupportTicket, "__init__")
- mock_send_ticket_to_zendesk = mocker.patch(
- "app.main.views.service_settings.zendesk_client.send_ticket_to_zendesk",
- autospec=True,
- )
- page = client_request.post(
- "main.request_to_go_live", service_id=SERVICE_ONE_ID, _follow_redirects=True
- )
-
- expected_message = (
- "Service: service one\n"
- "http://localhost/services/{service_id}\n"
- "\n"
- "---\n"
- "Organization type: Federal government (domain is user.gsa.gov).\n"
- "\n"
- "{formatted_displayed_volumes}"
- "\n"
- "Consent to research: Yes\n"
- "Other live services for that user: No\n"
- "\n"
- "Service reply-to address: test@example.com\n"
- "\n"
- "---\n"
- "Request sent by test@user.gsa.gov\n"
- "Requester’s user page: http://localhost/users/{user_id}\n"
- ).format(
- service_id=SERVICE_ONE_ID,
- formatted_displayed_volumes=formatted_displayed_volumes,
- user_id=active_user_with_permissions["id"],
- )
- mock_create_ticket.assert_called_once_with(
- ANY,
- subject="Request to go live - service one",
- message=expected_message,
- ticket_type="question",
- user_name=active_user_with_permissions["name"],
- user_email=active_user_with_permissions["email_address"],
- requester_sees_message_content=False,
- org_id=None,
- org_type="federal",
- service_id=SERVICE_ONE_ID,
- )
- mock_send_ticket_to_zendesk.assert_called_once()
-
- assert normalize_spaces(page.select_one(".banner-default").text) == (
- "Thanks for your request to go live. We’ll get back to you within one working day."
- )
- assert normalize_spaces(page.select_one("h1").text) == ("Settings")
- mock_update_service.assert_called_once_with(
- SERVICE_ONE_ID, go_live_user=active_user_with_permissions["id"]
- )
-
-
-@pytest.mark.usefixtures("_mock_get_service_settings_page_common")
-def test_request_to_go_live_displays_go_live_notes_in_zendesk_ticket(
- client_request,
- mocker,
- active_user_with_permissions,
- single_reply_to_email_address,
- mock_get_organizations_and_services_for_user,
- single_sms_sender,
- mock_get_service_organization,
- mock_get_service_templates,
- mock_get_users_by_service,
- mock_update_service,
- mock_get_invites_without_manage_permission,
-):
- go_live_note = "This service is not allowed to go live"
-
- mocker.patch(
- "app.organizations_client.get_organization",
- side_effect=lambda org_id: organization_json(
- ORGANISATION_ID,
- "Org 1",
- request_to_go_live_notes=go_live_note,
- ),
- )
- mock_create_ticket = mocker.spy(NotifySupportTicket, "__init__")
- mock_send_ticket_to_zendesk = mocker.patch(
- "app.main.views.service_settings.zendesk_client.send_ticket_to_zendesk",
- autospec=True,
- )
- client_request.post(
- "main.request_to_go_live", service_id=SERVICE_ONE_ID, _follow_redirects=True
- )
-
- expected_message = (
- "Service: service one\n"
- "http://localhost/services/{service_id}\n"
- "\n"
- "---\n"
- "Organization type: Federal government (organization is Org 1). {go_live_note}\n"
- "\n"
- "Emails in next year: 111,111\n"
- "Text messages in next year: 222,222\n"
- "\n"
- "Consent to research: Yes\n"
- "Other live services for that user: No\n"
- "\n"
- "Service reply-to address: test@example.com\n"
- "\n"
- "---\n"
- "Request sent by test@user.gsa.gov\n"
- "Requester’s user page: http://localhost/users/{user_id}\n"
- ).format(
- service_id=SERVICE_ONE_ID,
- go_live_note=go_live_note,
- user_id=active_user_with_permissions["id"],
- )
-
- mock_create_ticket.assert_called_once_with(
- ANY,
- subject="Request to go live - service one",
- message=expected_message,
- ticket_type="question",
- user_name=active_user_with_permissions["name"],
- user_email=active_user_with_permissions["email_address"],
- requester_sees_message_content=False,
- org_id=ORGANISATION_ID,
- org_type="federal",
- service_id=SERVICE_ONE_ID,
- )
- mock_send_ticket_to_zendesk.assert_called_once()
-
-
-@pytest.mark.usefixtures("_mock_get_service_settings_page_common")
-def test_request_to_go_live_displays_mou_signatories(
- client_request,
- mocker,
- fake_uuid,
- active_user_with_permissions,
- single_reply_to_email_address,
- mock_get_organizations_and_services_for_user,
- single_sms_sender,
- mock_get_service_organization,
- mock_get_service_templates,
- mock_get_users_by_service,
- mock_update_service,
- mock_get_invites_without_manage_permission,
-):
- mocker.patch(
- "app.organizations_client.get_organization",
- side_effect=lambda org_id: organization_json(
- ORGANISATION_ID,
- "Org 1",
- agreement_signed=True,
- agreement_signed_by_id=fake_uuid,
- agreement_signed_on_behalf_of_email_address="bigdog@example.gsa.gov",
- ),
- )
- mocker.patch(
- "app.main.views.service_settings.zendesk_client.send_ticket_to_zendesk",
- autospec=True,
- )
- mock_create_ticket = mocker.spy(NotifySupportTicket, "__init__")
- client_request.post(
- "main.request_to_go_live", service_id=SERVICE_ONE_ID, _follow_redirects=True
- )
-
- assert ("Organization type: Federal government") in mock_create_ticket.call_args[1][
- "message"
- ]
-
- assert ("Emails in next year: 111,111\n") in mock_create_ticket.call_args[1][
- "message"
- ]
-
-
-@pytest.mark.usefixtures("_mock_get_service_settings_page_common")
-def test_should_be_able_to_request_to_go_live_with_no_organization(
+def test_should_not_allow_duplicate_service_names(
client_request,
- mocker,
- single_reply_to_email_address,
- mock_get_organizations_and_services_for_user,
- single_sms_sender,
- mock_get_service_templates,
- mock_get_users_by_service,
- mock_update_service,
- mock_get_invites_without_manage_permission,
+ mock_update_service_raise_httperror_duplicate_name,
+ service_one,
):
- for channel in {"email", "sms"}:
- mocker.patch(
- "app.models.service.Service.volume_{}".format(channel),
- create=True,
- new_callable=PropertyMock,
- return_value=1,
- )
- mock_post = mocker.patch(
- "app.main.views.service_settings.zendesk_client.send_ticket_to_zendesk",
- autospec=True,
- )
-
- client_request.post(
- "main.request_to_go_live", service_id=SERVICE_ONE_ID, _follow_redirects=True
+ page = client_request.post(
+ "main.service_name_change",
+ service_id=SERVICE_ONE_ID,
+ _data={"name": "SErvICE TWO"},
+ _expected_status=200,
)
- assert mock_post.called is True
+ assert "This service name is already in use" in page.text
-@pytest.mark.parametrize(
- (
- "has_team_members",
- "has_templates",
- "has_email_templates",
- "has_sms_templates",
- "has_email_reply_to_address",
- "shouldnt_use_govuk_as_sms_sender",
- "sms_sender_is_govuk",
- "volume_email",
- "volume_sms",
- "expected_readyness",
- "agreement_signed",
- ),
- [
- ( # Just sending email
- True,
- True,
- True,
- False,
- True,
- True,
- True,
- 1,
- 0,
- "Yes",
- True,
- ),
- ( # Needs to set reply to address
- True,
- True,
- True,
- False,
- False,
- True,
- True,
- 1,
- 0,
- "No",
- True,
- ),
- ( # Just sending SMS
- True,
- True,
- False,
- True,
- True,
- True,
- False,
- 0,
- 1,
- "Yes",
- True,
- ),
- ( # Needs to change SMS sender
- True,
- True,
- False,
- True,
- True,
- True,
- True,
- 0,
- 1,
- "No",
- True,
- ),
- ( # Needs team members
- False,
- True,
- False,
- True,
- True,
- True,
- False,
- 1,
- 0,
- "No",
- True,
- ),
- ( # Needs templates
- True,
- False,
- False,
- True,
- True,
- True,
- False,
- 0,
- 1,
- "No",
- True,
- ),
- ( # Not done anything yet
- False,
- False,
- False,
- False,
- False,
- False,
- True,
- None,
- None,
- "No",
- False,
- ),
- ],
-)
-def test_ready_to_go_live(
+def test_should_redirect_after_service_name_change(
client_request,
- mocker,
- mock_get_service_organization,
- has_team_members,
- has_templates,
- has_email_templates,
- has_sms_templates,
- has_email_reply_to_address,
- shouldnt_use_govuk_as_sms_sender,
- sms_sender_is_govuk,
- volume_email,
- volume_sms,
- expected_readyness,
- agreement_signed,
+ mock_update_service,
):
- mocker.patch(
- "app.organizations_client.get_organization",
- return_value=organization_json(agreement_signed=agreement_signed),
- )
-
- for prop in {
- "has_team_members",
- "has_templates",
- "has_email_templates",
- "has_sms_templates",
- "has_email_reply_to_address",
- "shouldnt_use_govuk_as_sms_sender",
- "sms_sender_is_govuk",
- }:
- mocker.patch(
- "app.models.service.Service.{}".format(prop), new_callable=PropertyMock
- ).return_value = locals()[prop]
-
- for channel, volume in (
- ("sms", volume_sms),
- ("email", volume_email),
- ):
- mocker.patch(
- "app.models.service.Service.volume_{}".format(channel),
- create=True,
- new_callable=PropertyMock,
- return_value=volume,
- )
+ client_request.post(
+ "main.service_name_change",
+ service_id=SERVICE_ONE_ID,
+ _data={"name": "New Name"},
+ _expected_status=302,
+ _expected_redirect=url_for(
+ "main.service_settings",
+ service_id=SERVICE_ONE_ID,
+ ),
+ )
- assert (
- app.models.service.Service(
- {"id": SERVICE_ONE_ID}
- ).go_live_checklist_completed_as_yes_no
- == expected_readyness
+ mock_update_service.assert_called_once_with(
+ SERVICE_ONE_ID,
+ name="New Name",
+ email_from="new.name",
)
@@ -1700,8 +553,6 @@ def test_ready_to_go_live(
[
"main.service_settings",
"main.service_name_change",
- "main.request_to_go_live",
- "main.submit_request_to_go_live",
"main.archive_service",
],
)
@@ -1735,8 +586,6 @@ def test_route_permissions(
[
"main.service_settings",
"main.service_name_change",
- "main.request_to_go_live",
- "main.submit_request_to_go_live",
"main.service_switch_live",
"main.archive_service",
],
@@ -1769,8 +618,6 @@ def test_route_invalid_permissions(
[
"main.service_settings",
"main.service_name_change",
- "main.request_to_go_live",
- "main.submit_request_to_go_live",
],
)
def test_route_for_platform_admin(
@@ -2823,247 +1670,6 @@ def test_does_not_show_research_mode_indicator(
assert not element
-@pytest.mark.parametrize(
- ("current_branding", "expected_values", "expected_labels"),
- [
- (
- None,
- [
- "__NONE__",
- "1",
- "2",
- "3",
- "4",
- "5",
- ],
- ["GOV.UK", "org 1", "org 2", "org 3", "org 4", "org 5"],
- ),
- (
- "5",
- [
- "5",
- "__NONE__",
- "1",
- "2",
- "3",
- "4",
- ],
- [
- "org 5",
- "GOV.UK",
- "org 1",
- "org 2",
- "org 3",
- "org 4",
- ],
- ),
- ],
-)
-@pytest.mark.parametrize(
- ("endpoint", "extra_args"),
- [
- (
- "main.service_set_email_branding",
- {"service_id": SERVICE_ONE_ID},
- ),
- (
- "main.edit_organization_email_branding",
- {"org_id": ORGANISATION_ID},
- ),
- ],
-)
-def test_should_show_branding_styles(
- mocker,
- client_request,
- platform_admin_user,
- service_one,
- mock_get_all_email_branding,
- current_branding,
- expected_values,
- expected_labels,
- endpoint,
- extra_args,
-):
- service_one["email_branding"] = current_branding
- mocker.patch(
- "app.organizations_client.get_organization",
- side_effect=lambda org_id: organization_json(
- org_id,
- "Org 1",
- email_branding_id=current_branding,
- ),
- )
-
- client_request.login(platform_admin_user)
- page = client_request.get(endpoint, **extra_args)
-
- branding_style_choices = page.find_all("input", attrs={"name": "branding_style"})
-
- radio_labels = [
- page.find("label", attrs={"for": branding_style_choices[idx]["id"]})
- .get_text()
- .strip()
- for idx, element in enumerate(branding_style_choices)
- ]
-
- assert len(branding_style_choices) == 6
-
- for index, expected_value in enumerate(expected_values):
- assert branding_style_choices[index]["value"] == expected_value
-
- # radios should be in alphabetical order, based on their labels
- assert radio_labels == expected_labels
-
- assert "checked" in branding_style_choices[0].attrs
- assert "checked" not in branding_style_choices[1].attrs
- assert "checked" not in branding_style_choices[2].attrs
- assert "checked" not in branding_style_choices[3].attrs
- assert "checked" not in branding_style_choices[4].attrs
- assert "checked" not in branding_style_choices[5].attrs
-
- app.email_branding_client.get_all_email_branding.assert_called_once_with()
- app.service_api_client.get_service.assert_called_once_with(service_one["id"])
-
-
-@pytest.mark.parametrize(
- ("endpoint", "extra_args", "expected_redirect"),
- [
- (
- "main.service_set_email_branding",
- {"service_id": SERVICE_ONE_ID},
- "main.service_preview_email_branding",
- ),
- (
- "main.edit_organization_email_branding",
- {"org_id": ORGANISATION_ID},
- "main.organization_preview_email_branding",
- ),
- ],
-)
-def test_should_send_branding_and_organizations_to_preview(
- client_request,
- platform_admin_user,
- service_one,
- mock_get_organization,
- mock_get_all_email_branding,
- mock_update_service,
- endpoint,
- extra_args,
- expected_redirect,
-):
- client_request.login(platform_admin_user)
- client_request.post(
- endpoint,
- _data={"branding_type": "org", "branding_style": "1"},
- _expected_status=302,
- _expected_location=url_for(expected_redirect, branding_style="1", **extra_args),
- **extra_args,
- )
-
- mock_get_all_email_branding.assert_called_once_with()
-
-
-@pytest.mark.parametrize(
- ("endpoint", "extra_args"),
- [
- (
- "main.service_preview_email_branding",
- {"service_id": SERVICE_ONE_ID},
- ),
- (
- "main.organization_preview_email_branding",
- {"org_id": ORGANISATION_ID},
- ),
- ],
-)
-def test_should_preview_email_branding(
- client_request,
- platform_admin_user,
- mock_get_organization,
- endpoint,
- extra_args,
-):
- client_request.login(platform_admin_user)
- page = client_request.get(
- endpoint, branding_type="org", branding_style="1", **extra_args
- )
-
- iframe = page.find("iframe", attrs={"class": "branding-preview"})
- iframeURLComponents = urlparse(iframe["src"])
- iframeQString = parse_qs(iframeURLComponents.query)
-
- assert page.find("input", attrs={"id": "branding_style"})["value"] == "1"
- assert iframeURLComponents.path == "/_email"
- assert iframeQString["branding_style"] == ["1"]
-
-
-@pytest.mark.parametrize(
- ("posted_value", "submitted_value"),
- [
- ("1", "1"),
- ("__NONE__", None),
- pytest.param("None", None, marks=pytest.mark.xfail(raises=AssertionError)),
- ],
-)
-@pytest.mark.parametrize(
- ("endpoint", "extra_args", "expected_redirect"),
- [
- (
- "main.service_preview_email_branding",
- {"service_id": SERVICE_ONE_ID},
- "main.service_settings",
- ),
- (
- "main.organization_preview_email_branding",
- {"org_id": ORGANISATION_ID},
- "main.organization_settings",
- ),
- ],
-)
-def test_should_set_branding_and_organizations(
- client_request,
- platform_admin_user,
- service_one,
- mock_get_organization,
- mock_get_organization_services,
- mock_update_service,
- mock_update_organization,
- posted_value,
- submitted_value,
- endpoint,
- extra_args,
- expected_redirect,
-):
- client_request.login(platform_admin_user)
- client_request.post(
- endpoint,
- _data={"branding_style": posted_value},
- _expected_status=302,
- _expected_redirect=url_for(expected_redirect, **extra_args),
- **extra_args,
- )
-
- if endpoint == "main.service_preview_email_branding":
- mock_update_service.assert_called_once_with(
- SERVICE_ONE_ID,
- email_branding=submitted_value,
- )
- assert mock_update_organization.called is False
- elif endpoint == "main.organization_preview_email_branding":
- mock_update_organization.assert_called_once_with(
- ORGANISATION_ID,
- email_branding_id=submitted_value,
- cached_service_ids=[
- "12345",
- "67890",
- "596364a0-858e-42c8-9062-a8fe822260eb",
- ],
- )
- assert mock_update_service.called is False
- else:
- raise Exception
-
-
@pytest.mark.parametrize("method", ["get", "post"])
@pytest.mark.parametrize(
"endpoint",
@@ -4170,33 +2776,6 @@ def test_update_service_organization_does_not_update_if_same_value(
assert mock_update_service_organization.called is False
-@pytest.mark.skip(reason="Email currently deactivated")
-@pytest.mark.parametrize(
- ("single_branding_option", "expected_href"),
- [
- (
- True,
- f"/services/{SERVICE_ONE_ID}/service-settings/email-branding/something-else",
- ),
- ],
-)
-def test_service_settings_links_to_branding_request_page_for_emails(
- service_one,
- client_request,
- no_reply_to_email_addresses,
- single_sms_sender,
- single_branding_option,
- expected_href,
-):
- if single_branding_option:
- # should only have a "something else" option
- # so we go straight to that form
- service_one["organization_type"] = "other"
-
- page = client_request.get(".service_settings", service_id=SERVICE_ONE_ID)
- assert len(page.find_all("a", attrs={"href": expected_href})) == 1
-
-
def test_show_service_data_retention(
client_request,
platform_admin_user,
diff --git a/tests/app/main/views/test_accept_invite.py b/tests/app/main/views/test_accept_invite.py
index 696a3ea836..1deeca299f 100644
--- a/tests/app/main/views/test_accept_invite.py
+++ b/tests/app/main/views/test_accept_invite.py
@@ -14,6 +14,91 @@
normalize_spaces,
)
+FAKE_ONE_OFF_NOTIFICATION = {
+ "links": {},
+ "notifications": [
+ {
+ "api_key": None,
+ "billable_units": 0,
+ "carrier": None,
+ "client_reference": None,
+ "created_at": "2023-12-14T20:35:55+00:00",
+ "created_by": {
+ "email_address": "grsrbsrgsrf@fake.gov",
+ "id": "de059e0a-42e5-48bb-939e-4f76804ab739",
+ "name": "grsrbsrgsrf",
+ },
+ "document_download_count": None,
+ "id": "a3442b43-0ba1-4854-9e0a-d2fba1cc9b81",
+ "international": False,
+ "job": {
+ "id": "55b242b5-9f62-4271-aff7-039e9c320578",
+ "original_file_name": "1127b78e-a4a8-4b70-8f4f-9f4fbf03ece2.csv",
+ },
+ "job_row_number": 0,
+ "key_name": None,
+ "key_type": "normal",
+ "normalised_to": "+16615555555",
+ "notification_type": "sms",
+ "personalisation": {
+ "dayofweek": "2",
+ "favecolor": "3",
+ "phonenumber": "+16615555555",
+ },
+ "phone_prefix": "1",
+ "provider_response": None,
+ "rate_multiplier": 1.0,
+ "reference": None,
+ "reply_to_text": "development",
+ "sent_at": None,
+ "sent_by": None,
+ "service": "f62d840f-8bcb-4b36-b959-4687e16dd1a1",
+ "status": "created",
+ "template": {
+ "content": "((day of week)) and ((fave color))",
+ "id": "bd9caa7e-00ee-4c5a-839e-10ae1a7e6f73",
+ "name": "personalized",
+ "redact_personalisation": False,
+ "subject": None,
+ "template_type": "sms",
+ "version": 1,
+ },
+ "to": "+16615555555",
+ "updated_at": None,
+ }
+ ],
+ "page_size": 50,
+ "total": 1,
+}
+
+MOCK_JOBS = {
+ "data": [
+ {
+ "archived": False,
+ "created_at": "2024-01-04T20:43:52+00:00",
+ "created_by": {
+ "id": "mocked_user_id",
+ "name": "mocked_user",
+ },
+ "id": "mocked_notification_id",
+ "job_status": "finished",
+ "notification_count": 1,
+ "original_file_name": "mocked_file.csv",
+ "processing_finished": "2024-01-25T23:02:25+00:00",
+ "processing_started": "2024-01-25T23:02:24+00:00",
+ "scheduled_for": None,
+ "service": "21b3ee3d-1cb0-4666-bfa0-9c5ac26d3fe3",
+ "service_name": {"name": "Mock Texting Service"},
+ "statistics": [{"count": 1, "status": "sending"}],
+ "template": "6a456418-498c-4c86-b0cd-9403c14a216c",
+ "template_name": "Mock Template Name",
+ "template_type": "sms",
+ "template_version": 3,
+ "updated_at": "2024-01-25T23:02:25+00:00",
+ }
+ ]
+}
+
@pytest.fixture()
def _mock_no_users_for_service(mocker):
@@ -207,7 +292,11 @@ def test_accepting_invite_removes_invite_from_session(
sample_invite["email_address"] = user["email_address"]
client_request.login(user)
-
+ mocker.patch("app.job_api_client.get_jobs", return_value=MOCK_JOBS)
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
page = client_request.get(
"main.accept_invite",
token="thisisnotarealtoken",
@@ -619,6 +708,11 @@ def test_new_invited_user_verifies_and_added_to_service(
token="thisisnotarealtoken",
_expected_redirect=url_for("main.register_from_invite"),
)
+ mocker.patch("app.job_api_client.get_jobs", return_value=MOCK_JOBS)
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
# get redirected to register from invite
data = {
diff --git a/tests/app/main/views/test_add_service.py b/tests/app/main/views/test_add_service.py
index 0d04715ac6..4f2ab99643 100644
--- a/tests/app/main/views/test_add_service.py
+++ b/tests/app/main/views/test_add_service.py
@@ -97,7 +97,6 @@ def test_should_add_service_and_redirect_to_tour_when_no_services(
mock_create_service_template,
mock_get_services_with_no_services,
api_user_active,
- mock_get_all_email_branding,
inherited,
email_address,
posted,
@@ -153,7 +152,6 @@ def test_add_service_has_to_choose_org_type(
mock_create_service_template,
mock_get_services_with_no_services,
api_user_active,
- mock_get_all_email_branding,
platform_admin_user,
):
client_request.login(platform_admin_user)
@@ -227,7 +225,6 @@ def test_should_add_service_and_redirect_to_dashboard_when_existing_service(
api_user_active,
organization_type,
free_allowance,
- mock_get_all_email_branding,
platform_admin_user,
):
client_request.login(platform_admin_user)
diff --git a/tests/app/main/views/test_dashboard.py b/tests/app/main/views/test_dashboard.py
index 010b53d5da..8e9ecb5315 100644
--- a/tests/app/main/views/test_dashboard.py
+++ b/tests/app/main/views/test_dashboard.py
@@ -28,6 +28,91 @@
normalize_spaces,
)
+FAKE_ONE_OFF_NOTIFICATION = {
+ "links": {},
+ "notifications": [
+ {
+ "api_key": None,
+ "billable_units": 0,
+ "carrier": None,
+ "client_reference": None,
+ "created_at": "2023-12-14T20:35:55+00:00",
+ "created_by": {
+ "email_address": "grsrbsrgsrf@fake.gov",
+ "id": "de059e0a-42e5-48bb-939e-4f76804ab739",
+ "name": "grsrbsrgsrf",
+ },
+ "document_download_count": None,
+ "id": "a3442b43-0ba1-4854-9e0a-d2fba1cc9b81",
+ "international": False,
+ "job": {
+ "id": "55b242b5-9f62-4271-aff7-039e9c320578",
+ "original_file_name": "1127b78e-a4a8-4b70-8f4f-9f4fbf03ece2.csv",
+ },
+ "job_row_number": 0,
+ "key_name": None,
+ "key_type": "normal",
+ "normalised_to": "+16615555555",
+ "notification_type": "sms",
+ "personalisation": {
+ "dayofweek": "2",
+ "favecolor": "3",
+ "phonenumber": "+16615555555",
+ },
+ "phone_prefix": "1",
+ "provider_response": None,
+ "rate_multiplier": 1.0,
+ "reference": None,
+ "reply_to_text": "development",
+ "sent_at": None,
+ "sent_by": None,
+ "service": "f62d840f-8bcb-4b36-b959-4687e16dd1a1",
+ "status": "created",
+ "template": {
+ "content": "((day of week)) and ((fave color))",
+ "id": "bd9caa7e-00ee-4c5a-839e-10ae1a7e6f73",
+ "name": "personalized",
+ "redact_personalisation": False,
+ "subject": None,
+ "template_type": "sms",
+ "version": 1,
+ },
+ "to": "+16615555555",
+ "updated_at": None,
+ }
+ ],
+ "page_size": 50,
+ "total": 1,
+}
+
+MOCK_JOBS = {
+ "data": [
+ {
+ "archived": False,
+ "created_at": "2024-01-04T20:43:52+00:00",
+ "created_by": {
+ "id": "mocked_user_id",
+ "name": "mocked_user",
+ },
+ "id": "mocked_notification_id",
+ "job_status": "finished",
+ "notification_count": 1,
+ "original_file_name": "mocked_file.csv",
+ "processing_finished": "2024-01-25T23:02:25+00:00",
+ "processing_started": "2024-01-25T23:02:24+00:00",
+ "scheduled_for": None,
+ "service": "21b3ee3d-1cb0-4666-bfa0-9c5ac26d3fe3",
+ "service_name": {"name": "Mock Texting Service"},
+ "statistics": [{"count": 1, "status": "sending"}],
+ "template": "6a456418-498c-4c86-b0cd-9403c14a216c",
+ "template_name": "Mock Template Name",
+ "template_type": "sms",
+ "template_version": 3,
+ "updated_at": "2024-01-25T23:02:25+00:00",
+ }
+ ]
+}
+
stub_template_stats = [
{
"template_type": "sms",
@@ -116,7 +201,11 @@ def test_get_started(
"app.template_statistics_client.get_template_statistics_for_service",
return_value=copy.deepcopy(stub_template_stats),
)
-
+ mocker.patch("app.job_api_client.get_jobs", return_value=MOCK_JOBS)
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
page = client_request.get(
"main.service_dashboard",
service_id=SERVICE_ONE_ID,
@@ -142,6 +231,11 @@ def test_get_started_is_hidden_once_templates_exist(
"app.template_statistics_client.get_template_statistics_for_service",
return_value=copy.deepcopy(stub_template_stats),
)
+ mocker.patch("app.job_api_client.get_jobs", return_value=MOCK_JOBS)
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
page = client_request.get(
"main.service_dashboard",
service_id=SERVICE_ONE_ID,
@@ -164,7 +258,11 @@ def test_inbound_messages_not_visible_to_service_without_permissions(
mock_get_inbound_sms_summary,
):
service_one["permissions"] = []
-
+ mocker.patch("app.job_api_client.get_jobs", return_value=MOCK_JOBS)
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
page = client_request.get(
"main.service_dashboard",
service_id=SERVICE_ONE_ID,
@@ -188,11 +286,16 @@ def test_inbound_messages_shows_count_of_messages_when_there_are_messages(
mock_get_inbound_sms_summary,
):
service_one["permissions"] = ["inbound_sms"]
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
page = client_request.get(
"main.service_dashboard",
service_id=SERVICE_ONE_ID,
)
- mock_get_jobs.assert_called_once_with(SERVICE_ONE_ID)
+ mock_get_jobs.assert_called_with(SERVICE_ONE_ID)
+ mocker.patch("app.job_api_client.get_jobs", return_value=MOCK_JOBS)
banner = page.select("a.banner-dashboard")[1]
assert (
normalize_spaces(banner.text)
@@ -215,11 +318,16 @@ def test_inbound_messages_shows_count_of_messages_when_there_are_no_messages(
mock_get_inbound_sms_summary_with_no_messages,
):
service_one["permissions"] = ["inbound_sms"]
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
page = client_request.get(
"main.service_dashboard",
service_id=SERVICE_ONE_ID,
)
- mock_get_jobs.assert_called_once_with(SERVICE_ONE_ID)
+ mock_get_jobs.assert_called_with(SERVICE_ONE_ID)
+ mocker.patch("app.job_api_client.get_jobs", return_value=MOCK_JOBS)
banner = page.select("a.banner-dashboard")[1]
assert normalize_spaces(banner.text) == "0 text messages received"
assert banner["href"] == url_for("main.inbox", service_id=SERVICE_ONE_ID)
@@ -460,7 +568,11 @@ def test_should_show_recent_templates_on_dashboard(
"app.template_statistics_client.get_template_statistics_for_service",
return_value=copy.deepcopy(stub_template_stats),
)
-
+ mocker.patch("app.job_api_client.get_jobs", return_value=MOCK_JOBS)
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
page = client_request.get(
"main.service_dashboard",
service_id=SERVICE_ONE_ID,
@@ -513,7 +625,11 @@ def test_should_not_show_recent_templates_on_dashboard_if_only_one_template_used
"app.template_statistics_client.get_template_statistics_for_service",
return_value=stats,
)
-
+ mocker.patch("app.job_api_client.get_jobs", return_value=MOCK_JOBS)
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
page = client_request.get("main.service_dashboard", service_id=SERVICE_ONE_ID)
main = page.select_one("main").text
@@ -638,6 +754,7 @@ def test_monthly_has_equal_length_tables(
@freeze_time("2016-01-01 11:09:00.061258")
def test_should_show_upcoming_jobs_on_dashboard(
+ mocker,
client_request,
mock_get_service_templates,
mock_get_template_statistics,
@@ -648,12 +765,17 @@ def test_should_show_upcoming_jobs_on_dashboard(
mock_get_free_sms_fragment_limit,
mock_get_inbound_sms_summary,
):
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
page = client_request.get(
"main.service_dashboard",
service_id=SERVICE_ONE_ID,
)
- mock_get_jobs.assert_called_once_with(SERVICE_ONE_ID)
+ mock_get_jobs.assert_called_with(SERVICE_ONE_ID)
+ mocker.patch("app.job_api_client.get_jobs", return_value=MOCK_JOBS)
mock_get_scheduled_job_stats.assert_called_once_with(SERVICE_ONE_ID)
assert normalize_spaces(page.select_one("main h2").text) == ("In the next few days")
@@ -685,6 +807,11 @@ def test_should_not_show_upcoming_jobs_on_dashboard_if_count_is_0(
"soonest_scheduled_for": None,
},
)
+ mocker.patch("app.job_api_client.get_jobs", return_value=MOCK_JOBS)
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
page = client_request.get(
"main.service_dashboard",
service_id=SERVICE_ONE_ID,
@@ -706,6 +833,11 @@ def test_should_not_show_upcoming_jobs_on_dashboard_if_service_has_no_jobs(
mock_get_free_sms_fragment_limit,
mock_get_inbound_sms_summary,
):
+ mocker.patch("app.job_api_client.get_jobs", return_value=MOCK_JOBS)
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
page = client_request.get(
"main.service_dashboard",
service_id=SERVICE_ONE_ID,
@@ -755,7 +887,11 @@ def test_correct_font_size_for_big_numbers(
service_one["permissions"] = permissions
mocker.patch("app.main.views.dashboard.get_dashboard_totals", return_value=totals)
-
+ mocker.patch("app.job_api_client.get_jobs", return_value=MOCK_JOBS)
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
page = client_request.get(
"main.service_dashboard",
service_id=service_one["id"],
@@ -773,6 +909,7 @@ def test_correct_font_size_for_big_numbers(
def test_should_not_show_jobs_on_dashboard_for_users_with_uploads_page(
+ mocker,
client_request,
service_one,
mock_get_service_templates,
@@ -784,11 +921,16 @@ def test_should_not_show_jobs_on_dashboard_for_users_with_uploads_page(
mock_get_free_sms_fragment_limit,
mock_get_inbound_sms_summary,
):
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
page = client_request.get(
"main.service_dashboard",
service_id=SERVICE_ONE_ID,
)
- mock_get_jobs.assert_called_once_with(SERVICE_ONE_ID)
+ mock_get_jobs.assert_called_with(SERVICE_ONE_ID)
+ mocker.patch("app.job_api_client.get_jobs", return_value=MOCK_JOBS)
for filename in {
"export 1/1/2016.xls",
"all email addresses.xlsx",
@@ -833,8 +975,8 @@ def test_usage_page(
sms_column = normalize_spaces(annual_usage[0].text)
assert (
- "You have sent 251,800 messages of your 250,000 free messages allowance. You have 0 messages remaining."
- in sms_column
+ "You have sent 251,800 text message parts of your 250,000 free message parts allowance."
+ " You have 0 message parts remaining." in sms_column
)
assert "$29.85 spent" not in sms_column
assert "1,500 at 1.65 pence" not in sms_column
@@ -870,8 +1012,8 @@ def test_usage_page_no_sms_spend(
annual_usage = page.find_all("div", {"class": "keyline-block"})
sms_column = normalize_spaces(annual_usage[0].text)
assert (
- "You have sent 1,000 messages of your 250,000 free messages allowance. You have 249,000 messages remaining."
- in sms_column
+ "You have sent 1,000 text message parts of your 250,000 free message parts allowance."
+ " You have 249,000 message parts remaining." in sms_column
)
assert "$0.00 spent" not in sms_column
assert "pence per message" not in sms_column
@@ -947,7 +1089,7 @@ def test_usage_page_with_0_free_allowance(
sms_column = normalize_spaces(annual_usage[0].text)
assert (
- "You have sent 251,800 messages of your 0 free messages allowance. You have"
+ "You have sent 251,800 text message parts of your 0 free message parts allowance. You have"
in sms_column
)
assert "free allowance remaining" not in sms_column
@@ -1020,6 +1162,20 @@ def _test_dashboard_menu(client_request, mocker, usr, service, permissions):
return client_request.get("main.service_dashboard", service_id=service["id"])
+def _test_settings_menu(client_request, mocker, usr, service, permissions):
+ usr["permissions"][str(service["id"])] = permissions
+ usr["services"] = [service["id"]]
+ mocker.patch("app.user_api_client.check_verify_code", return_value=(True, ""))
+ mocker.patch(
+ "app.service_api_client.get_services", return_value={"data": [service]}
+ )
+ mocker.patch("app.user_api_client.get_user", return_value=usr)
+ mocker.patch("app.user_api_client.get_user_by_email", return_value=usr)
+ mocker.patch("app.service_api_client.get_service", return_value={"data": service})
+ client_request.login(usr)
+ return client_request.get("main.service_dashboard", service_id=service["id"])
+
+
def test_menu_send_messages(
client_request,
mocker,
@@ -1036,6 +1192,11 @@ def test_menu_send_messages(
):
service_one["permissions"] = ["email", "sms"]
+ mocker.patch("app.job_api_client.get_jobs", return_value=MOCK_JOBS)
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
page = _test_dashboard_menu(
client_request,
mocker,
@@ -1051,11 +1212,8 @@ def test_menu_send_messages(
)
in page
)
- # assert url_for('main.uploads', service_id=service_one['id']) in page
- assert url_for("main.manage_users", service_id=service_one["id"]) in page
-
- assert url_for("main.service_settings", service_id=service_one["id"]) not in page
- # assert url_for('main.api_keys', service_id=service_one['id']) not in page
+ assert url_for("main.manage_users", service_id=service_one["id"]) not in page
+ assert url_for("main.service_settings", service_id=service_one["id"]) in page
def test_menu_manage_service(
@@ -1071,6 +1229,11 @@ def test_menu_manage_service(
mock_get_inbound_sms_summary,
mock_get_free_sms_fragment_limit,
):
+ mocker.patch("app.job_api_client.get_jobs", return_value=MOCK_JOBS)
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
page = _test_dashboard_menu(
client_request,
mocker,
@@ -1086,10 +1249,44 @@ def test_menu_manage_service(
)
in page
)
- assert url_for("main.manage_users", service_id=service_one["id"]) in page
- assert url_for("main.service_settings", service_id=service_one["id"]) in page
+ assert url_for(".service_dashboard", service_id=service_one["id"]) in page
+ assert url_for(".choose_template", service_id=service_one["id"]) in page
- # assert url_for('main.api_keys', service_id=service_one['id']) not in page
+
+def test_menu_main_settings(
+ client_request,
+ mocker,
+ api_user_active,
+ service_one,
+ mock_get_service_templates,
+ mock_has_no_jobs,
+ mock_get_template_statistics,
+ mock_get_service_statistics,
+ mock_get_annual_usage_for_service,
+ mock_get_inbound_sms_summary,
+ mock_get_free_sms_fragment_limit,
+):
+ mocker.patch("app.job_api_client.get_jobs", return_value=MOCK_JOBS)
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
+ page = _test_settings_menu(
+ client_request,
+ mocker,
+ api_user_active,
+ service_one,
+ ["view_activity", "user_profile", "manage_users", "manage_settings"],
+ )
+ page = str(page)
+ assert (
+ url_for(
+ "main.service_settings",
+ service_id=service_one["id"],
+ )
+ in page
+ )
+ assert url_for("main.service_settings", service_id=service_one["id"]) in page
def test_menu_manage_api_keys(
@@ -1105,6 +1302,11 @@ def test_menu_manage_api_keys(
mock_get_inbound_sms_summary,
mock_get_free_sms_fragment_limit,
):
+ mocker.patch("app.job_api_client.get_jobs", return_value=MOCK_JOBS)
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
page = _test_dashboard_menu(
client_request,
mocker,
@@ -1122,8 +1324,8 @@ def test_menu_manage_api_keys(
)
in page
)
- assert url_for("main.manage_users", service_id=service_one["id"]) in page
- assert url_for("main.service_settings", service_id=service_one["id"]) in page
+ # assert url_for("main.manage_users", service_id=service_one["id"]) not in page
+ # assert url_for("main.service_settings", service_id=service_one["id"]) not in page
assert url_for("main.api_integration", service_id=service_one["id"]) in page
@@ -1140,13 +1342,18 @@ def test_menu_all_services_for_platform_admin_user(
mock_get_inbound_sms_summary,
mock_get_free_sms_fragment_limit,
):
+ mocker.patch("app.job_api_client.get_jobs", return_value=MOCK_JOBS)
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
page = _test_dashboard_menu(
client_request, mocker, platform_admin_user, service_one, []
)
page = str(page)
assert url_for("main.choose_template", service_id=service_one["id"]) in page
- assert url_for("main.manage_users", service_id=service_one["id"]) in page
- assert url_for("main.service_settings", service_id=service_one["id"]) in page
+ # assert url_for("main.manage_users", service_id=service_one["id"]) in page
+ # assert url_for("main.service_settings", service_id=service_one["id"]) in page
# assert url_for('main.view_notifications', service_id=service_one['id'], message_type='email') in page
assert (
url_for(
@@ -1180,7 +1387,11 @@ def _get(mocker):
mocker.patch(
"app.service_api_client.get_global_notification_count", side_effect=_get
)
-
+ mocker.patch("app.job_api_client.get_jobs", return_value=MOCK_JOBS)
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
validate_route_permission(
mocker,
notify_admin,
@@ -1232,6 +1443,11 @@ def test_service_dashboard_updates_gets_dashboard_totals(
"sms": {"requested": 456, "delivered": 0, "failed": 0},
},
)
+ mocker.patch("app.job_api_client.get_jobs", return_value=MOCK_JOBS)
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
page = client_request.get(
"main.service_dashboard",
@@ -1345,6 +1561,7 @@ def test_get_tuples_of_financial_years_defaults_to_2015():
def test_org_breadcrumbs_do_not_show_if_service_has_no_org(
+ mocker,
client_request,
mock_get_template_statistics,
mock_get_service_templates_when_no_templates_exist,
@@ -1352,6 +1569,11 @@ def test_org_breadcrumbs_do_not_show_if_service_has_no_org(
mock_get_annual_usage_for_service,
mock_get_free_sms_fragment_limit,
):
+ mocker.patch("app.job_api_client.get_jobs", return_value=MOCK_JOBS)
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
page = client_request.get("main.service_dashboard", service_id=SERVICE_ONE_ID)
assert not page.select(".navigation-organization-link")
@@ -1414,6 +1636,11 @@ def test_org_breadcrumbs_show_if_user_is_a_member_of_the_services_org(
id_=ORGANISATION_ID,
),
)
+ mocker.patch("app.job_api_client.get_jobs", return_value=MOCK_JOBS)
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
page = client_request.get("main.service_dashboard", service_id=SERVICE_ONE_ID)
assert page.select_one(".navigation-organization-link")["href"] == url_for(
@@ -1445,6 +1672,13 @@ def test_org_breadcrumbs_do_not_show_if_user_is_a_member_of_the_services_org_but
)
mocker.patch("app.models.service.Organization")
+ mocker.patch("app.job_api_client.get_jobs", return_value=MOCK_JOBS)
+
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
+
page = client_request.get("main.service_dashboard", service_id=SERVICE_ONE_ID)
assert not page.select(".navigation-breadcrumb")
@@ -1476,6 +1710,13 @@ def test_org_breadcrumbs_show_if_user_is_platform_admin(
),
)
+ mocker.patch("app.job_api_client.get_jobs", return_value=MOCK_JOBS)
+
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
+
client_request.login(platform_admin_user, service_one_json)
page = client_request.get("main.service_dashboard", service_id=SERVICE_ONE_ID)
@@ -1504,6 +1745,13 @@ def test_breadcrumb_shows_if_service_is_suspended(
mocker.patch(
"app.service_api_client.get_service", return_value={"data": service_one_json}
)
+
+ mocker.patch("app.job_api_client.get_jobs", return_value=MOCK_JOBS)
+
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
page = client_request.get("main.service_dashboard", service_id=SERVICE_ONE_ID)
assert "Suspended" in page.select_one(".navigation-service-name").text
@@ -1532,11 +1780,21 @@ def test_service_dashboard_shows_usage(
"count": 500,
},
)
+ mocker.patch("app.job_api_client.get_jobs", return_value=MOCK_JOBS)
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
service_one["permissions"] = permissions
page = client_request.get("main.service_dashboard", service_id=SERVICE_ONE_ID)
- table_rows = page.find_all("tbody")[0].find_all("tr")
+ usage_table = page.find("table", class_="usage-table")
+
+ # Check if the "Usage" table exists
+ assert usage_table is not None
+
+ table_rows = usage_table.find_all("tbody")[0].find_all("tr")
assert len(table_rows) == 1
@@ -1565,9 +1823,43 @@ def test_service_dashboard_shows_free_allowance(
}
],
)
+ mocker.patch("app.job_api_client.get_jobs", return_value=MOCK_JOBS)
+
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
page = client_request.get("main.service_dashboard", service_id=SERVICE_ONE_ID)
usage_text = normalize_spaces(page.select_one("[data-key=usage]").text)
assert "spent on text messages" not in usage_text
assert "Daily Usage Remaining 1,000 249,000" in usage_text
+
+
+def test_service_dashboard_shows_batched_jobs(
+ mocker,
+ client_request,
+ service_one,
+ mock_get_service_templates,
+ mock_get_template_statistics,
+ mock_has_no_jobs,
+ mock_get_annual_usage_for_service,
+ mock_get_free_sms_fragment_limit,
+):
+ mocker.patch("app.job_api_client.get_jobs", return_value=MOCK_JOBS)
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
+
+ page = client_request.get("main.service_dashboard", service_id=SERVICE_ONE_ID)
+
+ job_table = page.find("div", class_="job-table")
+
+ # Check if the "Job" table exists
+ assert job_table is not None
+
+ table_rows = job_table.find_all("tbody")[0].find_all("tr")
+
+ assert len(table_rows) == 1
diff --git a/tests/app/main/views/test_email_branding.py b/tests/app/main/views/test_email_branding.py
deleted file mode 100644
index fe86c108cb..0000000000
--- a/tests/app/main/views/test_email_branding.py
+++ /dev/null
@@ -1,451 +0,0 @@
-from io import BytesIO
-from unittest.mock import call
-
-import pytest
-from flask import url_for
-from notifications_python_client.errors import HTTPError
-
-from app.s3_client.s3_logo_client import EMAIL_LOGO_LOCATION_STRUCTURE, TEMP_TAG
-from tests.conftest import create_email_branding, normalize_spaces
-
-
-def test_email_branding_page_shows_full_branding_list(
- client_request, platform_admin_user, mock_get_all_email_branding
-):
- client_request.login(platform_admin_user)
- page = client_request.get(".email_branding")
-
- links = page.select(".message-name a")
- brand_names = [normalize_spaces(link.text) for link in links]
- hrefs = [link["href"] for link in links]
-
- assert normalize_spaces(page.select_one("h1").text) == "Email branding"
-
- assert page.select(".grid-col-9 a")[-1]["href"] == url_for(
- "main.create_email_branding"
- )
-
- assert brand_names == [
- "org 1",
- "org 2",
- "org 3",
- "org 4",
- "org 5",
- ]
- assert hrefs == [
- url_for(".update_email_branding", branding_id=1),
- url_for(".update_email_branding", branding_id=2),
- url_for(".update_email_branding", branding_id=3),
- url_for(".update_email_branding", branding_id=4),
- url_for(".update_email_branding", branding_id=5),
- ]
-
-
-def test_edit_email_branding_shows_the_correct_branding_info(
- client_request, platform_admin_user, mock_get_email_branding, fake_uuid
-):
- client_request.login(platform_admin_user)
- page = client_request.get(
- ".update_email_branding",
- branding_id=fake_uuid,
- _test_page_title=False, # TODO: Fix page titles
- )
-
- assert page.select_one("#logo-img > img")["src"].endswith("/example.png")
- assert page.select_one("#name").attrs.get("value") == "Organization name"
- assert page.select_one("#file").attrs.get("accept") == ".png"
- assert page.select_one("#text").attrs.get("value") == "Organization text"
- assert page.select_one("#colour").attrs.get("value") == "#f00"
-
-
-def test_create_email_branding_does_not_show_any_branding_info(
- client_request, platform_admin_user, mock_no_email_branding
-):
- client_request.login(platform_admin_user)
- page = client_request.get(
- ".create_email_branding",
- _test_page_title=False, # TODO: Fix page titles
- )
-
- assert page.select_one("#logo-img > img") is None
- assert page.select_one("#name").attrs.get("value") is None
- assert page.select_one("#file").attrs.get("accept") == ".png"
- assert page.select_one("#text").attrs.get("value") is None
- assert page.select_one("#colour").attrs.get("value") is None
-
-
-def test_create_new_email_branding_without_logo(
- client_request,
- platform_admin_user,
- mocker,
- fake_uuid,
- mock_create_email_branding,
-):
- data = {
- "logo": None,
- "colour": "#ff0000",
- "text": "new text",
- "name": "new name",
- "brand_type": "org",
- }
-
- mock_persist = mocker.patch("app.main.views.email_branding.persist_logo")
- mocker.patch("app.main.views.email_branding.delete_email_temp_files_created_by")
-
- client_request.login(platform_admin_user)
- client_request.post(
- ".create_email_branding",
- _content_type="multipart/form-data",
- _data=data,
- )
-
- assert mock_create_email_branding.called
- assert mock_create_email_branding.call_args == call(
- logo=data["logo"],
- name=data["name"],
- text=data["text"],
- colour=data["colour"],
- brand_type=data["brand_type"],
- )
- assert mock_persist.call_args_list == []
-
-
-def test_create_email_branding_requires_a_name_when_submitting_logo_details(
- client_request,
- mocker,
- mock_create_email_branding,
- platform_admin_user,
-):
- mocker.patch("app.main.views.email_branding.persist_logo")
- mocker.patch("app.main.views.email_branding.delete_email_temp_files_created_by")
- data = {
- "operation": "email-branding-details",
- "logo": "",
- "colour": "#ff0000",
- "text": "new text",
- "name": "",
- "brand_type": "org",
- }
- client_request.login(platform_admin_user)
- page = client_request.post(
- ".create_email_branding",
- _content_type="multipart/form-data",
- _data=data,
- _expected_status=200,
- )
-
- assert (
- page.select_one(".usa-error-message").text.strip()
- == "Error: This field is required"
- )
- assert mock_create_email_branding.called is False
-
-
-def test_create_email_branding_does_not_require_a_name_when_uploading_a_file(
- client_request,
- mocker,
- platform_admin_user,
-):
- mocker.patch(
- "app.main.views.email_branding.upload_email_logo", return_value="temp_filename"
- )
- data = {
- "file": (BytesIO("".encode("utf-8")), "test.png"),
- "colour": "",
- "text": "",
- "name": "",
- "brand_type": "org",
- }
- client_request.login(platform_admin_user)
- page = client_request.post(
- ".create_email_branding",
- _content_type="multipart/form-data",
- _data=data,
- _follow_redirects=True,
- )
-
- assert not page.find(".error-message")
-
-
-def test_create_new_email_branding_when_branding_saved(
- client_request, platform_admin_user, mocker, mock_create_email_branding, fake_uuid
-):
- with client_request.session_transaction() as session:
- user_id = session["user_id"]
-
- data = {
- "logo": "test.png",
- "colour": "#ff0000",
- "text": "new text",
- "name": "new name",
- "brand_type": "org_banner",
- }
-
- temp_filename = EMAIL_LOGO_LOCATION_STRUCTURE.format(
- temp=TEMP_TAG.format(user_id=user_id),
- unique_id=fake_uuid,
- filename=data["logo"],
- )
-
- mocker.patch("app.main.views.email_branding.persist_logo")
- mocker.patch("app.main.views.email_branding.delete_email_temp_files_created_by")
-
- client_request.login(platform_admin_user)
- client_request.post(
- ".create_email_branding",
- logo=temp_filename,
- _content_type="multipart/form-data",
- _data={
- "colour": data["colour"],
- "name": data["name"],
- "text": data["text"],
- "cdn_url": "https://static-logos.cdn.com",
- "brand_type": data["brand_type"],
- },
- )
-
- updated_logo_name = "{}-{}".format(fake_uuid, data["logo"])
-
- assert mock_create_email_branding.called
- assert mock_create_email_branding.call_args == call(
- logo=updated_logo_name,
- name=data["name"],
- text=data["text"],
- colour=data["colour"],
- brand_type=data["brand_type"],
- )
-
-
-@pytest.mark.parametrize(
- ("endpoint", "has_data"),
- [
- ("main.create_email_branding", False),
- ("main.update_email_branding", True),
- ],
-)
-def test_deletes_previous_temp_logo_after_uploading_logo(
- client_request, platform_admin_user, mocker, endpoint, has_data, fake_uuid
-):
- if has_data:
- mocker.patch(
- "app.email_branding_client.get_email_branding",
- return_value=create_email_branding(fake_uuid),
- )
-
- with client_request.session_transaction() as session:
- user_id = session["user_id"]
-
- temp_old_filename = EMAIL_LOGO_LOCATION_STRUCTURE.format(
- temp=TEMP_TAG.format(user_id=user_id),
- unique_id=fake_uuid,
- filename="old_test.png",
- )
-
- temp_filename = EMAIL_LOGO_LOCATION_STRUCTURE.format(
- temp=TEMP_TAG.format(user_id=user_id), unique_id=fake_uuid, filename="test.png"
- )
-
- mocked_upload_email_logo = mocker.patch(
- "app.main.views.email_branding.upload_email_logo", return_value=temp_filename
- )
-
- mocked_delete_email_temp_file = mocker.patch(
- "app.main.views.email_branding.delete_email_temp_file"
- )
-
- client_request.login(platform_admin_user)
- client_request.post(
- "main.create_email_branding",
- logo=temp_old_filename,
- branding_id=fake_uuid,
- _data={"file": (BytesIO("".encode("utf-8")), "test.png")},
- _content_type="multipart/form-data",
- )
-
- assert mocked_upload_email_logo.called
- assert mocked_delete_email_temp_file.called
- assert mocked_delete_email_temp_file.call_args == call(temp_old_filename)
-
-
-def test_update_existing_branding(
- client_request,
- platform_admin_user,
- mocker,
- fake_uuid,
- mock_get_email_branding,
- mock_update_email_branding,
-):
- with client_request.session_transaction() as session:
- user_id = session["user_id"]
-
- data = {
- "logo": "test.png",
- "colour": "#0000ff",
- "text": "new text",
- "name": "new name",
- "brand_type": "both",
- }
-
- temp_filename = EMAIL_LOGO_LOCATION_STRUCTURE.format(
- temp=TEMP_TAG.format(user_id=user_id),
- unique_id=fake_uuid,
- filename=data["logo"],
- )
-
- mocker.patch("app.main.views.email_branding.persist_logo")
- mocker.patch("app.main.views.email_branding.delete_email_temp_files_created_by")
-
- client_request.login(platform_admin_user)
- client_request.post(
- ".update_email_branding",
- logo=temp_filename,
- branding_id=fake_uuid,
- _content_type="multipart/form-data",
- _data={
- "colour": data["colour"],
- "name": data["name"],
- "text": data["text"],
- "cdn_url": "https://static-logos.cdn.com",
- "brand_type": data["brand_type"],
- },
- )
-
- updated_logo_name = "{}-{}".format(fake_uuid, data["logo"])
-
- assert mock_update_email_branding.called
- assert mock_update_email_branding.call_args == call(
- branding_id=fake_uuid,
- logo=updated_logo_name,
- name=data["name"],
- text=data["text"],
- colour=data["colour"],
- brand_type=data["brand_type"],
- )
-
-
-def test_temp_logo_is_shown_after_uploading_logo(
- client_request,
- platform_admin_user,
- mocker,
- fake_uuid,
-):
- with client_request.session_transaction() as session:
- user_id = session["user_id"]
-
- temp_filename = EMAIL_LOGO_LOCATION_STRUCTURE.format(
- temp=TEMP_TAG.format(user_id=user_id), unique_id=fake_uuid, filename="test.png"
- )
-
- mocker.patch(
- "app.main.views.email_branding.upload_email_logo", return_value=temp_filename
- )
- mocker.patch("app.main.views.email_branding.delete_email_temp_file")
-
- client_request.login(platform_admin_user)
- page = client_request.post(
- "main.create_email_branding",
- _data={"file": (BytesIO("".encode("utf-8")), "test.png")},
- _content_type="multipart/form-data",
- _follow_redirects=True,
- )
-
- assert page.select_one("#logo-img > img").attrs["src"].endswith(temp_filename)
-
-
-def test_logo_persisted_when_organization_saved(
- client_request, platform_admin_user, mock_create_email_branding, mocker, fake_uuid
-):
- with client_request.session_transaction() as session:
- user_id = session["user_id"]
-
- temp_filename = EMAIL_LOGO_LOCATION_STRUCTURE.format(
- temp=TEMP_TAG.format(user_id=user_id), unique_id=fake_uuid, filename="test.png"
- )
-
- mocked_upload_email_logo = mocker.patch(
- "app.main.views.email_branding.upload_email_logo"
- )
- mocked_persist_logo = mocker.patch("app.main.views.email_branding.persist_logo")
- mocked_delete_email_temp_files_by = mocker.patch(
- "app.main.views.email_branding.delete_email_temp_files_created_by"
- )
-
- client_request.login(platform_admin_user)
- client_request.post(
- ".create_email_branding",
- logo=temp_filename,
- _content_type="multipart/form-data",
- )
-
- assert not mocked_upload_email_logo.called
- assert mocked_persist_logo.called
- assert mocked_delete_email_temp_files_by.called
- assert mocked_delete_email_temp_files_by.call_args == call(user_id)
- assert mock_create_email_branding.called
-
-
-def test_logo_does_not_get_persisted_if_updating_email_branding_client_throws_an_error(
- client_request, platform_admin_user, mock_create_email_branding, mocker, fake_uuid
-):
- with client_request.session_transaction() as session:
- user_id = session["user_id"]
-
- temp_filename = EMAIL_LOGO_LOCATION_STRUCTURE.format(
- temp=TEMP_TAG.format(user_id=user_id), unique_id=fake_uuid, filename="test.png"
- )
-
- mocked_persist_logo = mocker.patch("app.main.views.email_branding.persist_logo")
- mocked_delete_email_temp_files_by = mocker.patch(
- "app.main.views.email_branding.delete_email_temp_files_created_by"
- )
- mocker.patch(
- "app.main.views.email_branding.email_branding_client.create_email_branding",
- side_effect=HTTPError(),
- )
-
- client_request.login(platform_admin_user)
- client_request.post(
- ".create_email_branding",
- logo=temp_filename,
- _content_type="multipart/form-data",
- _expected_status=500,
- )
-
- assert not mocked_persist_logo.called
- assert not mocked_delete_email_temp_files_by.called
-
-
-@pytest.mark.parametrize(
- ("colour_hex", "expected_status_code"),
- [
- ("#FF00FF", 302),
- ("hello", 200),
- ("", 302),
- ],
-)
-def test_colour_regex_validation(
- client_request,
- platform_admin_user,
- mocker,
- fake_uuid,
- colour_hex,
- expected_status_code,
- mock_create_email_branding,
-):
- data = {
- "logo": None,
- "colour": colour_hex,
- "text": "new text",
- "name": "new name",
- "brand_type": "org",
- }
-
- mocker.patch("app.main.views.email_branding.delete_email_temp_files_created_by")
-
- client_request.login(platform_admin_user)
- client_request.post(
- ".create_email_branding",
- _content_type="multipart/form-data",
- _data=data,
- _expected_status=expected_status_code,
- )
diff --git a/tests/app/main/views/test_email_preview.py b/tests/app/main/views/test_email_preview.py
deleted file mode 100644
index 60604a9f3f..0000000000
--- a/tests/app/main/views/test_email_preview.py
+++ /dev/null
@@ -1,95 +0,0 @@
-import re
-
-import pytest
-
-
-@pytest.mark.parametrize(
- ("query_args", "result"), [({}, True), ({"govuk_banner": "false"}, "false")]
-)
-def test_renders(client_request, mocker, query_args, result):
- mocker.patch(
- "app.main.views.index.HTMLEmailTemplate.__str__", return_value="rendered"
- )
-
- response = client_request.get_response("main.email_template", **query_args)
-
- assert response.get_data(as_text=True) == "rendered"
-
-
-def test_displays_both_branding(
- client_request, mock_get_email_branding_with_both_brand_type
-):
- page = client_request.get(
- "main.email_template", branding_style="1", _test_page_title=False
- )
-
- mock_get_email_branding_with_both_brand_type.assert_called_once_with("1")
-
- assert page.find("img", attrs={"src": re.compile("example.png$")})
- assert (
- page.select(
- "body > table:nth-of-type(3) table > tr:nth-of-type(1) > td:nth-of-type(2)"
- )[0]
- .get_text()
- .strip()
- == "Organization text"
- ) # brand text is set
-
-
-def test_displays_org_branding(client_request, mock_get_email_branding):
- # mock_get_email_branding has 'brand_type' of 'org'
- page = client_request.get(
- "main.email_template", branding_style="1", _test_page_title=False
- )
-
- mock_get_email_branding.assert_called_once_with("1")
-
- assert not page.find("a", attrs={"href": "https://www.gsa.gov"})
- assert page.find("img", attrs={"src": re.compile("example.png")})
- assert not page.select(
- "body > table > tr > td[bgcolor='#f00']"
- ) # banner colour is not set
- assert (
- page.select(
- "body > table:nth-of-type(1) > tr:nth-of-type(1) > td:nth-of-type(2)"
- )[0]
- .get_text()
- .strip()
- == "Organization text"
- ) # brand text is set
-
-
-def test_displays_org_branding_with_banner(
- client_request, mock_get_email_branding_with_org_banner_brand_type
-):
- page = client_request.get(
- "main.email_template", branding_style="1", _test_page_title=False
- )
-
- mock_get_email_branding_with_org_banner_brand_type.assert_called_once_with("1")
-
- assert not page.find("a", attrs={"href": "https://www.gsa.gov"})
- assert page.find("img", attrs={"src": re.compile("example.png")})
- assert page.select("body > table > tr > td[bgcolor='#f00']") # banner colour is set
- assert (
- page.select("body > table table > tr > td > span")[0].get_text().strip()
- == "Organization text"
- ) # brand text is set
-
-
-def test_displays_org_branding_with_banner_without_brand_text(
- client_request, mock_get_email_branding_without_brand_text
-):
- # mock_get_email_branding_without_brand_text has 'brand_type' of 'org_banner'
- page = client_request.get(
- "main.email_template", branding_style="1", _test_page_title=False
- )
-
- mock_get_email_branding_without_brand_text.assert_called_once_with("1")
-
- assert not page.find("a", attrs={"href": "https://www.gsa.gov"})
- assert page.find("img", attrs={"src": re.compile("example.png")})
- assert page.select("body > table > tr > td[bgcolor='#f00']") # banner colour is set
- assert (
- not page.select("body > table table > tr > td > span") == 0
- ) # brand text is not set
diff --git a/tests/app/main/views/test_feedback.py b/tests/app/main/views/test_feedback.py
index e3ab37e932..633ad54c48 100644
--- a/tests/app/main/views/test_feedback.py
+++ b/tests/app/main/views/test_feedback.py
@@ -1,812 +1,15 @@
-from functools import partial
-from unittest.mock import ANY, PropertyMock
-
-import pytest
-from flask import url_for
from freezegun import freeze_time
-from notifications_utils.clients.zendesk.zendesk_client import NotifySupportTicket
-
-from app.main.views.feedback import in_business_hours
-from app.models.feedback import (
- GENERAL_TICKET_TYPE,
- PROBLEM_TICKET_TYPE,
- QUESTION_TICKET_TYPE,
-)
-from tests.conftest import SERVICE_ONE_ID, normalize_spaces
-
-def no_redirect():
- return lambda: None
-
-
-@pytest.mark.skip(reason="Not currently using Zendesk")
-def test_get_support_index_page(
- client_request,
-):
- page = client_request.get(".support")
- assert page.select_one("form")["method"] == "post"
- assert "action" not in page.select_one("form")
- assert normalize_spaces(page.select_one("h1").text) == "Support"
- assert (
- normalize_spaces(page.select_one("form label[for=support_type-0]").text)
- == "Report a problem"
- )
- assert page.select_one("form input#support_type-0")["value"] == "report-problem"
- assert (
- normalize_spaces(page.select_one("form label[for=support_type-1]").text)
- == "Ask a question or give feedback"
- )
- assert (
- page.select_one("form input#support_type-1")["value"]
- == "ask-question-give-feedback"
- )
- assert (
- normalize_spaces(page.select_one("form button[type=submit]").text) == "Continue"
- )
-
-
-@pytest.mark.skip(reason="Not currently using Zendesk")
-def test_get_support_index_page_when_signed_out(
- client_request,
-):
- client_request.logout()
- page = client_request.get(".support")
- assert page.select_one("form")["method"] == "post"
- assert "action" not in page.select_one("form")
- assert normalize_spaces(page.select_one("form label[for=who-0]").text) == (
- "I work in the public sector and need to send emails or text messages"
- )
- assert page.select_one("form input#who-0")["value"] == "public-sector"
- assert normalize_spaces(page.select_one("form label[for=who-1]").text) == (
- "I’m a member of the public with a question for the government"
- )
- assert page.select_one("form input#who-1")["value"] == "public"
- assert (
- normalize_spaces(page.select_one("form button[type=submit]").text) == "Continue"
- )
-
-
-@freeze_time("2016-12-12 12:00:00.000000")
-@pytest.mark.parametrize(
- ("support_type", "expected_h1"),
- [
- (PROBLEM_TICKET_TYPE, "Report a problem"),
- (QUESTION_TICKET_TYPE, "Ask a question or give feedback"),
- ],
-)
-def test_choose_support_type(
- client_request,
- mock_get_non_empty_organizations_and_services_for_user,
- support_type,
- expected_h1,
-):
- page = client_request.post(
- "main.support",
- _data={"support_type": support_type},
- _follow_redirects=True,
- )
- assert page.h1.string.strip() == expected_h1
- assert not page.select_one("input[name=name]")
- assert not page.select_one("input[name=email_address]")
- assert page.find("form").find("p").text.strip() == (
- "We’ll reply to test@user.gsa.gov"
- )
+from tests.conftest import normalize_spaces
@freeze_time("2016-12-12 12:00:00.000000")
def test_get_support_as_someone_in_the_public_sector(
- client_request,
-):
- client_request.logout()
- page = client_request.post(
- "main.support",
- _data={"who": "public-sector"},
- _follow_redirects=True,
- )
- assert normalize_spaces(page.select("h1")) == ("Contact Notify.gov support")
- assert page.select_one("form textarea[name=feedback]")
- assert page.select_one("form input[name=name]")
- assert page.select_one("form input[name=email_address]")
- assert page.select_one("form button[type=submit]")
-
-
-def test_get_support_as_member_of_public(
- client_request,
-):
- client_request.logout()
- page = client_request.post(
- "main.support",
- _data={"who": "public"},
- _follow_redirects=True,
- )
- assert normalize_spaces(page.select("h1")) == (
- "The Notify.gov service is for people who work in the government"
- )
- assert len(page.select("h2 a")) == 3
- assert not page.select("form")
- assert not page.select("input")
- assert not page.select("form [type=submit]")
-
-
-@freeze_time("2016-12-12 12:00:00.000000")
-@pytest.mark.parametrize(
- ("ticket_type", "expected_status_code"),
- [(PROBLEM_TICKET_TYPE, 200), (QUESTION_TICKET_TYPE, 200), ("gripe", 404)],
-)
-def test_get_feedback_page(client_request, ticket_type, expected_status_code):
- client_request.logout()
- client_request.get(
- "main.feedback",
- ticket_type=ticket_type,
- _expected_status=expected_status_code,
- )
-
-
-@freeze_time("2016-12-12 12:00:00.000000")
-@pytest.mark.parametrize(
- ("ticket_type", "zendesk_ticket_type"),
- [
- (PROBLEM_TICKET_TYPE, "incident"),
- (QUESTION_TICKET_TYPE, "question"),
- (GENERAL_TICKET_TYPE, "question"),
- ],
-)
-def test_passed_non_logged_in_user_details_through_flow(
- client_request, mocker, ticket_type, zendesk_ticket_type
-):
- client_request.logout()
- mock_create_ticket = mocker.spy(NotifySupportTicket, "__init__")
- mock_send_ticket_to_zendesk = mocker.patch(
- "app.main.views.feedback.zendesk_client.send_ticket_to_zendesk",
- autospec=True,
- )
-
- data = {
- "feedback": "blah",
- "name": "Anne Example",
- "email_address": "anne@example.com",
- }
-
- client_request.post(
- "main.feedback",
- ticket_type=ticket_type,
- _data=data,
- _expected_redirect=url_for(
- "main.thanks",
- out_of_hours_emergency=False,
- email_address_provided=True,
- ),
- )
-
- mock_create_ticket.assert_called_once_with(
- ANY,
- subject="Notify feedback",
- message="blah\n",
- ticket_type=zendesk_ticket_type,
- p1=False,
- user_name="Anne Example",
- user_email="anne@example.com",
- org_id=None,
- org_type=None,
- service_id=None,
- )
- mock_send_ticket_to_zendesk.assert_called_once()
-
-
-@freeze_time("2016-12-12 12:00:00.000000")
-@pytest.mark.parametrize(
- "data",
- [
- {"feedback": "blah"},
- {"feedback": "blah", "name": "Ignored", "email_address": "ignored@email.com"},
- ],
-)
-@pytest.mark.parametrize(
- ("ticket_type", "zendesk_ticket_type"),
- [
- (PROBLEM_TICKET_TYPE, "incident"),
- (QUESTION_TICKET_TYPE, "question"),
- (GENERAL_TICKET_TYPE, "question"),
- ],
-)
-def test_passes_user_details_through_flow(
- client_request,
- mock_get_non_empty_organizations_and_services_for_user,
- mocker,
- ticket_type,
- zendesk_ticket_type,
- data,
-):
- mock_create_ticket = mocker.spy(NotifySupportTicket, "__init__")
- mock_send_ticket_to_zendesk = mocker.patch(
- "app.main.views.feedback.zendesk_client.send_ticket_to_zendesk",
- autospec=True,
- )
-
- client_request.post(
- "main.feedback",
- ticket_type=ticket_type,
- _data=data,
- _expected_status=302,
- _expected_redirect=url_for(
- "main.thanks",
- email_address_provided=True,
- out_of_hours_emergency=False,
- ),
- )
- mock_create_ticket.assert_called_once_with(
- ANY,
- subject="Notify feedback",
- message=ANY,
- ticket_type=zendesk_ticket_type,
- p1=False,
- user_name="Test User",
- user_email="test@user.gsa.gov",
- org_id=None,
- org_type="federal",
- service_id=SERVICE_ONE_ID,
- )
-
- assert mock_create_ticket.call_args[1]["message"] == "\n".join(
- [
- "blah",
- 'Service: "service one"',
- url_for(
- "main.service_dashboard",
- service_id=SERVICE_ONE_ID,
- _external=True,
- ),
- "",
- ]
- )
- mock_send_ticket_to_zendesk.assert_called_once()
-
-
-@freeze_time("2016-12-12 12:00:00.000000")
-@pytest.mark.parametrize(
- "data",
- [
- {"feedback": "blah", "name": "Fred"},
- {"feedback": "blah"},
- ],
-)
-@pytest.mark.parametrize(
- "ticket_type",
- [
- PROBLEM_TICKET_TYPE,
- QUESTION_TICKET_TYPE,
- ],
-)
-def test_email_address_required_for_problems_and_questions(
- client_request,
- mocker,
- data,
- ticket_type,
-):
- mocker.patch("app.main.views.feedback.zendesk_client")
- client_request.logout()
- page = client_request.post(
- "main.feedback", ticket_type=ticket_type, _data=data, _expected_status=200
- )
- assert normalize_spaces(page.select_one(".usa-error-message").text) == (
- "Error: Cannot be empty"
- )
-
-
-@freeze_time("2016-12-12 12:00:00.000000")
-@pytest.mark.parametrize("ticket_type", [PROBLEM_TICKET_TYPE, QUESTION_TICKET_TYPE])
-def test_email_address_must_be_valid_if_provided_to_support_form(
- client_request,
- mocker,
- ticket_type,
-):
- client_request.logout()
- page = client_request.post(
- "main.feedback",
- ticket_type=ticket_type,
- _data={
- "feedback": "blah",
- "email_address": "not valid",
- },
- _expected_status=200,
- )
-
- assert normalize_spaces(page.select_one("span.usa-error-message").text) == (
- "Error: Enter a valid email address"
- )
-
-
-@pytest.mark.parametrize(
- ("ticket_type", "severe", "is_in_business_hours", "is_out_of_hours_emergency"),
- [
- # business hours, never an emergency
- (PROBLEM_TICKET_TYPE, "yes", True, False),
- (QUESTION_TICKET_TYPE, "yes", True, False),
- (PROBLEM_TICKET_TYPE, "no", True, False),
- (QUESTION_TICKET_TYPE, "no", True, False),
- # out of hours, if the user says it’s not an emergency
- (PROBLEM_TICKET_TYPE, "no", False, False),
- (QUESTION_TICKET_TYPE, "no", False, False),
- # out of hours, only problems can be emergencies
- (PROBLEM_TICKET_TYPE, "yes", False, True),
- (QUESTION_TICKET_TYPE, "yes", False, False),
- ],
-)
-def test_urgency(
- client_request,
- mock_get_non_empty_organizations_and_services_for_user,
- mocker,
- ticket_type,
- severe,
- is_in_business_hours,
- is_out_of_hours_emergency,
-):
- mocker.patch(
- "app.main.views.feedback.in_business_hours", return_value=is_in_business_hours
- )
-
- mock_ticket = mocker.patch("app.main.views.feedback.NotifySupportTicket")
- mocker.patch(
- "app.main.views.feedback.zendesk_client.send_ticket_to_zendesk",
- autospec=True,
- )
-
- client_request.post(
- "main.feedback",
- ticket_type=ticket_type,
- severe=severe,
- _data={"feedback": "blah", "email_address": "test@example.com"},
- _expected_status=302,
- _expected_redirect=url_for(
- "main.thanks",
- out_of_hours_emergency=is_out_of_hours_emergency,
- email_address_provided=True,
- ),
- )
- assert mock_ticket.call_args[1]["p1"] == is_out_of_hours_emergency
-
-
-ids, params = zip(
- *[
- (
- "non-logged in users always have to triage",
- (
- GENERAL_TICKET_TYPE,
- False,
- False,
- True,
- 302,
- partial(url_for, "main.triage", ticket_type=GENERAL_TICKET_TYPE),
- ),
- ),
- (
- "trial services are never high priority",
- (PROBLEM_TICKET_TYPE, False, True, False, 200, no_redirect()),
- ),
- (
- "we can triage in hours",
- (PROBLEM_TICKET_TYPE, True, True, True, 200, no_redirect()),
- ),
- (
- "only problems are high priority",
- (QUESTION_TICKET_TYPE, False, True, True, 200, no_redirect()),
- ),
- (
- "should triage out of hours",
- (
- PROBLEM_TICKET_TYPE,
- False,
- True,
- True,
- 302,
- partial(url_for, "main.triage", ticket_type=PROBLEM_TICKET_TYPE),
- ),
- ),
- ]
-)
-
-
-@pytest.mark.parametrize(
- (
- "ticket_type",
- "is_in_business_hours",
- "logged_in",
- "has_live_services",
- "expected_status",
- "expected_redirect",
- ),
- params,
- ids=ids,
-)
-def test_redirects_to_triage(
- client_request,
- api_user_active,
- mocker,
- mock_get_user,
- ticket_type,
- is_in_business_hours,
- logged_in,
- has_live_services,
- expected_status,
- expected_redirect,
-):
- mocker.patch(
- "app.models.user.User.live_services",
- new_callable=PropertyMock,
- return_value=[{}, {}] if has_live_services else [],
- )
- mocker.patch(
- "app.main.views.feedback.in_business_hours", return_value=is_in_business_hours
- )
- if not logged_in:
- client_request.logout()
-
- client_request.get(
- "main.feedback",
- ticket_type=ticket_type,
- _expected_status=expected_status,
- _expected_redirect=expected_redirect(),
- )
-
-
-@pytest.mark.parametrize(
- ("ticket_type", "expected_h1"),
- [
- (PROBLEM_TICKET_TYPE, "Report a problem"),
- (GENERAL_TICKET_TYPE, "Contact Notify.gov support"),
- ],
-)
-def test_options_on_triage_page(
- client_request,
- ticket_type,
- expected_h1,
-):
- page = client_request.get("main.triage", ticket_type=ticket_type)
- assert normalize_spaces(page.select_one("h1").text) == expected_h1
- assert page.select("form input[type=radio]")[0]["value"] == "yes"
- assert page.select("form input[type=radio]")[1]["value"] == "no"
-
-
-def test_doesnt_lose_message_if_post_across_closing(
- client_request,
- mocker,
-):
- mocker.patch("app.models.user.User.live_services", return_value=True)
- mocker.patch("app.main.views.feedback.in_business_hours", return_value=False)
-
- page = client_request.post(
- "main.feedback",
- ticket_type=PROBLEM_TICKET_TYPE,
- _data={"feedback": "foo"},
- _expected_status=302,
- _expected_redirect=url_for(".triage", ticket_type=PROBLEM_TICKET_TYPE),
- )
- with client_request.session_transaction() as session:
- assert session["feedback_message"] == "foo"
-
- page = client_request.get(
- "main.feedback",
- ticket_type=PROBLEM_TICKET_TYPE,
- severe="yes",
- )
-
- with client_request.session_transaction() as session:
- assert page.find("textarea", {"name": "feedback"}).text == "\r\nfoo"
- assert "feedback_message" not in session
-
-
-@pytest.mark.parametrize(
- ("when", "is_in_business_hours"),
- [
- ("2016-06-06 09:29:59+0100", False), # opening time, summer and winter
- ("2016-12-12 09:29:59+0000", False),
- ("2016-06-06 09:30:00+0100", True),
- ("2016-12-12 09:30:00+0000", True),
- ("2016-12-12 12:00:00+0000", True), # middle of the day
- ("2016-12-12 17:29:59+0000", True), # closing time
- ("2016-12-12 17:30:00+0000", False),
- ("2016-12-10 12:00:00+0000", False), # Saturday
- ("2016-12-11 12:00:00+0000", False), # Sunday
- ("2016-01-01 12:00:00+0000", False), # Bank holiday
- ],
-)
-def test_in_business_hours(when, is_in_business_hours):
- with freeze_time(when):
- assert in_business_hours() == is_in_business_hours
-
-
-@pytest.mark.parametrize(
- "ticket_type",
- [
- GENERAL_TICKET_TYPE,
- PROBLEM_TICKET_TYPE,
- ],
-)
-@pytest.mark.parametrize(
- ("choice", "expected_redirect_param"),
- [
- ("yes", "yes"),
- ("no", "no"),
- ],
-)
-def test_triage_redirects_to_correct_url(
- client_request,
- ticket_type,
- choice,
- expected_redirect_param,
-):
- client_request.post(
- "main.triage",
- ticket_type=ticket_type,
- _data={"severe": choice},
- _expected_status=302,
- _expected_redirect=url_for(
- "main.feedback",
- ticket_type=ticket_type,
- severe=expected_redirect_param,
- ),
- )
-
-
-@pytest.mark.parametrize(
- ("extra_args", "expected_back_link"),
- [
- (
- {"severe": "yes"},
- partial(url_for, "main.triage", ticket_type=PROBLEM_TICKET_TYPE),
- ),
- (
- {"severe": "no"},
- partial(url_for, "main.triage", ticket_type=PROBLEM_TICKET_TYPE),
- ),
- ({"severe": "foo"}, partial(url_for, "main.support")), # hacking the URL
- ({}, partial(url_for, "main.support")),
- ],
-)
-@freeze_time("2012-12-12 12:12")
-def test_back_link_from_form(
- client_request,
- mock_get_non_empty_organizations_and_services_for_user,
- extra_args,
- expected_back_link,
-):
- page = client_request.get(
- "main.feedback", ticket_type=PROBLEM_TICKET_TYPE, **extra_args
- )
- assert page.select_one(".usa-back-link")["href"] == expected_back_link()
- assert normalize_spaces(page.select_one("h1").text) == "Report a problem"
-
-
-@pytest.mark.parametrize(
- (
- "is_in_business_hours",
- "severe",
- "expected_status_code",
- "expected_redirect",
- "expected_status_code_when_logged_in",
- "expected_redirect_when_logged_in",
- ),
- [
- (True, "yes", 200, no_redirect(), 200, no_redirect()),
- (True, "no", 200, no_redirect(), 200, no_redirect()),
- (
- False,
- "no",
- 200,
- no_redirect(),
- 200,
- no_redirect(),
- ),
- # Treat empty query param as mangled URL – ask question again
- (
- False,
- "",
- 302,
- partial(url_for, "main.triage", ticket_type=PROBLEM_TICKET_TYPE),
- 302,
- partial(url_for, "main.triage", ticket_type=PROBLEM_TICKET_TYPE),
- ),
- # User hasn’t answered the triage question
- (
- False,
- None,
- 302,
- partial(url_for, "main.triage", ticket_type=PROBLEM_TICKET_TYPE),
- 302,
- partial(url_for, "main.triage", ticket_type=PROBLEM_TICKET_TYPE),
- ),
- # Escalation is needed for non-logged-in users
- (
- False,
- "yes",
- 302,
- partial(url_for, "main.bat_phone"),
- 200,
- no_redirect(),
- ),
- ],
-)
-def test_should_be_shown_the_bat_email(
- client_request,
- active_user_with_permissions,
mocker,
- service_one,
- mock_get_non_empty_organizations_and_services_for_user,
- is_in_business_hours,
- severe,
- expected_status_code,
- expected_redirect,
- expected_status_code_when_logged_in,
- expected_redirect_when_logged_in,
-):
- mocker.patch(
- "app.main.views.feedback.in_business_hours", return_value=is_in_business_hours
- )
-
- feedback_page = url_for(
- "main.feedback", ticket_type=PROBLEM_TICKET_TYPE, severe=severe
- )
-
- client_request.logout()
- client_request.get_url(
- feedback_page,
- _expected_status=expected_status_code,
- _expected_redirect=expected_redirect(),
- )
-
- # logged in users should never be redirected to the bat email page
- client_request.login(active_user_with_permissions)
- client_request.get_url(
- feedback_page,
- _expected_status=expected_status_code_when_logged_in,
- _expected_redirect=expected_redirect_when_logged_in(),
- )
-
-
-@pytest.mark.parametrize(
- (
- "severe",
- "expected_status_code",
- "expected_redirect",
- "expected_status_code_when_logged_in",
- "expected_redirect_when_logged_in",
- ),
- [
- # User hasn’t answered the triage question
- (
- None,
- 302,
- partial(url_for, "main.triage", ticket_type=GENERAL_TICKET_TYPE),
- 302,
- partial(url_for, "main.triage", ticket_type=GENERAL_TICKET_TYPE),
- ),
- # Escalation is needed for non-logged-in users
- (
- "yes",
- 302,
- partial(url_for, "main.bat_phone"),
- 200,
- no_redirect(),
- ),
- ],
-)
-def test_should_be_shown_the_bat_email_for_general_questions(
- client_request,
- active_user_with_permissions,
- mocker,
- service_one,
- mock_get_non_empty_organizations_and_services_for_user,
- severe,
- expected_status_code,
- expected_redirect,
- expected_status_code_when_logged_in,
- expected_redirect_when_logged_in,
-):
- mocker.patch("app.main.views.feedback.in_business_hours", return_value=False)
-
- feedback_page = url_for(
- "main.feedback", ticket_type=GENERAL_TICKET_TYPE, severe=severe
- )
-
- client_request.logout()
- client_request.get_url(
- feedback_page,
- _expected_status=expected_status_code,
- _expected_redirect=expected_redirect(),
- )
-
- # logged in users should never be redirected to the bat email page
- client_request.login(active_user_with_permissions)
- client_request.get_url(
- feedback_page,
- _expected_status=expected_status_code_when_logged_in,
- _expected_redirect=expected_redirect_when_logged_in(),
- )
-
-
-def test_bat_email_page(
- client_request,
active_user_with_permissions,
- mocker,
- service_one,
-):
- bat_phone_page = "main.bat_phone"
-
- client_request.logout()
- page = client_request.get(bat_phone_page)
-
- assert page.select_one(".usa-back-link").text == "Back"
- assert page.select_one(".usa-back-link")["href"] == url_for("main.support")
- assert page.select("main a")[1].text == "Fill in this form"
- assert page.select("main a")[1]["href"] == url_for(
- "main.feedback", ticket_type=PROBLEM_TICKET_TYPE, severe="no"
- )
- next_page = client_request.get_url(page.select("main a")[1]["href"])
- assert next_page.h1.text.strip() == "Report a problem"
-
- client_request.login(active_user_with_permissions)
- client_request.get(
- bat_phone_page,
- _expected_redirect=url_for("main.feedback", ticket_type=PROBLEM_TICKET_TYPE),
- )
-
-
-@pytest.mark.parametrize(
- ("out_of_hours_emergency", "email_address_provided", "out_of_hours", "message"),
- [
- # Out of hours emergencies trump everything else
- (
- True,
- True,
- True,
- "We’ll reply in the next 30 minutes.",
- ),
- (
- True,
- False,
- False, # Not a real scenario
- "We’ll reply in the next 30 minutes.",
- ),
- # Anonymous tickets don’t promise a reply
- (
- False,
- False,
- False,
- "We’ll aim to read your message in the next 30 minutes.",
- ),
- (
- False,
- False,
- True,
- "We’ll read your message when we’re back in the office.",
- ),
- # When we look at your ticket depends on whether we’re in normal
- # business hours
- (
- False,
- True,
- False,
- "We’ll aim to read your message in the next 30 minutes and we’ll reply within one working day.",
- ),
- (False, True, True, "We’ll reply within one working day."),
- ],
-)
-def test_thanks(
client_request,
- mocker,
- api_user_active,
- mock_get_user,
- out_of_hours_emergency,
- email_address_provided,
- out_of_hours,
- message,
):
- mocker.patch(
- "app.main.views.feedback.in_business_hours", return_value=(not out_of_hours)
- )
page = client_request.get(
- "main.thanks",
- out_of_hours_emergency=out_of_hours_emergency,
- email_address_provided=email_address_provided,
+ "main.support",
)
- assert normalize_spaces(page.find("main").find("p").text) == message
+ assert normalize_spaces(page.select("h1")) == ("Contact us")
diff --git a/tests/app/main/views/test_index.py b/tests/app/main/views/test_index.py
index 0b584962ad..315ef635a8 100644
--- a/tests/app/main/views/test_index.py
+++ b/tests/app/main/views/test_index.py
@@ -5,7 +5,7 @@
from flask import url_for
from freezegun import freeze_time
-from tests.conftest import SERVICE_ONE_ID, normalize_spaces, sample_uuid
+from tests.conftest import SERVICE_ONE_ID, normalize_spaces
def test_non_logged_in_user_can_see_homepage(
@@ -15,7 +15,9 @@ def test_non_logged_in_user_can_see_homepage(
client_request.logout()
page = client_request.get("main.index", _test_page_title=False)
- assert page.h1.text.strip() == ("Reach people where they are with government-powered text messages")
+ assert page.h1.text.strip() == (
+ "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",
@@ -63,14 +65,6 @@ def test_robots(client_request):
("endpoint", "kwargs"),
[
("sign_in", {}),
- ("support", {}),
- ("support_public", {}),
- ("triage", {}),
- ("feedback", {"ticket_type": "ask-question-give-feedback"}),
- ("feedback", {"ticket_type": "general"}),
- ("feedback", {"ticket_type": "report-problem"}),
- ("bat_phone", {}),
- ("thanks", {}),
("register", {}),
pytest.param("index", {}, marks=pytest.mark.xfail(raises=AssertionError)),
],
@@ -102,12 +96,10 @@ def test_hiding_pages_from_search_engines(
"documentation",
"security",
"message_status",
- "features_email",
"features_sms",
"how_to_pay",
"get_started",
"guidance_index",
- "branding_and_customisation",
"create_and_send_messages",
"edit_and_format_messages",
"send_files_by_email",
@@ -263,36 +255,6 @@ def test_css_is_served_from_correct_path(client_request):
# assert logo_svg_fallback['src'].startswith('https://static.example.com/images/us-notify-color.png')
-@pytest.mark.parametrize(
- ("extra_args", "email_branding_retrieved"),
- [
- (
- {},
- False,
- ),
- (
- {"branding_style": "__NONE__"},
- False,
- ),
- (
- {"branding_style": sample_uuid()},
- True,
- ),
- ],
-)
-def test_email_branding_preview(
- client_request,
- mock_get_email_branding,
- extra_args,
- email_branding_retrieved,
-):
- page = client_request.get(
- "main.email_template", _test_page_title=False, **extra_args
- )
- assert page.title.text == "Email branding preview"
- assert mock_get_email_branding.called is email_branding_retrieved
-
-
@pytest.mark.parametrize(
("current_date", "expected_rate"),
[
diff --git a/tests/app/main/views/test_jobs.py b/tests/app/main/views/test_jobs.py
index cc611f32bc..42cd5ecbf9 100644
--- a/tests/app/main/views/test_jobs.py
+++ b/tests/app/main/views/test_jobs.py
@@ -349,9 +349,10 @@ def test_should_show_scheduled_job(
job_id=fake_uuid,
)
- assert normalize_spaces(page.select("main p")[1].text) == (
+ assert normalize_spaces(page.select("main div p")[1].text) == (
"Sending Two week reminder today at 00:00 US/Eastern"
)
+
assert page.select("main p a")[0]["href"] == url_for(
"main.view_template_version",
service_id=SERVICE_ONE_ID,
@@ -481,3 +482,22 @@ def test_should_show_updates_for_scheduled_job_as_json(
@freeze_time("2016-01-10 12:00:00.000000")
def test_time_left(job_created_at, expected_message):
assert get_time_left(job_created_at) == expected_message
+
+
+def test_should_show_message_note(
+ client_request,
+ mock_get_service_template,
+ mock_get_scheduled_job,
+ mock_get_service_data_retention,
+ mock_get_notifications,
+ fake_uuid,
+):
+ page = client_request.get(
+ "main.view_job",
+ service_id=SERVICE_ONE_ID,
+ job_id=fake_uuid,
+ )
+
+ assert normalize_spaces(page.select_one("main p.notification-status").text) == (
+ "Messages will remain in pending state until carrier status is received, typically 5 minutes."
+ )
diff --git a/tests/app/main/views/test_manage_users.py b/tests/app/main/views/test_manage_users.py
index c5d97bd030..9bdabf9253 100644
--- a/tests/app/main/views/test_manage_users.py
+++ b/tests/app/main/views/test_manage_users.py
@@ -27,7 +27,7 @@
(
create_active_user_with_permissions(),
(
- "Test User (you) "
+ "Test User(you) "
"Permissions "
"Can See dashboard "
"Can Send messages "
@@ -39,18 +39,18 @@
),
(
create_active_user_empty_permissions(),
- ("Test User With Empty Permissions (you) " "Permissions"),
+ ("Test User With Empty Permissions(you) " "Permissions"),
False,
),
(
create_active_user_view_permissions(),
- ("Test User With Permissions (you) " "Permissions " "Can See dashboard"),
+ ("Test User With Permissions(you) " "Permissions " "Can See dashboard"),
False,
),
(
create_active_user_manage_template_permissions(),
(
- "Test User With Permissions (you) "
+ "Test User With Permissions(you) "
"Permissions "
"Can See dashboard "
"Can Add and edit templates"
@@ -232,7 +232,7 @@ def test_should_show_caseworker_on_overview_page(
assert normalize_spaces(page.select_one("h1").text) == "Team members"
assert normalize_spaces(page.select(".user-list-item")[0].text) == (
- "Test User With Permissions (you) " "Permissions " "Can See dashboard"
+ "Test User With Permissions(you) " "Permissions " "Can See dashboard"
)
# [1:5] are invited users
assert normalize_spaces(page.select(".user-list-item")[6].text) == (
@@ -1157,6 +1157,35 @@ def test_invite_user_with_email_auth_service(
)
+def test_resend_expired_invitation(
+ client_request,
+ mock_get_invites_for_service,
+ expired_invite,
+ active_user_with_permissions,
+ mock_get_users_by_service,
+ mock_get_template_folders,
+ mocker,
+):
+ mock_resend = mocker.patch("app.invite_api_client.resend_invite")
+ mocker.patch(
+ "app.invite_api_client.get_invited_user_for_service",
+ return_value=expired_invite,
+ )
+ page = client_request.get(
+ "main.resend_invite",
+ service_id=SERVICE_ONE_ID,
+ invited_user_id=expired_invite["id"],
+ _follow_redirects=True,
+ )
+ assert normalize_spaces(page.h1.text) == "Team members"
+ assert mock_resend.called
+ called_args = set(mock_resend.call_args.args) | set(
+ mock_resend.call_args.kwargs.values()
+ )
+ assert SERVICE_ONE_ID in called_args
+ assert expired_invite["id"] in called_args
+
+
def test_cancel_invited_user_cancels_user_invitations(
client_request,
mock_get_invites_for_service,
@@ -1168,7 +1197,8 @@ def test_cancel_invited_user_cancels_user_invitations(
):
mock_cancel = mocker.patch("app.invite_api_client.cancel_invited_user")
mocker.patch(
- "app.invite_api_client.get_invited_user_for_service", return_value=sample_invite
+ "app.invite_api_client.get_invited_user_for_service",
+ return_value=sample_invite,
)
page = client_request.get(
@@ -1210,7 +1240,7 @@ def test_cancel_invited_user_doesnt_work_if_user_not_invited_to_this_service(
(
"pending",
(
- "invited_user@test.gsa.gov (invited) "
+ "invited_user@test.gsa.gov(invited) "
"Permissions "
"Can See dashboard "
"Can Send messages "
@@ -1222,7 +1252,7 @@ def test_cancel_invited_user_doesnt_work_if_user_not_invited_to_this_service(
(
"cancelled",
(
- "invited_user@test.gsa.gov (cancelled invite) "
+ "invited_user@test.gsa.gov(cancelled invite) "
"Permissions"
# all permissions are greyed out
),
@@ -1383,10 +1413,7 @@ def test_manage_user_page_shows_how_many_folders_user_can_view(
user_div = page.select_one(
"h2[title='notify@digital.cabinet-office.gov.uk']"
).parent
- assert (
- user_div.select_one(".tick-cross-list-hint:last-child").text.strip()
- == expected_message
- )
+ assert user_div.select_one(".tick-cross-list-hint").text.strip() == expected_message
def test_manage_user_page_doesnt_show_folder_hint_if_service_has_no_folders(
diff --git a/tests/app/main/views/test_platform_admin.py b/tests/app/main/views/test_platform_admin.py
index 49206d8737..f5e7862a78 100644
--- a/tests/app/main/views/test_platform_admin.py
+++ b/tests/app/main/views/test_platform_admin.py
@@ -757,7 +757,6 @@ def test_clear_cache_shows_form(
"user",
"service",
"template",
- "email_branding",
"organization",
}
diff --git a/tests/app/main/views/test_send.py b/tests/app/main/views/test_send.py
index 0d9c896e4a..7448c44cf3 100644
--- a/tests/app/main/views/test_send.py
+++ b/tests/app/main/views/test_send.py
@@ -30,6 +30,63 @@
normalize_spaces,
)
+FAKE_ONE_OFF_NOTIFICATION = {
+ "links": {},
+ "notifications": [
+ {
+ "api_key": None,
+ "billable_units": 0,
+ "carrier": None,
+ "client_reference": None,
+ "created_at": "2023-12-14T20:35:55+00:00",
+ "created_by": {
+ "email_address": "grsrbsrgsrf@fake.gov",
+ "id": "de059e0a-42e5-48bb-939e-4f76804ab739",
+ "name": "grsrbsrgsrf",
+ },
+ "document_download_count": None,
+ "id": "a3442b43-0ba1-4854-9e0a-d2fba1cc9b81",
+ "international": False,
+ "job": {
+ "id": "55b242b5-9f62-4271-aff7-039e9c320578",
+ "original_file_name": "1127b78e-a4a8-4b70-8f4f-9f4fbf03ece2.csv",
+ },
+ "job_row_number": 0,
+ "key_name": None,
+ "key_type": "normal",
+ "normalised_to": "+16615555555",
+ "notification_type": "sms",
+ "personalisation": {
+ "dayofweek": "2",
+ "favecolor": "3",
+ "phonenumber": "+16615555555",
+ },
+ "phone_prefix": "1",
+ "provider_response": None,
+ "rate_multiplier": 1.0,
+ "reference": None,
+ "reply_to_text": "development",
+ "sent_at": None,
+ "sent_by": None,
+ "service": "f62d840f-8bcb-4b36-b959-4687e16dd1a1",
+ "status": "created",
+ "template": {
+ "content": "((day of week)) and ((fave color))",
+ "id": "bd9caa7e-00ee-4c5a-839e-10ae1a7e6f73",
+ "name": "personalized",
+ "redact_personalisation": False,
+ "subject": None,
+ "template_type": "sms",
+ "version": 1,
+ },
+ "to": "+16615555555",
+ "updated_at": None,
+ }
+ ],
+ "page_size": 50,
+ "total": 1,
+}
+
template_types = ["email", "sms"]
unchanging_fake_uuid = uuid.uuid4()
@@ -2060,10 +2117,19 @@ def test_route_permissions_send_check_notifications(
route,
response_code,
method,
+ mock_create_job,
+ mock_s3_upload,
):
with client_request.session_transaction() as session:
session["recipient"] = "2028675301"
session["placeholders"] = {"name": "a"}
+
+ mocker.patch("app.main.views.send.check_messages")
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
+
validate_route_permission_with_client(
mocker,
client_request,
@@ -2578,28 +2644,31 @@ def test_check_notification_shows_preview(
def test_send_notification_submits_data(
client_request,
fake_uuid,
- mock_send_notification,
mock_get_service_template,
template,
recipient,
placeholders,
expected_personalisation,
+ mocker,
+ mock_create_job,
+ mock_s3_upload,
):
with client_request.session_transaction() as session:
session["recipient"] = recipient
session["placeholders"] = placeholders
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
+
+ mocker.patch("app.main.views.send.check_messages", return_value="")
+
client_request.post(
"main.send_notification", service_id=SERVICE_ONE_ID, template_id=fake_uuid
)
- mock_send_notification.assert_called_once_with(
- SERVICE_ONE_ID,
- template_id=fake_uuid,
- recipient=recipient,
- personalisation=expected_personalisation,
- sender_id=None,
- )
+ mock_create_job.assert_called_once()
def test_send_notification_clears_session(
@@ -2608,11 +2677,20 @@ def test_send_notification_clears_session(
fake_uuid,
mock_send_notification,
mock_get_service_template,
+ mocker,
+ mock_create_job,
+ mock_s3_upload,
):
with client_request.session_transaction() as session:
session["recipient"] = "2028675301"
session["placeholders"] = {"a": "b"}
+ mocker.patch("app.main.views.send.check_messages")
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
+
client_request.post(
"main.send_notification", service_id=service_one["id"], template_id=fake_uuid
)
@@ -2661,22 +2739,26 @@ def test_send_notification_redirects_to_view_page(
mock_get_service_template,
extra_args,
extra_redirect_args,
+ mocker,
+ mock_create_job,
+ mock_s3_upload,
):
with client_request.session_transaction() as session:
session["recipient"] = "2028675301"
session["placeholders"] = {"a": "b"}
+ mocker.patch("app.main.views.send.check_messages")
+
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
+
client_request.post(
"main.send_notification",
service_id=SERVICE_ONE_ID,
template_id=fake_uuid,
_expected_status=302,
- _expected_redirect=url_for(
- ".view_notification",
- service_id=SERVICE_ONE_ID,
- notification_id=fake_uuid,
- **extra_redirect_args,
- ),
**extra_args,
)
@@ -2715,13 +2797,24 @@ def test_send_notification_shows_error_if_400(
fake_uuid,
mocker,
mock_get_service_template_with_placeholders,
+ mock_create_job,
exception_msg,
expected_h1,
expected_err_details,
+ mock_s3_upload,
):
class MockHTTPError(HTTPError):
message = exception_msg
+ mocker.patch(
+ "app.main.views.send.check_messages",
+ )
+
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
+
mocker.patch(
"app.notification_api_client.send_notification",
side_effect=MockHTTPError(),
@@ -2730,18 +2823,14 @@ class MockHTTPError(HTTPError):
session["recipient"] = "2028675301"
session["placeholders"] = {"name": "a" * 900}
+ # This now redirects to the jobs results page
page = client_request.post(
"main.send_notification",
service_id=service_one["id"],
template_id=fake_uuid,
- _expected_status=200,
+ _expected_status=302,
)
- assert normalize_spaces(page.select(".banner-dangerous h1")[0].text) == expected_h1
- assert (
- normalize_spaces(page.select(".banner-dangerous p")[0].text)
- == expected_err_details
- )
assert not page.find("input[type=submit]")
@@ -2750,11 +2839,19 @@ def test_send_notification_shows_email_error_in_trial_mode(
fake_uuid,
mocker,
mock_get_service_email_template,
+ mock_create_job,
+ mock_s3_upload,
):
class MockHTTPError(HTTPError):
message = TRIAL_MODE_MSG
status_code = 400
+ mocker.patch("app.main.views.send.check_messages")
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
+
mocker.patch(
"app.notification_api_client.send_notification",
side_effect=MockHTTPError(),
@@ -2763,18 +2860,12 @@ class MockHTTPError(HTTPError):
session["recipient"] = "test@example.com"
session["placeholders"] = {"date": "foo", "thing": "bar"}
- page = client_request.post(
+ # Calling this means we successful ran a job so we will be redirect to the jobs page
+ client_request.post(
"main.send_notification",
service_id=SERVICE_ONE_ID,
template_id=fake_uuid,
- _expected_status=200,
- )
-
- assert normalize_spaces(page.select(".banner-dangerous h1")[0].text) == (
- "You cannot send to this email address"
- )
- assert normalize_spaces(page.select(".banner-dangerous p")[0].text) == (
- "In trial mode you can only send to yourself and members of your team"
+ _expected_status=302,
)
diff --git a/tests/app/main/views/test_sign_in.py b/tests/app/main/views/test_sign_in.py
index 018321fbed..7970afa089 100644
--- a/tests/app/main/views/test_sign_in.py
+++ b/tests/app/main/views/test_sign_in.py
@@ -3,6 +3,7 @@
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
@@ -39,6 +40,18 @@ def test_render_sign_in_template_with_next_link_for_password_reset(client_reques
)
+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")
diff --git a/tests/app/main/views/test_sign_out.py b/tests/app/main/views/test_sign_out.py
index 9ab3ad3cc6..d70c5d0f80 100644
--- a/tests/app/main/views/test_sign_out.py
+++ b/tests/app/main/views/test_sign_out.py
@@ -1,5 +1,90 @@
from tests.conftest import SERVICE_ONE_ID
+FAKE_ONE_OFF_NOTIFICATION = {
+ "links": {},
+ "notifications": [
+ {
+ "api_key": None,
+ "billable_units": 0,
+ "carrier": None,
+ "client_reference": None,
+ "created_at": "2023-12-14T20:35:55+00:00",
+ "created_by": {
+ "email_address": "grsrbsrgsrf@fake.gov",
+ "id": "de059e0a-42e5-48bb-939e-4f76804ab739",
+ "name": "grsrbsrgsrf",
+ },
+ "document_download_count": None,
+ "id": "a3442b43-0ba1-4854-9e0a-d2fba1cc9b81",
+ "international": False,
+ "job": {
+ "id": "55b242b5-9f62-4271-aff7-039e9c320578",
+ "original_file_name": "1127b78e-a4a8-4b70-8f4f-9f4fbf03ece2.csv",
+ },
+ "job_row_number": 0,
+ "key_name": None,
+ "key_type": "normal",
+ "normalised_to": "+16615555555",
+ "notification_type": "sms",
+ "personalisation": {
+ "dayofweek": "2",
+ "favecolor": "3",
+ "phonenumber": "+16615555555",
+ },
+ "phone_prefix": "1",
+ "provider_response": None,
+ "rate_multiplier": 1.0,
+ "reference": None,
+ "reply_to_text": "development",
+ "sent_at": None,
+ "sent_by": None,
+ "service": "f62d840f-8bcb-4b36-b959-4687e16dd1a1",
+ "status": "created",
+ "template": {
+ "content": "((day of week)) and ((fave color))",
+ "id": "bd9caa7e-00ee-4c5a-839e-10ae1a7e6f73",
+ "name": "personalized",
+ "redact_personalisation": False,
+ "subject": None,
+ "template_type": "sms",
+ "version": 1,
+ },
+ "to": "+16615555555",
+ "updated_at": None,
+ }
+ ],
+ "page_size": 50,
+ "total": 1,
+}
+
+MOCK_JOBS = {
+ "data": [
+ {
+ "archived": False,
+ "created_at": "2024-01-04T20:43:52+00:00",
+ "created_by": {
+ "id": "mocked_user_id",
+ "name": "mocked_user",
+ },
+ "id": "mocked_notification_id",
+ "job_status": "finished",
+ "notification_count": 1,
+ "original_file_name": "mocked_file.csv",
+ "processing_finished": "2024-01-25T23:02:25+00:00",
+ "processing_started": "2024-01-25T23:02:24+00:00",
+ "scheduled_for": None,
+ "service": "21b3ee3d-1cb0-4666-bfa0-9c5ac26d3fe3",
+ "service_name": {"name": "Mock Texting Service"},
+ "statistics": [{"count": 1, "status": "sending"}],
+ "template": "6a456418-498c-4c86-b0cd-9403c14a216c",
+ "template_name": "Mock Template Name",
+ "template_type": "sms",
+ "template_version": 3,
+ "updated_at": "2024-01-25T23:02:25+00:00",
+ }
+ ]
+}
+
def test_render_sign_out_redirects_to_sign_in(client_request):
# TODO with the change to using login.gov, we no longer redirect directly to the sign in page.
@@ -18,6 +103,7 @@ def test_render_sign_out_redirects_to_sign_in(client_request):
def test_sign_out_user(
+ mocker,
client_request,
mock_get_service,
api_user_active,
@@ -36,6 +122,13 @@ def test_sign_out_user(
with client_request.session_transaction() as session:
assert session.get("user_id") is not None
# Check we are logged in
+ mocker.patch("app.job_api_client.get_jobs", return_value=MOCK_JOBS)
+
+ mocker.patch(
+ "app.notification_api_client.get_notifications_for_service",
+ return_value=FAKE_ONE_OFF_NOTIFICATION,
+ )
+
client_request.get(
"main.service_dashboard",
service_id=SERVICE_ONE_ID,
diff --git a/tests/app/main/views/test_templates.py b/tests/app/main/views/test_templates.py
index 4fddce76d2..d9fd5b1534 100644
--- a/tests/app/main/views/test_templates.py
+++ b/tests/app/main/views/test_templates.py
@@ -632,7 +632,7 @@ def test_should_show_sms_template_with_downgraded_unicode_characters(
fake_uuid,
):
msg = "here:\tare some “fancy quotes” and zero\u200Bwidth\u200Bspaces"
- rendered_msg = 'here: are some “fancy quotes” and zerowidthspaces'
+ rendered_msg = "here: are some “fancy quotes” and zerowidthspaces"
mocker.patch(
"app.service_api_client.get_service_template",
@@ -1955,7 +1955,7 @@ def test_set_template_sender(
"sms",
False,
"a" * 160,
- "Will be charged as 2 text messages",
+ "Will be charged as 1 text message",
None,
),
(
@@ -1969,7 +1969,7 @@ def test_set_template_sender(
"sms",
True,
"a" * 147,
- "Will be charged as 2 text messages",
+ "Will be charged as 1 text message",
None,
),
(
@@ -1984,7 +1984,7 @@ def test_set_template_sender(
"sms",
False,
"a" * 918,
- "Will be charged as 7 text messages",
+ "Will be charged as 6 text messages",
None,
),
(
@@ -2025,11 +2025,8 @@ def test_set_template_sender(
(
"sms",
False,
- # The length of this string in bytes is 210, needing two fragments.
- # Seems like previous calculation was wrong unless the number of characters
- # somehow has priority over the number of bytes in a fragment (?)
"Ẅ" * 70,
- "Will be charged as 2 text messages",
+ "Will be charged as 1 text message. Use of characters outside the IEC_8859-1 character set may increase the message fragment count, resulting in additional charges, and these IEC_8859-1 characters may not display properly on some older mobile devices.", # noqa E501
None,
),
(
@@ -2043,11 +2040,7 @@ def test_set_template_sender(
"sms",
False,
"Ẅ" * 918,
- # The length of this string in bytes is 2754. Divide by 140 and we get 19. Then round up.
- # Don't know why it was previously calculated as 14. They seem to have charged by characters
- # rather than length of the fragment in bytes.
- # "Will be charged as 14 text messages",
- "Will be charged as 20 text messages",
+ "Will be charged as 14 text messages",
None,
),
(
diff --git a/tests/app/models/test_event.py b/tests/app/models/test_event.py
index 4f64f7a32f..bbff4fe913 100644
--- a/tests/app/models/test_event.py
+++ b/tests/app/models/test_event.py
@@ -12,7 +12,6 @@
("active", False, True, ("Unsuspended this service")),
("active", True, False, ("Deleted this service")),
("contact_link", "x", "y", ("Set the contact details for this service to ‘y’")),
- ("email_branding", "foo", "bar", ("Updated this service’s email branding")),
(
"inbound_api",
"foo",
diff --git a/tests/app/models/test_user.py b/tests/app/models/test_user.py
index 4611d5ea60..8c4c269072 100644
--- a/tests/app/models/test_user.py
+++ b/tests/app/models/test_user.py
@@ -10,7 +10,6 @@ def test_anonymous_user(notify_admin):
assert AnonymousUser().default_organization.name is None
assert AnonymousUser().default_organization.domains == []
assert AnonymousUser().default_organization.organization_type is None
- assert AnonymousUser().default_organization.request_to_go_live_notes is None
def test_user(notify_admin):
diff --git a/tests/app/notify_client/test_email_branding_client.py b/tests/app/notify_client/test_email_branding_client.py
deleted file mode 100644
index 85b2c12c9b..0000000000
--- a/tests/app/notify_client/test_email_branding_client.py
+++ /dev/null
@@ -1,104 +0,0 @@
-from unittest.mock import call
-
-from app.notify_client.email_branding_client import EmailBrandingClient
-
-
-def test_get_email_branding(mocker, fake_uuid):
- mock_get = mocker.patch(
- "app.notify_client.email_branding_client.EmailBrandingClient.get",
- return_value={"foo": "bar"},
- )
- mock_redis_get = mocker.patch(
- "app.extensions.RedisClient.get",
- return_value=None,
- )
- mock_redis_set = mocker.patch(
- "app.extensions.RedisClient.set",
- )
- EmailBrandingClient().get_email_branding(fake_uuid)
- mock_get.assert_called_once_with(url="/email-branding/{}".format(fake_uuid))
- mock_redis_get.assert_called_once_with("email_branding-{}".format(fake_uuid))
- mock_redis_set.assert_called_once_with(
- "email_branding-{}".format(fake_uuid),
- '{"foo": "bar"}',
- ex=604800,
- )
-
-
-def test_get_all_email_branding(mocker):
- mock_get = mocker.patch(
- "app.notify_client.email_branding_client.EmailBrandingClient.get",
- return_value={"email_branding": [1, 2, 3]},
- )
- mock_redis_get = mocker.patch(
- "app.extensions.RedisClient.get",
- return_value=None,
- )
- mock_redis_set = mocker.patch(
- "app.extensions.RedisClient.set",
- )
- EmailBrandingClient().get_all_email_branding()
- mock_get.assert_called_once_with(url="/email-branding")
- mock_redis_get.assert_called_once_with("email_branding")
- mock_redis_set.assert_called_once_with(
- "email_branding",
- "[1, 2, 3]",
- ex=604800,
- )
-
-
-def test_create_email_branding(mocker):
- org_data = {
- "logo": "test.png",
- "name": "test name",
- "text": "test name",
- "colour": "red",
- "brand_type": "org",
- }
-
- mock_post = mocker.patch(
- "app.notify_client.email_branding_client.EmailBrandingClient.post"
- )
- mock_redis_delete = mocker.patch("app.extensions.RedisClient.delete")
- EmailBrandingClient().create_email_branding(
- logo=org_data["logo"],
- name=org_data["name"],
- text=org_data["text"],
- colour=org_data["colour"],
- brand_type="org",
- )
-
- mock_post.assert_called_once_with(url="/email-branding", data=org_data)
-
- mock_redis_delete.assert_called_once_with("email_branding")
-
-
-def test_update_email_branding(mocker, fake_uuid):
- org_data = {
- "logo": "test.png",
- "name": "test name",
- "text": "test name",
- "colour": "red",
- "brand_type": "org",
- }
-
- mock_post = mocker.patch(
- "app.notify_client.email_branding_client.EmailBrandingClient.post"
- )
- mock_redis_delete = mocker.patch("app.extensions.RedisClient.delete")
- EmailBrandingClient().update_email_branding(
- branding_id=fake_uuid,
- logo=org_data["logo"],
- name=org_data["name"],
- text=org_data["text"],
- colour=org_data["colour"],
- brand_type="org",
- )
-
- mock_post.assert_called_once_with(
- url="/email-branding/{}".format(fake_uuid), data=org_data
- )
- assert mock_redis_delete.call_args_list == [
- call("email_branding-{}".format(fake_uuid)),
- call("email_branding"),
- ]
diff --git a/tests/app/notify_client/test_service_api_client.py b/tests/app/notify_client/test_service_api_client.py
index acffa3f8a6..815f3e5e27 100644
--- a/tests/app/notify_client/test_service_api_client.py
+++ b/tests/app/notify_client/test_service_api_client.py
@@ -632,7 +632,6 @@ def test_client_updates_service_with_allowed_attributes(
"consent_to_research",
"contact_link",
"count_as_live",
- "email_branding",
"email_from",
"free_sms_fragment_limit",
"go_live_at",
diff --git a/tests/app/test_navigation.py b/tests/app/test_navigation.py
index 9873d64223..110bae914a 100644
--- a/tests/app/test_navigation.py
+++ b/tests/app/test_navigation.py
@@ -31,13 +31,12 @@
"api_keys",
"archive_service",
"archive_user",
- "bat_phone",
"begin_tour",
"billing_details",
- "branding_and_customisation",
"callbacks",
"cancel_invited_org_user",
"cancel_invited_user",
+ "resend_invite",
"cancel_job",
"change_user_auth",
"check_and_resend_text_code",
@@ -61,7 +60,6 @@
"count_content_length",
"create_and_send_messages",
"create_api_key",
- "create_email_branding",
"data_retention",
"delete_service_template",
"delete_template_folder",
@@ -75,8 +73,6 @@
"edit_data_retention",
"edit_organization_billing_details",
"edit_organization_domains",
- "edit_organization_email_branding",
- "edit_organization_go_live_notes",
"edit_organization_name",
"edit_organization_notes",
"edit_organization_type",
@@ -87,20 +83,10 @@
"edit_user_email",
"edit_user_mobile_number",
"edit_user_permissions",
- "email_branding",
- "email_branding_govuk",
- "email_branding_govuk_and_org",
- "email_branding_organization",
- "email_branding_request",
- "email_branding_something_else",
"email_not_received",
- "email_template",
"error",
- "estimate_usage",
"features",
- "features_email",
"features_sms",
- "feedback",
"find_services_by_name",
"find_users_by_email",
"forgot_password",
@@ -146,7 +132,6 @@
"old_using_notify",
"organization_billing",
"organization_dashboard",
- "organization_preview_email_branding",
"organization_settings",
"organization_trial_mode_services",
"organizations",
@@ -165,7 +150,6 @@
"registration_continue",
"remove_user_from_organization",
"remove_user_from_service",
- "request_to_go_live",
"resend_email_link",
"resend_email_verification",
"resume_service",
@@ -193,10 +177,8 @@
"service_edit_sms_sender",
"service_email_reply_to",
"service_name_change",
- "service_preview_email_branding",
"service_set_auth_type",
"service_set_channel",
- "service_set_email_branding",
"service_set_inbound_number",
"service_set_inbound_sms",
"service_set_international_sms",
@@ -219,16 +201,12 @@
"sign_in",
"sign_out",
"start_job",
- "submit_request_to_go_live",
"support",
- "support_public",
"suspend_service",
"template_history",
"template_usage",
"terms",
- "thanks",
"tour_step",
- "triage",
"trial_mode",
"trial_mode_new",
"trial_services",
@@ -236,7 +214,6 @@
"two_factor_email",
"two_factor_email_interstitial",
"two_factor_email_sent",
- "update_email_branding",
"uploads",
"usage",
"user_information",
@@ -423,9 +400,9 @@ def test_navigation_urls(
assert [a["href"] for a in page.select(".nav a")] == [
"/services/{}/templates".format(SERVICE_ONE_ID),
"/services/{}".format(SERVICE_ONE_ID),
- "/services/{}/users".format(SERVICE_ONE_ID),
"/services/{}/usage".format(SERVICE_ONE_ID),
- "/services/{}/service-settings".format(SERVICE_ONE_ID),
+ # "/services/{}/users".format(SERVICE_ONE_ID),
+ # "/services/{}/service-settings".format(SERVICE_ONE_ID),
# '/services/{}/api'.format(SERVICE_ONE_ID),
]
@@ -441,7 +418,7 @@ def test_caseworkers_get_caseworking_navigation(
client_request.login(active_caseworking_user)
page = client_request.get("main.choose_template", service_id=SERVICE_ONE_ID)
assert normalize_spaces(page.select_one("header + .grid-container nav").text) == (
- "Send messages Sent messages Team members"
+ "Send messages Sent messages"
)
@@ -456,5 +433,5 @@ def test_caseworkers_see_jobs_nav_if_jobs_exist(
client_request.login(active_caseworking_user)
page = client_request.get("main.choose_template", service_id=SERVICE_ONE_ID)
assert normalize_spaces(page.select_one("header + .grid-container nav").text) == (
- "Send messages Sent messages Team members"
+ "Send messages Sent messages"
)
diff --git a/tests/app/utils/test_branding.py b/tests/app/utils/test_branding.py
deleted file mode 100644
index 8c1aae4bdb..0000000000
--- a/tests/app/utils/test_branding.py
+++ /dev/null
@@ -1,189 +0,0 @@
-from unittest.mock import PropertyMock
-
-import pytest
-
-from app.models.service import Service
-from app.utils.branding import get_email_choices
-from tests import organization_json
-from tests.conftest import create_email_branding
-
-
-@pytest.mark.parametrize("function", [get_email_choices])
-@pytest.mark.parametrize(
- ("org_type", "expected_options"),
- [
- ("federal", []),
- ("state", []),
- ],
-)
-def test_get_choices_service_not_assigned_to_org(
- service_one,
- function,
- org_type,
- expected_options,
-):
- service_one["organization_type"] = org_type
- service = Service(service_one)
-
- options = function(service)
- assert list(options) == expected_options
-
-
-@pytest.mark.parametrize(
- ("org_type", "branding_id", "expected_options"),
- [
- (
- "federal",
- None,
- [
- ("govuk_and_org", "GOV.UK and Test Organization"),
- ("organization", "Test Organization"),
- ],
- ),
- (
- "federal",
- "some-branding-id",
- [
- ("govuk", "GOV.UK"), # central orgs can switch back to gsa.gov
- ("govuk_and_org", "GOV.UK and Test Organization"),
- ("organization", "Test Organization"),
- ],
- ),
- ("state", None, [("organization", "Test Organization")]),
- ("state", "some-branding-id", [("organization", "Test Organization")]),
- # ('nhs_central', None, [
- # ('nhs', 'NHS')
- # ]),
- # ('nhs_central', NHS_EMAIL_BRANDING_ID, [
- # # don't show NHS if it's the current branding
- # ]),
- ],
-)
-@pytest.mark.skip(reason="Update for TTS")
-def test_get_email_choices_service_assigned_to_org(
- mocker,
- service_one,
- org_type,
- branding_id,
- expected_options,
- mock_get_service_organization,
- mock_get_email_branding,
-):
- service = Service(service_one)
-
- mocker.patch(
- "app.organizations_client.get_organization",
- return_value=organization_json(organization_type=org_type),
- )
- mocker.patch(
- "app.models.service.Service.email_branding_id",
- new_callable=PropertyMock,
- return_value=branding_id,
- )
-
- options = get_email_choices(service)
- assert list(options) == expected_options
-
-
-@pytest.mark.parametrize(
- ("org_type", "branding_id", "expected_options"),
- [
- (
- "federal",
- "some-branding-id",
- [
- # don't show gsa.gov options as org default supersedes it
- ("organization", "Test Organization"),
- ],
- ),
- (
- "federal",
- "org-branding-id",
- [
- # also don't show org option if it's the current branding
- ],
- ),
- (
- "state",
- "org-branding-id",
- [
- # don't show org option if it's the current branding
- ],
- ),
- ],
-)
-@pytest.mark.skip(reason="Update for TTS")
-def test_get_email_choices_org_has_default_branding(
- mocker,
- service_one,
- org_type,
- branding_id,
- expected_options,
- mock_get_service_organization,
- mock_get_email_branding,
-):
- service = Service(service_one)
-
- mocker.patch(
- "app.organizations_client.get_organization",
- return_value=organization_json(
- organization_type=org_type, email_branding_id="org-branding-id"
- ),
- )
- mocker.patch(
- "app.models.service.Service.email_branding_id",
- new_callable=PropertyMock,
- return_value=branding_id,
- )
-
- options = get_email_choices(service)
- assert list(options) == expected_options
-
-
-@pytest.mark.parametrize(
- ("branding_name", "expected_options"),
- [
- (
- "gsa.gov and something else",
- [
- ("govuk", "GOV.UK"),
- ("govuk_and_org", "GOV.UK and Test Organization"),
- ("organization", "Test Organization"),
- ],
- ),
- (
- "gsa.gov and test OrganisatioN",
- [
- ("govuk", "GOV.UK"),
- ("organization", "Test Organization"),
- ],
- ),
- ],
-)
-@pytest.mark.skip(reason="Update for TTS")
-def test_get_email_choices_branding_name_in_use(
- mocker,
- service_one,
- branding_name,
- expected_options,
- mock_get_service_organization,
-):
- service = Service(service_one)
-
- mocker.patch(
- "app.organizations_client.get_organization",
- return_value=organization_json(organization_type="central"),
- )
- mocker.patch(
- "app.models.service.Service.email_branding_id",
- new_callable=PropertyMock,
- return_value="some-branding-id",
- )
- mocker.patch(
- "app.email_branding_client.get_email_branding",
- return_value=create_email_branding("_id", {"name": branding_name}),
- )
-
- options = get_email_choices(service)
- # don't show option if its name is similar to current branding
- assert list(options) == expected_options
diff --git a/tests/app/utils/test_csv.py b/tests/app/utils/test_csv.py
index b4800a6991..b237d8ad55 100644
--- a/tests/app/utils/test_csv.py
+++ b/tests/app/utils/test_csv.py
@@ -91,14 +91,14 @@ def get_notifications_csv_mock(
None,
[
"Recipient,Template,Type,Sent by,Job,Carrier,Carrier Response,Status,Time\n",
- "foo@bar.com,foo,sms,,,ATT Mobility,Did not like it,Delivered,1943-04-19 08:00:00 US/Eastern\r\n",
+ "foo@bar.com,foo,sms,,,ATT Mobility,Did not like it,Delivered,1943-04-19 08:00:00 AM US/Eastern\r\n",
],
),
(
"Anne Example",
[
"Recipient,Template,Type,Sent by,Job,Carrier,Carrier Response,Status,Time\n",
- "foo@bar.com,foo,sms,Anne Example,,ATT Mobility,Did not like it,Delivered,1943-04-19 08:00:00 US/Eastern\r\n", # noqa
+ "foo@bar.com,foo,sms,Anne Example,,ATT Mobility,Did not like it,Delivered,1943-04-19 08:00:00 AM US/Eastern\r\n", # noqa
],
),
],
@@ -151,7 +151,7 @@ def test_generate_notifications_csv_without_job(
"ATT Mobility",
"Did not like it",
"Delivered",
- "1943-04-19 08:00:00 US/Eastern",
+ "1943-04-19 08:00:00 AM US/Eastern",
],
),
(
@@ -187,7 +187,7 @@ def test_generate_notifications_csv_without_job(
"ATT Mobility",
"Did not like it",
"Delivered",
- "1943-04-19 08:00:00 US/Eastern",
+ "1943-04-19 08:00:00 AM US/Eastern",
],
),
(
@@ -223,7 +223,7 @@ def test_generate_notifications_csv_without_job(
"ATT Mobility",
"Did not like it",
"Delivered",
- "1943-04-19 08:00:00 US/Eastern",
+ "1943-04-19 08:00:00 AM US/Eastern",
],
),
],
@@ -398,4 +398,4 @@ def test_get_errors_for_csv(
def test_convert_report_date_to_preferred_timezone():
original = "2023-11-16 05:00:00"
altered = convert_report_date_to_preferred_timezone(original)
- assert altered == "2023-11-16 00:00:00 US/Eastern"
+ assert altered == "2023-11-16 12:00:00 AM US/Eastern"
diff --git a/tests/conftest.py b/tests/conftest.py
index 9d55b796ba..ec4ff8ad16 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -1396,7 +1396,15 @@ def _verify(user_id, code, code_type):
@pytest.fixture()
def mock_create_job(mocker, api_user_active):
- def _create(job_id, service_id, scheduled_for=None):
+ def _create(
+ job_id,
+ service_id,
+ scheduled_for=None,
+ template_id=None,
+ original_file_name=None,
+ notification_count=None,
+ valid=None,
+ ):
return job_json(
service_id,
api_user_active,
@@ -1892,6 +1900,30 @@ def sample_invite(mocker, service_one):
)
+@pytest.fixture()
+def expired_invite(service_one):
+ id_ = USER_ONE_ID
+ from_user = service_one["users"][0]
+ email_address = "invited_user@test.gsa.gov"
+ service_id = service_one["id"]
+ permissions = "view_activity,send_emails,send_texts,manage_settings,manage_users,manage_api_keys"
+ created_at = str(datetime.utcnow() - timedelta(days=3))
+ auth_type = "sms_auth"
+ folder_permissions = []
+
+ return invite_json(
+ id_,
+ from_user,
+ service_id,
+ email_address,
+ permissions,
+ created_at,
+ "expired",
+ auth_type,
+ folder_permissions,
+ )
+
+
@pytest.fixture()
def mock_create_invite(mocker, sample_invite):
def _create_invite(
@@ -2182,152 +2214,6 @@ def mock_send_already_registered_email(mocker):
return mocker.patch("app.user_api_client.send_already_registered_email")
-def create_email_brandings(
- number_of_brandings, non_standard_values=None, shuffle=False
-):
- brandings = [
- {
- "id": str(idx),
- "name": "org {}".format(idx),
- "text": "org {}".format(idx),
- "colour": None,
- "logo": "logo{}.png".format(idx),
- "brand_type": "org",
- }
- for idx in range(1, number_of_brandings + 1)
- ]
-
- for idx, row in enumerate(non_standard_values or {}):
- brandings[row["idx"]].update(non_standard_values[idx])
-
- if shuffle:
- brandings.insert(3, brandings.pop(4))
-
- return brandings
-
-
-@pytest.fixture()
-def mock_get_all_email_branding(mocker):
- def _get_all_email_branding(sort_key=None):
- non_standard_values = [
- {"idx": 1, "colour": "red"},
- {"idx": 2, "colour": "orange"},
- {"idx": 3, "text": None},
- {"idx": 4, "colour": "blue"},
- ]
- shuffle = sort_key is None
- return create_email_brandings(
- 5, non_standard_values=non_standard_values, shuffle=shuffle
- )
-
- return mocker.patch(
- "app.notify_client.email_branding_client.email_branding_client.get_all_email_branding",
- side_effect=_get_all_email_branding,
- )
-
-
-@pytest.fixture()
-def mock_no_email_branding(mocker):
- def _get_email_branding():
- return []
-
- return mocker.patch(
- "app.email_branding_client.get_all_email_branding",
- side_effect=_get_email_branding,
- )
-
-
-def create_email_branding(id, non_standard_values=None):
- branding = {
- "logo": "example.png",
- "name": "Organization name",
- "text": "Organization text",
- "id": id,
- "colour": "#f00",
- "brand_type": "org",
- }
-
- if non_standard_values:
- branding.update(non_standard_values)
-
- return {"email_branding": branding}
-
-
-@pytest.fixture()
-def mock_get_email_branding(mocker, fake_uuid):
- def _get_email_branding(id):
- return create_email_branding(fake_uuid)
-
- return mocker.patch(
- "app.email_branding_client.get_email_branding", side_effect=_get_email_branding
- )
-
-
-@pytest.fixture()
-def mock_get_email_branding_with_govuk_brand_type(mocker, fake_uuid):
- def _get_email_branding(id):
- return create_email_branding(fake_uuid, {"brand_type": "govuk"})
-
- return mocker.patch(
- "app.email_branding_client.get_email_branding", side_effect=_get_email_branding
- )
-
-
-@pytest.fixture()
-def mock_get_email_branding_with_both_brand_type(mocker, fake_uuid):
- def _get_email_branding(id):
- return create_email_branding(fake_uuid, {"brand_type": "both"})
-
- return mocker.patch(
- "app.email_branding_client.get_email_branding", side_effect=_get_email_branding
- )
-
-
-@pytest.fixture()
-def mock_get_email_branding_with_org_banner_brand_type(mocker, fake_uuid):
- def _get_email_branding(id):
- return create_email_branding(fake_uuid, {"brand_type": "org_banner"})
-
- return mocker.patch(
- "app.email_branding_client.get_email_branding", side_effect=_get_email_branding
- )
-
-
-@pytest.fixture()
-def mock_get_email_branding_without_brand_text(mocker, fake_uuid):
- def _get_email_branding_without_brand_text(id):
- return create_email_branding(
- fake_uuid, {"text": "", "brand_type": "org_banner"}
- )
-
- return mocker.patch(
- "app.email_branding_client.get_email_branding",
- side_effect=_get_email_branding_without_brand_text,
- )
-
-
-@pytest.fixture()
-def mock_create_email_branding(mocker):
- def _create_email_branding(logo, name, text, colour, brand_type):
- return
-
- return mocker.patch(
- "app.email_branding_client.create_email_branding",
- side_effect=_create_email_branding,
- )
-
-
-@pytest.fixture()
-def mock_update_email_branding(mocker):
- def _update_email_branding(branding_id, logo, name, text, colour, brand_type):
- return
-
- return mocker.patch(
- "app.email_branding_client.update_email_branding",
- side_effect=_update_email_branding,
- )
-
-
@pytest.fixture()
def mock_get_guest_list(mocker):
def _get_guest_list(service_id):
@@ -2437,6 +2323,10 @@ def _get(mocker):
"app.service_api_client.get_global_notification_count", side_effect=_get
)
+ mocker.patch(
+ "app.billing_api_client.create_or_update_free_sms_fragment_limit", autospec=True
+ )
+
class ClientRequest:
@staticmethod
@contextmanager
@@ -3708,3 +3598,21 @@ def end_to_end_authenticated_context(browser):
context = browser.new_context(storage_state=auth_state_path)
return context
+
+
+@pytest.fixture()
+def fake_markdown_file():
+ input = "#Test"
+ return input
+
+
+@pytest.fixture()
+def fake_jinja_template():
+ input = "{% if True %}True{% endif %}"
+ return input
+
+
+@pytest.fixture()
+def fake_soup_template():
+ input = "
Test
"
+ return input
diff --git a/tests/javascripts/consent.test.js b/tests/javascripts/consent.test.js
deleted file mode 100644
index 9217bd81cb..0000000000
--- a/tests/javascripts/consent.test.js
+++ /dev/null
@@ -1,55 +0,0 @@
-const helpers = require('./support/helpers');
-
-beforeAll(() => {
-
- require('../../app/assets/javascripts/govuk/cookie-functions.js');
- require('../../app/assets/javascripts/consent.js');
-
-});
-
-afterAll(() => {
-
- require('./support/teardown.js');
-
-});
-
-describe("Cookie consent", () => {
-
- describe("hasConsentFor", () => {
-
- afterEach(() => {
-
- // remove cookie set by tests
- helpers.deleteCookie('cookies_policy');
-
- });
-
- test("If there is no consent cookie, return false", () => {
-
- expect(window.GOVUK.hasConsentFor('analytics')).toBe(false);
-
- });
-
- describe("If a consent cookie is set", () => {
-
- test("If the category is not saved in the cookie, return false", () => {
-
- window.GOVUK.setConsentCookie({ 'usage': true });
-
- expect(window.GOVUK.hasConsentFor('analytics')).toBe(false);
-
- });
-
- test("If the category is saved in the cookie, return its value", () => {
-
- window.GOVUK.setConsentCookie({ 'analytics': true });
-
- expect(window.GOVUK.hasConsentFor('analytics')).toBe(true);
-
- });
-
- });
-
- });
-
-});
diff --git a/tests/javascripts/liveSearch.test.js b/tests/javascripts/liveSearch.test.js
index e0868285ef..548e5505fb 100644
--- a/tests/javascripts/liveSearch.test.js
+++ b/tests/javascripts/liveSearch.test.js
@@ -25,451 +25,6 @@ describe('Live search', () => {
}
};
- describe("With a list of radios", () => {
-
- searchLabelText = "Search branding styles by name";
-
- beforeEach(() => {
-
- const departmentData = {
- name: 'departments',
- hideLegend: true,
- fields: [
- {
- 'label': 'NHS',
- 'id': 'nhs',
- 'name': 'branding',
- 'value': 'nhs'
- },
- {
- 'label': 'Department for Work and Pensions',
- 'id': 'dwp',
- 'name': 'branding',
- 'value': 'dwp'
- },
- {
- 'label': 'Department for Education',
- 'id': 'dfe',
- 'name': 'branding',
- 'value': 'dfe'
- },
- {
- 'label': 'Home Office',
- 'id': 'home-office',
- 'name': 'branding',
- 'value': 'home-office'
- }
- ]
- };
-
- // set up DOM
- document.body.innerHTML = `
-
-
`;
-
- searchTextbox = document.getElementById('search');
- liveRegion = document.querySelector('.live-search__status');
- list = document.querySelector('form');
-
- // getRadioGroup returns a DOM node so append once DOM is set up
- list.appendChild(helpers.getRadioGroup(departmentData));
-
- });
-
- describe("When the page loads", () => {
-
- test("If there is no search term, the results should be unchanged", () => {
-
- // start the module
- window.GOVUK.modules.start();
-
- const listItems = list.querySelectorAll('.usa-radio');
- const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none');
-
- expect(listItemsShowing.length).toEqual(listItems.length);
- expect(searchTextbox.hasAttribute('aria-label')).toBe(false);
-
- });
-
- test("If there is a single word search term, only the results that match should show", () => {
-
- searchTextbox.value = 'Department';
-
- // start the module
- window.GOVUK.modules.start();
-
- const listItems = list.querySelectorAll('.usa-radio');
- const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none');
-
- expect(listItemsShowing.length).toEqual(2);
- expect(searchTextbox.hasAttribute('aria-label')).toBe(true);
- expect(searchTextbox.getAttribute('aria-label')).toEqual(`${searchLabelText}, ${liveRegionResults(2)}`);
-
- });
-
- test("If there is a search term made of several words, only the results that match should show", () => {
-
- searchTextbox.value = 'Department for Work';
-
- // start the module
- window.GOVUK.modules.start();
-
- const listItems = list.querySelectorAll('.usa-radio');
- const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none');
-
- expect(listItemsShowing.length).toEqual(1);
- expect(searchTextbox.hasAttribute('aria-label')).toBe(true);
- expect(searchTextbox.getAttribute('aria-label')).toEqual(`${searchLabelText}, ${liveRegionResults(1)}`);
-
- });
-
- test("If an item doesn't match the search term but is selected, it should still show in the results", () => {
-
- searchTextbox.value = 'Department for Work';
-
- // mark an item as selected
- checkedItem = list.querySelector('input[id=nhs]');
- checkedItem.checked = true;
-
- // start the module
- window.GOVUK.modules.start();
-
- expect(window.getComputedStyle(checkedItem).display).not.toEqual('none');
-
- });
-
- });
-
- describe("When the search text changes", () => {
-
- test("If there is no search term, the results should be unchanged", () => {
-
- searchTextbox.value = 'Department';
-
- // start the module
- window.GOVUK.modules.start();
-
- // simulate the input of new search text
- searchTextbox.value = '';
- helpers.triggerEvent(searchTextbox, 'input');
-
- const listItems = list.querySelectorAll('.usa-radio');
- const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none');
-
- expect(listItemsShowing.length).toEqual(listItems.length);
- expect(liveRegion.textContent.trim()).toEqual(liveRegionResults(listItemsShowing.length));
-
- });
-
- test("If there is a single word search term, only the results that match should show", () => {
-
- searchTextbox.value = 'Department';
-
- // start the module
- window.GOVUK.modules.start();
-
- // simulate the input of new search text
- searchTextbox.value = 'Home';
- helpers.triggerEvent(searchTextbox, 'input');
-
- const listItems = list.querySelectorAll('.usa-radio');
- const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none');
-
- expect(listItemsShowing.length).toEqual(1);
- expect(liveRegion.textContent.trim()).toEqual(liveRegionResults(1));
-
- });
-
- test("If there is a search term made of several words, only the results that match should show", () => {
-
- searchTextbox.value = 'Department';
-
- // start the module
- window.GOVUK.modules.start();
-
- // simulate the input of new search text
- searchTextbox.value = 'Department for';
- helpers.triggerEvent(searchTextbox, 'input');
-
- const listItems = list.querySelectorAll('.usa-radio');
- const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none');
-
- expect(listItemsShowing.length).toEqual(2);
- expect(liveRegion.textContent.trim()).toEqual(liveRegionResults(2));
-
- });
-
- test("If an item doesn't match the search term but is selected, it should still show in the results", () => {
-
- searchTextbox.value = 'Department';
-
- // mark an item as selected
- checkedItem = list.querySelector('input[id=nhs]');
- checkedItem.checked = true;
-
- // start the module
- window.GOVUK.modules.start();
-
- // simulate the input of new search text
- searchTextbox.value = 'Home Office';
- helpers.triggerEvent(searchTextbox, 'input');
-
- expect(window.getComputedStyle(checkedItem).display).not.toEqual('none');
-
- });
-
- });
-
- });
-
- describe("With a list of checkboxes", () => {
-
- searchLabelText = "Search branding styles by name";
-
- beforeEach(() => {
-
- const templatesAndFolders = [
- {
- "label": "Appointments",
- "type": "folder",
- "meta": "2 templates"
- },
- {
- "label": "New patient",
- "type": "template",
- "meta": "Email template"
- },
- {
- "label": "Prescriptions",
- "type": "folder",
- "meta": "1 template, 1 folder"
- },
- {
- "label": "New doctor",
- "type": "template",
- "meta": "Email template"
- }
- ];
-
- // set up DOM
- document.body.innerHTML = `
-
-
`;
-
- searchTextbox = document.getElementById('search');
- liveRegion = document.querySelector('.live-search__status');
- list = document.querySelector('form');
-
- });
-
- describe("When the page loads", () => {
-
- test("If there is no search term, the results should be unchanged", () => {
-
- // start the module
- window.GOVUK.modules.start();
-
- const listItems = list.querySelectorAll('.template-list-item');
- const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none');
-
- expect(listItemsShowing.length).toEqual(listItems.length);
- expect(searchTextbox.hasAttribute('aria-label')).toBe(false);
-
- });
-
- test("If there is a single word search term, only the results that match should show", () => {
-
- searchTextbox.value = 'New';
-
- // start the module
- window.GOVUK.modules.start();
-
- const listItems = list.querySelectorAll('.template-list-item');
- const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none');
-
- // should match 'New patient' and 'New doctor'
- expect(listItemsShowing.length).toEqual(2);
- expect(searchTextbox.hasAttribute('aria-label')).toBe(true);
- expect(searchTextbox.getAttribute('aria-label')).toEqual(`${searchLabelText}, ${liveRegionResults(2)}`);
-
- });
-
- test("If there is a search term made of several words, only the results that match should show", () => {
-
- searchTextbox.value = 'New patient';
-
- // start the module
- window.GOVUK.modules.start();
-
- const listItems = list.querySelectorAll('.template-list-item');
- const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none');
-
- expect(listItemsShowing.length).toEqual(1);
- expect(searchTextbox.hasAttribute('aria-label')).toBe(true);
- expect(searchTextbox.getAttribute('aria-label')).toEqual(`${searchLabelText}, ${liveRegionResults(1)}`);
-
- });
-
- test("If an item doesn't match the search term but is selected, it should still show in the results", () => {
-
- searchTextbox.value = 'New patient';
-
- // mark 'Appointments' item as selected
- checkedItem = list.querySelector('input[id=templates-or-folder-0]');
- checkedItem.checked = true;
-
- // start the module
- window.GOVUK.modules.start();
-
- // should show despite not matching
- expect(window.getComputedStyle(checkedItem).display).not.toEqual('none');
-
- });
-
- test("If the items have a block of text to match against, only results that match it should show", () => {
-
- searchTextbox.value = 'Email template';
-
- // start the module
- window.GOVUK.modules.start();
-
- const listItems = list.querySelectorAll('.template-list-item');
- const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none');
-
- // 2 items contain the "Email template" text
- // only the text containing the name of the item is matched against (ie 'New patient')
- expect(listItemsShowing.length).toEqual(0);
- expect(searchTextbox.getAttribute('aria-label')).toEqual(`${searchLabelText}, ${liveRegionResults(0)}`);
-
- });
-
- });
-
- describe("When the search text changes", () => {
-
- test("If there is no search term, the results should be unchanged", () => {
-
- searchTextbox.value = 'Appointments';
-
- // start the module
- window.GOVUK.modules.start();
-
- // simulate input of new search text
- searchTextbox.value = '';
- helpers.triggerEvent(searchTextbox, 'input');
-
- const listItems = list.querySelectorAll('.template-list-item');
- const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none');
-
- expect(listItemsShowing.length).toEqual(listItems.length);
- expect(liveRegion.textContent.trim()).toEqual(liveRegionResults(listItemsShowing.length));
-
- });
-
- test("If there is a single word search term, only the results that match should show", () => {
-
- searchTextbox.value = 'Appointments';
-
- // start the module
- window.GOVUK.modules.start();
-
- // simulate input of new search text
- searchTextbox.value = 'Prescriptions';
- helpers.triggerEvent(searchTextbox, 'input');
-
- const listItems = list.querySelectorAll('.template-list-item');
- const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none');
-
- expect(listItemsShowing.length).toEqual(1);
- expect(liveRegion.textContent.trim()).toEqual(liveRegionResults(1));
-
- });
-
- test("If there is a search term made of several words, only the results that match should show", () => {
-
- searchTextbox.value = 'Appointments';
-
- // start the module
- window.GOVUK.modules.start();
-
- // simulate input of new search text
- searchTextbox.value = 'New doctor';
- helpers.triggerEvent(searchTextbox, 'input');
-
- const listItems = list.querySelectorAll('.template-list-item');
- const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none');
-
- expect(listItemsShowing.length).toEqual(1);
- expect(liveRegion.textContent.trim()).toEqual(liveRegionResults(1));
-
- });
-
- test("If an item doesn't match the search term but is selected, it should still show in the results", () => {
-
- searchTextbox.value = 'Appointments';
-
- // mark 'Appointments' item as selected
- checkedItem = list.querySelector('input[id=templates-or-folder-0]');
- checkedItem.checked = true;
-
- // start the module
- window.GOVUK.modules.start();
-
- // simulate input of new search text
- searchTextbox.value = 'Prescriptions';
- helpers.triggerEvent(searchTextbox, 'input');
-
- // should show despite not matching
- expect(window.getComputedStyle(checkedItem).display).not.toEqual('none');
-
- });
-
- test("If the items have a block of text to match against, only results that match it should show", () => {
-
- searchTextbox.value = 'Appointments';
-
- // start the module
- window.GOVUK.modules.start();
-
- // simulate input of new search text
- searchTextbox.value = 'Email template';
- helpers.triggerEvent(searchTextbox, 'input');
-
- const listItems = list.querySelectorAll('.template-list-item');
- const listItemsShowing = Array.from(listItems).filter(item => window.getComputedStyle(item).display !== 'none');
-
- // 2 items contain the "Email template" text
- // only the text containing the name of the item is matched against (ie 'New patient')
- expect(listItemsShowing.length).toEqual(0);
- expect(liveRegion.textContent.trim()).toEqual(liveRegionResults(0));
-
- });
-
- });
-
- })
-
describe("With a list of content items", () => {
searchLabelText = "Search by name or email address";
diff --git a/tests/javascripts/previewPane.test.js b/tests/javascripts/previewPane.test.js
deleted file mode 100644
index beacaa0cb8..0000000000
--- a/tests/javascripts/previewPane.test.js
+++ /dev/null
@@ -1,234 +0,0 @@
-const helpers = require('./support/helpers.js');
-
-const emailPageURL = '/services/6658542f-0cad-491f-bec8-ab8457700ead/service-settings/set-email-branding';
-const emailPreviewConfirmationURL = '/services/6658542f-0cad-491f-bec8-ab8457700ead/service-settings/preview-email-branding';
-const letterPageURL = '/services/6658542f-0cad-491f-bec8-ab8457700ead/service-settings/set-letter-branding';
-const letterPreviewConfirmationURL = '/services/6658542f-0cad-491f-bec8-ab8457700ead/service-settings/preview-letter-branding';
-
-let locationMock;
-
-beforeAll(() => {
-
- // mock calls to window.location
- // default to the email page, the pathname can be changed inside specific tests
- locationMock = new helpers.LocationMock(emailPageURL);
-
-});
-
-afterAll(() => {
-
- // reset window.location to its original state
- locationMock.reset();
- require('./support/teardown.js');
-
-});
-
-describe('Preview pane', () => {
-
- let form;
- let radios;
-
- beforeEach(() => {
-
- const brands = {
- "name": "branding_style",
- "label": "Branding style",
- "cssClasses": [],
- "fields": [
- {
- "label": "Department for Education",
- "value": "dfe",
- "checked": true
- },
- {
- "label": "Home Office",
- "value": "ho",
- "checked": false
- },
- {
- "label": "Her Majesty's Revenue and Customs",
- "value": "hmrc",
- "checked": false
- },
- {
- "label": "Department for Work and Pensions",
- "value": "dwp",
- "checked": false
- }
- ]
- };
-
- // set up DOM
- document.body.innerHTML =
- `
`;
-
- document.querySelector('.govuk-grid-column-full').appendChild(helpers.getRadioGroup(brands));
- form = document.querySelector('form');
- radios = form.querySelector('fieldset');
-
- });
-
- afterEach(() => {
-
- document.body.innerHTML = '';
-
- // we run the previewPane.js script every test
- // the module cache needs resetting each time for the script to execute
- jest.resetModules();
-
- });
-
- describe("If the page type is 'email'", () => {
-
- describe("When the page loads", () => {
-
- test("it should add the preview pane", () => {
-
- // run preview pane script
- require('../../app/assets/javascripts/previewPane.js');
-
- expect(document.querySelector('iframe')).not.toBeNull();
-
- });
-
- test("it should change the form to submit the selection instead of posting to a preview page", () => {
-
- // run preview pane script
- require('../../app/assets/javascripts/previewPane.js');
-
- expect(form.getAttribute('action')).toEqual(emailPreviewConfirmationURL);
-
- });
-
- test("the preview pane should show the page for the selected brand", () => {
-
- // run preview pane script
- require('../../app/assets/javascripts/previewPane.js');
-
- const selectedValue = Array.from(radios.querySelectorAll('input[type=radio]')).filter(radio => radio.checked)[0].value;
-
- expect(document.querySelector('iframe').getAttribute('src')).toEqual(`/_email?branding_style=${selectedValue}`);
-
- });
-
- test("the submit button should change from 'Preview' to 'Save'", () => {
-
- // run preview pane script
- require('../../app/assets/javascripts/previewPane.js');
-
- expect(document.querySelector('button[type=submit]').textContent).toEqual('Save');
-
- });
-
- });
-
- describe("If the selection changes", () => {
-
- test("the page shown should match the selected brand", () => {
-
- // run preview pane script
- require('../../app/assets/javascripts/previewPane.js');
-
- const newSelection = radios.querySelectorAll('input[type=radio]')[1];
-
- helpers.moveSelectionToRadio(newSelection);
-
- expect(document.querySelector('iframe').getAttribute('src')).toEqual(`/_email?branding_style=${newSelection.value}`);
-
- });
-
- });
-
- });
-
- describe("If the page type is 'letter'", () => {
-
- beforeEach(() => {
-
- // set page URL and page type to 'letter'
- window.location.pathname = letterPreviewConfirmationURL;
- form.setAttribute('data-preview-type', 'letter');
-
- });
-
- describe("When the page loads", () => {
-
- test("it should add the preview pane", () => {
-
- // run preview pane script
- require('../../app/assets/javascripts/previewPane.js');
-
- expect(document.querySelector('iframe')).not.toBeNull();
-
- });
-
- test("it should change the form to submit the selection instead of posting to a preview page", () => {
-
- // run preview pane script
- require('../../app/assets/javascripts/previewPane.js');
-
- expect(form.getAttribute('action')).toEqual(letterPreviewConfirmationURL);
-
- });
-
- test("the preview pane should show the page for the selected brand", () => {
-
- // run preview pane script
- require('../../app/assets/javascripts/previewPane.js');
-
- const selectedValue = Array.from(radios.querySelectorAll('input[type=radio]')).filter(radio => radio.checked)[0].value;
-
- expect(document.querySelector('iframe').getAttribute('src')).toEqual(`/_letter?branding_style=${selectedValue}`);
-
- });
-
- test("the submit button should change from 'Preview' to 'Save'", () => {
-
- // run preview pane script
- require('../../app/assets/javascripts/previewPane.js');
-
- expect(document.querySelector('button[type=submit]').textContent).toEqual('Save');
-
- });
-
- });
-
- describe("If the selection changes", () => {
-
- test("the page shown should match the selected brand", () => {
-
- // run preview pane script
- require('../../app/assets/javascripts/previewPane.js');
-
- const newSelection = radios.querySelectorAll('input[type=radio]')[1];
-
- helpers.moveSelectionToRadio(newSelection);
-
- expect(document.querySelector('iframe').getAttribute('src')).toEqual(`/_letter?branding_style=${newSelection.value}`);
-
- });
-
- });
-
- });
-
-});
diff --git a/tests/javascripts/support/helpers.js b/tests/javascripts/support/helpers.js
index b0f6d635bb..6f3197e83d 100644
--- a/tests/javascripts/support/helpers.js
+++ b/tests/javascripts/support/helpers.js
@@ -1,7 +1,6 @@
const globals = require('./helpers/globals.js');
const events = require('./helpers/events.js');
const domInterfaces = require('./helpers/dom_interfaces.js');
-const cookies = require('./helpers/cookies.js');
const html = require('./helpers/html.js');
const elements = require('./helpers/elements.js');
const rendering = require('./helpers/rendering.js');
@@ -15,8 +14,6 @@ exports.moveSelectionToRadio = events.moveSelectionToRadio;
exports.activateRadioWithSpace = events.activateRadioWithSpace;
exports.RangeMock = domInterfaces.RangeMock;
exports.SelectionMock = domInterfaces.SelectionMock;
-exports.deleteCookie = cookies.deleteCookie;
-exports.setCookie = cookies.setCookie;
exports.getRadioGroup = html.getRadioGroup;
exports.getRadios = html.getRadios;
exports.templatesAndFoldersCheckboxes = html.templatesAndFoldersCheckboxes;
diff --git a/tests/javascripts/support/helpers/cookies.js b/tests/javascripts/support/helpers/cookies.js
deleted file mode 100644
index d4824c9fe6..0000000000
--- a/tests/javascripts/support/helpers/cookies.js
+++ /dev/null
@@ -1,25 +0,0 @@
-// Helper for deleting a cookie
-function deleteCookie (cookieName, options) {
- if (typeof options === 'undefined') {
- options = {};
- }
- if (!options.domain) { options.domain = window.location.hostname; }
- document.cookie = cookieName + '=; path=/; domain=' + options.domain + '; expires=' + (new Date());
-};
-
-function setCookie (name, value, options) {
- if (typeof options === 'undefined') {
- options = {};
- }
- if (!options.domain) { options.domain = window.location.hostname; }
- var cookieString = name + '=' + value + '; path=/; domain=' + options.domain;
- if (options.days) {
- var date = new Date();
- date.setTime(date.getTime() + (options.days * 24 * 60 * 60 * 1000));
- cookieString = cookieString + '; expires=' + date.toGMTString();
- }
- document.cookie = cookieString;
-};
-
-exports.deleteCookie = deleteCookie;
-exports.setCookie = setCookie;