diff --git a/app/main/uploader.py b/app/main/uploader.py index 3010c034e..7eaf12698 100644 --- a/app/main/uploader.py +++ b/app/main/uploader.py @@ -1,17 +1,13 @@ -import uuid - from boto3 import resource -def s3upload(service_id, filedata): - upload_id = str(uuid.uuid4()) +def s3upload(upload_id, service_id, filedata): s3 = resource('s3') bucket_name = 'service-{}-{}-notify'.format(service_id, upload_id) s3.create_bucket(Bucket=bucket_name) contents = '\n'.join(filedata['data']) key = s3.Object(bucket_name, upload_id) key.put(Body=contents, ServerSideEncryption='AES256') - return upload_id def s3download(service_id, upload_id): diff --git a/app/main/views/jobs.py b/app/main/views/jobs.py index ae653d405..c9f2bdf2e 100644 --- a/app/main/views/jobs.py +++ b/app/main/views/jobs.py @@ -4,84 +4,64 @@ import time from flask import ( render_template, - session + abort ) from flask_login import login_required +from client.errors import HTTPError +from app import job_api_client from app.main import main -from ._jobs import jobs now = time.strftime('%H:%M') -messages = [ - { - 'phone': '+44 7700 900 579', - 'message': 'Vehicle tax: Your vehicle tax for LV75 TDG expires on 18 January 2016. Renew at www.gov.uk/vehicletax', # noqa - 'status': 'Delivered', - 'time': now, - 'id': '0' - }, - { - 'phone': '+44 7700 900 306', - 'message': 'Vehicle tax: Your vehicle tax for PL53 GBD expires on 18 January 2016. Renew at www.gov.uk/vehicletax', # noqa - 'status': 'Delivered', - 'time': now, - 'id': '1' - }, - { - 'phone': '+44 7700 900 454', - 'message': 'Vehicle tax: Your vehicle tax for LV75 TDG expires on 18 January 2016. Renew at www.gov.uk/vehicletax', # noqa - 'status': 'Delivered', - 'time': now, - 'id': '2' - }, - { - 'phone': '+44 7700 900 522', - 'message': 'Vehicle tax: Your vehicle tax for RE67 PLM expires on 18 January 2016. Renew at www.gov.uk/vehicletax', # noqa - 'status': 'Failed', - 'time': now, - 'id': '3' - } -] - @main.route("/services//jobs") @login_required def view_jobs(service_id): - return render_template( - 'views/jobs.html', - jobs=[], # use `jobs` for placeholder data - service_id=service_id - ) + try: + jobs = job_api_client.get_job(service_id)['data'] + return render_template( + 'views/jobs.html', + jobs=jobs, + service_id=service_id + ) + except HTTPError as e: + if e.status_code == 404: + abort(404) + else: + raise e @main.route("/services//jobs/") @login_required def view_job(service_id, job_id): - - # TODO the uploaded file name could be part of job definition - # so won't need to be passed on from last view via session - uploaded_file_name = session.get(job_id) - - return render_template( - 'views/job.html', - messages=messages, - counts={ - 'total': len(messages), - 'delivered': len([ - message for message in messages if message['status'] == 'Delivered' - ]), - 'failed': len([ - message for message in messages if message['status'] == 'Failed' - ]) - }, - cost=u'£0.00', - uploaded_file_name=uploaded_file_name, - uploaded_file_time=now, - template_used='Test message 1', - flash_message=u'We’ve started sending your messages', - service_id=service_id - ) + try: + job = job_api_client.get_job(service_id, job_id)['data'] + messages = [] + return render_template( + 'views/job.html', + messages=messages, + counts={ + 'total': len(messages), + 'delivered': len([ + message for message in messages if message['status'] == 'Delivered' + ]), + 'failed': len([ + message for message in messages if message['status'] == 'Failed' + ]) + }, + cost=u'£0.00', + uploaded_file_name=job['original_file_name'], + uploaded_file_time=job['created_at'], + template_used=job['template'], + flash_message="We’ve accepted {} for processing".format(job['original_file_name']), + service_id=service_id + ) + except HTTPError as e: + if e.status_code == 404: + abort(404) + else: + raise e @main.route("/services//jobs//notification/") diff --git a/app/main/views/sms.py b/app/main/views/sms.py index 6f1f91bee..8055f4c75 100644 --- a/app/main/views/sms.py +++ b/app/main/views/sms.py @@ -1,5 +1,6 @@ import csv import re +import uuid from datetime import date @@ -34,7 +35,8 @@ def send_sms(service_id): try: csv_file = form.file.data filedata = _get_filedata(csv_file) - upload_id = s3upload(service_id, filedata) + upload_id = str(uuid.uuid4()) + s3upload(upload_id, service_id, filedata) return redirect(url_for('.check_sms', service_id=service_id, upload_id=upload_id, @@ -85,7 +87,7 @@ def check_sms(service_id, upload_id): # that will be done in another story template_id = 1 - job_api_client.create_job(service_id, template_id, file_name) + job_api_client.create_job(upload_id, service_id, template_id, file_name) return redirect(url_for('main.view_job', service_id=service_id, job_id=upload_id)) diff --git a/app/notify_client/job_api_client.py b/app/notify_client/job_api_client.py index a87d53346..37350b29f 100644 --- a/app/notify_client/job_api_client.py +++ b/app/notify_client/job_api_client.py @@ -14,8 +14,13 @@ class JobApiClient(BaseAPIClient): self.client_id = app.config['ADMIN_CLIENT_USER_NAME'] self.secret = app.config['ADMIN_CLIENT_SECRET'] - def create_job(self, service_id, template_id, file_name): - job_id = str(uuid.uuid4()) + def get_job(self, service_id, job_id=None): + if job_id: + return self.get(url='/service/{}/job/{}'.format(service_id, job_id)) + else: + return self.get(url='/service/{}/job'.format(service_id)) + + def create_job(self, job_id, service_id, template_id, file_name): data = { "id": job_id, "service": service_id, diff --git a/app/templates/views/job.html b/app/templates/views/job.html index 01916ea24..3ec59026b 100644 --- a/app/templates/views/job.html +++ b/app/templates/views/job.html @@ -38,7 +38,7 @@ GOV.UK Notify | Notifications activity

- Sent with template {{ template_used }} at {{ uploaded_file_time }} + Sent with template {{ template_used }} on {{ uploaded_file_time | format_datetime}}

{% call(item) list_table( diff --git a/app/templates/views/jobs.html b/app/templates/views/jobs.html index 6d321c433..8e16742f2 100644 --- a/app/templates/views/jobs.html +++ b/app/templates/views/jobs.html @@ -17,13 +17,13 @@ GOV.UK Notify | Notifications activity field_headings=['Job', 'File', 'Time', 'Status'] ) %} {% call field() %} - {{ item.file }} + {{ item.id }} {% endcall %} {% call field() %} - {{ item.job }} + {{ item.original_file_name }} {% endcall %} {% call field() %} - {{ item.time }} + {{ item.created_at | format_datetime}} {% endcall %} {% call field() %} {{ item.status }} diff --git a/requirements_for_test.txt b/requirements_for_test.txt index e23499713..3fe7eb737 100644 --- a/requirements_for_test.txt +++ b/requirements_for_test.txt @@ -4,3 +4,4 @@ pytest==2.8.1 pytest-mock==0.8.1 moto==0.4.19 httpretty==0.8.10 +beautifulsoup4==4.4.1 diff --git a/tests/__init__.py b/tests/__init__.py index 2d325a5cd..8842d5537 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -80,3 +80,21 @@ def create_another_test_user(state): def get_test_user(): from app.main.dao import users_dao return users_dao.get_user_by_email(TEST_USER_EMAIL) + + +def job_json(): + import uuid + import datetime + uuid.uuid4() + job_id = str(uuid.uuid4()) + created_at = str(datetime.datetime.now().time()) + data = { + 'id': str(job_id), + 'service': 1, + 'template': 1, + 'original_file_name': 'thisisatest.csv', + 'bucket_name': 'service-1-{}-notify'.format(job_id), + 'file_name': '{}.csv'.format(job_id), + 'created_at': created_at + } + return data diff --git a/tests/app/main/views/test_jobs.py b/tests/app/main/views/test_jobs.py index ec084dc3b..6b32dd549 100644 --- a/tests/app/main/views/test_jobs.py +++ b/tests/app/main/views/test_jobs.py @@ -1,4 +1,5 @@ from flask import url_for +from bs4 import BeautifulSoup def test_should_return_list_of_all_jobs(app_, @@ -6,14 +7,18 @@ def test_should_return_list_of_all_jobs(app_, api_user_active, mock_get_user, mock_get_user_by_email, - mock_login): + mock_login, + mock_get_jobs): with app_.test_request_context(): with app_.test_client() as client: client.login(api_user_active) response = client.get(url_for('main.view_jobs', service_id=101)) assert response.status_code == 200 - assert 'You haven’t sent any notifications yet' in response.get_data(as_text=True) + page = BeautifulSoup(response.data.decode('utf-8'), 'html.parser') + assert page.h1.string == 'Notifications activity' + jobs = page.tbody.find_all('tr') + assert len(jobs) == 5 def test_should_show_page_for_one_job(app_, @@ -21,35 +26,17 @@ def test_should_show_page_for_one_job(app_, api_user_active, mock_login, mock_get_user, - mock_get_user_by_email): - with app_.test_request_context(): - with app_.test_client() as client: - # TODO filename will be part of job metadata not in session - with client.session_transaction() as s: - s[456] = 'dispatch_20151114.csv' - client.login(api_user_active) - response = client.get(url_for('main.view_job', service_id=123, job_id=456)) + mock_get_user_by_email, + job_data, + mock_get_job): + service_id = job_data['service'] + job_id = job_data['id'] + file_name = job_data['original_file_name'] - assert response.status_code == 200 - assert 'dispatch_20151114.csv' in response.get_data(as_text=True) - assert 'Test message 1' in response.get_data(as_text=True) - - -def test_should_show_page_for_one_notification(app_, - service_one, - api_user_active, - mock_get_user, - mock_get_user_by_email, - mock_login): with app_.test_request_context(): with app_.test_client() as client: client.login(api_user_active) - response = client.get(url_for( - 'main.view_notification', - service_id=101, - job_id=123, - notification_id=3)) + response = client.get(url_for('main.view_job', service_id=service_id, job_id=job_id)) assert response.status_code == 200 - assert 'Text message' in response.get_data(as_text=True) - assert '+44 7700 900 522' in response.get_data(as_text=True) + assert file_name in response.get_data(as_text=True) diff --git a/tests/app/main/views/test_sms.py b/tests/app/main/views/test_sms.py index 8e7f3b4c5..95d493eba 100644 --- a/tests/app/main/views/test_sms.py +++ b/tests/app/main/views/test_sms.py @@ -124,10 +124,12 @@ def test_create_job_should_call_api(app_, mock_get_user_by_email, mock_login, job_data, - mock_create_job): + mock_create_job, + mock_get_job): + import uuid service_id = service_one['id'] - job_id = job_data['id'] + job_id = str(uuid.uuid4()) file_name = job_data['file_name'] # TODO - template id should come from form but is not wired in yet. @@ -141,5 +143,5 @@ def test_create_job_should_call_api(app_, response = client.post(url, data=job_data, follow_redirects=True) assert response.status_code == 200 - mock_create_job.assert_called_with(service_id, template_id, file_name) + mock_create_job.assert_called_with(job_id, service_id, template_id, file_name) assert job_data['bucket_name'] == "service-{}-{}-notify".format(service_id, job_id) diff --git a/tests/conftest.py b/tests/conftest.py index e6f8d5e55..e7c0f7a6a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,17 +1,16 @@ import os from datetime import date import pytest -from alembic.command import upgrade -from alembic.config import Config -from flask.ext.migrate import Migrate, MigrateCommand -from flask.ext.script import Manager -from sqlalchemy.schema import MetaData from app import create_app from . import ( - create_test_user, service_json, TestClient, - get_test_user, template_json, api_key_json) + service_json, + TestClient, + template_json, + api_key_json, + job_json +) @pytest.fixture(scope='session') @@ -422,24 +421,41 @@ def mock_check_verify_code_code_expired(mocker): @pytest.fixture(scope='function') -def job_data(mocker): - import uuid - job_id = uuid.uuid4() - file_name = 'thisisatest.csv' - data = { - 'id': str(job_id), - 'file_name': file_name, - } - return data +def job_data(): + return job_json() @pytest.fixture(scope='function') def mock_create_job(mocker, job_data): - def _create(service_id, template_id, file_name): + def _create(job_id, service_id, template_id, file_name): + job_data['id'] = job_id job_data['service'] = service_id job_data['template'] = template_id - job_data['bucket_name'] = 'service-{}-{}-notify'.format(service_id, job_data['id']) + job_data['bucket_name'] = 'service-{}-{}-notify'.format(service_id, job_id) job_data['original_file_name'] = file_name - job_data['file_name'] = '{}.csv'.format(job_data['id']) + job_data['file_name'] = '{}.csv'.format(job_id) return job_data return mocker.patch('app.job_api_client.create_job', side_effect=_create) + + +@pytest.fixture(scope='function') +def mock_get_job(mocker, job_data): + def _get_job(service_id, job_id): + job_data['id'] = job_id + job_data['service'] = service_id + return {"data": job_data} + return mocker.patch('app.job_api_client.get_job', side_effect=_get_job) + + +@pytest.fixture(scope='function') +def mock_get_jobs(mocker): + def _get_jobs(service_id): + import uuid + data = [] + for i in range(5): + job_data = job_json() + job_data['id'] = str(uuid.uuid4()) + job_data['service'] = service_id + data.append(job_data) + return {"data": data} + return mocker.patch('app.job_api_client.get_job', side_effect=_get_jobs)