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.
519 lines
16 KiB
Python
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
|