mirror of
https://github.com/GSA/notifications-admin.git
synced 2026-09-06 14:58:25 -04:00
@@ -96,6 +96,7 @@ class Development(Config):
|
|||||||
|
|
||||||
class Test(Development):
|
class Test(Development):
|
||||||
DEBUG = True
|
DEBUG = True
|
||||||
|
TESTING = True
|
||||||
STATSD_ENABLED = True
|
STATSD_ENABLED = True
|
||||||
WTF_CSRF_ENABLED = False
|
WTF_CSRF_ENABLED = False
|
||||||
CSV_UPLOAD_BUCKET_NAME = 'test-notifications-csv-upload'
|
CSV_UPLOAD_BUCKET_NAME = 'test-notifications-csv-upload'
|
||||||
|
|||||||
@@ -29,4 +29,5 @@ from app.main.views import (
|
|||||||
platform_admin,
|
platform_admin,
|
||||||
letter_jobs,
|
letter_jobs,
|
||||||
conversation,
|
conversation,
|
||||||
|
notifications
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,8 +1,5 @@
|
|||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
import ago
|
|
||||||
import dateutil
|
|
||||||
from orderedset import OrderedSet
|
from orderedset import OrderedSet
|
||||||
from datetime import datetime, timedelta, timezone
|
|
||||||
from itertools import chain
|
from itertools import chain
|
||||||
|
|
||||||
from flask import (
|
from flask import (
|
||||||
@@ -35,6 +32,7 @@ from app.utils import (
|
|||||||
generate_notifications_csv,
|
generate_notifications_csv,
|
||||||
get_help_argument,
|
get_help_argument,
|
||||||
get_template,
|
get_template,
|
||||||
|
get_time_left,
|
||||||
REQUESTED_STATUSES,
|
REQUESTED_STATUSES,
|
||||||
FAILURE_STATUSES,
|
FAILURE_STATUSES,
|
||||||
SENDING_STATUSES,
|
SENDING_STATUSES,
|
||||||
@@ -364,7 +362,7 @@ def get_job_partials(job):
|
|||||||
)
|
)
|
||||||
return {
|
return {
|
||||||
'counts': render_template(
|
'counts': render_template(
|
||||||
'partials/jobs/count.html',
|
'partials/count.html',
|
||||||
counts=_get_job_counts(job, request.args.get('help', 0)),
|
counts=_get_job_counts(job, request.args.get('help', 0)),
|
||||||
status=filter_args['status']
|
status=filter_args['status']
|
||||||
),
|
),
|
||||||
@@ -388,16 +386,3 @@ def get_job_partials(job):
|
|||||||
job=job
|
job=job
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def get_time_left(job_created_at):
|
|
||||||
return ago.human(
|
|
||||||
(
|
|
||||||
datetime.now(timezone.utc).replace(hour=23, minute=59, second=59)
|
|
||||||
) - (
|
|
||||||
dateutil.parser.parse(job_created_at) + timedelta(days=8)
|
|
||||||
),
|
|
||||||
future_tense='Data available for {}',
|
|
||||||
past_tense='Data no longer available', # No-one should ever see this
|
|
||||||
precision=1
|
|
||||||
)
|
|
||||||
|
|||||||
137
app/main/views/notifications.py
Normal file
137
app/main/views/notifications.py
Normal file
@@ -0,0 +1,137 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
from flask import (
|
||||||
|
render_template,
|
||||||
|
jsonify,
|
||||||
|
request,
|
||||||
|
url_for,
|
||||||
|
current_app
|
||||||
|
)
|
||||||
|
from flask_login import login_required
|
||||||
|
|
||||||
|
from app import (
|
||||||
|
notification_api_client,
|
||||||
|
current_service
|
||||||
|
)
|
||||||
|
from app.main import main
|
||||||
|
from app.utils import (
|
||||||
|
user_has_permissions,
|
||||||
|
get_help_argument,
|
||||||
|
get_template,
|
||||||
|
get_time_left,
|
||||||
|
REQUESTED_STATUSES,
|
||||||
|
FAILURE_STATUSES,
|
||||||
|
SENDING_STATUSES,
|
||||||
|
DELIVERED_STATUSES,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def get_status_arg(filter_args):
|
||||||
|
if 'status' not in filter_args or not filter_args['status']:
|
||||||
|
return REQUESTED_STATUSES
|
||||||
|
elif filter_args['status'] == 'sending':
|
||||||
|
return SENDING_STATUSES
|
||||||
|
elif filter_args['status'] == 'delivered':
|
||||||
|
return DELIVERED_STATUSES
|
||||||
|
elif filter_args['status'] == 'failed':
|
||||||
|
return FAILURE_STATUSES
|
||||||
|
else:
|
||||||
|
current_app.logger.info('Unrecognised status filter: {}'.format(filter_args['status']))
|
||||||
|
return REQUESTED_STATUSES
|
||||||
|
|
||||||
|
|
||||||
|
@main.route("/services/<service_id>/one-off-notification/<notification_id>")
|
||||||
|
@login_required
|
||||||
|
@user_has_permissions('view_activity', admin_override=True)
|
||||||
|
def view_notification(service_id, notification_id):
|
||||||
|
notification = notification_api_client.get_notification(service_id, notification_id)
|
||||||
|
return render_template(
|
||||||
|
'views/notifications/notification.html',
|
||||||
|
finished=(notification['status'] in (DELIVERED_STATUSES + FAILURE_STATUSES)),
|
||||||
|
uploaded_file_name='Report',
|
||||||
|
template=get_template(
|
||||||
|
notification['template'],
|
||||||
|
current_service,
|
||||||
|
letter_preview_url=url_for(
|
||||||
|
'.view_template_version_preview',
|
||||||
|
service_id=service_id,
|
||||||
|
template_id=notification['template']['id'],
|
||||||
|
version=notification['template_version'],
|
||||||
|
filetype='png',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
status=request.args.get('status'),
|
||||||
|
updates_url=url_for(
|
||||||
|
".view_notification_updates",
|
||||||
|
service_id=service_id,
|
||||||
|
notification_id=notification['id'],
|
||||||
|
status=request.args.get('status'),
|
||||||
|
help=get_help_argument()
|
||||||
|
),
|
||||||
|
partials=get_single_notification_partials(notification),
|
||||||
|
help=get_help_argument()
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@main.route("/services/<service_id>/one-off-notification/<notification_id>.json")
|
||||||
|
@user_has_permissions('view_activity', admin_override=True)
|
||||||
|
def view_notification_updates(service_id, notification_id):
|
||||||
|
return jsonify(**get_single_notification_partials(
|
||||||
|
notification_api_client.get_notification(service_id, notification_id)
|
||||||
|
))
|
||||||
|
|
||||||
|
|
||||||
|
def _get_single_notification_counts(notification, help_argument):
|
||||||
|
return [
|
||||||
|
(
|
||||||
|
label,
|
||||||
|
query_param,
|
||||||
|
url_for(
|
||||||
|
".view_notification",
|
||||||
|
service_id=notification['service'],
|
||||||
|
notification_id=notification['id'],
|
||||||
|
status=query_param,
|
||||||
|
help=help_argument
|
||||||
|
),
|
||||||
|
count
|
||||||
|
) for label, query_param, count in [
|
||||||
|
[
|
||||||
|
'total', '',
|
||||||
|
1
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'sending', 'sending',
|
||||||
|
int(notification['status'] in SENDING_STATUSES)
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'delivered', 'delivered',
|
||||||
|
int(notification['status'] in DELIVERED_STATUSES)
|
||||||
|
],
|
||||||
|
[
|
||||||
|
'failed', 'failed',
|
||||||
|
int(notification['status'] in FAILURE_STATUSES)
|
||||||
|
]
|
||||||
|
]
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def get_single_notification_partials(notification):
|
||||||
|
status_args = get_status_arg(request.args)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'counts': render_template(
|
||||||
|
'partials/count.html',
|
||||||
|
counts=_get_single_notification_counts(notification, request.args.get('help', 0)),
|
||||||
|
status=status_args
|
||||||
|
),
|
||||||
|
'notifications': render_template(
|
||||||
|
'partials/notifications/notifications.html',
|
||||||
|
notification=notification,
|
||||||
|
more_than_one_page=False,
|
||||||
|
percentage_complete=100,
|
||||||
|
time_left=get_time_left(notification['created_at']),
|
||||||
|
),
|
||||||
|
'status': render_template(
|
||||||
|
'partials/notifications/status.html',
|
||||||
|
notification=notification
|
||||||
|
),
|
||||||
|
}
|
||||||
@@ -56,6 +56,4 @@ class NotificationApiClient(NotifyAdminAPIClient):
|
|||||||
)
|
)
|
||||||
|
|
||||||
def get_notification(self, service_id, notification_id):
|
def get_notification(self, service_id, notification_id):
|
||||||
return self.get(
|
return self.get(url='/service/{}/notifications/{}'.format(service_id, notification_id))
|
||||||
url='/service/{}/notifications/{}'.format(service_id, notification_id)
|
|
||||||
)
|
|
||||||
|
|||||||
31
app/templates/partials/notifications/notifications.html
Normal file
31
app/templates/partials/notifications/notifications.html
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
{% from "components/table.html" import list_table, field, right_aligned_field_heading, row_heading, notification_status_field %}
|
||||||
|
{% from "components/page-footer.html" import page_footer %}
|
||||||
|
|
||||||
|
<div class="ajax-block-container" aria-labelledby='pill-selected-item'>
|
||||||
|
<div class="dashboard-table bottom-gutter-3-2">
|
||||||
|
|
||||||
|
{% call(item, row_number) list_table(
|
||||||
|
[notification],
|
||||||
|
caption=None,
|
||||||
|
caption_visible=False,
|
||||||
|
empty_message=None,
|
||||||
|
field_headings=[
|
||||||
|
'Recipient',
|
||||||
|
'Status'
|
||||||
|
],
|
||||||
|
field_headings_visible=False
|
||||||
|
) %}
|
||||||
|
{% call row_heading() %}
|
||||||
|
<p>{{ item.to }}</p>
|
||||||
|
{% endcall %}
|
||||||
|
{{ notification_status_field(item) }}
|
||||||
|
{% endcall %}
|
||||||
|
|
||||||
|
{% if more_than_one_page %}
|
||||||
|
<p class="table-show-more-link">
|
||||||
|
Only showing the first 50 rows
|
||||||
|
</p>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
6
app/templates/partials/notifications/status.html
Normal file
6
app/templates/partials/notifications/status.html
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<div class="ajax-block-container">
|
||||||
|
<p class='heading-small bottom-gutter'>
|
||||||
|
Sent {% if notification.created_by %}by {{ notification.created_by.name }} {% endif %}
|
||||||
|
on {{ notification.created_at|format_datetime_short }}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
27
app/templates/views/notifications/notification.html
Normal file
27
app/templates/views/notifications/notification.html
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
{% extends "withnav_template.html" %}
|
||||||
|
{% from "components/banner.html" import banner %}
|
||||||
|
{% from "components/ajax-block.html" import ajax_block %}
|
||||||
|
{% from "components/page-footer.html" import page_footer %}
|
||||||
|
|
||||||
|
{% block service_page_title %}
|
||||||
|
Report
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block maincolumn_content %}
|
||||||
|
|
||||||
|
<h1 class="heading-large">
|
||||||
|
Report
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
{{ template|string }}
|
||||||
|
|
||||||
|
{{ ajax_block(partials, updates_url, 'status', finished=finished) }}
|
||||||
|
{{ ajax_block(partials, updates_url, 'counts', finished=finished) }}
|
||||||
|
{{ ajax_block(partials, updates_url, 'notifications', finished=finished) }}
|
||||||
|
|
||||||
|
{{ page_footer(
|
||||||
|
secondary_link=url_for('.view_template', service_id=current_service.id, template_id=template.id),
|
||||||
|
secondary_link_text='Back to {}'.format(template.name)
|
||||||
|
) }}
|
||||||
|
|
||||||
|
{% endblock %}
|
||||||
33
app/utils.py
33
app/utils.py
@@ -1,11 +1,13 @@
|
|||||||
import re
|
import re
|
||||||
import csv
|
import csv
|
||||||
from io import StringIO, BytesIO
|
from io import StringIO
|
||||||
from os import path
|
from os import path
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
import unicodedata
|
import unicodedata
|
||||||
from datetime import datetime
|
from datetime import datetime, timedelta, timezone
|
||||||
|
|
||||||
|
import dateutil
|
||||||
|
import ago
|
||||||
from flask import (
|
from flask import (
|
||||||
abort,
|
abort,
|
||||||
current_app,
|
current_app,
|
||||||
@@ -15,6 +17,11 @@ from flask import (
|
|||||||
url_for
|
url_for
|
||||||
)
|
)
|
||||||
from flask_login import current_user
|
from flask_login import current_user
|
||||||
|
import pyexcel
|
||||||
|
import pyexcel.ext.io
|
||||||
|
import pyexcel.ext.xls
|
||||||
|
import pyexcel.ext.xlsx
|
||||||
|
import pyexcel.ext.ods3
|
||||||
|
|
||||||
from notifications_utils.template import (
|
from notifications_utils.template import (
|
||||||
SMSPreviewTemplate,
|
SMSPreviewTemplate,
|
||||||
@@ -23,12 +30,6 @@ from notifications_utils.template import (
|
|||||||
LetterPreviewTemplate,
|
LetterPreviewTemplate,
|
||||||
)
|
)
|
||||||
|
|
||||||
import pyexcel
|
|
||||||
import pyexcel.ext.io
|
|
||||||
import pyexcel.ext.xls
|
|
||||||
import pyexcel.ext.xlsx
|
|
||||||
import pyexcel.ext.ods3
|
|
||||||
|
|
||||||
|
|
||||||
SENDING_STATUSES = ['created', 'pending', 'sending']
|
SENDING_STATUSES = ['created', 'pending', 'sending']
|
||||||
DELIVERED_STATUSES = ['delivered', 'sent']
|
DELIVERED_STATUSES = ['delivered', 'sent']
|
||||||
@@ -144,13 +145,14 @@ def generate_notifications_csv(**kwargs):
|
|||||||
notification['status'],
|
notification['status'],
|
||||||
notification['created_at']
|
notification['created_at']
|
||||||
]
|
]
|
||||||
line = ','.join([str(i) for i in values]) + '\n'
|
line = ','.join(str(i) for i in values) + '\n'
|
||||||
yield line
|
yield line
|
||||||
|
|
||||||
if notifications_resp['links'].get('next'):
|
if notifications_resp['links'].get('next'):
|
||||||
kwargs['page'] += 1
|
kwargs['page'] += 1
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
|
raise Exception("Should never reach here")
|
||||||
|
|
||||||
|
|
||||||
def get_page_from_request():
|
def get_page_from_request():
|
||||||
@@ -301,3 +303,16 @@ def get_current_financial_year():
|
|||||||
current_month = int(now.strftime('%-m'))
|
current_month = int(now.strftime('%-m'))
|
||||||
current_year = int(now.strftime('%Y'))
|
current_year = int(now.strftime('%Y'))
|
||||||
return current_year if current_month > 3 else current_year - 1
|
return current_year if current_month > 3 else current_year - 1
|
||||||
|
|
||||||
|
|
||||||
|
def get_time_left(created_at):
|
||||||
|
return ago.human(
|
||||||
|
(
|
||||||
|
datetime.now(timezone.utc).replace(hour=23, minute=59, second=59)
|
||||||
|
) - (
|
||||||
|
dateutil.parser.parse(created_at) + timedelta(days=8)
|
||||||
|
),
|
||||||
|
future_tense='Data available for {}',
|
||||||
|
past_tense='Data no longer available', # No-one should ever see this
|
||||||
|
precision=1
|
||||||
|
)
|
||||||
|
|||||||
@@ -245,18 +245,16 @@ def notification_json(
|
|||||||
'notifications': [{
|
'notifications': [{
|
||||||
'id': uuid.uuid4(),
|
'id': uuid.uuid4(),
|
||||||
'to': to,
|
'to': to,
|
||||||
'template': {
|
|
||||||
'id': template['id'],
|
|
||||||
'name': template['name'],
|
|
||||||
'template_type': template['template_type'],
|
|
||||||
},
|
|
||||||
'body': template['content'],
|
'body': template['content'],
|
||||||
|
'template': template,
|
||||||
'job': job_payload,
|
'job': job_payload,
|
||||||
'sent_at': sent_at,
|
'sent_at': sent_at,
|
||||||
'status': status,
|
'status': status,
|
||||||
'created_at': created_at,
|
'created_at': created_at,
|
||||||
|
'created_by': None,
|
||||||
'updated_at': updated_at,
|
'updated_at': updated_at,
|
||||||
'job_row_number': job_row_number,
|
'job_row_number': job_row_number,
|
||||||
|
'service': service_id,
|
||||||
'template_version': template['version']
|
'template_version': template['version']
|
||||||
} for i in range(rows)],
|
} for i in range(rows)],
|
||||||
'total': rows,
|
'total': rows,
|
||||||
|
|||||||
78
tests/app/main/views/test_notifications.py
Normal file
78
tests/app/main/views/test_notifications.py
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
from freezegun import freeze_time
|
||||||
|
import pytest
|
||||||
|
from werkzeug.datastructures import MultiDict
|
||||||
|
|
||||||
|
from app.main.views.notifications import get_status_arg
|
||||||
|
from app.utils import (
|
||||||
|
REQUESTED_STATUSES,
|
||||||
|
FAILURE_STATUSES,
|
||||||
|
SENDING_STATUSES,
|
||||||
|
DELIVERED_STATUSES,
|
||||||
|
)
|
||||||
|
|
||||||
|
from tests.conftest import mock_get_notification
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('multidict_args, expected_statuses', [
|
||||||
|
([], REQUESTED_STATUSES),
|
||||||
|
([('status', '')], REQUESTED_STATUSES),
|
||||||
|
([('status', 'garbage')], REQUESTED_STATUSES),
|
||||||
|
([('status', 'sending')], SENDING_STATUSES),
|
||||||
|
([('status', 'delivered')], DELIVERED_STATUSES),
|
||||||
|
([('status', 'failed')], FAILURE_STATUSES),
|
||||||
|
])
|
||||||
|
def test_status_filters(mocker, multidict_args, expected_statuses):
|
||||||
|
mocker.patch('app.main.views.notifications.current_app')
|
||||||
|
|
||||||
|
args = MultiDict(multidict_args)
|
||||||
|
args['status'] = get_status_arg(args)
|
||||||
|
|
||||||
|
assert sorted(args['status']) == sorted(expected_statuses)
|
||||||
|
|
||||||
|
|
||||||
|
@freeze_time("2016-01-01 11:09:00.061258")
|
||||||
|
def test_notification_status_page_shows_details(
|
||||||
|
client_request,
|
||||||
|
mock_get_notification,
|
||||||
|
service_one,
|
||||||
|
fake_uuid,
|
||||||
|
):
|
||||||
|
page = client_request.get(
|
||||||
|
'main.view_notification',
|
||||||
|
service_id=service_one['id'],
|
||||||
|
notification_id=fake_uuid
|
||||||
|
)
|
||||||
|
|
||||||
|
assert page.find('div', {'class': 'sms-message-wrapper'}).text.strip() == 'service one: template content'
|
||||||
|
assert ' '.join(page.find('tbody').find('tr').text.split()) == '07123456789 Delivered 1 January at 11:10am'
|
||||||
|
|
||||||
|
mock_get_notification.assert_called_with(
|
||||||
|
service_one['id'],
|
||||||
|
fake_uuid
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('notification_status, expected_big_number_vals', [
|
||||||
|
('created', [1, 1, 0, 0]),
|
||||||
|
('sending', [1, 1, 0, 0]),
|
||||||
|
('delivered', [1, 0, 1, 0]),
|
||||||
|
('temporary-failure', [1, 0, 0, 1]),
|
||||||
|
])
|
||||||
|
def test_notification_status_page_shows_correct_numbers(
|
||||||
|
client_request,
|
||||||
|
mocker,
|
||||||
|
service_one,
|
||||||
|
fake_uuid,
|
||||||
|
notification_status,
|
||||||
|
expected_big_number_vals
|
||||||
|
):
|
||||||
|
mock_get_notification(mocker, fake_uuid, notification_status=notification_status)
|
||||||
|
|
||||||
|
page = client_request.get(
|
||||||
|
'main.view_notification',
|
||||||
|
service_id=service_one['id'],
|
||||||
|
notification_id=fake_uuid
|
||||||
|
)
|
||||||
|
|
||||||
|
big_numbers = page.find_all('div', {'class': 'big-number-number'})
|
||||||
|
assert expected_big_number_vals == [int(num.text.strip()) for num in big_numbers]
|
||||||
@@ -33,3 +33,11 @@ def test_client_gets_notifications_for_service_and_job_by_page(mocker, arguments
|
|||||||
mock_get = mocker.patch('app.notify_client.notification_api_client.NotificationApiClient.get')
|
mock_get = mocker.patch('app.notify_client.notification_api_client.NotificationApiClient.get')
|
||||||
NotificationApiClient().get_notifications_for_service('abcd1234', **arguments)
|
NotificationApiClient().get_notifications_for_service('abcd1234', **arguments)
|
||||||
mock_get.assert_called_once_with(**expected_call)
|
mock_get.assert_called_once_with(**expected_call)
|
||||||
|
|
||||||
|
|
||||||
|
def test_get_notification(mocker):
|
||||||
|
mock_get = mocker.patch('app.notify_client.notification_api_client.NotificationApiClient.get')
|
||||||
|
NotificationApiClient().get_notification('foo', 'bar')
|
||||||
|
mock_get.assert_called_once_with(
|
||||||
|
url='/service/foo/notifications/bar'
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
|
|
||||||
import os
|
import os
|
||||||
from datetime import date, datetime, timedelta
|
from datetime import date, datetime, timedelta
|
||||||
from unittest.mock import Mock
|
from unittest.mock import Mock
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from notifications_python_client.errors import HTTPError
|
from notifications_python_client.errors import HTTPError
|
||||||
|
from flask import url_for
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
|
||||||
from app import create_app
|
from app import create_app
|
||||||
from app.notify_client.models import (
|
from app.notify_client.models import (
|
||||||
@@ -1096,22 +1099,6 @@ def mock_get_notifications(mocker, api_user_active):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope='function')
|
|
||||||
def mock_get_notification(mocker, api_user_active):
|
|
||||||
def _get_notification(
|
|
||||||
service_id,
|
|
||||||
notification_id,
|
|
||||||
):
|
|
||||||
return single_notification_json(
|
|
||||||
service_id,
|
|
||||||
)
|
|
||||||
|
|
||||||
return mocker.patch(
|
|
||||||
'app.notification_api_client.get_notification',
|
|
||||||
side_effect=_get_notification
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope='function')
|
@pytest.fixture(scope='function')
|
||||||
def mock_get_notifications_with_previous_next(mocker):
|
def mock_get_notifications_with_previous_next(mocker):
|
||||||
def _get_notifications(service_id,
|
def _get_notifications(service_id,
|
||||||
@@ -1630,6 +1617,33 @@ def mock_reset_failed_login_count(mocker):
|
|||||||
return mocker.patch('app.user_api_client.reset_failed_login_count')
|
return mocker.patch('app.user_api_client.reset_failed_login_count')
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def mock_get_notification(mocker, fake_uuid, notification_status='delivered'):
|
||||||
|
def _get_notification(
|
||||||
|
service_id,
|
||||||
|
notification_id,
|
||||||
|
):
|
||||||
|
noti = notification_json(
|
||||||
|
service_id,
|
||||||
|
rows=1,
|
||||||
|
status=notification_status
|
||||||
|
)['notifications'][0]
|
||||||
|
|
||||||
|
noti['id'] = notification_id
|
||||||
|
noti['created_by'] = {
|
||||||
|
'id': fake_uuid,
|
||||||
|
'name': 'Test User',
|
||||||
|
'email_address': 'test@user.gov.uk'
|
||||||
|
}
|
||||||
|
noti['template'] = template_json(service_id, str(generate_uuid()))
|
||||||
|
return noti
|
||||||
|
|
||||||
|
return mocker.patch(
|
||||||
|
'app.notification_api_client.get_notification',
|
||||||
|
side_effect=_get_notification
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope='function')
|
@pytest.fixture(scope='function')
|
||||||
def client(app_):
|
def client(app_):
|
||||||
with app_.test_request_context(), app_.test_client() as client:
|
with app_.test_request_context(), app_.test_client() as client:
|
||||||
@@ -1671,3 +1685,29 @@ def os_environ():
|
|||||||
os.environ = {}
|
os.environ = {}
|
||||||
yield
|
yield
|
||||||
os.environ = old_env
|
os.environ = old_env
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def client_request(logged_in_client):
|
||||||
|
class ClientRequest:
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get(endpoint, _expected_status=200, _follow_redirects=False, **endpoint_kwargs):
|
||||||
|
resp = logged_in_client.get(
|
||||||
|
url_for(endpoint, **(endpoint_kwargs or {})),
|
||||||
|
follow_redirects=_follow_redirects,
|
||||||
|
)
|
||||||
|
assert resp.status_code == _expected_status
|
||||||
|
return BeautifulSoup(resp.data.decode('utf-8'), 'html.parser')
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def post(endpoint, _data=None, _expected_status=302, _follow_redirects=False, **endpoint_kwargs):
|
||||||
|
resp = logged_in_client.post(
|
||||||
|
url_for(endpoint, **(endpoint_kwargs or {})),
|
||||||
|
data=_data,
|
||||||
|
follow_redirects=_follow_redirects,
|
||||||
|
)
|
||||||
|
assert resp.status_code == _expected_status
|
||||||
|
return BeautifulSoup(resp.data.decode('utf-8'), 'html.parser')
|
||||||
|
|
||||||
|
return ClientRequest
|
||||||
|
|||||||
Reference in New Issue
Block a user