Merge pull request #3366 from alphagov/upload-contact-list

Let teams upload an emergency contact list for later use
This commit is contained in:
Chris Hill-Scott
2020-03-16 14:49:15 +00:00
committed by GitHub
26 changed files with 2019 additions and 38 deletions

View File

@@ -6,12 +6,14 @@ from glob import glob
from io import BytesIO
from itertools import repeat
from os import path
from unittest.mock import ANY
from uuid import uuid4
from zipfile import BadZipFile
import pytest
from bs4 import BeautifulSoup
from flask import url_for
from freezegun import freeze_time
from notifications_python_client.errors import HTTPError
from notifications_utils.recipients import RecipientCSV
from notifications_utils.template import (
@@ -36,6 +38,7 @@ from tests.conftest import (
create_active_user_with_permissions,
create_multiple_email_reply_to_addresses,
create_multiple_sms_senders,
create_platform_admin_user,
create_template,
mock_get_service_email_template,
mock_get_service_letter_template,
@@ -1308,16 +1311,17 @@ def test_send_one_off_has_skip_link(
_follow_redirects=True,
)
skip_links = page.select('a.top-gutter-4-3')
skip_links = page.select('form a')
if expected_link_text and expected_link_url:
assert skip_links[0].text.strip() == expected_link_text
assert skip_links[0]['href'] == expected_link_url(
assert skip_links[1].text.strip() == expected_link_text
assert skip_links[1]['href'] == expected_link_url(
service_id=service_one['id'],
template_id=fake_uuid,
)
else:
assert not skip_links
with pytest.raises(IndexError):
skip_links[1]
@pytest.mark.parametrize('template_type, expected_sticky', [
@@ -1374,9 +1378,9 @@ def test_skip_link_will_not_show_on_sms_one_off_if_service_has_no_mobile_number(
assert not skip_links
@pytest.mark.parametrize('user, link_index', (
(create_active_user_with_permissions(), 2),
(create_active_caseworking_user(), 1),
@pytest.mark.parametrize('user', (
create_active_user_with_permissions(),
create_active_caseworking_user(),
))
def test_send_one_off_offers_link_to_upload(
client_request,
@@ -1384,7 +1388,6 @@ def test_send_one_off_offers_link_to_upload(
mock_get_service_template,
mock_has_jobs,
user,
link_index,
):
client_request.login(user)
@@ -1396,7 +1399,7 @@ def test_send_one_off_offers_link_to_upload(
)
back_link = page.select('main a')[0]
link = page.select('main a')[link_index]
link = page.select_one('form a')
assert back_link.text.strip() == 'Back'
@@ -1408,6 +1411,87 @@ def test_send_one_off_offers_link_to_upload(
)
@pytest.mark.parametrize('user', (
pytest.param(
create_platform_admin_user(),
),
pytest.param(
create_active_user_with_permissions(),
marks=pytest.mark.xfail(raises=AssertionError),
),
))
def test_platform_admin_has_link_to_use_existing_list(
client_request,
mock_get_service_template,
mock_has_jobs,
mock_get_contact_lists,
fake_uuid,
user,
):
client_request.login(user)
page = client_request.get(
'main.send_one_off',
service_id=SERVICE_ONE_ID,
template_id=fake_uuid,
_follow_redirects=True,
)
assert [
(link.text, link['href']) for link in page.select('form a')
] == [
(
'Upload a list of phone numbers',
url_for(
'main.send_messages',
service_id=SERVICE_ONE_ID,
template_id=fake_uuid,
),
),
(
'Use a saved list',
url_for(
'main.choose_from_contact_list',
service_id=SERVICE_ONE_ID,
template_id=fake_uuid,
),
),
(
'Use my phone number',
url_for(
'main.send_test',
service_id=SERVICE_ONE_ID,
template_id=fake_uuid,
),
),
]
def test_no_link_to_use_existing_list_for_service_without_lists(
mocker,
client_request,
mock_get_service_template,
mock_has_jobs,
fake_uuid,
):
mocker.patch(
'app.models.contact_list.ContactLists.client_method',
return_value=[],
)
client_request.login(create_platform_admin_user())
page = client_request.get(
'main.send_one_off',
service_id=SERVICE_ONE_ID,
template_id=fake_uuid,
_follow_redirects=True,
)
assert [
link.text for link in page.select('form a')
] == [
'Upload a list of phone numbers',
'Use my phone number',
]
@pytest.mark.parametrize('user', (
create_active_user_with_permissions(),
create_active_caseworking_user(),
@@ -3780,3 +3864,163 @@ def test_redirects_to_template_if_job_exists_already(
_external=True,
)
)
@pytest.mark.parametrize((
'template_type, '
'expected_list_id, '
'expected_filename, '
'expected_time, '
'expected_count'
), (
(
'email',
'6ce466d0-fd6a-11e5-82f5-e0accb9d11a6',
'EmergencyContactList.xls',
'Uploaded today at 10:59am',
'100 email addresses',
),
(
'sms',
'd7b0bd1a-d1c7-4621-be5c-3c1b4278a2ad',
'phone number list.csv',
'Uploaded today at 1:00pm',
'123 phone numbers',
),
))
@freeze_time('2020-03-13 13:00')
def test_choose_from_contact_list(
mocker,
client_request,
mock_get_contact_lists,
fake_uuid,
template_type,
expected_list_id,
expected_filename,
expected_time,
expected_count,
):
template = create_template(template_type=template_type)
mocker.patch(
'app.service_api_client.get_service_template',
return_value={'data': template},
)
page = client_request.get(
'main.choose_from_contact_list',
service_id=SERVICE_ONE_ID,
template_id=fake_uuid,
)
assert len(page.select('.file-list-filename-large')) == 1
assert normalize_spaces(page.select_one('.file-list-filename-large').text) == (
expected_filename
)
assert page.select_one('a.file-list-filename-large')['href'] == url_for(
'main.send_from_contact_list',
service_id=SERVICE_ONE_ID,
template_id=template['id'],
contact_list_id=expected_list_id,
)
assert normalize_spaces(page.select_one('.file-list-hint-large').text) == (
expected_time
)
assert normalize_spaces(page.select_one('.big-number-smallest').text) == (
expected_count
)
def test_choose_from_contact_list_with_personalised_template(
mocker,
client_request,
mock_get_contact_lists,
fake_uuid,
):
template = create_template(
content="Hey ((name)) ((thing)) is happening"
)
mocker.patch(
'app.service_api_client.get_service_template',
return_value={'data': template},
)
page = client_request.get(
'main.choose_from_contact_list',
service_id=SERVICE_ONE_ID,
template_id=fake_uuid,
)
assert [
normalize_spaces(p.text) for p in page.select('main p')
] == [
'You cannot use a saved contact list with this template because '
'it is personalised with ((name)) and ((thing)).',
'Saved contact lists can only store email addresses or phone numbers.',
]
assert not page.select('table')
def test_choose_from_contact_list_with_no_lists(
mocker,
client_request,
mock_get_service_template,
fake_uuid,
):
mocker.patch(
'app.models.contact_list.ContactLists.client_method',
return_value=[],
)
page = client_request.get(
'main.choose_from_contact_list',
service_id=SERVICE_ONE_ID,
template_id=fake_uuid,
)
assert [
normalize_spaces(p.text) for p in page.select('main p')
] == [
'You have not saved any lists of phone numbers yet.',
'To upload and save a new contact list, go to the uploads page.',
]
assert page.select_one('main p a')['href'] == url_for(
'main.uploads',
service_id=SERVICE_ONE_ID,
)
assert not page.select('table')
def test_send_from_contact_list(
mocker,
client_request,
fake_uuid,
mock_get_contact_lists,
):
new_uuid = uuid.uuid4()
mock_download = mocker.patch('app.models.contact_list.s3download', return_value='contents')
mock_get_metadata = mocker.patch('app.models.contact_list.get_csv_metadata', return_value={
'example_key': 'example value',
})
mock_upload = mocker.patch('app.models.contact_list.s3upload', return_value=new_uuid)
mock_set_metadata = mocker.patch('app.models.contact_list.set_metadata_on_csv_upload')
client_request.get(
'main.send_from_contact_list',
service_id=SERVICE_ONE_ID,
template_id=fake_uuid,
contact_list_id=fake_uuid,
_expected_status=302,
_expected_redirect=url_for(
'main.check_messages',
service_id=SERVICE_ONE_ID,
template_id=fake_uuid,
upload_id=new_uuid,
original_file_name='EmergencyContactList.xls',
_external=True,
)
)
mock_download.assert_called_once_with(
SERVICE_ONE_ID, fake_uuid, bucket='test-contact-list'
)
mock_get_metadata.assert_called_once_with(
SERVICE_ONE_ID, fake_uuid, bucket='test-contact-list'
)
mock_upload.assert_called_once_with(
SERVICE_ONE_ID, {'data': 'contents'}, ANY
)
mock_set_metadata.assert_called_once_with(
SERVICE_ONE_ID, new_uuid, example_key='example value'
)

View File

@@ -1,6 +1,8 @@
import re
import urllib
from unittest.mock import Mock
import uuid
from io import BytesIO
from unittest.mock import ANY, Mock
import pytest
from flask import make_response, url_for
@@ -13,6 +15,7 @@ from tests.conftest import (
SERVICE_ONE_ID,
create_active_caseworking_user,
create_active_user_with_permissions,
create_platform_admin_user,
)
@@ -30,6 +33,7 @@ def test_no_upload_letters_button_without_permission(
service_one,
mock_get_uploads,
mock_get_jobs,
mock_get_no_contact_lists,
extra_permissions,
):
service_one['permissions'] += extra_permissions
@@ -37,6 +41,31 @@ def test_no_upload_letters_button_without_permission(
assert not page.find('a', text=re.compile('Upload a letter'))
@pytest.mark.parametrize('user', (
pytest.param(
create_platform_admin_user(),
),
pytest.param(
create_active_user_with_permissions(),
marks=pytest.mark.xfail(raises=AssertionError),
),
))
def test_platform_admin_has_upload_contact_list(
client_request,
mock_get_uploads,
mock_get_jobs,
mock_get_no_contact_lists,
user,
):
client_request.login(user)
page = client_request.get('main.uploads', service_id=SERVICE_ONE_ID)
button = page.find('a', text=re.compile('Upload an emergency contact list'))
assert button
assert button['href'] == url_for(
'main.upload_contact_list', service_id=SERVICE_ONE_ID,
)
@pytest.mark.parametrize('extra_permissions, expected_empty_message', (
(['letter'], (
'You have not uploaded any files recently. '
@@ -53,6 +82,7 @@ def test_get_upload_hub_with_no_uploads(
client_request,
service_one,
mock_get_no_uploads,
mock_get_no_contact_lists,
extra_permissions,
expected_empty_message,
):
@@ -70,6 +100,7 @@ def test_get_upload_hub_page(
client_request,
service_one,
mock_get_uploads,
mock_get_no_contact_lists,
):
mocker.patch('app.job_api_client.get_jobs', return_value={'data': []})
service_one['permissions'] += ['letter', 'upload_letters']
@@ -97,7 +128,7 @@ def test_get_upload_hub_page(
'123 Example Street'
)
assert normalize_spaces(str(uploads[1].select_one('.govuk-body'))) == (
'<p class="govuk-body govuk-!-margin-bottom-1"> '
'<p class="govuk-body letter-recipient-summary"> '
'Firstname Lastname<br/> '
'123 Example Street<br/> '
'</p>'
@@ -656,6 +687,7 @@ def test_uploads_page_shows_scheduled_jobs(
client_request,
mock_get_no_uploads,
mock_get_jobs,
mock_get_no_contact_lists,
user,
):
client_request.login(user)
@@ -681,11 +713,56 @@ def test_uploads_page_shows_scheduled_jobs(
assert not page.select('.table-empty-message')
@freeze_time('2020-03-15')
def test_uploads_page_shows_contact_lists_first(
mocker,
client_request,
mock_get_no_uploads,
mock_get_jobs,
mock_get_contact_lists,
):
page = client_request.get('main.uploads', service_id=SERVICE_ONE_ID)
assert [
normalize_spaces(row.text) for row in page.select('tr')
] == [
(
'File Status'
),
(
'phone number list.csv '
'Uploaded 13 March at 1:00pm '
'123 saved phone numbers'
),
(
'EmergencyContactList.xls '
'Uploaded 13 March at 10:59am '
'100 saved email addresses'
),
(
'even_later.csv '
'Sending 1 January 2016 at 11:09pm '
'1 text message waiting to send'
),
(
'send_me_later.csv '
'Sending 1 January 2016 at 11:09am '
'1 text message waiting to send'
),
]
assert page.select_one('.file-list-filename-large')['href'] == url_for(
'main.contact_list',
service_id=SERVICE_ONE_ID,
contact_list_id='d7b0bd1a-d1c7-4621-be5c-3c1b4278a2ad',
)
def test_get_uploads_shows_pagination(
client_request,
active_user_with_permissions,
mock_get_jobs,
mock_get_uploads,
mock_get_no_contact_lists,
):
page = client_request.get('main.uploads', service_id=SERVICE_ONE_ID)
@@ -697,3 +774,493 @@ def test_get_uploads_shows_pagination(
'Previous page '
'page 0'
)
def test_upload_contact_list_page(client_request):
page = client_request.get(
'main.upload_contact_list',
service_id=SERVICE_ONE_ID,
)
assert 'action' not in page.select_one('form')
assert page.select_one('form input')['name'] == 'file'
assert page.select_one('form input')['type'] == 'file'
assert normalize_spaces(page.select('.spreadsheet')[0].text) == (
'Example A '
'1 email address '
'2 test@example.gov.uk'
)
assert normalize_spaces(page.select('.spreadsheet')[1].text) == (
'Example A '
'1 phone number '
'2 07700 900123'
)
@pytest.mark.parametrize('file_contents, expected_error, expected_thead, expected_tbody,', [
(
"""
telephone,name
+447700900986
""",
(
'Your file has too many columns '
'It needs to have 1 column, called email address or phone number. '
'Right now it has 2 columns called telephone and name. '
'Skip to file contents'
),
'Row in file 1 telephone name',
'2 +447700900986',
),
(
"""
phone number, email address
+447700900986, test@example.com
""",
(
'Your file has too many columns '
'It needs to have 1 column, called email address or phone number. '
'Right now it has 2 columns called phone number and email address. '
'Skip to file contents'
),
'Row in file 1 phone number email address',
'2 +447700900986 test@example.com',
),
(
"""
email address
+447700900986
""",
(
'Theres a problem with invalid.csv '
'You need to fix 1 email address. '
'Skip to file contents'
),
'Row in file 1 email address',
'2 Not a valid email address +447700900986',
),
(
"""
phone number
test@example.com
""",
(
'Theres a problem with invalid.csv '
'You need to fix 1 phone number. '
'Skip to file contents'
),
'Row in file 1 phone number',
'2 Must not contain letters or symbols test@example.com',
),
(
"""
phone number, phone number, PHONE_NUMBER
+447700900111,+447700900222,+447700900333,
""",
(
'Your file has too many columns '
'It needs to have 1 column, called email address or phone number. '
'Right now it has 3 columns called phone number, phone number and PHONE_NUMBER. '
'Skip to file contents'
),
'Row in file 1 phone number phone number PHONE_NUMBER',
'2 +447700900333 +447700900333 +447700900333',
),
(
"""
phone number
""",
(
'Your file is missing some rows '
'It needs at least one row of data. '
'Skip to file contents'
),
'Row in file 1 phone number',
'',
),
(
"+447700900986",
(
'Your file is missing some rows '
'It needs at least one row of data, in a column called '
'email address or phone number. '
'Skip to file contents'
),
'Row in file 1 +447700900986',
'',
),
(
"",
(
'Your file is missing some rows '
'It needs at least one row of data, in a column called '
'email address or phone number. '
'Skip to file contents'
),
'Row in file 1',
'',
),
(
"""
phone number
+447700900986
+447700900986
""",
(
'Theres a problem with invalid.csv '
'You need to enter missing data in 1 row. '
'Skip to file contents'
),
'Row in file 1 phone number',
(
'3 Missing'
)
),
(
"""
phone number
+447700900
""",
(
'Theres a problem with invalid.csv '
'You need to fix 1 phone number. '
'Skip to file contents'
),
'Row in file 1 phone number',
'2 Not enough digits +447700900',
),
(
"""
email address
ok@example.com
bad@example1
bad@example2
""",
(
'Theres a problem with invalid.csv '
'You need to fix 2 email addresses. '
'Skip to file contents'
),
'Row in file 1 email address',
(
'3 Not a valid email address bad@example1 '
'4 Not a valid email address bad@example2'
),
),
])
def test_upload_csv_file_shows_error_banner(
client_request,
mocker,
mock_s3_upload,
mock_get_job_doesnt_exist,
mock_get_users_by_service,
fake_uuid,
file_contents,
expected_error,
expected_thead,
expected_tbody,
):
mock_upload = mocker.patch(
'app.models.contact_list.s3upload',
return_value=fake_uuid,
)
mock_download = mocker.patch(
'app.models.contact_list.s3download',
return_value=file_contents,
)
page = client_request.post(
'main.upload_contact_list',
service_id=SERVICE_ONE_ID,
_data={'file': (BytesIO(''.encode('utf-8')), 'invalid.csv')},
_follow_redirects=True,
)
mock_upload.assert_called_once_with(
SERVICE_ONE_ID,
{'data': '', 'file_name': 'invalid.csv'},
ANY,
bucket='test-contact-list',
)
mock_download.assert_called_once_with(
SERVICE_ONE_ID,
fake_uuid,
bucket='test-contact-list',
)
assert normalize_spaces(page.select_one('.banner-dangerous').text) == expected_error
assert page.select_one('form')['action'] == url_for(
'main.upload_contact_list',
service_id=SERVICE_ONE_ID,
)
assert page.select_one('form input')['type'] == 'file'
assert normalize_spaces(page.select_one('thead').text) == expected_thead
assert normalize_spaces(page.select_one('tbody').text) == expected_tbody
def test_upload_csv_file_shows_error_banner_for_too_many_rows(
client_request,
mocker,
mock_s3_upload,
mock_get_job_doesnt_exist,
mock_get_users_by_service,
fake_uuid,
):
mocker.patch('app.models.contact_list.s3upload', return_value=fake_uuid)
mocker.patch('app.models.contact_list.s3download', return_value='\n'.join(
['phone number'] + (['07700900986'] * 50001)
))
page = client_request.post(
'main.upload_contact_list',
service_id=SERVICE_ONE_ID,
_data={'file': (BytesIO(''.encode('utf-8')), 'invalid.csv')},
_follow_redirects=True,
)
assert normalize_spaces(page.select_one('.banner-dangerous').text) == (
'Your file has too many rows '
'Notify can store files up to 50,000 rows in size. '
'Your file has 50,001 rows. '
'Skip to file contents'
)
assert len(page.select('tbody tr')) == 50
assert normalize_spaces(page.select_one('.table-show-more-link').text) == (
'Only showing the first 50 rows'
)
def test_upload_csv_shows_trial_mode_error(
client_request,
mock_get_users_by_service,
mock_get_job_doesnt_exist,
fake_uuid,
mocker
):
mocker.patch('app.models.contact_list.s3upload', return_value=fake_uuid)
mocker.patch('app.models.contact_list.s3download', return_value=(
'phone number\n'
'07900900321' # Not in team
))
page = client_request.get(
'main.check_contact_list',
service_id=SERVICE_ONE_ID,
upload_id=fake_uuid,
_test_page_title=False,
)
assert normalize_spaces(page.select_one('.banner-dangerous').text) == (
'You cannot save this phone number '
'In trial mode you can only send to yourself and members of your team '
'Skip to file contents'
)
assert page.select_one('.banner-dangerous a')['href'] == url_for(
'main.trial_mode_new'
)
def test_upload_csv_shows_ok_page(
client_request,
mock_get_live_service,
mock_get_users_by_service,
mock_get_job_doesnt_exist,
fake_uuid,
mocker
):
mocker.patch('app.models.contact_list.s3download', return_value='\n'.join(
['email address'] + ['test@example.com'] * 51
))
mock_metadata_set = mocker.patch('app.models.contact_list.set_metadata_on_csv_upload')
page = client_request.get(
'main.check_contact_list',
service_id=SERVICE_ONE_ID,
upload_id=fake_uuid,
original_file_name='good times.xlsx',
_test_page_title=False,
)
mock_metadata_set.assert_called_once_with(
SERVICE_ONE_ID,
fake_uuid,
bucket='test-contact-list',
row_count=51,
original_file_name='good times.xlsx',
template_type='email',
valid=True,
)
assert normalize_spaces(page.select_one('h1').text) == (
'good times.xlsx'
)
assert normalize_spaces(page.select_one('main p').text) == (
'51 email addresses found'
)
assert page.select_one('form')['action'] == url_for(
'main.save_contact_list',
service_id=SERVICE_ONE_ID,
upload_id=fake_uuid,
)
assert normalize_spaces(page.select_one('form [type=submit]').text) == (
'Save contact list'
)
assert normalize_spaces(page.select_one('thead').text) == (
'Row in file 1 email address'
)
assert len(page.select('tbody tr')) == 50
assert normalize_spaces(page.select_one('tbody tr').text) == (
'2 test@example.com'
)
assert normalize_spaces(page.select_one('.table-show-more-link').text) == (
'Only showing the first 50 rows'
)
def test_save_contact_list(
mocker,
client_request,
fake_uuid,
mock_create_contact_list,
):
mock_get_metadata = mocker.patch('app.models.contact_list.get_csv_metadata', return_value={
'row_count': 999,
'valid': True,
'original_file_name': 'example.csv',
'template_type': 'email'
})
client_request.post(
'main.save_contact_list',
service_id=SERVICE_ONE_ID,
upload_id=fake_uuid,
_expected_status=302,
_expected_redirect=url_for(
'main.contact_list',
service_id=SERVICE_ONE_ID,
contact_list_id=fake_uuid,
_external=True,
)
)
mock_get_metadata.assert_called_once_with(
SERVICE_ONE_ID,
fake_uuid,
bucket='test-contact-list',
)
mock_create_contact_list.assert_called_once_with(
service_id=SERVICE_ONE_ID,
upload_id=fake_uuid,
original_file_name='example.csv',
row_count=999,
template_type='email',
)
def test_cant_save_bad_contact_list(
mocker,
client_request,
fake_uuid,
mock_create_contact_list,
):
mocker.patch('app.models.contact_list.get_csv_metadata', return_value={
'row_count': 999,
'valid': False,
'original_file_name': 'example.csv',
'template_type': 'email'
})
client_request.post(
'main.save_contact_list',
service_id=SERVICE_ONE_ID,
upload_id=fake_uuid,
_expected_status=403,
)
assert mock_create_contact_list.called is False
@freeze_time('2020-03-13 16:51:56')
def test_view_contact_list(
mocker,
client_request,
mock_get_contact_lists,
fake_uuid,
):
mocker.patch('app.models.contact_list.s3download', return_value='\n'.join(
['email address'] + ['test@example.com'] * 51
))
page = client_request.get(
'main.contact_list',
service_id=SERVICE_ONE_ID,
contact_list_id=fake_uuid,
)
assert normalize_spaces(page.select_one('h1').text) == (
'EmergencyContactList.xls'
)
assert normalize_spaces(page.select('main p')[0].text) == (
'Uploaded by Test User today at 10:59am'
)
assert normalize_spaces(page.select('main p')[1].text) == (
'Download this list 51 email addresses'
)
assert page.select_one('a[download]')['href'] == url_for(
'main.download_contact_list',
service_id=SERVICE_ONE_ID,
contact_list_id=fake_uuid,
)
assert normalize_spaces(page.select_one('table').text).startswith(
'Email addresses '
'1 email address '
'2 test@example.com '
'3 test@example.com '
)
assert normalize_spaces(page.select_one('table').text).endswith(
'50 test@example.com '
'51 test@example.com'
)
assert normalize_spaces(page.select_one('.table-show-more-link').text) == (
'Only showing the first 50 rows'
)
def test_view_contact_list_404s_for_non_existing_list(
client_request,
mock_get_contact_lists,
fake_uuid,
):
client_request.get(
'main.contact_list',
service_id=SERVICE_ONE_ID,
contact_list_id=uuid.uuid4(),
_expected_status=404,
)
def test_download_contact_list(
mocker,
logged_in_client,
fake_uuid,
mock_get_contact_lists,
):
mocker.patch(
'app.models.contact_list.s3download',
return_value='phone number\n07900900321'
)
response = logged_in_client.get(url_for(
'main.download_contact_list',
service_id=SERVICE_ONE_ID,
contact_list_id=fake_uuid,
))
assert response.status_code == 200
assert response.headers['Content-Type'] == (
'text/csv; '
'charset=utf-8'
)
assert response.headers['Content-Disposition'] == (
'attachment; '
'filename=EmergencyContactList.csv'
)
assert response.get_data(as_text=True) == (
'phone number\n'
'07900900321'
)