- add a base domain class - extract shared model code to mixin - rename invitation classes - invitation model relationship to portfolio_role name is now more generic "role"
450 lines
15 KiB
Python
450 lines
15 KiB
Python
from datetime import datetime, timedelta
|
|
|
|
from flask import url_for
|
|
import pytest
|
|
|
|
from atst.domain.task_orders import TaskOrders
|
|
from atst.models import InvitationStatus
|
|
from atst.models.portfolio_role import Status as PortfolioStatus
|
|
from atst.queue import queue
|
|
|
|
from tests.factories import (
|
|
PortfolioFactory,
|
|
TaskOrderFactory,
|
|
UserFactory,
|
|
PortfolioRoleFactory,
|
|
PortfolioInvitationFactory,
|
|
)
|
|
|
|
|
|
def test_invite(client, user_session):
|
|
portfolio = PortfolioFactory.create()
|
|
user_session(portfolio.owner)
|
|
to = TaskOrderFactory.create(portfolio=portfolio)
|
|
response = client.post(
|
|
url_for("task_orders.invite", task_order_id=to.id), follow_redirects=False
|
|
)
|
|
redirect = url_for("task_orders.view_task_order", task_order_id=to.id)
|
|
assert redirect in response.headers["Location"]
|
|
|
|
|
|
def test_invite_officers_to_task_order(client, user_session, queue):
|
|
task_order = TaskOrderFactory.create(
|
|
ko_invite=True, cor_invite=True, so_invite=True
|
|
)
|
|
portfolio = task_order.portfolio
|
|
|
|
user_session(portfolio.owner)
|
|
client.post(url_for("task_orders.invite", task_order_id=task_order.id))
|
|
|
|
# owner and three officers are portfolio members
|
|
assert len(portfolio.members) == 4
|
|
# email invitations are enqueued
|
|
assert len(queue.get_queue()) == 3
|
|
# task order has relationship to user for each officer role
|
|
assert task_order.contracting_officer.dod_id == task_order.ko_dod_id
|
|
assert task_order.contracting_officer_representative.dod_id == task_order.cor_dod_id
|
|
assert task_order.security_officer.dod_id == task_order.so_dod_id
|
|
|
|
|
|
def test_add_officer_but_do_not_invite(client, user_session, queue):
|
|
task_order = TaskOrderFactory.create(
|
|
ko_invite=False, cor_invite=False, so_invite=False
|
|
)
|
|
portfolio = task_order.portfolio
|
|
|
|
user_session(portfolio.owner)
|
|
client.post(url_for("task_orders.invite", task_order_id=task_order.id))
|
|
|
|
portfolio = task_order.portfolio
|
|
# owner is only portfolio member
|
|
assert len(portfolio.members) == 1
|
|
# no invitations are enqueued
|
|
assert len(queue.get_queue()) == 0
|
|
|
|
|
|
def test_does_not_resend_officer_invitation(client, user_session):
|
|
user = UserFactory.create()
|
|
contracting_officer = UserFactory.create()
|
|
portfolio = PortfolioFactory.create(owner=user)
|
|
task_order = TaskOrderFactory.create(
|
|
creator=user,
|
|
portfolio=portfolio,
|
|
ko_first_name=contracting_officer.first_name,
|
|
ko_last_name=contracting_officer.last_name,
|
|
ko_dod_id=contracting_officer.dod_id,
|
|
ko_invite=True,
|
|
)
|
|
|
|
user_session(user)
|
|
for i in range(2):
|
|
client.post(url_for("task_orders.invite", task_order_id=task_order.id))
|
|
assert len(contracting_officer.portfolio_invitations) == 1
|
|
|
|
|
|
def test_does_not_invite_if_task_order_incomplete(client, user_session, queue):
|
|
task_order = TaskOrderFactory.create(
|
|
scope=None, ko_invite=True, cor_invite=True, so_invite=True
|
|
)
|
|
portfolio = task_order.portfolio
|
|
|
|
user_session(portfolio.owner)
|
|
response = client.post(url_for("task_orders.invite", task_order_id=task_order.id))
|
|
|
|
# redirected to review screen
|
|
assert response.headers["Location"] == url_for(
|
|
"task_orders.new", screen=4, task_order_id=task_order.id, _external=True
|
|
)
|
|
# only owner is portfolio member
|
|
assert len(portfolio.members) == 1
|
|
# no email invitations are enqueued
|
|
assert len(queue.get_queue()) == 0
|
|
|
|
|
|
@pytest.fixture
|
|
def portfolio():
|
|
return PortfolioFactory.create()
|
|
|
|
|
|
@pytest.fixture
|
|
def user():
|
|
return UserFactory.create()
|
|
|
|
|
|
class TestTaskOrderInvitations:
|
|
def setup(self):
|
|
self.portfolio = PortfolioFactory.create()
|
|
self.task_order = TaskOrderFactory.create(portfolio=self.portfolio)
|
|
|
|
def _post(self, client, updates):
|
|
return client.post(
|
|
url_for("task_orders.invitations_edit", task_order_id=self.task_order.id),
|
|
headers={"Content-Type": "application/x-www-form-urlencoded"},
|
|
data=updates,
|
|
)
|
|
|
|
def test_editing_with_partial_data(self, user_session, client):
|
|
queue_length = len(queue.get_queue())
|
|
user_session(self.portfolio.owner)
|
|
response = self._post(
|
|
client,
|
|
{
|
|
"contracting_officer-first_name": "Luke",
|
|
"contracting_officer-last_name": "Skywalker",
|
|
"security_officer-first_name": "Boba",
|
|
"security_officer-last_name": "Fett",
|
|
},
|
|
)
|
|
updated_task_order = TaskOrders.get(self.task_order.id)
|
|
assert updated_task_order.ko_first_name == "Luke"
|
|
assert updated_task_order.ko_last_name == "Skywalker"
|
|
assert updated_task_order.so_first_name == "Boba"
|
|
assert updated_task_order.so_last_name == "Fett"
|
|
assert len(queue.get_queue()) == queue_length
|
|
assert response.status_code == 302
|
|
assert (
|
|
url_for(
|
|
"task_orders.invitations",
|
|
task_order_id=self.task_order.id,
|
|
_external=True,
|
|
)
|
|
== response.headers["Location"]
|
|
)
|
|
|
|
def test_editing_with_complete_data(self, user_session, client):
|
|
queue_length = len(queue.get_queue())
|
|
|
|
user_session(self.portfolio.owner)
|
|
response = self._post(
|
|
client,
|
|
{
|
|
"contracting_officer-first_name": "Luke",
|
|
"contracting_officer-last_name": "Skywalker",
|
|
"contracting_officer-dod_id": "0123456789",
|
|
"contracting_officer-email": "luke@skywalker.mil",
|
|
"contracting_officer-phone_number": "0123456789",
|
|
"contracting_officer-invite": "y",
|
|
},
|
|
)
|
|
updated_task_order = TaskOrders.get(self.task_order.id)
|
|
|
|
assert updated_task_order.ko_invite == True
|
|
assert updated_task_order.ko_first_name == "Luke"
|
|
assert updated_task_order.ko_last_name == "Skywalker"
|
|
assert updated_task_order.ko_email == "luke@skywalker.mil"
|
|
assert updated_task_order.ko_phone_number == "0123456789"
|
|
assert len(queue.get_queue()) == queue_length + 1
|
|
assert response.status_code == 302
|
|
assert (
|
|
url_for(
|
|
"task_orders.invitations",
|
|
task_order_id=self.task_order.id,
|
|
_external=True,
|
|
)
|
|
== response.headers["Location"]
|
|
)
|
|
|
|
def test_editing_with_invalid_data(self, user_session, client):
|
|
queue_length = len(queue.get_queue())
|
|
user_session(self.portfolio.owner)
|
|
response = self._post(
|
|
client,
|
|
{
|
|
"contracting_officer-phone_number": "invalid input",
|
|
"security_officer-first_name": "Boba",
|
|
"security_officer-last_name": "Fett",
|
|
},
|
|
)
|
|
|
|
assert "There were some errors" in response.data.decode()
|
|
|
|
updated_task_order = TaskOrders.get(self.task_order.id)
|
|
assert updated_task_order.so_first_name != "Boba"
|
|
assert len(queue.get_queue()) == queue_length
|
|
assert response.status_code == 400
|
|
|
|
def test_user_can_only_invite_to_task_order_in_their_portfolio(
|
|
self, user_session, client, portfolio
|
|
):
|
|
other_task_order = TaskOrderFactory.create()
|
|
user_session(portfolio.owner)
|
|
|
|
# user can't see invites
|
|
response = client.get(
|
|
url_for("task_orders.invitations", task_order_id=other_task_order.id)
|
|
)
|
|
assert response.status_code == 404
|
|
|
|
# user can't send invites
|
|
time_updated = other_task_order.time_updated
|
|
response = client.post(
|
|
url_for("task_orders.invitations_edit", task_order_id=other_task_order.id),
|
|
data={
|
|
"contracting_officer-first_name": "Luke",
|
|
"contracting_officer-last_name": "Skywalker",
|
|
"contracting_officer-dod_id": "0123456789",
|
|
"contracting_officer-email": "luke@skywalker.mil",
|
|
"contracting_officer-phone_number": "0123456789",
|
|
"contracting_officer-invite": "y",
|
|
},
|
|
)
|
|
assert response.status_code == 404
|
|
assert time_updated == other_task_order.time_updated
|
|
|
|
# user can't resend invites
|
|
response = client.post(
|
|
url_for(
|
|
"task_orders.resend_invite",
|
|
task_order_id=other_task_order.id,
|
|
invite_type="ko_invite",
|
|
)
|
|
)
|
|
assert response.status_code == 404
|
|
assert time_updated == other_task_order.time_updated
|
|
|
|
def test_does_not_render_resend_invite_if_user_is_mo_and_user_is_cor(
|
|
self, client, user_session
|
|
):
|
|
task_order = TaskOrderFactory.create(
|
|
portfolio=self.portfolio,
|
|
creator=self.portfolio.owner,
|
|
cor_first_name=self.portfolio.owner.first_name,
|
|
cor_last_name=self.portfolio.owner.last_name,
|
|
cor_email=self.portfolio.owner.email,
|
|
cor_phone_number=self.portfolio.owner.phone_number,
|
|
cor_dod_id=self.portfolio.owner.dod_id,
|
|
cor_invite=True,
|
|
)
|
|
user_session(self.portfolio.owner)
|
|
response = client.get(
|
|
url_for("task_orders.invitations", task_order_id=task_order.id)
|
|
)
|
|
assert "Resend Invitation" not in response.data.decode()
|
|
|
|
def test_renders_resend_invite_if_user_is_mo_and_user_is_not_cor(
|
|
self, client, user_session
|
|
):
|
|
cor = UserFactory.create()
|
|
task_order = TaskOrderFactory.create(
|
|
portfolio=self.portfolio,
|
|
creator=self.portfolio.owner,
|
|
contracting_officer_representative=cor,
|
|
cor_invite=True,
|
|
)
|
|
portfolio_role = PortfolioRoleFactory.create(portfolio=self.portfolio, user=cor)
|
|
PortfolioInvitationFactory.create(
|
|
inviter=self.portfolio.owner,
|
|
role=portfolio_role,
|
|
user=cor,
|
|
status=InvitationStatus.PENDING,
|
|
)
|
|
|
|
user_session(self.portfolio.owner)
|
|
response = client.get(
|
|
url_for("task_orders.invitations", task_order_id=task_order.id)
|
|
)
|
|
assert "Resend Invitation" in response.data.decode()
|
|
|
|
|
|
def test_can_view_task_order_invitations_when_complete(client, user_session, portfolio):
|
|
user_session(portfolio.owner)
|
|
task_order = TaskOrderFactory.create(portfolio=portfolio)
|
|
response = client.get(
|
|
url_for("task_orders.invitations", task_order_id=task_order.id)
|
|
)
|
|
assert response.status_code == 200
|
|
|
|
|
|
def test_cant_view_task_order_invitations_when_not_complete(
|
|
client, user_session, portfolio
|
|
):
|
|
user_session(portfolio.owner)
|
|
task_order = TaskOrderFactory.create(portfolio=portfolio, clin_01=None)
|
|
response = client.get(
|
|
url_for("task_orders.invitations", task_order_id=task_order.id)
|
|
)
|
|
assert response.status_code == 404
|
|
|
|
|
|
def test_resend_invite_when_invalid_invite_officer(
|
|
app, client, user_session, portfolio, user
|
|
):
|
|
queue_length = len(queue.get_queue())
|
|
|
|
task_order = TaskOrderFactory.create(
|
|
portfolio=portfolio, contracting_officer=user, ko_invite=True
|
|
)
|
|
|
|
PortfolioRoleFactory.create(
|
|
portfolio=portfolio, user=user, status=PortfolioStatus.ACTIVE
|
|
)
|
|
|
|
user_session(user)
|
|
|
|
response = client.post(
|
|
url_for(
|
|
"task_orders.resend_invite", task_order_id=task_order.id, _external=True
|
|
),
|
|
data={"invite_type": "invalid_invite_type"},
|
|
)
|
|
|
|
assert response.status_code == 404
|
|
assert len(queue.get_queue()) == queue_length
|
|
|
|
|
|
def test_resend_invite_when_officer_type_missing(
|
|
app, client, user_session, portfolio, user
|
|
):
|
|
queue_length = len(queue.get_queue())
|
|
|
|
task_order = TaskOrderFactory.create(
|
|
portfolio=portfolio, contracting_officer=None, ko_invite=True
|
|
)
|
|
|
|
PortfolioRoleFactory.create(
|
|
portfolio=portfolio, user=user, status=PortfolioStatus.ACTIVE
|
|
)
|
|
|
|
user_session(user)
|
|
|
|
response = client.post(
|
|
url_for(
|
|
"task_orders.resend_invite", task_order_id=task_order.id, _external=True
|
|
),
|
|
data={"invite_type": "contracting_officer_invite"},
|
|
)
|
|
|
|
assert response.status_code == 404
|
|
assert len(queue.get_queue()) == queue_length
|
|
|
|
|
|
def test_resend_invite_when_not_pending(app, client, user_session, portfolio, user):
|
|
queue_length = len(queue.get_queue())
|
|
|
|
task_order = TaskOrderFactory.create(
|
|
portfolio=portfolio, contracting_officer=user, ko_invite=True
|
|
)
|
|
|
|
portfolio_role = PortfolioRoleFactory.create(
|
|
portfolio=portfolio, user=user, status=PortfolioStatus.ACTIVE
|
|
)
|
|
|
|
original_invitation = PortfolioInvitationFactory.create(
|
|
inviter=user,
|
|
role=portfolio_role,
|
|
email=user.email,
|
|
status=InvitationStatus.ACCEPTED,
|
|
)
|
|
|
|
user_session(user)
|
|
|
|
response = client.post(
|
|
url_for(
|
|
"task_orders.resend_invite", task_order_id=task_order.id, _external=True
|
|
),
|
|
data={"invite_type": "ko_invite"},
|
|
)
|
|
|
|
assert original_invitation.status == InvitationStatus.ACCEPTED
|
|
assert response.status_code == 404
|
|
assert len(queue.get_queue()) == queue_length
|
|
|
|
|
|
def test_resending_revoked_invite(app, client, user_session, portfolio, user):
|
|
task_order = TaskOrderFactory.create(
|
|
portfolio=portfolio, contracting_officer=user, ko_invite=True
|
|
)
|
|
|
|
portfolio_role = PortfolioRoleFactory.create(portfolio=portfolio, user=user)
|
|
|
|
invite = PortfolioInvitationFactory.create(
|
|
inviter=user,
|
|
role=portfolio_role,
|
|
email=user.email,
|
|
status=InvitationStatus.REVOKED,
|
|
)
|
|
|
|
user_session(user)
|
|
|
|
response = client.post(
|
|
url_for(
|
|
"task_orders.resend_invite",
|
|
task_order_id=task_order.id,
|
|
invite_type="ko_invite",
|
|
_external=True,
|
|
)
|
|
)
|
|
|
|
assert invite.is_revoked
|
|
assert response.status_code == 404
|
|
|
|
|
|
def test_resending_expired_invite(app, client, user_session, portfolio):
|
|
queue_length = len(queue.get_queue())
|
|
|
|
ko = UserFactory.create()
|
|
task_order = TaskOrderFactory.create(
|
|
portfolio=portfolio, contracting_officer=ko, ko_invite=True
|
|
)
|
|
portfolio_role = PortfolioRoleFactory.create(portfolio=portfolio, user=ko)
|
|
invite = PortfolioInvitationFactory.create(
|
|
inviter=portfolio.owner,
|
|
role=portfolio_role,
|
|
email=ko.email,
|
|
expiration_time=datetime.now() - timedelta(days=1),
|
|
)
|
|
user_session(portfolio.owner)
|
|
|
|
response = client.post(
|
|
url_for(
|
|
"task_orders.resend_invite",
|
|
task_order_id=task_order.id,
|
|
invite_type="ko_invite",
|
|
_external=True,
|
|
)
|
|
)
|
|
|
|
assert invite.is_expired
|
|
assert response.status_code == 302
|
|
assert len(queue.get_queue()) == queue_length + 1
|