Skip to content

Commit 08470f4

Browse files
author
AndrewTorrance
authored
Feature/support multiple internal users (#140)
* initial commit - wip - for visibility by other devs. tests not passing * more wip to share with other devs * wip * existing tests passing with support for internal user searches not using user id . I.e a prerequisite to having multiple internal users * Removed unused versioning code * removed unused versioning code * Merge with Master * fixing inadvertent change from 'database' to 'repository' in strings * changes for pr comments
1 parent 47da096 commit 08470f4

26 files changed

+358
-136
lines changed

.pylintrc

+1-1
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,7 @@ max-nested-blocks=5
193193
[FORMAT]
194194

195195
# Maximum number of characters on a single line.
196-
max-line-length=150
196+
max-line-length=160
197197

198198
# Regexp for a line that is allowed to be longer than the limit.
199199
ignore-long-lines=^\s*(# )?<?https?://\S+>?$

run_tests.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
os.environ['APP_SETTINGS'] = 'TestConfig'
88

99
from behave import __main__ as behave_executable
10-
behave = behave_executable.main()
10+
behave_errors = behave_executable.main()
1111

1212
test_dirs = os.listdir('./tests')
1313
suites_list = []
@@ -18,6 +18,6 @@
1818
suite = loader.discover(test_path)
1919
suites_list.append(suite)
2020
result = unittest.TextTestRunner(verbosity=2).run(suite)
21-
i = len(result.failures) + len(result.errors)
22-
if i != 0 or behave == 1:
21+
if result.failures or result.errors or behave_errors:
2322
sys.exit(1)
23+

secure_message/api_mocks/internal_user_service_mock.py

+42-6
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,50 @@
77

88
class InternalUserServiceMock:
99
def __init__(self):
10-
self.internal_user_dict = {"Someuuid": {"id": "Someuuid",
11-
"firstName": "fred",
12-
"lastName": "flinstone",
13-
"emailAddress": "mock@email.com"}}
10+
self.internal_user_dict = {
11+
"Someuuid": {
12+
"id": "Someuuid",
13+
"firstName": "fred",
14+
"lastName": "flinstone",
15+
"emailAddress": "mock@email.com"
16+
},
17+
"01b51fcc-ed43-4cdb-ad1c-450f9986859b": {
18+
"id": "01b51fcc-ed43-4cdb-ad1c-450f9986859b",
19+
"firstName": "fred",
20+
"lastName": "flinstone",
21+
"emailAddress": "mock@email.com"
22+
},
23+
"f62dfda8-73b0-4e0e-97cf-1b06327a6712": {
24+
"id": "01b51fcc-ed43-4cdb-ad1c-450f9986859b",
25+
"firstName": "fred",
26+
"lastName": "flinstone",
27+
"emailAddress": "mock@email.com"
28+
},
29+
"Tej": {
30+
"id": "Tej",
31+
"firstName": "Tejas",
32+
"lastName": "patel",
33+
"emailAddress": "mock@email.com"
34+
},
35+
"BRES": {
36+
"id": "TRES",
37+
"firstName": "BRES",
38+
"lastName": "",
39+
"emailAddress": "mock@email.com"
40+
},
41+
"SomeStringUntilWeGetIds": {
42+
"id": "SomeStringUntilWeGetIds",
43+
"firstName": "MadeUpUser",
44+
"lastName": "",
45+
"emailAddress": "mock@email.com"
46+
}
47+
}
1448

1549
def get_user_details(self, uuid):
1650
"""gets the user details from the internal user service"""
1751
logger.debug("getting mock user details for uaa")
1852
if uuid:
19-
return self.internal_user_dict[uuid], 200
20-
return "error retrieving details"
53+
found = self.internal_user_dict.get(uuid)
54+
if found:
55+
return found, 200
56+
return "error retrieving details", 404

secure_message/application.py

+2-22
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,9 @@
1010

1111
from secure_message.exception.exceptions import MissingEnvironmentVariable
1212
from secure_message.repository import database
13-
from secure_message.resources.health import Health, DatabaseHealth, HealthDetails
14-
from secure_message.resources.info import Info
15-
from secure_message.resources.labels import Labels
16-
from secure_message.resources.messages import MessageList, MessageSend, MessageById, MessageModifyById
1713
from secure_message.authentication.authenticator import authenticate
18-
from secure_message.resources.drafts import DraftSave, DraftById, DraftModifyById, DraftList
19-
from secure_message.resources.threads import ThreadById, ThreadList
2014
from secure_message.logger_config import logger_initial_config
21-
15+
from secure_message.v1.application import set_v1_resources
2216

2317
logger_initial_config(service_name='ras-secure-message')
2418
logger = wrap_logger(logging.getLogger(__name__))
@@ -48,21 +42,7 @@ def create_app(config=None):
4842
database.db.create_all()
4943
database.db.session.commit() # NOQA pylint:disable=no-member
5044

51-
api.add_resource(Health, '/health')
52-
api.add_resource(DatabaseHealth, '/health/db')
53-
api.add_resource(HealthDetails, '/health/details')
54-
api.add_resource(Info, '/info')
55-
api.add_resource(MessageList, '/messages')
56-
api.add_resource(MessageSend, '/message/send')
57-
api.add_resource(MessageById, '/message/<message_id>')
58-
api.add_resource(MessageModifyById, '/message/<message_id>/modify')
59-
api.add_resource(DraftSave, '/draft/save')
60-
api.add_resource(DraftModifyById, '/draft/<draft_id>/modify')
61-
api.add_resource(DraftById, '/draft/<draft_id>')
62-
api.add_resource(ThreadById, '/thread/<thread_id>')
63-
api.add_resource(DraftList, '/drafts')
64-
api.add_resource(ThreadList, '/threads')
65-
api.add_resource(Labels, '/labels')
45+
set_v1_resources(api)
6646

6747
@app.before_request
6848
def before_request(): # NOQA pylint:disable=unused-variable

secure_message/repository/database.py

+3-9
Original file line numberDiff line numberDiff line change
@@ -74,12 +74,7 @@ def serialize(self, user, body_summary=False):
7474
'_links': '',
7575
'labels': []}
7676

77-
if user.is_internal:
78-
actor = constants.BRES_USER
79-
else:
80-
actor = user.user_uuid
81-
82-
self._populate_to_and_from(actor, message)
77+
self._populate_to_and_from(user, message)
8378

8479
self._populate_events(message)
8580

@@ -100,11 +95,10 @@ def _populate_events(self, message):
10095
elif row.event == EventsApi.READ.value:
10196
message['read_date'] = str(row.date_time)
10297

103-
def _populate_to_and_from(self, actor, message):
98+
def _populate_to_and_from(self, user, message):
10499
for row in self.statuses:
105-
if row.actor == actor:
100+
if row.actor == user.user_uuid or (user.is_internal and (row.actor == constants.NON_SPECIFIC_INTERNAL_USER or row.actor == constants.BRES_USER)):
106101
message['labels'].append(row.label)
107-
108102
if row.label == Labels.INBOX.value:
109103
message['msg_to'].append(row.actor)
110104
elif row.label == Labels.SENT.value:

secure_message/repository/modifier.py

+3-1
Original file line numberDiff line numberDiff line change
@@ -91,16 +91,18 @@ def del_draft(draft_id):
9191
del_draft_status = "DELETE FROM securemessage.status WHERE msg_id='{0}' AND label='{1}'".format(draft_id, Labels.DRAFT.value)
9292
del_draft_event = "DELETE FROM securemessage.events WHERE msg_id='{0}'".format(draft_id)
9393
del_draft_inbox_status = "DELETE FROM securemessage.status WHERE msg_id='{0}' AND label='{1}'".format(draft_id, Labels.DRAFT_INBOX.value)
94+
del_actors = "DELETE FROM securemessage.actors where msg_id='{0}'".format(draft_id)
9495
del_draft_msg = "DELETE FROM securemessage.secure_message WHERE msg_id='{0}'".format(draft_id)
9596

9697
try:
9798
db.get_engine(app=db.get_app()).execute(del_draft_status)
9899
db.get_engine(app=db.get_app()).execute(del_draft_inbox_status)
99100
db.get_engine(app=db.get_app()).execute(del_draft_event)
101+
db.get_engine(app=db.get_app()).execute(del_actors)
100102
db.get_engine(app=db.get_app()).execute(del_draft_msg)
101103

102104
except Exception as e:
103-
logger.error('Error deleting draft from database', msg_id=draft_id, error=e)
105+
logger.exception('Error deleting draft from database', msg_id=draft_id, error=e)
104106
raise InternalServerError(description="Error deleting draft from database")
105107

106108
@staticmethod

secure_message/repository/retriever.py

+97-26
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,22 @@
1616

1717
class Retriever:
1818
"""Created when retrieving messages"""
19+
1920
@staticmethod
2021
def retrieve_message_list(page, limit, user, ru_id=None, survey=None, cc=None, ce=None, label=None, descend=True):
2122
"""returns list of messages from db"""
22-
conditions = []
23-
status_conditions = []
2423

2524
if user.is_respondent:
26-
status_conditions.append(Status.actor == str(user.user_uuid))
27-
else:
28-
status_conditions.append(Status.actor == constants.BRES_USER)
25+
return Retriever._retrieve_message_list_respondent(page, limit, user=user, ru_id=ru_id, survey=survey,
26+
cc=cc, ce=ce, label=label, descend=descend)
27+
return Retriever._retrieve_message_list_internal(page, limit, ru_id=ru_id, survey=survey,
28+
cc=cc, ce=ce, label=label, descend=descend)
29+
30+
@staticmethod
31+
def _retrieve_message_list_respondent(page, limit, user, ru_id, survey, cc, ce, label, descend):
32+
"""returns list of messages from db"""
33+
conditions = []
34+
status_conditions = [Status.actor == str(user.user_uuid)]
2935

3036
if label is not None:
3137
status_conditions.append(Status.label == str(label))
@@ -46,38 +52,95 @@ def retrieve_message_list(page, limit, user, ru_id=None, survey=None, cc=None, c
4652

4753
try:
4854
t = db.session.query(SecureMessage.msg_id, func.max(Events.date_time) # pylint:disable=no-member
49-
.label('max_date'))\
55+
.label('max_date')) \
5056
.join(Events).join(Status).outerjoin(Actors) \
5157
.filter(and_(*conditions)) \
5258
.filter(and_(*status_conditions)) \
5359
.filter(or_(Events.event == EventsApi.SENT.value, Events.event == EventsApi.DRAFT_SAVED.value)) \
5460
.group_by(SecureMessage.msg_id).subquery('t')
5561

5662
if descend:
57-
result = SecureMessage.query\
58-
.filter(SecureMessage.msg_id == t.c.msg_id) \
59-
.order_by(t.c.max_date.desc()).paginate(page, limit, False)
60-
63+
order = t.c.max_date.desc()
6164
else:
62-
result = SecureMessage.query \
63-
.filter(SecureMessage.msg_id == t.c.msg_id) \
64-
.order_by(t.c.max_date.asc()).paginate(page, limit, False)
65+
order = t.c.max_date.asc()
66+
67+
result = SecureMessage.query \
68+
.filter(SecureMessage.msg_id == t.c.msg_id) \
69+
.order_by(order).paginate(page, limit, False)
6570

6671
except Exception as e:
6772
logger.error('Error retrieving messages from database', error=e)
6873
raise InternalServerError(description="Error retrieving messages from database")
6974

7075
return True, result
7176

77+
@staticmethod
78+
def _retrieve_message_list_internal(page, limit, ru_id, survey, cc, ce, label, descend):
79+
"""returns list of messages from db"""
80+
conditions = []
81+
status_reject_conditions = []
82+
valid_statuses = []
83+
actor_conditions = []
84+
85+
if label is not None:
86+
valid_statuses.append(label)
87+
if label in [Labels.INBOX.value, Labels.ARCHIVE.value, Labels.UNREAD.value]:
88+
actor_conditions.append(Actors.sent_from_internal == False) # NOQA pylint:disable=singleton-comparison
89+
if label in [Labels.DRAFT.value, Labels.SENT.value]:
90+
actor_conditions.append(Actors.sent_from_internal == True) # NOQA pylint:disable=singleton-comparison
91+
else:
92+
status_reject_conditions.append(Labels.DRAFT_INBOX.value)
93+
valid_statuses = [Labels.INBOX.value, Labels.DRAFT.value]
94+
actor_conditions.append(True)
95+
96+
if ru_id is not None:
97+
conditions.append(SecureMessage.ru_id == str(ru_id))
98+
99+
if survey is not None:
100+
conditions.append(SecureMessage.survey == str(survey))
101+
102+
if cc is not None:
103+
conditions.append(SecureMessage.collection_case == str(cc))
104+
105+
if ce is not None:
106+
conditions.append(SecureMessage.collection_exercise == str(ce))
107+
108+
try:
109+
t = db.session.query(SecureMessage.msg_id, func.max(Events.date_time) # pylint:disable=no-member ~ below used to obtain not in
110+
.label('max_date')) \
111+
.join(Events).join(Status).outerjoin(Actors) \
112+
.filter(and_(*conditions)) \
113+
.filter(or_(*actor_conditions)) \
114+
.filter(~Status.label.in_(status_reject_conditions)) \
115+
.filter(Status.label.in_(valid_statuses)) \
116+
.filter(or_(Events.event == EventsApi.SENT.value, Events.event == EventsApi.DRAFT_SAVED.value)) \
117+
.group_by(SecureMessage.msg_id).subquery('t')
118+
119+
if descend:
120+
order = t.c.max_date.desc()
121+
else:
122+
order = t.c.max_date.asc()
123+
124+
result = SecureMessage.query \
125+
.filter(SecureMessage.msg_id == t.c.msg_id) \
126+
.order_by(order).paginate(page, limit, False)
127+
128+
except Exception as e:
129+
logger.exception('Error retrieving messages from database', error=e)
130+
raise InternalServerError(description="Error retrieving messages from database")
131+
132+
return True, result
133+
72134
@staticmethod
73135
def unread_message_count(user):
74136
"""Count users unread messages"""
75137
status_conditions = []
76138
status_conditions.append(Status.actor == str(user.user_uuid))
77139
try:
78-
result = SecureMessage.query.join(Status).filter(and_(*status_conditions)).filter(Status.label == 'UNREAD').count()
140+
result = SecureMessage.query.join(Status).filter(and_(*status_conditions)).filter(
141+
Status.label == 'UNREAD').count()
79142
except Exception as e:
80-
logger.error('Error retrieving count of unread messages from database', error=e)
143+
logger.exception('Error retrieving count of unread messages from database', error=e)
81144
raise InternalServerError(description="Error retrieving count of unread messages from database")
82145
return result
83146

@@ -86,33 +149,37 @@ def retrieve_thread_list(page, limit, user):
86149
"""returns list of threads from db"""
87150
status_conditions = []
88151
conditions = []
152+
actor_conditions = []
89153

90154
if user.is_respondent:
91-
status_conditions.append(Status.actor == str(user.user_uuid))
155+
actor_conditions.append(Status.actor == str(user.user_uuid))
92156
else:
93-
status_conditions.append(Status.actor == constants.BRES_USER)
157+
actor_conditions.append(Status.actor == str(user.user_uuid))
158+
actor_conditions.append(Status.actor == constants.BRES_USER)
159+
actor_conditions.append(Status.actor == constants.NON_SPECIFIC_INTERNAL_USER)
94160

95161
status_conditions.append(Status.label != Labels.DRAFT_INBOX.value)
96162

97163
try:
98-
t = db.session.query(SecureMessage.thread_id, func.max(Events.date_time) # pylint:disable=no-member
99-
.label('max_date')) \
164+
t = db.session.query(SecureMessage.thread_id, func.max(Events.id) # pylint:disable=no-member
165+
.label('max_id')) \
100166
.join(Events).join(Status) \
101167
.filter(and_(*status_conditions)) \
168+
.filter(or_(*actor_conditions)) \
102169
.filter(or_(Events.event == EventsApi.SENT.value, Events.event == EventsApi.DRAFT_SAVED.value)) \
103170
.group_by(SecureMessage.thread_id).subquery('t')
104171

105172
conditions.append(SecureMessage.thread_id == t.c.thread_id)
106-
conditions.append(Events.date_time == t.c.max_date)
173+
conditions.append(Events.id == t.c.max_id)
107174

108175
result = SecureMessage.query.join(Events).join(Status) \
109176
.filter(or_(Events.event == EventsApi.SENT.value, Events.event == EventsApi.DRAFT_SAVED.value)) \
110177
.filter(and_(*conditions)) \
111178
.filter(and_(*status_conditions)) \
112-
.order_by(t.c.max_date.desc()).paginate(page, limit, False)
179+
.order_by(t.c.max_id.desc()).paginate(page, limit, False)
113180

114181
except Exception as e:
115-
logger.error('Error retrieving messages from database', error=e)
182+
logger.exception('Error retrieving messages from database', error=e)
116183
raise InternalServerError(description="Error retrieving messages from database")
117184

118185
return True, result
@@ -137,11 +204,14 @@ def retrieve_message(message_id, user):
137204
def retrieve_thread(thread_id, user, page, limit):
138205
"""returns paginated list of messages for thread id"""
139206
status_conditions = []
207+
actor_conditions = []
140208

141209
if user.is_respondent:
142-
status_conditions.append(Status.actor == str(user.user_uuid))
210+
actor_conditions.append(Status.actor == str(user.user_uuid))
143211
else:
144-
status_conditions.append(Status.actor == constants.BRES_USER)
212+
actor_conditions.append(Status.actor == str(user.user_uuid))
213+
actor_conditions.append(Status.actor == constants.BRES_USER)
214+
actor_conditions.append(Status.actor == constants.NON_SPECIFIC_INTERNAL_USER)
145215

146216
status_conditions.append(Status.label != Labels.DRAFT_INBOX.value)
147217

@@ -150,6 +220,7 @@ def retrieve_thread(thread_id, user, page, limit):
150220
result = SecureMessage.query.join(Events).join(Status) \
151221
.filter(SecureMessage.thread_id == thread_id) \
152222
.filter(and_(*status_conditions)) \
223+
.filter(or_(*actor_conditions)) \
153224
.filter(or_(Events.event == EventsApi.SENT.value, Events.event == EventsApi.DRAFT_SAVED.value)) \
154225
.order_by(Events.date_time.desc()).paginate(page, limit, False)
155226

@@ -168,7 +239,7 @@ def retrieve_draft(message_id, user):
168239
"""returns single draft from db"""
169240

170241
try:
171-
result = SecureMessage.query.filter(SecureMessage.msg_id == message_id)\
242+
result = SecureMessage.query.filter(SecureMessage.msg_id == message_id) \
172243
.filter(SecureMessage.statuses.any(Status.label == Labels.DRAFT.value)).first()
173244
if result is None:
174245
logger.error('Draft does not exist', message_id=message_id)
@@ -203,7 +274,7 @@ def check_db_connection():
203274
def check_msg_id_is_a_draft(draft_id, user):
204275
"""Check msg_id is that of a valid draft and return true/false if no ID is present"""
205276
try:
206-
result = SecureMessage.query.filter(SecureMessage.msg_id == draft_id)\
277+
result = SecureMessage.query.filter(SecureMessage.msg_id == draft_id) \
207278
.filter(SecureMessage.statuses.any(Status.label == Labels.DRAFT.value)).first()
208279
except Exception as e:
209280
logger.error('Error retrieving message from database', error=e)

0 commit comments

Comments
 (0)