Skip to content

Commit 919c115

Browse files
Merge pull request #2003 from bcgov/fix/hamed-adjust-notification-filter-1916
Fix: Adjust Notification Filter for Analysts - 1916
2 parents 7c89e0a + 8944f48 commit 919c115

File tree

3 files changed

+120
-15
lines changed

3 files changed

+120
-15
lines changed

backend/lcfs/tests/notification/test_notification_services.py

+76-15
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import pytest
2+
import json
23
from unittest.mock import AsyncMock, MagicMock
4+
from lcfs.web.api.base import NotificationTypeEnum
35
from lcfs.db.models.user.Role import RoleEnum
46
from lcfs.db.models.notification import (
57
NotificationChannelSubscription,
@@ -8,9 +10,12 @@
810
from lcfs.web.api.notification.services import NotificationService
911
from lcfs.web.api.notification.repo import NotificationRepository
1012
from lcfs.web.api.notification.schema import (
13+
NotificationRequestSchema,
1114
SubscriptionSchema,
1215
NotificationMessageSchema,
1316
)
17+
from lcfs.web.api.email.services import CHESEmailService
18+
1419

1520
# Mock common data for reuse
1621
mock_notification_message = NotificationMessage(
@@ -34,13 +39,17 @@
3439
@pytest.fixture
3540
def notification_service():
3641
mock_repo = MagicMock(spec=NotificationRepository)
37-
service = NotificationService(repo=mock_repo)
38-
return service, mock_repo
42+
43+
mock_email_service = MagicMock(spec=CHESEmailService)
44+
mock_email_service.send_notification_email = AsyncMock()
45+
46+
service = NotificationService(repo=mock_repo, email_service=mock_email_service)
47+
return service, mock_repo, mock_email_service
3948

4049

4150
@pytest.mark.anyio
4251
async def test_get_notifications_by_user_id(notification_service):
43-
service, mock_repo = notification_service
52+
service, mock_repo, mock_email_service = notification_service
4453
user_id = 1
4554

4655
mock_repo.get_notification_messages_by_user = AsyncMock(
@@ -66,7 +75,7 @@ async def test_get_notifications_by_user_id(notification_service):
6675

6776
@pytest.mark.anyio
6877
async def test_get_notification_by_id(notification_service):
69-
service, mock_repo = notification_service
78+
service, mock_repo, mock_email_service = notification_service
7079
notification_id = 1
7180

7281
mock_notification = NotificationMessage(
@@ -95,7 +104,7 @@ async def test_get_notification_by_id(notification_service):
95104

96105
@pytest.mark.anyio
97106
async def test_count_unread_notifications_by_user_id(notification_service):
98-
service, mock_repo = notification_service
107+
service, mock_repo, mock_email_service = notification_service
99108
user_id = 1
100109
expected_unread_count = 5
101110

@@ -115,7 +124,7 @@ async def test_count_unread_notifications_by_user_id(notification_service):
115124

116125
@pytest.mark.anyio
117126
async def test_mark_notification_as_read(notification_service):
118-
service, mock_repo = notification_service
127+
service, mock_repo, mock_email_service = notification_service
119128
notification_id = 1
120129

121130
mock_notification = NotificationMessage(
@@ -143,7 +152,7 @@ async def mock_mark_as_read(notification_id):
143152

144153
@pytest.mark.anyio
145154
async def test_create_notification_message(notification_service):
146-
service, mock_repo = notification_service
155+
service, mock_repo, mock_email_service = notification_service
147156

148157
notification_data = NotificationMessageSchema(
149158
message="Test notification",
@@ -190,7 +199,7 @@ async def test_create_notification_message(notification_service):
190199

191200
@pytest.mark.anyio
192201
async def test_update_notification_message(notification_service):
193-
service, mock_repo = notification_service
202+
service, mock_repo, mock_email_service = notification_service
194203

195204
updated_data = NotificationMessageSchema(
196205
notification_message_id=1,
@@ -241,7 +250,7 @@ async def test_update_notification_message(notification_service):
241250

242251
@pytest.mark.anyio
243252
async def test_delete_notification_message(notification_service):
244-
service, mock_repo = notification_service
253+
service, mock_repo, mock_email_service = notification_service
245254

246255
user_id = 1
247256
notification_id = 123
@@ -269,7 +278,7 @@ async def test_delete_notification_message(notification_service):
269278

270279
@pytest.mark.anyio
271280
async def test_create_notification_channel_subscription(notification_service):
272-
service, mock_repo = notification_service
281+
service, mock_repo, mock_email_service = notification_service
273282

274283
subscription_data = SubscriptionSchema(
275284
is_enabled=True,
@@ -312,7 +321,7 @@ async def test_create_notification_channel_subscription(notification_service):
312321

313322
@pytest.mark.anyio
314323
async def test_get_notification_channel_subscriptions_by_user_id(notification_service):
315-
service, mock_repo = notification_service
324+
service, mock_repo, mock_email_service = notification_service
316325

317326
user_id = 1
318327
# Mock subscription data
@@ -349,7 +358,7 @@ async def test_get_notification_channel_subscriptions_by_user_id(notification_se
349358

350359
@pytest.mark.anyio
351360
async def test_get_notification_channel_subscription_by_id(notification_service):
352-
service, mock_repo = notification_service
361+
service, mock_repo, mock_email_service = notification_service
353362

354363
subscription_id = 123
355364
expected_subscription = NotificationChannelSubscription(
@@ -374,7 +383,7 @@ async def test_get_notification_channel_subscription_by_id(notification_service)
374383

375384
@pytest.mark.anyio
376385
async def test_delete_notification_channel_subscription(notification_service):
377-
service, mock_repo = notification_service
386+
service, mock_repo, mock_email_service = notification_service
378387

379388
user_profile_id = 1
380389
subscription_id = 456
@@ -403,7 +412,7 @@ async def test_delete_notification_channel_subscription(notification_service):
403412

404413
@pytest.mark.anyio
405414
async def test_service_delete_subscriptions_for_user_role(notification_service):
406-
service, mock_repo = notification_service
415+
service, mock_repo, mock_email_service = notification_service
407416
user_profile_id = 1
408417
role_enum = RoleEnum.ANALYST
409418

@@ -418,7 +427,7 @@ async def test_service_delete_subscriptions_for_user_role(notification_service):
418427

419428
@pytest.mark.anyio
420429
async def test_service_add_subscriptions_for_user_role(notification_service):
421-
service, mock_repo = notification_service
430+
service, mock_repo, mock_email_service = notification_service
422431
user_profile_id = 1
423432
role_enum = RoleEnum.DIRECTOR
424433

@@ -429,3 +438,55 @@ async def test_service_add_subscriptions_for_user_role(notification_service):
429438
mock_repo.add_subscriptions_for_user_role.assert_awaited_once_with(
430439
user_profile_id, role_enum
431440
)
441+
442+
443+
@pytest.mark.anyio
444+
async def test_send_notification_skip_analyst(notification_service):
445+
service, mock_repo, mock_email_service = notification_service
446+
447+
# Create the notification request
448+
message_data = {
449+
"service": "Transfer",
450+
"status": "recorded",
451+
"toOrganizationId": 2,
452+
}
453+
notif_msg_schema = NotificationMessageSchema(
454+
related_organization_id=1,
455+
message=json.dumps(message_data),
456+
)
457+
notification_req = NotificationRequestSchema(
458+
notification_types=[
459+
NotificationTypeEnum.BCEID__TRANSFER__DIRECTOR_DECISION,
460+
NotificationTypeEnum.IDIR_ANALYST__TRANSFER__DIRECTOR_RECORDED,
461+
],
462+
notification_data=notif_msg_schema,
463+
)
464+
465+
# Analyst
466+
mock_analyst_sub = MagicMock(spec=NotificationChannelSubscription)
467+
mock_analyst_sub.user_profile_id = 2
468+
mock_analyst_sub.user_profile = MagicMock()
469+
mock_analyst_sub.user_profile.user_roles = [MagicMock(name=RoleEnum.ANALYST)]
470+
471+
# Non-analyst
472+
mock_non_analyst_sub = MagicMock(spec=NotificationChannelSubscription)
473+
mock_non_analyst_sub.user_profile_id = 101
474+
mock_non_analyst_sub.user_profile = MagicMock()
475+
mock_non_analyst_sub.user_profile.user_roles = [MagicMock(name=RoleEnum.TRANSFER)]
476+
477+
# Configure the repo mock
478+
mock_repo.get_subscribed_users_by_channel = AsyncMock(
479+
return_value=[mock_analyst_sub, mock_non_analyst_sub]
480+
)
481+
mock_repo.create_notification_messages = AsyncMock()
482+
483+
# Call the method
484+
await service.send_notification(notification_req)
485+
486+
# Verify that the email service was not called
487+
called_args, _ = mock_repo.create_notification_messages.await_args
488+
created_notifications = called_args[0]
489+
490+
# Ensure that the analyst was skipped
491+
assert len(created_notifications) == 2
492+
assert created_notifications[0].related_user_profile_id == 2

backend/lcfs/web/api/notification/repo.py

+5
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,11 @@ async def get_subscribed_users_by_channel(
432432
"""
433433
query = (
434434
select(NotificationChannelSubscription)
435+
.options(
436+
selectinload(NotificationChannelSubscription.user_profile)
437+
.selectinload(UserProfile.user_roles)
438+
.selectinload(UserRole.role)
439+
)
435440
.join(
436441
NotificationType,
437442
NotificationType.notification_type_id

backend/lcfs/web/api/notification/services.py

+39
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import math
2+
import json
23
from typing import List, Optional
34
import json
45
from lcfs.db.models.notification import (
@@ -10,6 +11,7 @@
1011
PaginationRequestSchema,
1112
PaginationResponseSchema,
1213
)
14+
from lcfs.db.models.user.Role import RoleEnum
1315
from lcfs.web.api.email.services import CHESEmailService
1416
from lcfs.web.api.notification.schema import (
1517
NotificationRequestSchema,
@@ -261,13 +263,49 @@ async def send_notification(self, notification: NotificationRequestSchema):
261263
}
262264
)
263265

266+
# Extract receiving org ID, service and status
267+
to_org_id = None
268+
service_val = ""
269+
status_val = ""
270+
if notification.notification_data and notification.notification_data.message:
271+
try:
272+
msg = json.loads(notification.notification_data.message)
273+
to_org_id = msg.get("toOrganizationId")
274+
service_val = msg.get("service", "").lower()
275+
status_val = msg.get("status", "").lower()
276+
except (ValueError, TypeError):
277+
pass
278+
279+
# Decide if this is a Transfer that is Recorded/Refused
280+
is_recorded_or_refused = (
281+
service_val == "transfer" and status_val in ["recorded", "refused"]
282+
)
283+
264284
for notification_type in notification.notification_types:
265285
in_app_subscribed_users = await self.repo.get_subscribed_users_by_channel(
266286
notification_type,
267287
ChannelEnum.IN_APP,
268288
notification.notification_data.related_organization_id,
269289
)
270290

291+
# Skip sending to Analysts if the org is not the receiving org and status is Recorded/Refused
292+
final_subscriptions = []
293+
for sub in in_app_subscribed_users:
294+
skip = False
295+
296+
if is_recorded_or_refused and sub.user_profile:
297+
# Check if user is Analyst
298+
roles = [ur.role.name for ur in sub.user_profile.user_roles]
299+
if RoleEnum.ANALYST in roles:
300+
org_we_are_notifying = notification.notification_data.related_organization_id
301+
if to_org_id and org_we_are_notifying != to_org_id:
302+
skip = True
303+
304+
if not skip:
305+
final_subscriptions.append(sub)
306+
307+
in_app_subscribed_users = final_subscriptions
308+
271309
# Batch create in-app notifications
272310
in_app_notifications = [
273311
NotificationMessage(
@@ -282,6 +320,7 @@ async def send_notification(self, notification: NotificationRequestSchema):
282320
if in_app_notifications:
283321
await self.repo.create_notification_messages(in_app_notifications)
284322

323+
# Send any email notifications
285324
await self.email_service.send_notification_email(
286325
notification_type,
287326
notification.notification_context,

0 commit comments

Comments
 (0)