Coverage for middle_layer/review/application_layer/services/update_review_rules_service.py: 62.50%
32 statements
« prev ^ index » next coverage.py v7.10.5, created at 2026-03-09 06:13 +0000
« prev ^ index » next coverage.py v7.10.5, created at 2026-03-09 06:13 +0000
1# Copyright 2024 Associated Universities, Inc.
2#
3# This file is part of Telescope Time Allocation Tools (TTAT).
4#
5# TTAT is free software: you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation, either version 3 of the License, or
8# any later version.
9#
10# TTAT is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with TTAT. If not, see <https://www.gnu.org/licenses/>.
17#
18from typing import NamedTuple
21class PPRUpdatePermission(NamedTuple):
22 """
23 Represents the kinds of update that are permitted for a PPR update request.
25 can_update: True if the update can be performed
26 can_update_score: True if the score can also be updated
27 message: a message describing the reason why the update cannot be performed
28 """
30 can_update: bool
31 can_update_score: bool
32 message: str
35def check_ppr_update_permitted(
36 is_tta_member: bool, is_chair: bool, is_conflicted: bool, review_type: str, review_state: str
37) -> PPRUpdatePermission:
38 """
39 Ascertain whether or not this user is allowed to update a PPR Proposal Review, based on their access level,
40 whether or not they are conflicted, the chair, a TTA member, what sort of review they have done and what state
41 that review is in.
43 :param is_tta_member: True if this is on behalf of a TTA member
44 :param is_chair: True if this is on behalf of the chair of the relevant SRP
45 :param is_conflicted: True if this is on behalf of a conflicted reviewer
46 :param review_type: ReviewType, probably "Primary," "Secondary," or "Tertiary"
47 :param review_state: "Completed" or some other state from ReviewStates ("Blank," "Saved," "Closed," "Finalized")
49 :return: an UpdateResponse indicating whether the update is permitted, whether the score can also be updated,
50 and if the update is not permitted, a reason why.
51 """
52 # This method, while short, covers a lot of edge cases. There are two other ways to get a feel for the solution
53 # set of this in the tests:
54 #
55 # 1. The first set of tests, which describe the expected behavior declaratively and use parameterization
56 # 2. The second set of tests, which compare the behavior to a spreadsheet of expected behaviors.
57 is_primary = review_type == "Primary"
58 is_secondary = review_type == "Secondary"
59 is_unconflicted_chair = is_chair and not is_conflicted
61 # User requesting update doesn't have permissions to do so - must be tta, prim, sec, or unconflicted chair
62 if not is_tta_member and not (is_primary or is_secondary or is_unconflicted_chair):
63 return PPRUpdatePermission(False, False, "not authorized to update")
65 # User requesting update is conflicted and therefore cannot update
66 if not is_tta_member and is_conflicted:
67 return PPRUpdatePermission(False, False, "is conflicted")
69 if not is_tta_member and review_state == "Closed":
70 return PPRUpdatePermission(False, False, "is Closed")
72 # If PPRProposalReview is completed, primary and secondary reviewers can not update it
73 if review_state == "Completed" and not (is_tta_member or is_unconflicted_chair):
74 return PPRUpdatePermission(False, False, "is already completed")
76 # Only TTA members can update a finalized PPR review
77 if review_state == "Finalized" and not is_tta_member:
78 return PPRUpdatePermission(False, False, "is not authorized to update finalized")
80 return PPRUpdatePermission(
81 True, is_chair or is_tta_member, "can update because TTA Member" if is_tta_member else ""
82 )
85def generate_all_ppr_update_scenarios_csv(filename: str):
86 import csv
88 with open(filename, "w", newline="") as outputfile:
89 out = csv.writer(outputfile)
90 out.writerow(
91 [
92 "is_tta_member",
93 "is_chair",
94 "is_conflicted",
95 "review_type",
96 "review_state",
97 "can update?",
98 "can update score?",
99 "reason",
100 ]
101 )
102 for is_tta_member in [True, False]:
103 for is_chair in [True, False]:
104 for is_conflicted in [True, False]:
105 for review_type in ["Primary", "Secondary", "Tertiary", "Blank"]:
106 for review_state in ["Blank", "Saved", "Completed", "Finalized"]:
107 result = check_ppr_update_permitted(
108 is_tta_member, is_chair, is_conflicted, review_type, review_state
109 )
110 out.writerow(
111 [
112 "TTA MEMBER" if is_tta_member else "",
113 "CHAIR" if is_chair else "",
114 "CONFLICTED" if is_conflicted else "",
115 review_type,
116 review_state,
117 result.can_update,
118 result.can_update_score,
119 result.message,
120 ]
121 )