diff --git a/backend/notifications/api/views.py b/backend/notifications/api/views.py index 31d4fff7..304cc666 100644 --- a/backend/notifications/api/views.py +++ b/backend/notifications/api/views.py @@ -590,6 +590,13 @@ def subscribe_push(request): } ) + prefs, _ = UserChannelPreferences.objects.get_or_create( + user=request.user, + ) + if not prefs.push_enabled: + prefs.push_enabled = True + prefs.save(update_fields=["push_enabled"]) + return Response({ 'status': 'success', 'message': ( @@ -628,6 +635,18 @@ def unsubscribe_push(request): user=request.user ).delete() + has_active_devices = WebPushDevice.objects.filter( + user=request.user, + active=True, + ).exists() + if not has_active_devices: + prefs, _ = UserChannelPreferences.objects.get_or_create( + user=request.user, + ) + if prefs.push_enabled: + prefs.push_enabled = False + prefs.save(update_fields=["push_enabled"]) + return Response({ 'status': 'success', 'message': f'Deleted {deleted_count} push subscription(s)', diff --git a/backend/tests/api/v1/notifications/test_notifications_api.py b/backend/tests/api/v1/notifications/test_notifications_api.py index 0d0f4e59..f34e0db3 100644 --- a/backend/tests/api/v1/notifications/test_notifications_api.py +++ b/backend/tests/api/v1/notifications/test_notifications_api.py @@ -2,6 +2,7 @@ from unittest.mock import patch import pytest +from push_notifications.models import WebPushDevice from notifications.models import Notification from notifications.models import UserChannelPreferences @@ -158,3 +159,54 @@ class TestNotificationPreferencesApi: assert response.status_code == 200 prefs = UserChannelPreferences.objects.get(user=user) assert prefs.email_frequency == "never" + + def test_subscribe_push_enables_push_preferences( + self, api_client, user_factory + ): + user = user_factory() + UserChannelPreferences.objects.create(user=user, push_enabled=False) + api_client.force_authenticate(user=user) + + response = api_client.post( + "/api/v1/notifications/push/subscribe/", + { + "endpoint": "https://push.example.test/endpoint-1", + "keys": {"p256dh": "test-p256dh", "auth": "test-auth"}, + "device_name": "Chrome", + }, + format="json", + ) + + assert response.status_code == 200 + prefs = UserChannelPreferences.objects.get(user=user) + assert prefs.push_enabled is True + assert WebPushDevice.objects.filter( + user=user, + registration_id="https://push.example.test/endpoint-1", + active=True, + ).exists() + + def test_unsubscribe_last_push_device_disables_push_preferences( + self, api_client, user_factory + ): + user = user_factory() + UserChannelPreferences.objects.create(user=user, push_enabled=True) + WebPushDevice.objects.create( + user=user, + registration_id="https://push.example.test/endpoint-1", + p256dh="test-p256dh", + auth="test-auth", + browser="Chrome", + active=True, + ) + api_client.force_authenticate(user=user) + + response = api_client.delete( + "/api/v1/notifications/push/unsubscribe/", + {"endpoint": "https://push.example.test/endpoint-1"}, + format="json", + ) + + assert response.status_code == 200 + prefs = UserChannelPreferences.objects.get(user=user) + assert prefs.push_enabled is False diff --git a/backend/tests/api/v1/procurement/test_procurement_requests.py b/backend/tests/api/v1/procurement/test_procurement_requests.py index 722ebb36..93ff9e10 100644 --- a/backend/tests/api/v1/procurement/test_procurement_requests.py +++ b/backend/tests/api/v1/procurement/test_procurement_requests.py @@ -3,11 +3,13 @@ """ from decimal import Decimal +from unittest.mock import patch import pytest from django.contrib.auth.models import Permission from django.urls import reverse from django.utils import timezone +from push_notifications.models import WebPushDevice from rest_framework import status from rest_framework.test import APIClient @@ -33,6 +35,7 @@ from procurement.models import ( ProcurementItem, ProcurementRequest, ) +from notifications.models import Notification, UserChannelPreferences pytestmark = pytest.mark.django_db @@ -1637,6 +1640,63 @@ class TestProcessingDepartmentWorkflow: "ordered_quantity", ] + @pytest.mark.django_db(transaction=True) + def test_item_order_update_routes_push_to_requestor( + self, api_client, supply_user, processing_request, + procurement_item_factory + ): + item = procurement_item_factory( + request=processing_request, + quantity=3, + ) + processing_request.executor = supply_user + processing_request.status = ProcurementStatus.IN_PROGRESS + processing_request.save() + UserChannelPreferences.objects.update_or_create( + user=processing_request.requestor, + defaults={ + "web_enabled": False, + "email_enabled": False, + "push_enabled": True, + "disabled_verbs": [], + }, + ) + WebPushDevice.objects.create( + user=processing_request.requestor, + registration_id="https://push.example.test/procurement-item", + p256dh="test-p256dh", + auth="test-auth", + browser="Chrome", + active=True, + ) + + api_client.force_authenticate(user=supply_user) + item_url = reverse( + 'api:v1:procurement:procurementitem-detail', + kwargs={'pk': item.id}, + ) + + with patch( + "notifications.tasks.send_push_notification.delay" + ) as send_push: + response = api_client.patch( + item_url, + {"ordered_quantity": 2}, + format="json", + ) + + assert response.status_code == status.HTTP_200_OK + notification = Notification.objects.get( + recipient=processing_request.requestor, + verb="procurement_item_updated", + data__item_id=item.id, + ) + assert notification.data["changed_fields"] == [ + "ordered_quantity", + "execution_status", + ] + send_push.assert_called_once_with(notification.id) + def test_item_expected_delivery_dates_must_be_valid_dates( self, api_client, supply_user, processing_request, procurement_item_factory