From 3182f3438e7abf1a62149382e1637e76bb2d2bbd Mon Sep 17 00:00:00 2001 From: leigh-mil Date: Thu, 14 Mar 2019 12:03:42 -0400 Subject: [PATCH 1/4] Have officer invite url point to the TO view page --- atst/domain/task_orders.py | 11 +++++++++++ atst/routes/portfolios/invitations.py | 11 ++--------- 2 files changed, 13 insertions(+), 9 deletions(-) diff --git a/atst/domain/task_orders.py b/atst/domain/task_orders.py index 63239679..3aa5ba88 100644 --- a/atst/domain/task_orders.py +++ b/atst/domain/task_orders.py @@ -185,6 +185,17 @@ class TaskOrders(object): "{} is not an officer role on task orders".format(officer_type) ) + @classmethod + def is_officer_for_to(cls, task_order, user): + if ( + user == task_order.contracting_officer + or user == task_order.contracting_officer_representative + or user == task_order.security_officer + ): + return True + else: + return False + @classmethod def add_dd_254(user, task_order, dd_254_data): dd_254 = DD254(**dd_254_data) diff --git a/atst/routes/portfolios/invitations.py b/atst/routes/portfolios/invitations.py index 5b1bbce6..d2d992b8 100644 --- a/atst/routes/portfolios/invitations.py +++ b/atst/routes/portfolios/invitations.py @@ -3,6 +3,7 @@ from flask import g, redirect, url_for, render_template from . import portfolios_bp from atst.domain.portfolios import Portfolios from atst.domain.invitations import Invitations +from atst.domain.task_orders import TaskOrders from atst.queue import queue from atst.utils.flash import formatted_flash as flash @@ -26,7 +27,7 @@ def accept_invitation(token): # - the logged-in user has multiple roles on the TO (e.g., KO and COR) # - the logged-in user has officer roles on multiple unsigned TOs for task_order in invite.portfolio.task_orders: - if g.current_user == task_order.contracting_officer: + if TaskOrders.is_officer_for_to(task_order, g.current_user): return redirect( url_for( "portfolios.view_task_order", @@ -34,14 +35,6 @@ def accept_invitation(token): task_order_id=task_order.id, ) ) - elif g.current_user == task_order.contracting_officer_representative: - return redirect( - url_for("task_orders.new", screen=4, task_order_id=task_order.id) - ) - elif g.current_user == task_order.security_officer: - return redirect( - url_for("task_orders.new", screen=4, task_order_id=task_order.id) - ) return redirect( url_for("portfolios.show_portfolio", portfolio_id=invite.portfolio.id) From e3956180b4fee9103d29774a670850828f78105b Mon Sep 17 00:00:00 2001 From: leigh-mil Date: Thu, 14 Mar 2019 20:08:57 -0400 Subject: [PATCH 2/4] Add property to TO that returns a list of the officers --- atst/models/task_order.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/atst/models/task_order.py b/atst/models/task_order.py index 96bf6ebf..64c29e7a 100644 --- a/atst/models/task_order.py +++ b/atst/models/task_order.py @@ -221,6 +221,14 @@ class TaskOrder(Base, mixins.TimestampsMixin): """ return self.so_invite and not self.security_officer + @property + def officers(self): + return [ + self.contracting_officer, + self.contracting_officer_representative, + self.security_officer, + ] + _OFFICER_PREFIXES = { "contracting_officer": "ko", "contracting_officer_representative": "cor", From 4bf70f10d854eb6e8edbdc3dd4c2ffe1f15492b2 Mon Sep 17 00:00:00 2001 From: leigh-mil Date: Thu, 14 Mar 2019 20:09:42 -0400 Subject: [PATCH 3/4] Use list of officer to determine if the current_user is an officer Delete unused method --- atst/domain/task_orders.py | 11 ----------- atst/routes/portfolios/invitations.py | 3 +-- 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/atst/domain/task_orders.py b/atst/domain/task_orders.py index 3aa5ba88..63239679 100644 --- a/atst/domain/task_orders.py +++ b/atst/domain/task_orders.py @@ -185,17 +185,6 @@ class TaskOrders(object): "{} is not an officer role on task orders".format(officer_type) ) - @classmethod - def is_officer_for_to(cls, task_order, user): - if ( - user == task_order.contracting_officer - or user == task_order.contracting_officer_representative - or user == task_order.security_officer - ): - return True - else: - return False - @classmethod def add_dd_254(user, task_order, dd_254_data): dd_254 = DD254(**dd_254_data) diff --git a/atst/routes/portfolios/invitations.py b/atst/routes/portfolios/invitations.py index d2d992b8..269ca21d 100644 --- a/atst/routes/portfolios/invitations.py +++ b/atst/routes/portfolios/invitations.py @@ -3,7 +3,6 @@ from flask import g, redirect, url_for, render_template from . import portfolios_bp from atst.domain.portfolios import Portfolios from atst.domain.invitations import Invitations -from atst.domain.task_orders import TaskOrders from atst.queue import queue from atst.utils.flash import formatted_flash as flash @@ -27,7 +26,7 @@ def accept_invitation(token): # - the logged-in user has multiple roles on the TO (e.g., KO and COR) # - the logged-in user has officer roles on multiple unsigned TOs for task_order in invite.portfolio.task_orders: - if TaskOrders.is_officer_for_to(task_order, g.current_user): + if g.current_user in task_order.officers: return redirect( url_for( "portfolios.view_task_order", From 795c9cd4d6120ae3d5604425b91786faab17bc7a Mon Sep 17 00:00:00 2001 From: leigh-mil Date: Thu, 14 Mar 2019 20:10:19 -0400 Subject: [PATCH 4/4] Add tests for COR and SO accepting invites --- tests/routes/portfolios/test_invitations.py | 74 +++++++++++++++++++++ 1 file changed, 74 insertions(+) diff --git a/tests/routes/portfolios/test_invitations.py b/tests/routes/portfolios/test_invitations.py index 4e382127..bcca3023 100644 --- a/tests/routes/portfolios/test_invitations.py +++ b/tests/routes/portfolios/test_invitations.py @@ -245,3 +245,77 @@ def test_contracting_officer_accepts_invite(monkeypatch, client, user_session): _external=True, ) assert response.headers["Location"] == to_review_url + + +def test_cor_accepts_invite(monkeypatch, client, user_session): + portfolio = PortfolioFactory.create() + user_info = UserFactory.dictionary() + task_order = TaskOrderFactory.create( + portfolio=portfolio, + cor_first_name=user_info["first_name"], + cor_last_name=user_info["last_name"], + cor_email=user_info["email"], + cor_phone_number=user_info["phone_number"], + cor_dod_id=user_info["dod_id"], + cor_invite=True, + ) + + # create contracting officer representative + user_session(portfolio.owner) + client.post(url_for("task_orders.invite", task_order_id=task_order.id)) + + # contracting officer representative accepts invitation + user = Users.get_by_dod_id(user_info["dod_id"]) + token = user.invitations[0].token + monkeypatch.setattr( + "atst.domain.auth.should_redirect_to_user_profile", lambda *args: False + ) + user_session(user) + response = client.get(url_for("portfolios.accept_invitation", token=token)) + + # user is redirected to the task order review page + assert response.status_code == 302 + to_review_url = url_for( + "portfolios.view_task_order", + portfolio_id=task_order.portfolio_id, + task_order_id=task_order.id, + _external=True, + ) + assert response.headers["Location"] == to_review_url + + +def test_so_accepts_invite(monkeypatch, client, user_session): + portfolio = PortfolioFactory.create() + user_info = UserFactory.dictionary() + task_order = TaskOrderFactory.create( + portfolio=portfolio, + so_first_name=user_info["first_name"], + so_last_name=user_info["last_name"], + so_email=user_info["email"], + so_phone_number=user_info["phone_number"], + so_dod_id=user_info["dod_id"], + so_invite=True, + ) + + # create security officer + user_session(portfolio.owner) + client.post(url_for("task_orders.invite", task_order_id=task_order.id)) + + # security officer accepts invitation + user = Users.get_by_dod_id(user_info["dod_id"]) + token = user.invitations[0].token + monkeypatch.setattr( + "atst.domain.auth.should_redirect_to_user_profile", lambda *args: False + ) + user_session(user) + response = client.get(url_for("portfolios.accept_invitation", token=token)) + + # user is redirected to the task order review page + assert response.status_code == 302 + to_review_url = url_for( + "portfolios.view_task_order", + portfolio_id=task_order.portfolio_id, + task_order_id=task_order.id, + _external=True, + ) + assert response.headers["Location"] == to_review_url