Skip to content

Commit 1d1e932

Browse files
Log referral queries and bulk deletes in ReferralService (#608)
1 parent 7a0b868 commit 1d1e932

4 files changed

Lines changed: 154 additions & 79 deletions

File tree

‎app/routers/registrations.py‎

Lines changed: 3 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
1-
import logging
21
from typing import Annotated, Any
32

4-
import gfmodules.logging as gflog
53
from fastapi import APIRouter, Body, Depends, Query, Response
64

75
from app.dependencies import (
86
get_pseudonym_resolver,
97
get_referral_service,
108
)
11-
from app.logging.events import Log
129
from app.models.auth.context import AuthContext
1310
from app.models.auth.data import AuthorizationScope
1411
from app.models.registrations import (
@@ -21,7 +18,6 @@
2118
from app.services.pseudonym_resolver import PseudonymResolver
2219
from app.services.referral_service import ReferralService
2320

24-
logger = logging.getLogger(__name__)
2521
router = APIRouter(tags=["Registrations"], prefix="/registrations")
2622

2723

@@ -36,14 +32,7 @@ def get_registration(
3632
) -> Any:
3733
resolved = pseudonym_resolver.resolve(params.oprf_pseudonym())
3834

39-
results = referral_service.get_many(ura_number=ctx.claims.ura_number, encrypted_pseudonym=resolved.encrypted)
40-
41-
gflog.emit(
42-
logger,
43-
Log.REFERRALS_QUERIED,
44-
"Referrals queried",
45-
fields={"result_count": len(results)},
46-
)
35+
_, results = referral_service.query(ctx.claims.ura_number, encrypted_pseudonym=resolved.encrypted)
4736

4837
return RegistrationList.from_entities(results)
4938

@@ -83,20 +72,11 @@ def delete_registration(
8372
source: Annotated[str, Depends(require_managing_source)],
8473
) -> Any:
8574
resolved = pseudonym_resolver.resolve(params.oprf_pseudonym())
86-
encrypted_pseudonym = resolved.encrypted
8775

88-
deleted_count = referral_service.delete_many(
76+
referral_service.delete_many(
8977
ura_number=ctx.claims.ura_number,
90-
encrypted_pseudonym=encrypted_pseudonym,
78+
encrypted_pseudonym=resolved.encrypted,
9179
source=source,
9280
)
9381

94-
if deleted_count > 0:
95-
gflog.emit(
96-
logger,
97-
Log.ALL_PATIENT_REFERRALS_DELETED,
98-
"All patient referrals deleted",
99-
fields={"pseudonym_hash": str(encrypted_pseudonym), "deleted_count": deleted_count},
100-
)
101-
10282
return Response(status_code=204)

‎app/services/fhir/localization_list.py‎

Lines changed: 4 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -90,25 +90,13 @@ def query(
9090
resolved.encrypted, authenticated_ura, limit=page_size, offset=params.offset
9191
)
9292
else:
93-
encrypted_pseudonym = resolved.encrypted if resolved else None
94-
total = self.referral_service.count(
95-
encrypted_pseudonym=encrypted_pseudonym,
93+
total, referrals = self.referral_service.query(
94+
authenticated_ura,
95+
encrypted_pseudonym=(resolved.encrypted if resolved else None),
9696
source=params.source,
97-
ura_number=authenticated_ura,
98-
)
99-
referrals = self.referral_service.get_many(
100-
encrypted_pseudonym=encrypted_pseudonym,
101-
source=params.source,
102-
ura_number=authenticated_ura,
10397
limit=page_size,
10498
offset=params.offset,
10599
)
106-
gflog.emit(
107-
logger,
108-
Log.REFERRALS_QUERIED,
109-
"Referrals queried",
110-
fields={"result_count": total},
111-
)
112100

113101
bundle = Bundle(
114102
type="searchset",
@@ -158,37 +146,21 @@ def delete_by_query(
158146
authenticated_ura: UraNumber,
159147
source: str,
160148
) -> tuple[OperationOutcome, int]:
161-
ura_number = authenticated_ura
162-
163149
resolved: ResolvedPseudonym | None = None
164150
if params.subject:
165151
resolved = self._pseudonym_resolver.resolve(PseudonymToken(params.subject))
166152

167153
deleted_count = self.referral_service.delete_many(
168154
encrypted_pseudonym=(resolved.encrypted if resolved else None),
169155
source=source,
170-
ura_number=ura_number,
156+
ura_number=authenticated_ura,
171157
)
172158

173159
if deleted_count < 1:
174160
return (
175161
OperationOutcome.make_error_outcome(code="not-found", msg="No resources matched the given criteria"),
176162
404,
177163
)
178-
if resolved is not None:
179-
gflog.emit(
180-
logger,
181-
Log.ALL_PATIENT_REFERRALS_DELETED,
182-
"All patient referrals deleted",
183-
fields={"pseudonym_hash": str(resolved.response), "deleted_count": deleted_count},
184-
)
185-
else:
186-
gflog.emit(
187-
logger,
188-
Log.ALL_URA_REFERRALS_DELETED,
189-
"All URA referrals deleted",
190-
fields={"deleted_count": deleted_count},
191-
)
192164
return (
193165
OperationOutcome.make_good_outcome(f"{deleted_count} resources have been deleted successfully"),
194166
200,

‎app/services/referral_service.py‎

Lines changed: 59 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,26 @@ def count(
131131
source=source,
132132
)
133133

134+
def query(
135+
self,
136+
ura_number: UraNumber,
137+
encrypted_pseudonym: EncryptedPseudonym | None = None,
138+
source: str | None = None,
139+
limit: int | None = None,
140+
offset: int = 0,
141+
) -> tuple[int, Sequence[ReferralEntity]]:
142+
total = self.count(ura_number=ura_number, encrypted_pseudonym=encrypted_pseudonym, source=source)
143+
referrals = self.get_many(
144+
ura_number=ura_number, encrypted_pseudonym=encrypted_pseudonym, source=source, limit=limit, offset=offset
145+
)
146+
gflog.emit(
147+
logger,
148+
Log.REFERRALS_QUERIED,
149+
"Referrals queried",
150+
fields={"result_count": total},
151+
)
152+
return total, referrals
153+
134154
def localize(
135155
self,
136156
encrypted_pseudonym: EncryptedPseudonym,
@@ -165,7 +185,45 @@ def delete_many(
165185
ura_number: UraNumber,
166186
encrypted_pseudonym: EncryptedPseudonym | None = None,
167187
source: str | None = None,
168-
id: str | UUID | None = None,
188+
) -> int:
189+
deleted_count = self._delete(ura_number=ura_number, encrypted_pseudonym=encrypted_pseudonym, source=source)
190+
if deleted_count < 1:
191+
return deleted_count
192+
193+
if encrypted_pseudonym is not None:
194+
gflog.emit(
195+
logger,
196+
Log.ALL_PATIENT_REFERRALS_DELETED,
197+
"All patient referrals deleted",
198+
fields={"pseudonym_hash": str(encrypted_pseudonym), "deleted_count": deleted_count},
199+
)
200+
else:
201+
gflog.emit(
202+
logger,
203+
Log.ALL_URA_REFERRALS_DELETED,
204+
"All URA referrals deleted",
205+
fields={"deleted_count": deleted_count},
206+
)
207+
return deleted_count
208+
209+
def delete_by_id(self, id: UUID, ura_number: UraNumber, source: str) -> None:
210+
referral = self._get_owned(id, ura_number)
211+
if self._delete(ura_number=ura_number, source=source, id=id) < 1:
212+
raise NotFoundError()
213+
214+
gflog.emit(
215+
logger,
216+
Log.REFERRAL_DELETED,
217+
"Referral deleted",
218+
fields={"pseudonym_hash": referral.pseudonym},
219+
)
220+
221+
def _delete(
222+
self,
223+
ura_number: UraNumber,
224+
encrypted_pseudonym: EncryptedPseudonym | None = None,
225+
source: str | None = None,
226+
id: UUID | None = None,
169227
) -> int:
170228
with self.database.get_db_session() as session:
171229
repo = session.get_repository(ReferralRepository)
@@ -181,15 +239,3 @@ def delete_many(
181239
if affected_rows > 0:
182240
self.usage_metrics.deleted(ura_number, affected_rows)
183241
return affected_rows
184-
185-
def delete_by_id(self, id: UUID, ura_number: UraNumber, source: str) -> None:
186-
referral = self._get_owned(id, ura_number)
187-
if self.delete_many(ura_number=ura_number, source=source, id=id) < 1:
188-
raise NotFoundError()
189-
190-
gflog.emit(
191-
logger,
192-
Log.REFERRAL_DELETED,
193-
"Referral deleted",
194-
fields={"pseudonym_hash": referral.pseudonym},
195-
)

‎tests/services/test_referral_service.py‎

Lines changed: 88 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -230,27 +230,65 @@ def test_delet_by_id_should_raise_exception_when_no_match_found(
230230
assert "Record not found" in str(exec.value)
231231

232232

233+
def test_delete_many_with_a_pseudonym_logs_patient_scope(
234+
referral_service: ReferralService,
235+
key_info_service: KeyInfoService,
236+
ura_number: UraNumber,
237+
caplog: pytest.LogCaptureFixture,
238+
) -> None:
239+
store_referral(referral_service, key_info_service, ura_number)
240+
241+
with caplog.at_level(logging.INFO):
242+
deleted = referral_service.delete_many(ura_number, encrypted_pseudonym=PSEUDONYM, source=SOURCE)
243+
244+
(fields,) = logged_fields(caplog, Log.ALL_PATIENT_REFERRALS_DELETED)
245+
assert deleted == 1
246+
assert fields["pseudonym_hash"] == str(PSEUDONYM)
247+
assert fields["deleted_count"] == 1
248+
249+
250+
def test_delete_many_without_a_pseudonym_logs_ura_scope(
251+
referral_service: ReferralService,
252+
key_info_service: KeyInfoService,
253+
ura_number: UraNumber,
254+
caplog: pytest.LogCaptureFixture,
255+
) -> None:
256+
store_referral(referral_service, key_info_service, ura_number)
257+
258+
with caplog.at_level(logging.INFO):
259+
referral_service.delete_many(ura_number, source=SOURCE)
260+
261+
(fields,) = logged_fields(caplog, Log.ALL_URA_REFERRALS_DELETED)
262+
assert fields["deleted_count"] == 1
263+
264+
265+
def test_delete_many_logs_nothing_when_nothing_matched(
266+
referral_service: ReferralService,
267+
ura_number: UraNumber,
268+
caplog: pytest.LogCaptureFixture,
269+
) -> None:
270+
with caplog.at_level(logging.INFO):
271+
deleted = referral_service.delete_many(ura_number, encrypted_pseudonym=PSEUDONYM)
272+
273+
assert deleted == 0
274+
assert logged_fields(caplog, Log.ALL_PATIENT_REFERRALS_DELETED) == []
275+
assert logged_fields(caplog, Log.ALL_URA_REFERRALS_DELETED) == []
276+
277+
233278
def test_localize_logs_success_with_the_pseudonym_and_result_count(
234279
referral_service: ReferralService,
235280
key_info_service: KeyInfoService,
236281
ura_number: UraNumber,
237282
caplog: pytest.LogCaptureFixture,
238283
) -> None:
239-
key_info = key_info_service.add_one("label-1", "AES_CBC")
240-
pseudonym = EncryptedPseudonym("ps-1", "123")
241-
referral_service.add_one(
242-
encrypted_pseudonym=pseudonym,
243-
ura_number=ura_number,
244-
source="SomeDevice",
245-
key_id=key_info.id,
246-
)
284+
store_referral(referral_service, key_info_service, ura_number)
247285

248286
with caplog.at_level(logging.INFO):
249-
_, referrals = referral_service.localize(pseudonym, REQUESTING_URA)
287+
_, referrals = referral_service.localize(PSEUDONYM, REQUESTING_URA)
250288

251289
(fields,) = logged_fields(caplog, Log.LOCALIZATION_SUCCESS)
252290
assert len(referrals) == 1
253-
assert fields["pseudonym_hash"] == str(pseudonym)
291+
assert fields["pseudonym_hash"] == str(PSEUDONYM)
254292
assert fields["result_count"] == 1
255293

256294

@@ -259,8 +297,47 @@ def test_localize_logs_no_match_with_a_zero_result_count(
259297
caplog: pytest.LogCaptureFixture,
260298
) -> None:
261299
with caplog.at_level(logging.INFO):
262-
_, referrals = referral_service.localize(EncryptedPseudonym("ps-1", "123"), REQUESTING_URA)
300+
_, referrals = referral_service.localize(PSEUDONYM, REQUESTING_URA)
263301

264302
(fields,) = logged_fields(caplog, Log.LOCALIZATION_NO_MATCH)
265303
assert len(referrals) == 0
266304
assert fields["result_count"] == 0
305+
306+
307+
def test_localize_does_not_log_a_query(
308+
referral_service: ReferralService,
309+
caplog: pytest.LogCaptureFixture,
310+
) -> None:
311+
with caplog.at_level(logging.INFO):
312+
referral_service.localize(PSEUDONYM, REQUESTING_URA)
313+
314+
assert logged_fields(caplog, Log.REFERRALS_QUERIED) == []
315+
316+
317+
def test_query_logs_the_result_count(
318+
referral_service: ReferralService,
319+
key_info_service: KeyInfoService,
320+
ura_number: UraNumber,
321+
caplog: pytest.LogCaptureFixture,
322+
) -> None:
323+
store_referral(referral_service, key_info_service, ura_number)
324+
325+
with caplog.at_level(logging.INFO):
326+
_, referrals = referral_service.query(ura_number, encrypted_pseudonym=PSEUDONYM)
327+
328+
(fields,) = logged_fields(caplog, Log.REFERRALS_QUERIED)
329+
assert len(referrals) == 1
330+
assert fields["result_count"] == 1
331+
332+
333+
def test_query_is_scoped_to_the_callers_ura(
334+
referral_service: ReferralService,
335+
key_info_service: KeyInfoService,
336+
ura_number: UraNumber,
337+
) -> None:
338+
store_referral(referral_service, key_info_service, ura_number)
339+
340+
total, referrals = referral_service.query(REQUESTING_URA, encrypted_pseudonym=PSEUDONYM)
341+
342+
assert total == 0
343+
assert len(referrals) == 0

0 commit comments

Comments
 (0)