graham-dds 108f65f928 Use pendulum for datetime operations when possible
Currently, we use both Python's built-in datetime library and Pendulum
to do datetime operations. For the sake of consistency, we should try to
stick to one library for datetimes. We could have used either, but
Pendulum has a more ergonomic API, so I decided to go with it when
possible.

The places where were we didn't / couldn't replace datetime are:
- checking instances of datetimes. Pendulum's objects are subclasses of
  python native datetime objects, so it's still useful to import
  datetime in those cases of using is_instance()
- WTForms date validators expect datetime style string formats --
  Pendulum has its own format for formatting/ parsing strings. As such,
  our custom validator DateRange needs to use datetime.stptime() to
  account for this format.
2020-02-17 10:38:52 -05:00

519 lines
16 KiB
Python

import pytest
from flask import url_for, get_flashed_messages
import pendulum
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=None):
return {"pdf-filename": filename, "pdf-object_name": object_name or uuid4()}
@pytest.fixture
def task_order():
user = UserFactory.create()
portfolio = PortfolioFactory.create(owner=user)
task_order = TaskOrderFactory.create(portfolio=portfolio)
CLINFactory.create(task_order=task_order)
return task_order
@pytest.fixture
def incomplete_to():
user = UserFactory.create()
portfolio = PortfolioFactory.create(owner=user)
return TaskOrderFactory.create(portfolio=portfolio)
@pytest.fixture
def completed_task_order():
portfolio = PortfolioFactory.create()
task_order = TaskOrderFactory.create(
portfolio=portfolio,
create_clins=[{"number": "1234567890123456789012345678901234567890123"}],
)
return task_order
@pytest.fixture
def portfolio():
return PortfolioFactory.create()
@pytest.fixture
def user():
return UserFactory.create()
def test_task_orders_form_step_one_add_pdf(client, user_session, portfolio):
user_session(portfolio.owner)
response = client.get(
url_for("task_orders.form_step_one_add_pdf", portfolio_id=portfolio.id)
)
assert response.status_code == 200
def test_task_orders_submit_form_step_one_add_pdf(client, user_session, portfolio):
user_session(portfolio.owner)
response = client.post(
url_for("task_orders.submit_form_step_one_add_pdf", portfolio_id=portfolio.id),
data=build_pdf_form_data(),
)
assert response.status_code == 302
task_order = portfolio.task_orders[0]
assert task_order.pdf.filename == "sample.pdf"
def test_task_orders_form_step_one_add_pdf_existing_to(
client, user_session, task_order
):
user_session(task_order.portfolio.owner)
response = client.get(
url_for("task_orders.form_step_one_add_pdf", task_order_id=task_order.id)
)
assert response.status_code == 200
def test_task_orders_submit_form_step_one_add_pdf_existing_to(client, user_session):
task_order = TaskOrderFactory.create()
user_session(task_order.portfolio.owner)
response = client.post(
url_for(
"task_orders.submit_form_step_one_add_pdf", task_order_id=task_order.id
),
data=build_pdf_form_data(),
)
assert response.status_code == 302
assert task_order.pdf.filename == "sample.pdf"
def test_task_orders_submit_form_step_one_add_pdf_delete_pdf(
client, user_session, portfolio
):
user_session(portfolio.owner)
task_order = TaskOrderFactory.create(portfolio=portfolio)
response = client.post(
url_for(
"task_orders.submit_form_step_one_add_pdf", task_order_id=task_order.id
),
data=build_pdf_form_data(filename="", object_name=""),
)
assert task_order.pdf is None
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.portfolio.owner)
response = client.get(
url_for("task_orders.form_step_two_add_number", task_order_id=task_order.id)
)
assert response.status_code == 200
def test_task_orders_submit_form_step_two_add_number(client, user_session, task_order):
user_session(task_order.portfolio.owner)
form_data = {"number": "abc-1234567890"}
response = client.post(
url_for(
"task_orders.submit_form_step_two_add_number", task_order_id=task_order.id
),
data=form_data,
)
assert response.status_code == 302
assert task_order.number == "ABC1234567890" # pragma: allowlist secret
def test_task_orders_submit_form_step_two_enforces_unique_number(
client, user_session, task_order, session
):
number = "1234567890123"
dupe_task_order = TaskOrderFactory.create(number=number)
portfolio = task_order.portfolio
user_session(task_order.portfolio.owner)
form_data = {"number": number}
session.begin_nested()
response = client.post(
url_for(
"task_orders.submit_form_step_two_add_number", task_order_id=task_order.id
),
data=form_data,
)
session.rollback()
assert response.status_code == 400
def test_task_orders_submit_form_step_two_add_number_existing_to(
client, user_session, task_order
):
user_session(task_order.portfolio.owner)
form_data = {"number": "0000000000000"}
original_number = task_order.number
response = client.post(
url_for(
"task_orders.submit_form_step_two_add_number", task_order_id=task_order.id
),
data=form_data,
)
assert response.status_code == 302
assert task_order.number == "0000000000000"
assert task_order.number != original_number
def test_task_orders_form_step_three_add_clins(client, user_session, task_order):
user_session(task_order.portfolio.owner)
response = client.get(
url_for("task_orders.form_step_three_add_clins", task_order_id=task_order.id)
)
assert response.status_code == 200
def test_task_orders_submit_form_step_three_add_clins(client, user_session, task_order):
user_session(task_order.portfolio.owner)
form_data = {
"clins-0-jedi_clin_type": "JEDI_CLIN_1",
"clins-0-number": "1212",
"clins-0-start_date": "01/01/2020",
"clins-0-end_date": "01/01/2021",
"clins-0-obligated_amount": "5000",
"clins-0-total_amount": "10000",
"clins-1-jedi_clin_type": "JEDI_CLIN_1",
"clins-1-number": "1212",
"clins-1-start_date": "01/01/2020",
"clins-1-end_date": "01/01/2021",
"clins-1-obligated_amount": "5000",
"clins-1-total_amount": "5000",
}
response = client.post(
url_for(
"task_orders.submit_form_step_three_add_clins", task_order_id=task_order.id
),
data=form_data,
)
assert response.status_code == 302
assert len(task_order.clins) == 2
def test_task_orders_submit_form_step_three_add_clins_existing_to(
client, user_session, task_order
):
clin_list = [
{
"jedi_clin_type": "JEDI_CLIN_1",
"number": "12312",
"start_date": "01/01/2020",
"end_date": "01/01/2021",
"obligated_amount": "5000",
"total_amount": "10000",
},
{
"jedi_clin_type": "JEDI_CLIN_1",
"number": "12312",
"start_date": "01/01/2020",
"end_date": "01/01/2021",
"obligated_amount": "5000",
"total_amount": "10000",
},
]
TaskOrders.create_clins(task_order.id, clin_list)
assert len(task_order.clins) == 3
user_session(task_order.portfolio.owner)
form_data = {
"clins-0-jedi_clin_type": "JEDI_CLIN_1",
"clins-0-number": "1212",
"clins-0-start_date": "01/01/2020",
"clins-0-end_date": "01/01/2021",
"clins-0-obligated_amount": "5000",
"clins-0-total_amount": "10000",
}
response = client.post(
url_for(
"task_orders.submit_form_step_three_add_clins", task_order_id=task_order.id
),
data=form_data,
)
assert response.status_code == 302
assert len(task_order.clins) == 1
def test_task_orders_form_step_four_review(client, user_session, completed_task_order):
user_session(completed_task_order.portfolio.owner)
response = client.get(
url_for(
"task_orders.form_step_four_review", task_order_id=completed_task_order.id
)
)
assert response.status_code == 200
def test_task_orders_form_step_four_review_incomplete_to(
client, user_session, incomplete_to
):
user_session(incomplete_to.portfolio.owner)
response = client.get(
url_for("task_orders.form_step_four_review", task_order_id=incomplete_to.id)
)
assert response.status_code == 404
def test_task_orders_form_step_five_confirm_signature(
client, user_session, completed_task_order
):
user_session(completed_task_order.portfolio.owner)
response = client.get(
url_for(
"task_orders.form_step_five_confirm_signature",
task_order_id=completed_task_order.id,
)
)
assert response.status_code == 200
def test_task_orders_form_step_five_confirm_signature_incomplete_to(
client, user_session, incomplete_to
):
user_session(incomplete_to.portfolio.owner)
response = client.get(
url_for(
"task_orders.form_step_five_confirm_signature",
task_order_id=incomplete_to.id,
)
)
assert response.status_code == 404
def test_task_orders_submit_task_order(client, user_session, task_order):
user_session(task_order.portfolio.owner)
response = client.post(
url_for("task_orders.submit_task_order", task_order_id=task_order.id)
)
assert response.status_code == 302
active_start_date = pendulum.today().subtract(days=1)
active_task_order = TaskOrderFactory(portfolio=task_order.portfolio)
CLINFactory(task_order=active_task_order, start_date=active_start_date)
assert active_task_order.status == TaskOrderStatus.UNSIGNED
response = client.post(
url_for("task_orders.submit_task_order", task_order_id=active_task_order.id)
)
assert active_task_order.status == TaskOrderStatus.ACTIVE
upcoming_start_date = pendulum.today().add(days=1)
upcoming_task_order = TaskOrderFactory(portfolio=task_order.portfolio)
CLINFactory(task_order=upcoming_task_order, start_date=upcoming_start_date)
assert upcoming_task_order.status == TaskOrderStatus.UNSIGNED
response = client.post(
url_for("task_orders.submit_task_order", task_order_id=upcoming_task_order.id)
)
assert upcoming_task_order.status == TaskOrderStatus.UPCOMING
@pytest.mark.parametrize(
"to_factory_args,expected_step",
[
({"_pdf": None, "number": "", "clins": []}, "step_1"),
({"number": "", "clins": []}, "step_2"),
({"number": "1234567890123", "clins": []}, "step_3"),
({"number": "1234567890123", "create_clins": [{"number": 1}]}, "step_4"),
],
)
def test_task_orders_edit_redirects_to_latest_incomplete_step(
client, user_session, portfolio, to_factory_args, expected_step
):
task_order = TaskOrderFactory.create(portfolio=portfolio, **to_factory_args)
user_session(portfolio.owner)
response = client.get(url_for("task_orders.edit", task_order_id=task_order.id))
assert expected_step in response.location
def test_can_cancel_edit_and_save_task_order(client, user_session, task_order, session):
user_session(task_order.portfolio.owner)
response = client.post(
url_for("task_orders.cancel_edit", task_order_id=task_order.id, save=True),
data={"number": "7896564324567"},
)
assert response.status_code == 302
updated_task_order = session.query(TaskOrder).get(task_order.id)
assert updated_task_order.number == "7896564324567"
def test_cancel_can_create_new_to(client, user_session, portfolio):
user_session(portfolio.owner)
response = client.post(
url_for("task_orders.cancel_edit", portfolio_id=portfolio.id),
data={"number": "7643906432984"},
)
assert response.status_code == 302
def test_cancel_edit_causes_to_to_be_deleted(client, user_session, session):
task_order = TaskOrderFactory.create()
user_session(task_order.portfolio.owner)
response = client.post(
url_for("task_orders.cancel_edit", task_order_id=task_order.id, save=False),
data={},
)
assert response.status_code == 302
# TO should be deleted
assert session.query(TaskOrder).get(task_order.id) is None
def test_cancel_edit_and_save_with_invalid_input_does_not_flash(
app, client, user_session, session
):
task_order = TaskOrderFactory.create()
user_session(task_order.portfolio.owner)
bad_data = {"clins-0-jedi_clin_type": "foo"}
response = client.post(
url_for("task_orders.cancel_edit", task_order_id=task_order.id, save=True),
data=bad_data,
)
assert len(get_flashed_messages()) == 0
@pytest.mark.skip(reason="Reevaluate how form handles invalid data")
def test_task_orders_update_invalid_data(client, user_session, portfolio):
user_session(portfolio.owner)
num_task_orders = len(portfolio.task_orders)
response = client.post(
url_for("task_orders.update", portfolio_id=portfolio.id), data={"number": ""}
)
assert response.status_code == 400
assert num_task_orders == len(portfolio.task_orders)
assert "There were some errors" in response.data.decode()
@pytest.mark.skip(reason="Update after implementing errors on TO form")
def test_task_order_form_shows_errors(client, user_session, task_order):
user_session(task_order.portfolio.owner)
task_order_data = TaskOrderFactory.dictionary()
funding_data = slice_data_for_section(task_order_data, "funding")
funding_data = serialize_dates(funding_data)
funding_data.update({"clin_01": "one milllllion dollars"})
response = client.post(
url_for("task_orders.update", task_order_id=task_order.id),
data=funding_data,
follow_redirects=False,
)
body = response.data.decode()
assert "There were some errors" in body
assert "Not a valid decimal" in body
def test_update_and_render_next_handles_previous_valid_data(
client, user_session, task_order
):
user_session(task_order.portfolio.owner)
form_data = {"number": "0000000000000"}
original_number = task_order.number
response = client.post(
url_for(
"task_orders.submit_form_step_two_add_number",
task_order_id=task_order.id,
previous=True,
),
data=form_data,
)
assert response.status_code == 302
assert task_order.number == "0000000000000"
assert task_order.number != original_number
def test_update_and_render_next_handles_previous_invalid_data(
client, user_session, task_order
):
clin_list = [
{
"jedi_clin_type": "JEDI_CLIN_1",
"number": "12312",
"start_date": "01/01/2020",
"end_date": "01/01/2021",
"obligated_amount": "5000",
"total_amount": "10000",
},
]
TaskOrders.create_clins(task_order.id, clin_list)
assert len(task_order.clins) == 2
user_session(task_order.portfolio.owner)
form_data = {
"clins-0-jedi_clin_type": "JEDI_CLIN_1",
"clins-0-number": "12312",
"clins-0-start_date": "01/01/2020",
"clins-0-end_date": "01/01/2021",
"clins-0-obligated_amount": "5000",
"clins-0-total_amount": "10000",
"clins-1-jedi_clin_type": "JEDI_CLIN_1",
"clins-1-number": "1212",
}
response = client.post(
url_for(
"task_orders.submit_form_step_three_add_clins",
task_order_id=task_order.id,
previous=True,
),
data=form_data,
)
assert len(task_order.clins) == 2