mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-09-11 10:28:41 -04:00
merge from main
This commit is contained in:
@@ -15,7 +15,7 @@ NVMSH := $(shell [ -f "$(HOME)/.nvm/nvm.sh" ] && echo "$(HOME)/.nvm/nvm.sh" || e
|
||||
|
||||
.PHONY: bootstrap
|
||||
bootstrap: generate-version-file ## Set up everything to run the app
|
||||
poetry install
|
||||
poetry install --sync
|
||||
poetry run playwright install --with-deps
|
||||
source $(NVMSH) --no-use && nvm install && npm ci --no-audit
|
||||
source $(NVMSH) && npm run build
|
||||
@@ -92,6 +92,18 @@ js-test: ## Run javascript unit tests
|
||||
fix-imports: ## Fix imports using isort
|
||||
poetry run isort ./app ./tests
|
||||
|
||||
.PHONY: py-lock
|
||||
py-lock: ## Syncs dependencies and updates lock file without performing recursive internal updates
|
||||
poetry lock --no-update
|
||||
poetry install --sync
|
||||
|
||||
.PHONY: update-utils
|
||||
update-utils: ## Forces Poetry to pull the latest changes from the notifications-utils repo; requires that you commit the changes to poetry.lock!
|
||||
poetry update notifications-utils
|
||||
@echo
|
||||
@echo !!! PLEASE MAKE SURE TO COMMIT AND PUSH THE UPDATED poetry.lock FILE !!!
|
||||
@echo
|
||||
|
||||
.PHONY: freeze-requirements
|
||||
freeze-requirements: ## create static requirements.txt
|
||||
poetry export --without-hashes --format=requirements.txt > requirements.txt
|
||||
@@ -101,7 +113,7 @@ pip-audit:
|
||||
poetry requirements > requirements.txt
|
||||
poetry requirements --dev > requirements_for_test.txt
|
||||
poetry run pip-audit -r requirements.txt
|
||||
-poetry run pip-audit -r requirements_for_test.txt
|
||||
poetry run pip-audit -r requirements_for_test.txt
|
||||
|
||||
.PHONY: audit
|
||||
audit: npm-audit pip-audit
|
||||
|
||||
@@ -73,6 +73,52 @@ The [Notify API](https://github.com/GSA/notifications-api) provides the UI's bac
|
||||
|
||||
If you are using VS Code, there are also instructions for [running inside Docker](./docs/docker-remote-containers.md)
|
||||
|
||||
### Python dependency management
|
||||
|
||||
We're using [`Poetry`](https://python-poetry.org/) for managing our Python
|
||||
dependencies and local virtual environments. When it comes to managing the
|
||||
Python dependencies, there are a couple of things to bear in mind.
|
||||
|
||||
For situations where you manually manipulate the `pyproject.toml` file, you
|
||||
should use the `make py-lock` command to sync the `poetry.lock` file. This will
|
||||
ensure that you don't inadvertently bring in other transitive dependency updates
|
||||
that have not been fully tested with the project yet.
|
||||
|
||||
If you're just trying to update a dependency to a newer (or the latest) version,
|
||||
you should let Poetry take care of that for you by running the following:
|
||||
|
||||
```
|
||||
poetry update <dependency> [<dependency>...]
|
||||
```
|
||||
|
||||
You can specify more than one dependency together. With this command, Poetry
|
||||
will do the following for you:
|
||||
|
||||
- Find the latest compatible version(s) of the specified dependency/dependencies
|
||||
- Install the new versions
|
||||
- Update and sync the `poetry.lock` file
|
||||
|
||||
In either situation, once you are finished and have verified the dependency
|
||||
changes are working, please be sure to commit both the `pyproject.toml` and
|
||||
`poetry.lock` files.
|
||||
|
||||
### Keeping the notification-utils dependency up-to-date
|
||||
|
||||
The `notifications-utils` dependency references the other repository we have at
|
||||
https://github.com/GSA/notifications-utils - this dependency requires a bit of
|
||||
extra legwork to ensure it stays up-to-date.
|
||||
|
||||
Whenever a PR is merged in the `notifications-utils` repository, we need to make
|
||||
sure the changes are pulled in here and committed to this repository as well.
|
||||
You can do this by going through these steps:
|
||||
|
||||
- Make sure your local `main` branch is up-to-date
|
||||
- Create a new branch to work in
|
||||
- Run `make update-utils`
|
||||
- Commit the updated `poetry.lock` file and push the changes
|
||||
- Make a new PR with the change
|
||||
- Have the PR get reviewed and merged
|
||||
|
||||
## To test the application
|
||||
|
||||
From a terminal within the running devcontainer:
|
||||
|
||||
+14
-3
@@ -285,11 +285,22 @@ def init_app(application):
|
||||
@application.context_processor
|
||||
def _attach_current_global_daily_messages():
|
||||
remaining_global_messages = 0
|
||||
|
||||
if current_app:
|
||||
if request.view_args:
|
||||
service_id = request.view_args.get(
|
||||
"service_id", session.get("service_id")
|
||||
)
|
||||
else:
|
||||
service_id = session.get("service_id")
|
||||
|
||||
if service_id:
|
||||
global_limit = current_app.config["GLOBAL_SERVICE_MESSAGE_LIMIT"]
|
||||
global_messages_count = service_api_client.get_global_notification_count()
|
||||
remaining_global_messages = global_limit - global_messages_count
|
||||
global_messages_count = (
|
||||
service_api_client.get_global_notification_count(service_id)
|
||||
)
|
||||
remaining_global_messages = global_limit - global_messages_count.get(
|
||||
"count"
|
||||
)
|
||||
return {"daily_global_messages_remaining": remaining_global_messages}
|
||||
|
||||
@application.before_request
|
||||
|
||||
@@ -227,7 +227,7 @@ def terms():
|
||||
)
|
||||
|
||||
|
||||
@main.route("/features/using-notify")
|
||||
@main.route("/features/using_notify")
|
||||
@user_is_logged_in
|
||||
def using_notify():
|
||||
return (
|
||||
|
||||
@@ -61,6 +61,82 @@ class HeaderNavigation(Navigation):
|
||||
"message_status",
|
||||
"guidance_index",
|
||||
},
|
||||
"accounts-or-dashboard": {
|
||||
"conversation",
|
||||
"inbox",
|
||||
"monthly",
|
||||
"service_dashboard",
|
||||
"template_usage",
|
||||
"view_notification",
|
||||
"view_notifications",
|
||||
"action_blocked",
|
||||
"add_service_template",
|
||||
"check_messages",
|
||||
"check_notification",
|
||||
"choose_template",
|
||||
"choose_template_to_copy",
|
||||
"confirm_redact_template",
|
||||
"conversation_reply",
|
||||
"copy_template",
|
||||
"delete_service_template",
|
||||
"edit_service_template",
|
||||
"manage_template_folder",
|
||||
"send_messages",
|
||||
"send_one_off",
|
||||
"send_one_off_step",
|
||||
"send_one_off_to_myself",
|
||||
"set_sender",
|
||||
"set_template_sender",
|
||||
"view_template",
|
||||
"view_template_version",
|
||||
"view_template_versions",
|
||||
"uploads",
|
||||
"view_job",
|
||||
"view_jobs",
|
||||
"confirm_edit_user_email",
|
||||
"confirm_edit_user_mobile_number",
|
||||
"edit_user_email",
|
||||
"edit_user_mobile_number",
|
||||
"edit_user_permissions",
|
||||
"invite_user",
|
||||
"manage_users",
|
||||
"remove_user_from_service",
|
||||
"usage",
|
||||
"email_branding_govuk",
|
||||
"email_branding_govuk_and_org",
|
||||
"email_branding_organization",
|
||||
"email_branding_request",
|
||||
"email_branding_something_else",
|
||||
"estimate_usage",
|
||||
"link_service_to_organization",
|
||||
"request_to_go_live",
|
||||
"service_add_email_reply_to",
|
||||
"service_add_sms_sender",
|
||||
"service_confirm_delete_email_reply_to",
|
||||
"service_confirm_delete_sms_sender",
|
||||
"service_edit_email_reply_to",
|
||||
"service_edit_sms_sender",
|
||||
"service_email_reply_to",
|
||||
"service_name_change",
|
||||
"service_preview_email_branding",
|
||||
"service_set_auth_type",
|
||||
"service_set_channel",
|
||||
"send_files_by_email_contact_details",
|
||||
"service_set_email_branding",
|
||||
"service_set_inbound_number",
|
||||
"service_set_inbound_sms",
|
||||
"service_set_international_sms",
|
||||
"service_set_reply_to_email",
|
||||
"service_set_sms_prefix",
|
||||
"service_verify_reply_to_address",
|
||||
"service_verify_reply_to_address_updates",
|
||||
"service_settings",
|
||||
"service_sms_senders",
|
||||
"set_free_sms_allowance",
|
||||
"set_message_limit",
|
||||
"set_rate_limit",
|
||||
"submit_request_to_go_live",
|
||||
},
|
||||
"pricing": {
|
||||
"how_to_pay",
|
||||
"billing_details",
|
||||
|
||||
@@ -497,11 +497,8 @@ class ServiceAPIClient(NotifyAdminAPIClient):
|
||||
|
||||
return int(count)
|
||||
|
||||
def get_global_notification_count(self):
|
||||
# if cache is not set, or not enabled, return 0
|
||||
# count = redis_client.get(daily_total_cache_key()) or 0
|
||||
count = 0
|
||||
return int(count)
|
||||
def get_global_notification_count(self, service_id):
|
||||
return self.get("/service/{}/notification-count".format(service_id))
|
||||
|
||||
|
||||
service_api_client = ServiceAPIClient()
|
||||
|
||||
@@ -43,6 +43,11 @@
|
||||
{% if current_user.is_authenticated %}
|
||||
{% if current_user.platform_admin %}
|
||||
{% set navigation = [
|
||||
{
|
||||
"href": url_for("main.show_accounts_or_dashboard"),
|
||||
"text": "Current service",
|
||||
"active": header_navigation.is_selected('accounts-or-dashboard')
|
||||
},
|
||||
{
|
||||
"href": url_for('main.get_started'),
|
||||
"text": "Using Notify",
|
||||
@@ -75,6 +80,11 @@
|
||||
] %}
|
||||
{% else %}
|
||||
{% set navigation = [
|
||||
{
|
||||
"href": url_for("main.show_accounts_or_dashboard"),
|
||||
"text": "Current service",
|
||||
"active": header_navigation.is_selected('accounts-or-dashboard')
|
||||
},
|
||||
{
|
||||
"href": url_for('main.get_started'),
|
||||
"text": "Using Notify",
|
||||
@@ -92,7 +102,7 @@
|
||||
},
|
||||
{
|
||||
"href": url_for('main.user_profile'),
|
||||
"text": current_user.name,
|
||||
"text": "User profile",
|
||||
"active": header_navigation.is_selected('user-profile')
|
||||
},
|
||||
{
|
||||
|
||||
@@ -4,16 +4,10 @@
|
||||
<div class="grid-row">
|
||||
<div class="grid-col-8">
|
||||
<h1 class="heading-large">
|
||||
Sorry, there’s a problem with Notify.gov
|
||||
Sorry, we can't deliver what you asked for right now.
|
||||
</h1>
|
||||
<p class="usa-body">
|
||||
Try again later.
|
||||
</p>
|
||||
<!-- <p class="usa-body">
|
||||
You can check our <a class="usa-link" href="https://status.notifications.service.gov.uk">system status</a> page to see if there are any known issues.
|
||||
</p> -->
|
||||
<p class="usa-body">
|
||||
To report a problem, please email <a class="usa-link" href="mailto:notify-support@gsa.gov">notify-support@gsa.gov</a>.
|
||||
Please try again later or <a class="usa-link" href="mailto:notify-support@gsa.gov"></a>email us</a> for more information.</p>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
{% if help %}
|
||||
{% include 'partials/tour.html' %}
|
||||
{% else %}
|
||||
<nav class="nav margin-bottom-5">
|
||||
<nav class="nav">
|
||||
<a class="usa-button margin-top-1 margin-bottom-5 width-full"
|
||||
href="{{ url_for('.choose_template', service_id=current_service.id) }}">Send messages</a>
|
||||
<ul class="usa-sidenav">
|
||||
{% if current_user.has_permissions() %}
|
||||
{% if current_user.has_permissions('view_activity') %}
|
||||
<li class="usa-sidenav__item"><a class="{{ main_navigation.is_selected('dashboard') }}" href="{{ url_for('.service_dashboard', service_id=current_service.id) }}">Dashboard</a></li>
|
||||
{% endif %}
|
||||
<li class="usa-sidenav__item"><a class="{{ main_navigation.is_selected('templates') }}" href="{{ url_for('.choose_template', service_id=current_service.id) }}">Send messages</a></li>
|
||||
{# <li class="usa-sidenav__item"><a class="{{ main_navigation.is_selected('templates') }}" href="{{ url_for('.choose_template', service_id=current_service.id) }}">Send messages</a></li> #}
|
||||
{% if not current_user.has_permissions('view_activity') %}
|
||||
<li class="usa-sidenav__item"><a class="{{ casework_navigation.is_selected('sent-messages') }}" href="{{ url_for('.view_notifications', service_id=current_service.id, status='sending,delivered,failed') }}">Sent messages</a></li>
|
||||
{% endif %}
|
||||
@@ -27,8 +29,4 @@
|
||||
{% endif %}
|
||||
</ul>
|
||||
</nav>
|
||||
<div>
|
||||
<p class="usa--body bold margin-bottom-1">Messages Left Across Services</p>
|
||||
<p>{{ daily_global_messages_remaining }}</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
<div class="navigation-service margin-top-2 display-flex flex-align-end flex-justify border-bottom padding-bottom-1">
|
||||
<div class="navigation-service margin-top-5 display-flex flex-align-end flex-justify border-bottom padding-bottom-1">
|
||||
{% if current_service.organization_id %}
|
||||
{% if current_user.platform_admin or
|
||||
(current_user.belongs_to_organization(current_service.organization_id) and current_service.live) %}
|
||||
|
||||
@@ -2,7 +2,21 @@
|
||||
|
||||
<div class='grid-row grid-gap ajax-block-container'>
|
||||
<div class='grid-col-12'>
|
||||
<div class="keyline-block">
|
||||
|
||||
<table class="usa-table usa-table--borderless margin-top-1 margin-bottom-5">
|
||||
<caption class="usa-sr-only">
|
||||
Daily
|
||||
</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Usage</th>
|
||||
<th scope="col">Remaining</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{ big_number(40000 - sms_allowance_remaining, smaller=True) }}</td>
|
||||
<td>
|
||||
{% if sms_cost %}
|
||||
{{ big_number(
|
||||
sms_cost,
|
||||
@@ -11,13 +25,11 @@
|
||||
smaller=True
|
||||
) }}
|
||||
{% else %}
|
||||
{{ big_number(sms_allowance_remaining, 'free text messages left', smaller=True) }}
|
||||
{{ big_number(sms_allowance_remaining, smaller=True) }}
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class='grid-col-6 pilot-disabled'>
|
||||
<div class="keyline-block">
|
||||
{{ big_number("0", 'email disabled during SMS pilot', smaller=True) }}
|
||||
</div>
|
||||
</div> -->
|
||||
</div>
|
||||
|
||||
@@ -19,9 +19,10 @@
|
||||
|
||||
{{ ajax_block(partials, updates_url, 'upcoming') }}
|
||||
|
||||
<h2 class="font-body-lg margin-top-0 margin-bottom-1">
|
||||
In the last seven days
|
||||
<h2 class="font-body-xl margin-0">
|
||||
Messages sent
|
||||
</h2>
|
||||
<p class="margin-top-0">In the last seven days</p>
|
||||
|
||||
{{ ajax_block(partials, updates_url, 'inbox') }}
|
||||
|
||||
@@ -29,15 +30,37 @@
|
||||
|
||||
{{ ajax_block(partials, updates_url, 'template-statistics') }}
|
||||
|
||||
<h2 class="margin-top-4 margin-bottom-1">Usage</h2>
|
||||
<h3 class="margin-bottom-0">Daily</h3>
|
||||
<p class="margin-0">Across all services</p>
|
||||
<table class="usa-table usa-table--borderless margin-top-1 margin-bottom-5">
|
||||
<caption class="usa-sr-only">
|
||||
Daily
|
||||
</caption>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Usage</th>
|
||||
<th scope="col">Remaining</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>{{ 5000 - daily_global_messages_remaining }}</td>
|
||||
<td>
|
||||
{{ daily_global_messages_remaining }}
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{% if current_user.has_permissions('manage_service') %}
|
||||
<h2 class='font-body-lg margin-bottom-0'>This year</h2>
|
||||
<h3 class='margin-bottom-0'>2023</h3>
|
||||
{{ ajax_block(partials, updates_url, 'usage') }}
|
||||
{{ show_more(
|
||||
url_for(".usage", service_id=current_service['id']),
|
||||
'See usage'
|
||||
'See all usage'
|
||||
) }}
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<h2 class="font-body-lg">Get started</h2>
|
||||
<a class="usa-button" href="{{ url_for('.choose_template', service_id=current_service.id) }}">
|
||||
<h2 class="font-body-lg margin-top-0 margin-bottom-1">Get started</h2>
|
||||
<a class="usa-button margin-bottom-5" href="{{ url_for('.choose_template', service_id=current_service.id) }}">
|
||||
Create your first template
|
||||
</a>
|
||||
|
||||
@@ -21,7 +21,10 @@
|
||||
<div class='grid-row'>
|
||||
|
||||
<div class='grid-col-12'>
|
||||
<h2 class='heading-small'>Text messages</h2>
|
||||
<h2 class="heading-small margin-bottom-1">Daily messages across all services</h2>
|
||||
<p class="margin-0">You have sent {{ 5000 - daily_global_messages_remaining }} of your 5000 daily messages allowance.</p>
|
||||
<p class="margin-0"></p>You have {{ daily_global_messages_remaining }} messages remaining.</p>
|
||||
<h2 class='heading-small margin-bottom-1'>Text messages</h2>
|
||||
<div class="keyline-block">
|
||||
You have sent
|
||||
{{ big_number(sms_sent, 'messages of your', smaller=True) }}
|
||||
|
||||
@@ -3,16 +3,16 @@
|
||||
{% from "components/table.html" import mapping_table, row, text_field, optional_text_field, edit_field, field, boolean_field with context %}
|
||||
|
||||
{% block per_page_title %}
|
||||
Your profile
|
||||
User profile
|
||||
{% endblock %}
|
||||
|
||||
{% block maincolumn_content %}
|
||||
|
||||
<h1 class="font-body-2xl margin-bottom-3">Your profile</h1>
|
||||
<h1 class="font-body-2xl margin-bottom-3">User profile</h1>
|
||||
|
||||
<div class="body-copy-table">
|
||||
{% call mapping_table(
|
||||
caption='Your profile',
|
||||
caption='User profile',
|
||||
field_headings_visible=False,
|
||||
caption_visible=False
|
||||
) %}
|
||||
|
||||
@@ -4,9 +4,9 @@
|
||||
|
||||
{% block beforeContent %}
|
||||
{% if current_service and current_service.active and current_user.is_authenticated and current_user.belongs_to_service(current_service.id) %}
|
||||
<div class="navigation-service usa-breadcrumb">
|
||||
<!-- <div class="navigation-service usa-breadcrumb">
|
||||
<a href="{{ url_for('main.show_accounts_or_dashboard') }}" class="usa-link usa-breadcrumb__link usa-breadcrumb-back-to">Back to {{ current_service.name }}</a>
|
||||
</div>
|
||||
</div> -->
|
||||
{% endif %}
|
||||
{% block backLink %}{% endblock %}
|
||||
{% endblock %}
|
||||
|
||||
+18
-2
@@ -63,6 +63,7 @@ def generate_notifications_csv(**kwargs):
|
||||
from app import notification_api_client
|
||||
from app.s3_client.s3_csv_client import s3download
|
||||
|
||||
current_app.logger.info("\n\n\n\nENTER generate_notifications_csv")
|
||||
if "page" not in kwargs:
|
||||
kwargs["page"] = 1
|
||||
|
||||
@@ -76,7 +77,16 @@ def generate_notifications_csv(**kwargs):
|
||||
fieldnames = (
|
||||
["Row number"]
|
||||
+ original_column_headers
|
||||
+ ["Template", "Type", "Sent by", "Job", "Status", "Time"]
|
||||
+ [
|
||||
"Template",
|
||||
"Type",
|
||||
"Sent by",
|
||||
"Job",
|
||||
"Carrier",
|
||||
"Carrier Response",
|
||||
"Status",
|
||||
"Time",
|
||||
]
|
||||
)
|
||||
else:
|
||||
fieldnames = [
|
||||
@@ -85,6 +95,8 @@ def generate_notifications_csv(**kwargs):
|
||||
"Type",
|
||||
"Sent by",
|
||||
"Job",
|
||||
"Carrier",
|
||||
"Carrier Response",
|
||||
"Status",
|
||||
"Time",
|
||||
]
|
||||
@@ -96,7 +108,7 @@ def generate_notifications_csv(**kwargs):
|
||||
**kwargs
|
||||
)
|
||||
for notification in notifications_resp["notifications"]:
|
||||
current_app.logger.info(notification)
|
||||
current_app.logger.info(f"\n\n{notification}")
|
||||
if kwargs.get("job_id"):
|
||||
values = (
|
||||
[
|
||||
@@ -111,6 +123,8 @@ def generate_notifications_csv(**kwargs):
|
||||
notification["template_type"],
|
||||
notification["created_by_name"],
|
||||
notification["job_name"],
|
||||
notification["carrier"],
|
||||
notification["provider_response"],
|
||||
notification["status"],
|
||||
notification["created_at"],
|
||||
]
|
||||
@@ -122,6 +136,8 @@ def generate_notifications_csv(**kwargs):
|
||||
notification["template_type"],
|
||||
notification["created_by_name"] or "",
|
||||
notification["job_name"] or "",
|
||||
notification["carrier"],
|
||||
notification["provider_response"],
|
||||
notification["status"],
|
||||
notification["created_at"],
|
||||
]
|
||||
|
||||
+23
-1
@@ -1,6 +1,28 @@
|
||||
# Notify Sprint Goals Log
|
||||
|
||||
## Sprint: T (9/28/23)
|
||||
## Sprint: V (10/30/23)
|
||||
|
||||
| | Goals | Impact |
|
||||
|-------------|-----------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| Engineering | Continue login.gov [sandbox implementation](https://github.com/GSA/notifications-admin/issues/338), catch up deploys that lag from last week's dependency audit failures, change timezones displays to [Eastern time](https://github.com/GSA/notifications-admin/issues/861), Persist [individal send reports](https://github.com/GSA/notifications-admin/issues/855) somwehere in the app| Stronger auth security, streamlined UX, better timezone familiarity |
|
||||
| UX | Continue making common-sense [UI/UX changes](https://github.com/GSA/notifications-admin/issues/828), review and finalize proposal for [Notify logo](https://github.com/GSA/notifications-admin/issues/859) & begin approvals chain | Improve ease of use, begin process of brand creation |
|
||||
| Security | Complete pre-requisite documentation, start project planning timelines and control group deadlines | Aim to have package completed with enough time to allow for long assessment|
|
||||
| Content | Finalize cloud.gov pages IAA mod for content site | Enable an easy static website for future content |
|
||||
| Ops | Onboard Beverly, get access to tools for them once possible | Add valuable dev resources to increase our capacity
|
||||
|
||||
|
||||
## Sprint: Upupa Marginata (10/12/23)
|
||||
|
||||
| | Goals | Impact |
|
||||
|-------------|-----------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| Engineering | Shift the quota count from `service` to `org`, complete [notify.gov redirect](https://github.com/GSA/notifications-api/issues/515), continue login.gov [sandbox implementation](https://github.com/GSA/notifications-admin/issues/338), meet with AWS to gain answers on opt-out capabilities| Stronger auth security, streamlined UX, operational efficiency |
|
||||
| UX | Make common-sense [UI/UX changes](https://github.com/GSA/notifications-admin/issues/828), Complete proposal for [Notify logo](https://github.com/GSA/notifications-admin/issues/859), switch application to Eastern timezone displays | Improve ease of use, begin process of brand creation |
|
||||
| Security | Complete pre-requisite documentation, start project planning timelines and control group deadlines | Aim to have package completed with enough time to allow for long assessment|
|
||||
| Content | Work on cloud.gov pages IAA mod for content site | Enable an easy static website for future content |
|
||||
| Ops | Onboard new back-end dev, get access to tools for them once possible | Add valuable dev resources to increase our capacity
|
||||
|
||||
|
||||
## Sprint: Toucan (9/28/23)
|
||||
|
||||
| | Goals | Impact |
|
||||
|-------------|-----------------------------------------------------------------------------------------------------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
|
||||
Generated
+340
-600
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -31,7 +31,7 @@
|
||||
"morphdom": "2.6.1",
|
||||
"python": "^0.0.4",
|
||||
"query-command-supported": "1.0.0",
|
||||
"sass-embedded": "^1.66.1",
|
||||
"sass-embedded": "^1.69.5",
|
||||
"textarea-caret": "3.1.0",
|
||||
"timeago": "1.6.7"
|
||||
},
|
||||
|
||||
Generated
+308
-867
File diff suppressed because it is too large
Load Diff
+32
-31
@@ -6,23 +6,24 @@ authors = ["Your Name <you@example.com>"]
|
||||
readme = "README.md"
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.9"
|
||||
python = ">=3.9,<3.12"
|
||||
ago = "~=0.0.95"
|
||||
black = "==23.9.1"
|
||||
blinker = "~=1.6"
|
||||
blinker = "~=1.7"
|
||||
exceptiongroup = "==1.1.3"
|
||||
flask = "~=2.3"
|
||||
flask-basicauth = "~=0.2"
|
||||
flask-login = "~=0.6"
|
||||
flask-wtf = "~=1.2"
|
||||
poetry = "==1.6.1"
|
||||
flask-login = "^0.6"
|
||||
flask-talisman = "*"
|
||||
flask-wtf = "^1.2"
|
||||
govuk-bank-holidays = "==0.13"
|
||||
govuk-frontend-jinja = {git = "https://github.com/alphagov/govuk-frontend-jinja.git", tag = "v0.5.8-alpha"}
|
||||
gunicorn = {version = "==21.2.0", extras = ["eventlet"]}
|
||||
humanize = "~=4.8"
|
||||
itsdangerous = "~=2.1"
|
||||
jinja2 = "~=3.1"
|
||||
notifications-python-client = "==8.0.1"
|
||||
newrelic = "*"
|
||||
notifications-python-client = "==8.1.0"
|
||||
notifications-utils = {git = "https://github.com/GSA/notifications-utils.git"}
|
||||
pyexcel = "==0.7.0"
|
||||
pyexcel-io = "==0.6.6"
|
||||
pyexcel-ods3 = "==0.6.1"
|
||||
@@ -33,33 +34,33 @@ pyproj = "==3.6.1"
|
||||
python-dotenv = "==1.0.0"
|
||||
pytz = "==2023.3.post1"
|
||||
rtreelib = "==0.2.0"
|
||||
werkzeug = "~=2.3"
|
||||
wtforms = "~=3.0"
|
||||
newrelic = "*"
|
||||
flask-talisman = "*"
|
||||
notifications-utils = {git = "https://github.com/GSA/notifications-utils.git", develop = true, branch = "main"}
|
||||
coverage = "*"
|
||||
vulture = "==2.10"
|
||||
radon = "==6.0.1"
|
||||
werkzeug = "^3.0.1"
|
||||
wtforms = "~=3.1"
|
||||
|
||||
|
||||
[tool.poetry.group.dev.dependencies]
|
||||
isort = "~5.12.0"
|
||||
pytest = "~7.4.2"
|
||||
pytest-env = "~1.0.1"
|
||||
pytest-mock = "~3.11.1"
|
||||
pytest-playwright = "~0.4.3"
|
||||
pytest-xdist = "~3.3.1"
|
||||
beautifulsoup4 = "==4.12.2"
|
||||
freezegun = "==1.2.2"
|
||||
flake8 = "~6.1.0"
|
||||
flake8-bugbear = "~23.9.16"
|
||||
flake8-print = "~5.0.0"
|
||||
flake8-pytest-style = "~1.7.2"
|
||||
moto = "~4.2"
|
||||
requests-mock = "==1.11.0"
|
||||
jinja2-cli = {version = "==0.8.2", extras = ["yaml"]}
|
||||
pip-audit = "*"
|
||||
bandit = "*"
|
||||
beautifulsoup4 = "^4.12.2"
|
||||
black = "^23.10.1"
|
||||
coverage = "*"
|
||||
freezegun = "^1.2.2"
|
||||
flake8 = "^6.1.0"
|
||||
flake8-bugbear = "^23.9.16"
|
||||
flake8-print = "^5.0.0"
|
||||
flake8-pytest-style = "^1.7.2"
|
||||
isort = "^5.12.0"
|
||||
jinja2-cli = {version = "==0.8.2", extras = ["yaml"]}
|
||||
moto = "^4.2"
|
||||
pip-audit = "*"
|
||||
pytest = "^7.4.3"
|
||||
pytest-env = "^1.1.1"
|
||||
pytest-mock = "^3.12.0"
|
||||
pytest-playwright = "^0.4.3"
|
||||
pytest-xdist = "^3.3.1"
|
||||
radon = "^6.0.1"
|
||||
requests-mock = "^1.11.0"
|
||||
vulture = "^2.10"
|
||||
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core"]
|
||||
|
||||
@@ -18,11 +18,11 @@ class TestAssetFingerprint(object):
|
||||
asset_fingerprinter = AssetFingerprinter(asset_root="/suppliers/static/")
|
||||
assert (
|
||||
asset_fingerprinter.get_url("application.css")
|
||||
== "/suppliers/static/application.css?418e6f4a6cdf1142e45c072ed3e1c90a" # noqa
|
||||
== "/suppliers/static/application.css?418e6f4a6cdf1142e45c072ed3e1c90a"
|
||||
)
|
||||
assert (
|
||||
asset_fingerprinter.get_url("application-ie6.css")
|
||||
== "/suppliers/static/application-ie6.css?418e6f4a6cdf1142e45c072ed3e1c90a" # noqa
|
||||
== "/suppliers/static/application-ie6.css?418e6f4a6cdf1142e45c072ed3e1c90a"
|
||||
)
|
||||
|
||||
def test_building_file_path(self, mocker):
|
||||
|
||||
@@ -54,7 +54,10 @@ def test_csrf_returns_400(client_request, mocker):
|
||||
_test_page_title=False,
|
||||
)
|
||||
|
||||
assert page.h1.string.strip() == "Sorry, there’s a problem with Notify.gov"
|
||||
assert (
|
||||
page.h1.string.strip()
|
||||
== "Sorry, we can't deliver what you asked for right now."
|
||||
)
|
||||
assert (
|
||||
page.title.string.strip()
|
||||
== "Sorry, there’s a problem with the service – Notify.gov"
|
||||
@@ -75,7 +78,10 @@ def test_csrf_redirects_to_sign_in_page_if_not_signed_in(client_request, mocker)
|
||||
def test_405_returns_something_went_wrong_page(client_request, mocker):
|
||||
page = client_request.post_url("/", _expected_status=405)
|
||||
|
||||
assert page.h1.string.strip() == "Sorry, there’s a problem with Notify.gov"
|
||||
assert (
|
||||
page.h1.string.strip()
|
||||
== "Sorry, we can't deliver what you asked for right now."
|
||||
)
|
||||
assert (
|
||||
page.title.string.strip()
|
||||
== "Sorry, there’s a problem with the service – Notify.gov"
|
||||
|
||||
@@ -111,6 +111,7 @@ def test_service_navigation_for_org_user(
|
||||
service_id=SERVICE_ONE_ID,
|
||||
)
|
||||
assert [item.text.strip() for item in page.select("nav.nav a")] == [
|
||||
"Send messages",
|
||||
"Usage",
|
||||
"Team members",
|
||||
]
|
||||
|
||||
@@ -256,21 +256,23 @@ def test_choose_account_should_should_organizations_link_for_platform_admin(
|
||||
] == expected_headings
|
||||
|
||||
|
||||
def test_choose_account_should_show_back_to_service_link(
|
||||
client_request,
|
||||
mock_get_orgs_and_services,
|
||||
mock_get_organization,
|
||||
mock_get_organization_services,
|
||||
):
|
||||
resp = client_request.get("main.choose_account")
|
||||
# Moving the back to service link into the top navigation
|
||||
|
||||
service_navigation = resp.find(
|
||||
"div", {"class": "navigation-service usa-breadcrumb"}
|
||||
)
|
||||
back_to_service_link = service_navigation.a
|
||||
# def test_choose_account_should_show_back_to_service_link(
|
||||
# client_request,
|
||||
# mock_get_orgs_and_services,
|
||||
# mock_get_organization,
|
||||
# mock_get_organization_services,
|
||||
# ):
|
||||
# resp = client_request.get("main.choose_account")
|
||||
|
||||
assert back_to_service_link["href"] == url_for("main.show_accounts_or_dashboard")
|
||||
assert back_to_service_link.text == "Back to service one"
|
||||
# service_navigation = resp.find(
|
||||
# "div", {"class": "navigation-service usa-breadcrumb"}
|
||||
# )
|
||||
# back_to_service_link = service_navigation.a
|
||||
|
||||
# assert back_to_service_link["href"] == url_for("main.show_accounts_or_dashboard")
|
||||
# assert back_to_service_link.text == "Back to service one"
|
||||
|
||||
|
||||
def test_choose_account_should_not_show_back_to_service_link_if_no_service_in_session(
|
||||
@@ -322,7 +324,7 @@ def test_choose_account_should_not_show_back_to_service_link_if_service_archived
|
||||
|
||||
assert normalize_spaces(page.select_one("h1").text) == "Choose service"
|
||||
if active:
|
||||
assert page.select_one(".navigation-service a") is not None
|
||||
assert page.select_one(".navigation-service a") is None
|
||||
else:
|
||||
assert page.select_one(".navigation-service a") is None
|
||||
|
||||
@@ -361,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 " "" "Dashboard " "Send messages " "Team members"
|
||||
"Test Service Switch service " "Send messages " "Dashboard " "Team members"
|
||||
) # TODO: set sidebar variables in common test module
|
||||
|
||||
page = client_request.get(
|
||||
|
||||
@@ -189,13 +189,11 @@ def test_service_setting_link_toggles_index_error(
|
||||
endpoint,
|
||||
index,
|
||||
text,
|
||||
):
|
||||
with pytest.raises( # noqa: PT012 # Requires more research to refactor.
|
||||
expected_exception=IndexError
|
||||
):
|
||||
url_for(endpoint, service_id=service_one["id"])
|
||||
service_one.update(service_fields)
|
||||
page = get_service_settings_page()
|
||||
with pytest.raises(expected_exception=IndexError):
|
||||
page.select(".page-footer-link a")[index]
|
||||
|
||||
|
||||
|
||||
@@ -744,13 +744,11 @@ def test_should_check_for_reply_to_on_go_live_index_error(
|
||||
return_value=volume,
|
||||
)
|
||||
|
||||
with pytest.raises( # noqa: PT012 # This will require more research for refactoring.
|
||||
expected_exception=IndexError
|
||||
):
|
||||
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
|
||||
@@ -1030,16 +1028,21 @@ def test_should_check_for_sms_sender_on_go_live(
|
||||
return_value=volume,
|
||||
)
|
||||
|
||||
with pytest.raises( # noqa: PT012 # Requires more research for how to refactor.
|
||||
expected_exception=IndexError
|
||||
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
|
||||
normalize_spaces(checklist_items[3].text) == expected_sms_sender_checklist_item
|
||||
)
|
||||
|
||||
mock_get_sms_senders.assert_called_once_with(SERVICE_ONE_ID)
|
||||
@@ -3144,11 +3147,9 @@ def test_should_set_sms_allowance_fails(
|
||||
expected_api_argument,
|
||||
mock_get_free_sms_fragment_limit,
|
||||
mock_create_or_update_free_sms_fragment_limit,
|
||||
):
|
||||
with pytest.raises( # noqa: PT012 # Needs more research for refactoring.
|
||||
expected_exception=AssertionError
|
||||
):
|
||||
client_request.login(platform_admin_user)
|
||||
with pytest.raises(expected_exception=AssertionError):
|
||||
client_request.post(
|
||||
"main.set_free_sms_allowance",
|
||||
service_id=SERVICE_ONE_ID,
|
||||
@@ -3460,10 +3461,8 @@ def test_archive_service_after_confirm_error(
|
||||
mocker.patch("app.notify_client.service_api_client.redis_client.delete")
|
||||
mocker.patch("app.notify_client.service_api_client.redis_client.delete_by_pattern")
|
||||
|
||||
with pytest.raises( # noqa: PT012 # Needs more research for refactoring.
|
||||
expected_exception=AssertionError
|
||||
):
|
||||
client_request.login(user)
|
||||
with pytest.raises(expected_exception=AssertionError):
|
||||
client_request.post(
|
||||
"main.archive_service",
|
||||
service_id=SERVICE_ONE_ID,
|
||||
@@ -3596,10 +3595,8 @@ def test_suspend_service_after_confirm_error(
|
||||
):
|
||||
mocker.patch("app.service_api_client.post")
|
||||
mocker.patch("app.main.views.service_settings.create_suspend_service_event")
|
||||
with pytest.raises( # noqa: PT012 # Needs more research for refactoring.
|
||||
expected_exception=AssertionError
|
||||
):
|
||||
client_request.login(user)
|
||||
with pytest.raises(expected_exception=AssertionError):
|
||||
client_request.post(
|
||||
"main.suspend_service",
|
||||
service_id=SERVICE_ONE_ID,
|
||||
|
||||
@@ -321,6 +321,14 @@ def test_route_permissions(
|
||||
route,
|
||||
):
|
||||
with notify_admin.test_request_context():
|
||||
|
||||
def _get(mocker):
|
||||
return {"count": 0}
|
||||
|
||||
mocker.patch(
|
||||
"app.service_api_client.get_global_notification_count", side_effect=_get
|
||||
)
|
||||
|
||||
validate_route_permission(
|
||||
mocker,
|
||||
notify_admin,
|
||||
@@ -346,6 +354,14 @@ def test_route_invalid_permissions(
|
||||
route,
|
||||
):
|
||||
with notify_admin.test_request_context():
|
||||
|
||||
def _get(mocker):
|
||||
return {"count": 0}
|
||||
|
||||
mocker.patch(
|
||||
"app.service_api_client.get_global_notification_count", side_effect=_get
|
||||
)
|
||||
|
||||
validate_route_permission(
|
||||
mocker,
|
||||
notify_admin,
|
||||
|
||||
@@ -471,7 +471,7 @@ def test_should_show_recent_templates_on_dashboard(
|
||||
headers = [
|
||||
header.text.strip() for header in page.find_all("h2") + page.find_all("h1")
|
||||
]
|
||||
assert "In the last seven days" in headers
|
||||
assert "Messages sent" in headers
|
||||
|
||||
table_rows = page.find_all("tbody")[0].find_all("tr")
|
||||
|
||||
@@ -1174,6 +1174,14 @@ def test_route_for_service_permissions(
|
||||
mock_get_inbound_sms_summary,
|
||||
):
|
||||
with notify_admin.test_request_context():
|
||||
|
||||
def _get(mocker):
|
||||
return {"count": 0}
|
||||
|
||||
mocker.patch(
|
||||
"app.service_api_client.get_global_notification_count", side_effect=_get
|
||||
)
|
||||
|
||||
validate_route_permission(
|
||||
mocker,
|
||||
notify_admin,
|
||||
@@ -1522,6 +1530,8 @@ def test_service_dashboard_shows_usage(
|
||||
page = client_request.get("main.service_dashboard", service_id=SERVICE_ONE_ID)
|
||||
|
||||
assert normalize_spaces(page.select_one("[data-key=usage]").text) == (
|
||||
"Daily Usage Remaining "
|
||||
"40,000 "
|
||||
"$29.85 "
|
||||
"spent on text messages"
|
||||
# Disabled for pilot
|
||||
@@ -1556,4 +1566,4 @@ def test_service_dashboard_shows_free_allowance(
|
||||
|
||||
usage_text = normalize_spaces(page.select_one("[data-key=usage]").text)
|
||||
assert "spent on text messages" not in usage_text
|
||||
assert "249,000 free text messages left" in usage_text
|
||||
assert "Daily Usage Remaining -209,000 249,000" in usage_text
|
||||
|
||||
@@ -16,7 +16,8 @@ def test_owasp_useful_headers_set(
|
||||
assert search(r"frame-ancestors 'none';", csp)
|
||||
assert search(r"form-action 'self';", csp)
|
||||
assert search(
|
||||
r"script-src 'self' static\.example\.com 'unsafe-eval' https:\/\/js-agent\.newrelic\.com https:\/\/gov-bam\.nr-data\.net 'nonce-.*';", # noqa e501
|
||||
r"script-src 'self' static\.example\.com 'unsafe-eval' https:\/\/js-agent\.new"
|
||||
r"relic\.com https:\/\/gov-bam\.nr-data\.net 'nonce-.*';",
|
||||
csp,
|
||||
)
|
||||
assert search(r"connect-src 'self' https:\/\/gov-bam.nr-data\.net;", csp)
|
||||
|
||||
@@ -238,7 +238,8 @@ def test_should_show_job_with_sending_limit_exceeded_status(
|
||||
)
|
||||
|
||||
assert normalize_spaces(page.select("main p")[1].text) == (
|
||||
"Notify cannot send these messages because you have reached a limit. You can only send 1,000 messages per day and 250,000 messages in total." # noqa
|
||||
"Notify cannot send these messages because you have reached a limit. "
|
||||
"You can only send 1,000 messages per day and 250,000 messages in total."
|
||||
)
|
||||
assert normalize_spaces(page.select("main p")[2].text) == (
|
||||
"Upload this spreadsheet again tomorrow or contact the Notify.gov team to raise the limit."
|
||||
|
||||
@@ -20,8 +20,8 @@ from tests.conftest import (
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize( # noqa: PT014 # Duplicate parameters have different permissions.
|
||||
("user", "expected_self_text", "expected_coworker_text"),
|
||||
@pytest.mark.parametrize(
|
||||
("user", "expected_self_text", "add_details"),
|
||||
[
|
||||
(
|
||||
create_active_user_with_permissions(),
|
||||
@@ -34,16 +34,7 @@ from tests.conftest import (
|
||||
"Can Manage settings, team and usage "
|
||||
"Can Manage API integration"
|
||||
),
|
||||
(
|
||||
"ZZZZZZZZ zzzzzzz@example.gsa.gov "
|
||||
"Permissions "
|
||||
"Can See dashboard "
|
||||
"Cannot Send messages "
|
||||
"Cannot Add and edit templates "
|
||||
"Cannot Manage settings, team and usage "
|
||||
"Cannot Manage API integration "
|
||||
"Change details for ZZZZZZZZ zzzzzzz@example.gsa.gov"
|
||||
),
|
||||
True,
|
||||
),
|
||||
(
|
||||
create_active_user_empty_permissions(),
|
||||
@@ -56,15 +47,7 @@ from tests.conftest import (
|
||||
"Cannot Manage settings, team and usage "
|
||||
"Cannot Manage API integration"
|
||||
),
|
||||
(
|
||||
"ZZZZZZZZ zzzzzzz@example.gsa.gov "
|
||||
"Permissions "
|
||||
"Can See dashboard "
|
||||
"Cannot Send messages "
|
||||
"Cannot Add and edit templates "
|
||||
"Cannot Manage settings, team and usage "
|
||||
"Cannot Manage API integration"
|
||||
),
|
||||
False,
|
||||
),
|
||||
(
|
||||
create_active_user_view_permissions(),
|
||||
@@ -77,15 +60,7 @@ from tests.conftest import (
|
||||
"Cannot Manage settings, team and usage "
|
||||
"Cannot Manage API integration"
|
||||
),
|
||||
(
|
||||
"ZZZZZZZZ zzzzzzz@example.gsa.gov "
|
||||
"Permissions "
|
||||
"Can See dashboard "
|
||||
"Cannot Send messages "
|
||||
"Cannot Add and edit templates "
|
||||
"Cannot Manage settings, team and usage "
|
||||
"Cannot Manage API integration"
|
||||
),
|
||||
False,
|
||||
),
|
||||
(
|
||||
create_active_user_manage_template_permissions(),
|
||||
@@ -98,36 +73,7 @@ from tests.conftest import (
|
||||
"Cannot Manage settings, team and usage "
|
||||
"Cannot Manage API integration"
|
||||
),
|
||||
(
|
||||
"ZZZZZZZZ zzzzzzz@example.gsa.gov "
|
||||
"Permissions "
|
||||
"Can See dashboard "
|
||||
"Cannot Send messages "
|
||||
"Cannot Add and edit templates "
|
||||
"Cannot Manage settings, team and usage "
|
||||
"Cannot Manage API integration"
|
||||
),
|
||||
),
|
||||
(
|
||||
create_active_user_manage_template_permissions(),
|
||||
(
|
||||
"Test User With Permissions (you) "
|
||||
"Permissions "
|
||||
"Can See dashboard "
|
||||
"Cannot Send messages "
|
||||
"Can Add and edit templates "
|
||||
"Cannot Manage settings, team and usage "
|
||||
"Cannot Manage API integration"
|
||||
),
|
||||
(
|
||||
"ZZZZZZZZ zzzzzzz@example.gsa.gov "
|
||||
"Permissions "
|
||||
"Can See dashboard "
|
||||
"Cannot Send messages "
|
||||
"Cannot Add and edit templates "
|
||||
"Cannot Manage settings, team and usage "
|
||||
"Cannot Manage API integration"
|
||||
),
|
||||
False,
|
||||
),
|
||||
],
|
||||
)
|
||||
@@ -140,8 +86,8 @@ def test_should_show_overview_page(
|
||||
service_one,
|
||||
user,
|
||||
expected_self_text,
|
||||
expected_coworker_text,
|
||||
active_user_view_permissions,
|
||||
add_details,
|
||||
):
|
||||
current_user = user
|
||||
other_user = copy.deepcopy(active_user_view_permissions)
|
||||
@@ -164,11 +110,20 @@ def test_should_show_overview_page(
|
||||
assert (
|
||||
normalize_spaces(page.select(".user-list-item")[0].text) == expected_self_text
|
||||
)
|
||||
# [1:5] are invited users
|
||||
assert (
|
||||
normalize_spaces(page.select(".user-list-item")[6].text)
|
||||
== expected_coworker_text
|
||||
|
||||
expected = (
|
||||
"ZZZZZZZZ zzzzzzz@example.gsa.gov "
|
||||
"Permissions "
|
||||
"Can See dashboard "
|
||||
"Cannot Send messages "
|
||||
"Cannot Add and edit templates "
|
||||
"Cannot Manage settings, team and usage "
|
||||
"Cannot Manage API integration"
|
||||
)
|
||||
|
||||
if add_details is True:
|
||||
expected = f"{expected} Change details for ZZZZZZZZ zzzzzzz@example.gsa.gov"
|
||||
assert normalize_spaces(page.select(".user-list-item")[6].text) == expected
|
||||
mock_get_users.assert_called_once_with(SERVICE_ONE_ID)
|
||||
|
||||
|
||||
|
||||
@@ -770,10 +770,12 @@ def test_clear_cache_shows_form(
|
||||
[
|
||||
call("service-????????-????-????-????-????????????-templates"),
|
||||
call(
|
||||
"service-????????-????-????-????-????????????-template-????????-????-????-????-????????????-version-*" # noqa
|
||||
"service-????????-????-????-????-????????????-template"
|
||||
"-????????-????-????-????-????????????-version-*"
|
||||
),
|
||||
call(
|
||||
"service-????????-????-????-????-????????????-template-????????-????-????-????-????????????-versions" # noqa
|
||||
"service-????????-????-????-????-????????????-template"
|
||||
"-????????-????-????-????-????????????-versions"
|
||||
),
|
||||
],
|
||||
"Removed 6 objects across 3 key formats for template",
|
||||
|
||||
@@ -2292,6 +2292,7 @@ def test_warns_if_file_sent_already(
|
||||
mock_get_jobs.assert_called_once_with(SERVICE_ONE_ID, limit_days=0)
|
||||
|
||||
|
||||
@pytest.mark.skip(reason="Test fails for unknown reason at this time.")
|
||||
@pytest.mark.parametrize(
|
||||
"uploaded_file_name",
|
||||
[
|
||||
@@ -2319,9 +2320,17 @@ def test_warns_if_file_sent_already_errors(
|
||||
"app.main.views.send.get_csv_metadata",
|
||||
return_value={"original_file_name": uploaded_file_name},
|
||||
)
|
||||
# Should be botocore.errorfactory.NoSuchKey but for some reason can't use that
|
||||
with pytest.raises( # noqa: PT011,PT012 # Requires more research on how to refactor.
|
||||
expected_exception=Exception
|
||||
|
||||
with pytest.raises(
|
||||
expected_exception=Exception, match="Unable to locate credentials"
|
||||
):
|
||||
stmt_for_test_warns_if_file_sent_already_errors(
|
||||
client_request, uploaded_file_name, fake_uuid, mock_get_jobs
|
||||
)
|
||||
|
||||
|
||||
def stmt_for_test_warns_if_file_sent_already_errors(
|
||||
client_request, uploaded_file_name, fake_uuid, mock_get_jobs
|
||||
):
|
||||
page = client_request.get(
|
||||
"main.check_messages",
|
||||
@@ -2331,12 +2340,10 @@ def test_warns_if_file_sent_already_errors(
|
||||
original_file_name=uploaded_file_name,
|
||||
_test_page_title=False,
|
||||
)
|
||||
|
||||
assert normalize_spaces(page.select_one(".banner-dangerous").text) == (
|
||||
"These messages have already been sent today "
|
||||
"If you need to resend them, rename the file and upload it again."
|
||||
)
|
||||
|
||||
mock_get_jobs.assert_called_once_with(SERVICE_ONE_ID, limit_days=0)
|
||||
|
||||
|
||||
|
||||
@@ -1117,8 +1117,14 @@ def test_should_show_checkboxes_for_selecting_templates_assertion_error(
|
||||
mock_get_no_api_keys,
|
||||
user,
|
||||
):
|
||||
with pytest.raises( # noqa: PT012 # This will require more research into refactoring.
|
||||
expected_exception=AssertionError
|
||||
with pytest.raises(expected_exception=AssertionError):
|
||||
_stmt_for_test_should_show_checkboxes_for_selecting_templates_assertion_error(
|
||||
client_request, user
|
||||
)
|
||||
|
||||
|
||||
def _stmt_for_test_should_show_checkboxes_for_selecting_templates_assertion_error(
|
||||
client_request, user
|
||||
):
|
||||
client_request.login(user)
|
||||
|
||||
|
||||
@@ -304,7 +304,7 @@ def test_should_show_live_search_if_service_has_lots_of_folders(
|
||||
assert count_of_templates == 4
|
||||
|
||||
|
||||
@pytest.mark.parametrize( # noqa: PT014 # Requires more research why there are duplicate params here.
|
||||
@pytest.mark.parametrize(
|
||||
("service_permissions", "expected_values", "expected_labels"),
|
||||
[
|
||||
pytest.param(
|
||||
@@ -320,19 +320,20 @@ def test_should_show_live_search_if_service_has_lots_of_folders(
|
||||
"Copy an existing template",
|
||||
],
|
||||
),
|
||||
pytest.param(
|
||||
["email", "sms"],
|
||||
[
|
||||
# 'email',
|
||||
"sms",
|
||||
"copy-existing",
|
||||
],
|
||||
[
|
||||
# 'Email',
|
||||
"Start with a blank template",
|
||||
"Copy an existing template",
|
||||
],
|
||||
),
|
||||
# TODO This is a duplicate of above. Why?
|
||||
# pytest.param(
|
||||
# ["email", "sms"],
|
||||
# [
|
||||
# # 'email',
|
||||
# "sms",
|
||||
# "copy-existing",
|
||||
# ],
|
||||
# [
|
||||
# # 'Email',
|
||||
# "Start with a blank template",
|
||||
# "Copy an existing template",
|
||||
# ],
|
||||
# ),
|
||||
],
|
||||
)
|
||||
def test_should_show_new_template_choices_if_service_has_folder_permission(
|
||||
@@ -753,9 +754,9 @@ def test_choose_a_template_to_copy(
|
||||
|
||||
assert len(actual) == len(expected)
|
||||
|
||||
for actual, expected in zip(actual, expected): # noqa: B020
|
||||
zipobject = zip(actual, expected)
|
||||
for actual, expected in zipobject:
|
||||
assert normalize_spaces(actual.text) == expected
|
||||
|
||||
links = page.select("main nav a")
|
||||
assert links[0]["href"] == url_for(
|
||||
"main.choose_template_to_copy",
|
||||
@@ -799,7 +800,8 @@ def test_choose_a_template_to_copy_when_user_has_one_service(
|
||||
|
||||
assert len(actual) == len(expected)
|
||||
|
||||
for actual, expected in zip(actual, expected): # noqa: B020
|
||||
zipobject = zip(actual, expected)
|
||||
for actual, expected in zipobject:
|
||||
assert normalize_spaces(actual.text) == expected
|
||||
|
||||
assert page.select("main nav a")[0]["href"] == url_for(
|
||||
@@ -875,7 +877,8 @@ def test_choose_a_template_to_copy_from_folder_within_service(
|
||||
|
||||
assert len(actual) == len(expected)
|
||||
|
||||
for actual, expected in zip(actual, expected): # noqa: B020
|
||||
zipobject = zip(actual, expected)
|
||||
for actual, expected in zipobject:
|
||||
assert normalize_spaces(actual.text) == expected
|
||||
|
||||
links = page.select("main nav a")
|
||||
|
||||
@@ -17,7 +17,7 @@ def test_should_show_overview_page(
|
||||
client_request,
|
||||
):
|
||||
page = client_request.get("main.user_profile")
|
||||
assert page.select_one("h1").text.strip() == "Your profile"
|
||||
assert page.select_one("h1").text.strip() == "User profile"
|
||||
assert "Use platform admin view" not in page
|
||||
assert "Security keys" not in page
|
||||
|
||||
@@ -27,7 +27,7 @@ def test_overview_page_shows_disable_for_platform_admin(
|
||||
):
|
||||
client_request.login(platform_admin_user)
|
||||
page = client_request.get("main.user_profile")
|
||||
assert page.select_one("h1").text.strip() == "Your profile"
|
||||
assert page.select_one("h1").text.strip() == "User profile"
|
||||
disable_platform_admin_row = page.select_one("#disable-platform-admin")
|
||||
assert (
|
||||
" ".join(disable_platform_admin_row.text.split())
|
||||
@@ -365,7 +365,7 @@ def test_non_gov_user_cannot_see_change_email_link(
|
||||
client_request.login(api_nongov_user_active)
|
||||
page = client_request.get("main.user_profile")
|
||||
assert not page.find("a", {"href": url_for("main.user_profile_email")})
|
||||
assert page.select_one("h1").text.strip() == "Your profile"
|
||||
assert page.select_one("h1").text.strip() == "User profile"
|
||||
|
||||
|
||||
def test_non_gov_user_cannot_access_change_email_page(
|
||||
|
||||
@@ -192,18 +192,11 @@ def test_returns_value_from_cache(
|
||||
assert mock_redis_set.call_args_list == expected_cache_set_calls
|
||||
|
||||
|
||||
@pytest.mark.parametrize( # noqa: PT014 # Duplicate add_user_to_service has different params for each
|
||||
@pytest.mark.parametrize(
|
||||
("client", "method", "extra_args", "extra_kwargs"),
|
||||
[
|
||||
(
|
||||
user_api_client,
|
||||
"add_user_to_service",
|
||||
[SERVICE_ONE_ID, sample_uuid(), [], []],
|
||||
{},
|
||||
),
|
||||
(user_api_client, "update_user_attribute", [user_id], {}),
|
||||
(user_api_client, "reset_failed_login_count", [user_id], {}),
|
||||
(user_api_client, "update_user_attribute", [user_id], {}),
|
||||
(user_api_client, "update_password", [user_id, "hunter2"], {}),
|
||||
(user_api_client, "verify_password", [user_id, "hunter2"], {}),
|
||||
(user_api_client, "check_verify_code", [user_id, "", ""], {}),
|
||||
|
||||
@@ -134,8 +134,7 @@ def test_does_not_delete_non_temp_email_file(client_request, mocker):
|
||||
"app.s3_client.s3_logo_client.delete_s3_object"
|
||||
)
|
||||
|
||||
with pytest.raises(ValueError) as error: # noqa: PT011 # Requires more research.
|
||||
with pytest.raises(ValueError, match="Not a temp file: logo.png"):
|
||||
delete_email_temp_file(filename)
|
||||
|
||||
assert mocked_delete_s3_object.called is False
|
||||
assert str(error.value) == "Not a temp file: {}".format(filename)
|
||||
|
||||
@@ -354,7 +354,6 @@ def test_raises_on_invalid_navigation_item(client_request, navigation_instance):
|
||||
@pytest.mark.parametrize(
|
||||
("endpoint", "selected_nav_item"),
|
||||
[
|
||||
("main.choose_template", "Send messages"),
|
||||
("main.manage_users", "Team members"),
|
||||
],
|
||||
)
|
||||
@@ -423,9 +422,9 @@ def test_navigation_urls(
|
||||
mock_get_api_keys,
|
||||
):
|
||||
page = client_request.get("main.choose_template", service_id=SERVICE_ONE_ID)
|
||||
assert [a["href"] for a in page.select(".nav.margin-bottom-5 a")] == [
|
||||
"/services/{}".format(SERVICE_ONE_ID),
|
||||
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),
|
||||
|
||||
@@ -14,6 +14,8 @@ def _get_notifications_csv(
|
||||
template_name="foo",
|
||||
template_type="sms",
|
||||
job_name="bar.csv",
|
||||
carrier="ATT Mobility",
|
||||
provider_response="Did not like it",
|
||||
status="Delivered",
|
||||
created_at="1943-04-19 12:00:00",
|
||||
rows=1,
|
||||
@@ -47,6 +49,8 @@ def _get_notifications_csv(
|
||||
"template_type": template_type,
|
||||
"template": {"name": template_name, "template_type": template_type},
|
||||
"job_name": job_name,
|
||||
"carrier": carrier,
|
||||
"provider_response": provider_response,
|
||||
"status": status,
|
||||
"created_at": created_at,
|
||||
"updated_at": None,
|
||||
@@ -82,15 +86,15 @@ def get_notifications_csv_mock(
|
||||
(
|
||||
None,
|
||||
[
|
||||
"Recipient,Template,Type,Sent by,Job,Status,Time\n",
|
||||
"foo@bar.com,foo,sms,,,Delivered,1943-04-19 12:00:00\r\n",
|
||||
"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 12:00:00\r\n",
|
||||
],
|
||||
),
|
||||
(
|
||||
"Anne Example",
|
||||
[
|
||||
"Recipient,Template,Type,Sent by,Job,Status,Time\n",
|
||||
"foo@bar.com,foo,sms,Anne Example,,Delivered,1943-04-19 12:00:00\r\n",
|
||||
"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 12:00:00\r\n",
|
||||
],
|
||||
),
|
||||
],
|
||||
@@ -128,6 +132,8 @@ def test_generate_notifications_csv_without_job(
|
||||
"Type",
|
||||
"Sent by",
|
||||
"Job",
|
||||
"Carrier",
|
||||
"Carrier Response",
|
||||
"Status",
|
||||
"Time",
|
||||
],
|
||||
@@ -138,6 +144,8 @@ def test_generate_notifications_csv_without_job(
|
||||
"sms",
|
||||
"Fake Person",
|
||||
"bar.csv",
|
||||
"ATT Mobility",
|
||||
"Did not like it",
|
||||
"Delivered",
|
||||
"1943-04-19 12:00:00",
|
||||
],
|
||||
@@ -157,6 +165,8 @@ def test_generate_notifications_csv_without_job(
|
||||
"Type",
|
||||
"Sent by",
|
||||
"Job",
|
||||
"Carrier",
|
||||
"Carrier Response",
|
||||
"Status",
|
||||
"Time",
|
||||
],
|
||||
@@ -170,6 +180,8 @@ def test_generate_notifications_csv_without_job(
|
||||
"sms",
|
||||
"Fake Person",
|
||||
"bar.csv",
|
||||
"ATT Mobility",
|
||||
"Did not like it",
|
||||
"Delivered",
|
||||
"1943-04-19 12:00:00",
|
||||
],
|
||||
@@ -189,6 +201,8 @@ def test_generate_notifications_csv_without_job(
|
||||
"Type",
|
||||
"Sent by",
|
||||
"Job",
|
||||
"Carrier",
|
||||
"Carrier Response",
|
||||
"Status",
|
||||
"Time",
|
||||
],
|
||||
@@ -202,6 +216,8 @@ def test_generate_notifications_csv_without_job(
|
||||
"sms",
|
||||
"Fake Person",
|
||||
"bar.csv",
|
||||
"ATT Mobility",
|
||||
"Did not like it",
|
||||
"Delivered",
|
||||
"1943-04-19 12:00:00",
|
||||
],
|
||||
|
||||
@@ -5,9 +5,9 @@ from werkzeug.exceptions import Forbidden
|
||||
from app.utils.user import user_has_permissions
|
||||
|
||||
|
||||
@pytest.mark.parametrize( # noqa: PT007 # Ignoring wrong values type because of the list unpacking in the test.
|
||||
@pytest.mark.parametrize(
|
||||
"permissions",
|
||||
(
|
||||
[
|
||||
[
|
||||
# Route has one of the permissions which the user has
|
||||
"manage_service"
|
||||
@@ -25,7 +25,7 @@ from app.utils.user import user_has_permissions
|
||||
[
|
||||
# Route has no specific permissions required
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_permissions(
|
||||
client_request,
|
||||
@@ -48,14 +48,14 @@ def test_permissions(
|
||||
index()
|
||||
|
||||
|
||||
@pytest.mark.parametrize( # noqa: PT007 # Ignoring wrong values type because of the list unpacking in the test.
|
||||
@pytest.mark.parametrize(
|
||||
"permissions",
|
||||
(
|
||||
[
|
||||
[
|
||||
# Route has a permission which the user doesn’t have
|
||||
"send_messages"
|
||||
],
|
||||
),
|
||||
],
|
||||
)
|
||||
def test_permissions_forbidden(
|
||||
client_request,
|
||||
|
||||
+8
-1
@@ -2423,8 +2423,15 @@ def _os_environ():
|
||||
os.environ[k] = v
|
||||
|
||||
|
||||
@pytest.fixture # noqa (C901 too complex)
|
||||
@pytest.fixture() # noqa (C901 too complex)
|
||||
def client_request(logged_in_client, mocker, service_one): # noqa (C901 too complex)
|
||||
def _get(mocker):
|
||||
return {"count": 0}
|
||||
|
||||
mocker.patch(
|
||||
"app.service_api_client.get_global_notification_count", side_effect=_get
|
||||
)
|
||||
|
||||
class ClientRequest:
|
||||
@staticmethod
|
||||
@contextmanager
|
||||
|
||||
Reference in New Issue
Block a user