diff --git a/.secrets.baseline b/.secrets.baseline index 8926613d..717bf9b4 100644 --- a/.secrets.baseline +++ b/.secrets.baseline @@ -3,7 +3,7 @@ "files": null, "lines": null }, - "generated_at": "2019-08-13T09:59:21Z", + "generated_at": "2019-08-14T14:39:14Z", "plugins_used": [ { "base64_limit": 4.5, @@ -169,7 +169,7 @@ "hashed_secret": "e4f14805dfd1e6af030359090c535e149e6b4207", "is_secret": false, "is_verified": false, - "line_number": 30, + "line_number": 32, "type": "Hex High Entropy String" } ], diff --git a/atst/forms/task_order.py b/atst/forms/task_order.py index c9c9e19b..47323319 100644 --- a/atst/forms/task_order.py +++ b/atst/forms/task_order.py @@ -7,7 +7,7 @@ from wtforms.fields import ( HiddenField, ) from wtforms.fields.html5 import DateField -from wtforms.validators import Required, Optional +from wtforms.validators import Required, Optional, Length from flask_wtf import FlaskForm from .data import JEDI_CLIN_TYPES @@ -65,10 +65,25 @@ class CLINForm(FlaskForm): class AttachmentForm(BaseForm): - filename = HiddenField(id="attachment_filename") - object_name = HiddenField(id="attachment_object_name") + filename = HiddenField( + id="attachment_filename", + validators=[ + Length(max=100, message=translate("forms.attachment.filename.length_error")) + ], + ) + object_name = HiddenField( + id="attachment_object_name", + validators=[ + Length( + max=40, message=translate("forms.attachment.object_name.length_error") + ) + ], + ) accept = ".pdf,application/pdf" + def validate(self, *args, **kwargs): + return super().validate(*args, **{**kwargs, "flash_invalid": False}) + class TaskOrderForm(BaseForm): number = StringField(label=translate("forms.task_order.number_description")) diff --git a/js/components/upload_input.js b/js/components/upload_input.js index 22a7c81c..273c1d20 100644 --- a/js/components/upload_input.js +++ b/js/components/upload_input.js @@ -48,6 +48,7 @@ export default { attachment: this.initialData || null, showErrors: this.initialErrors, changed: false, + uploadError: null, } }, @@ -63,15 +64,15 @@ export default { methods: { addAttachment: async function(e) { const file = e.target.files[0] - try { - await this.uploader.upload(file, this.objectName) + + const response = await this.uploader.upload(file, this.objectName) + if (response.ok) { this.attachment = e.target.value - this.showErrors = false this.$refs.attachmentFilename.value = file.name this.$refs.attachmentObjectName.value = this.objectName - } catch (err) { - console.log(err) + } else { this.showErrors = true + this.uploadError = true } this.changed = true @@ -90,6 +91,7 @@ export default { this.$refs.attachmentInput.value = null } this.showErrors = false + this.uploadError = false this.changed = true emitEvent('field-change', this, { diff --git a/js/lib/upload.js b/js/lib/upload.js index f102c851..4a300b32 100644 --- a/js/lib/upload.js +++ b/js/lib/upload.js @@ -70,7 +70,7 @@ class MockUploader { } async upload(file, objectName) { - return Promise.resolve({}) + return Promise.resolve({ ok: true }) } } diff --git a/js/test_templates/upload_input_error_template.html b/js/test_templates/upload_input_error_template.html index efb83e29..02256f0d 100644 --- a/js/test_templates/upload_input_error_template.html +++ b/js/test_templates/upload_input_error_template.html @@ -25,14 +25,6 @@ Browse - - - - - + - ['Test Error Message'] + Test Error Message diff --git a/js/test_templates/upload_input_template.html b/js/test_templates/upload_input_template.html index 37412a66..70c73407 100644 --- a/js/test_templates/upload_input_template.html +++ b/js/test_templates/upload_input_template.html @@ -25,7 +25,6 @@ Browse - + diff --git a/script/seed_sample.py b/script/seed_sample.py index 7bc9cb9d..c6c164a4 100644 --- a/script/seed_sample.py +++ b/script/seed_sample.py @@ -1,10 +1,11 @@ # Add root application dir to the python path import os import sys -from datetime import timedelta, date, timedelta +from datetime import timedelta, date import random from faker import Faker from werkzeug.datastructures import FileStorage +from uuid import uuid4 parent_dir = os.path.abspath(os.path.join(os.path.dirname(__file__), "..")) sys.path.append(parent_dir) @@ -30,9 +31,9 @@ from atst.routes.dev import _DEV_USERS as DEV_USERS from tests.factories import ( random_service_branch, - random_task_order_number, TaskOrderFactory, CLINFactory, + AttachmentFactory, ) fake = Faker() @@ -167,20 +168,20 @@ def add_task_orders_to_portfolio(portfolio): yesterday = today - timedelta(days=1) five_days = timedelta(days=5) - with open("tests/fixtures/sample.pdf", "rb") as fp: - pdf = FileStorage(fp, content_type="application/pdf") + def build_pdf(): + return {"filename": "sample_task_order.pdf", "object_name": str(uuid4())} - draft_to = TaskOrderFactory.build(portfolio=portfolio, pdf=None) - unsigned_to = TaskOrderFactory.build(portfolio=portfolio, pdf=pdf) - upcoming_to = TaskOrderFactory.build( - portfolio=portfolio, signed_at=yesterday, pdf=pdf - ) - expired_to = TaskOrderFactory.build( - portfolio=portfolio, signed_at=yesterday, pdf=pdf - ) - active_to = TaskOrderFactory.build( - portfolio=portfolio, signed_at=yesterday, pdf=pdf - ) + draft_to = TaskOrderFactory.build(portfolio=portfolio, pdf=None) + unsigned_to = TaskOrderFactory.build(portfolio=portfolio, pdf=build_pdf()) + upcoming_to = TaskOrderFactory.build( + portfolio=portfolio, signed_at=yesterday, pdf=build_pdf() + ) + expired_to = TaskOrderFactory.build( + portfolio=portfolio, signed_at=yesterday, pdf=build_pdf() + ) + active_to = TaskOrderFactory.build( + portfolio=portfolio, signed_at=yesterday, pdf=build_pdf() + ) clins = [ CLINFactory.build( diff --git a/templates/components/upload_input.html b/templates/components/upload_input.html index 6e71d157..a56cb0bf 100644 --- a/templates/components/upload_input.html +++ b/templates/components/upload_input.html @@ -30,9 +30,6 @@ Browse - {% if field.errors %} - {{ Icon('alert',classes="icon-validation") }} - {% endif %} - {% for error, error_message in field.errors.items() %} - {{error_message}} + + {% for error, error_messages in field.errors.items() %} + {{error_messages[0]}} {% endfor %} diff --git a/tests/routes/task_orders/test_new.py b/tests/routes/task_orders/test_new.py index 478baedf..bdc3a76d 100644 --- a/tests/routes/task_orders/test_new.py +++ b/tests/routes/task_orders/test_new.py @@ -1,16 +1,18 @@ import pytest from flask import url_for, get_flashed_messages from datetime import timedelta, date +from uuid import uuid4 from atst.domain.task_orders import TaskOrders from atst.models.task_order import Status as TaskOrderStatus from atst.models import TaskOrder from tests.factories import CLINFactory, PortfolioFactory, TaskOrderFactory, UserFactory +from tests.utils import captured_templates -def build_pdf_form_data(filename="sample.pdf", object_name="object_name"): - return {"pdf-filename": filename, "pdf-object_name": object_name} +def build_pdf_form_data(filename="sample.pdf", object_name=None): + return {"pdf-filename": filename, "pdf-object_name": object_name or uuid4()} @pytest.fixture @@ -101,6 +103,42 @@ def test_task_orders_submit_form_step_one_add_pdf_delete_pdf( assert response.status_code == 302 +def test_task_orders_submit_form_step_one_validates_filename( + app, client, user_session, portfolio +): + user_session(portfolio.owner) + with captured_templates(app) as templates: + client.post( + url_for( + "task_orders.submit_form_step_one_add_pdf", portfolio_id=portfolio.id + ), + data={"pdf-filename": "a" * 1024}, + follow_redirects=True, + ) + + _, context = templates[-1] + + assert "filename" in context["form"].pdf.errors + + +def test_task_orders_submit_form_step_one_validates_object_name( + app, client, user_session, portfolio +): + user_session(portfolio.owner) + with captured_templates(app) as templates: + client.post( + url_for( + "task_orders.submit_form_step_one_add_pdf", portfolio_id=portfolio.id + ), + data={"pdf-object_name": "a" * 41}, + follow_redirects=True, + ) + + _, context = templates[-1] + + assert "object_name" in context["form"].pdf.errors + + def test_task_orders_form_step_two_add_number(client, user_session, task_order): user_session(task_order.creator) response = client.get( diff --git a/translations.yaml b/translations.yaml index f03ed93c..f57c61d3 100644 --- a/translations.yaml +++ b/translations.yaml @@ -149,7 +149,13 @@ forms: portfolio: name_label: Portfolio name name_length_validation_message: Portfolio names can be between 4-100 characters + attachment: + object_name: + length_error: Object name may be no longer than 40 characters. + filename: + length_error: Filename may be no longer than 100 characters. task_order: + upload_error: There was an error uploading your file. Please try again. If you encounter repeated problems uploading this file, please contact CCPO. app_migration: both: 'Yes, migrating from both an on-premise data center and another cloud provider' cloud: 'Yes, migrating from another cloud provider'