"""Tests for the feedback workflow realignment (Inquiry department-ownership).""" from django.contrib.auth.models import Group import pytest from django.test import TestCase from django.urls import reverse from apps.accounts.models import User from apps.organizations.models import Department, Hospital from apps.complaints.models import Inquiry def _ensure_group(name): """Get or create a role group (pytest runs with --nomigrations, so seeded groups may not exist in the test DB — mirrors the pattern in tests_routing_rejection.py and tests_workflow_fixes.py).""" grp, _ = Group.objects.get_or_create(name=name) return grp @pytest.mark.django_db class TestInquiryDepartmentResponsePermission(TestCase): """A department manager of the handling department can record a response. Uses self.client (full middleware incl. django.contrib.messages) rather than calling the view directly with RequestFactory, because the view calls messages.error() on the permission-denied path. """ def setUp(self): self.hospital = Hospital.objects.create(name="Test Hospital", code="TH01") self.dept = Department.objects.create(name="Dept A", hospital=self.hospital, code="DA") self.other_dept = Department.objects.create(name="Dept B", hospital=self.hospital, code="DB") _ensure_group("Department Manager") self.dept_manager = User.objects.create_user( username="dmgr", email="dmgr@test", password="x", department=self.dept ) self.dept_manager.groups.add(Group.objects.get(name="Department Manager")) self.other_manager = User.objects.create_user( username="other_dmgr", email="o@test", password="x", department=self.other_dept ) self.other_manager.groups.add(Group.objects.get(name="Department Manager")) self.inquiry = Inquiry.objects.create( hospital=self.hospital, department=self.dept, subject="Test", message="msg", status="in_progress", sent_to_department=True, ) def test_dept_manager_of_handling_dept_can_access_response_view(self): """GET to inquiry_department_response succeeds (200) for dept manager of handling dept.""" self.client.force_login(self.dept_manager) response = self.client.get( reverse("inquiries:inquiry_department_response", kwargs={"pk": self.inquiry.pk}) ) # 200 = permission granted, 302 = permission denied redirect. assert response.status_code == 200, \ f"dept_manager of handling dept should be allowed, got {response.status_code}" def test_dept_manager_of_other_dept_cannot_access(self): """Dept manager of a non-handling department is denied (302 redirect).""" self.client.force_login(self.other_manager) response = self.client.get( reverse("inquiries:inquiry_department_response", kwargs={"pk": self.inquiry.pk}) ) assert response.status_code == 302, \ f"dept_manager of other dept should be redirected (denied), got {response.status_code}" @pytest.mark.django_db class TestInquiryDetailUIRealignment(TestCase): """The inquiry detail page no longer surfaces the PX-side 'Response to Patient' action. Resolution happens via direct POST to inquiry_change_status. Locks in Task 5 of the workflow realignment: department owns resolution. """ def setUp(self): self.hospital = Hospital.objects.create(name="Test Hospital UI", code="TH03") self.dept = Department.objects.create(name="Dept UI", hospital=self.hospital, code="UI") _ensure_group("PX Admin") self.px_admin = User.objects.create_user( username="pxui", email="pxui@test", password="x", hospital=self.hospital ) self.px_admin.groups.add(Group.objects.get(name="PX Admin")) def _make_inquiry(self, **kwargs): defaults = dict( hospital=self.hospital, department=self.dept, subject="UI test", message="msg", status="in_progress", activated_at=kwargs.pop("activated_at", __import__("django.utils.timezone", fromlist=["now"]).now()), ) defaults.update(kwargs) return Inquiry.objects.create(**defaults) def test_response_to_patient_button_is_gone(self): """The 'Response to Patient' tile button must not appear anywhere on the page.""" inquiry = self._make_inquiry() self.client.force_login(self.px_admin) # PX admins are redirected to select-hospital unless the session has a # selected_hospital_id (HospitalSelectionMiddleware). Set it directly. session = self.client.session session["selected_hospital_id"] = str(self.hospital.id) session.save() response = self.client.get(reverse("inquiries:inquiry_detail", kwargs={"pk": inquiry.pk})) assert response.status_code == 200 # The PX-side respond modal and its trigger are removed. The button label # may still appear inside {% comment %} blocks (inert), but the live # onclick="showRespondModal()" button in the actions tile is gone. content = response.content.decode() # showRespondModal JS function definition must be gone. assert "function showRespondModal" not in content, \ "showRespondModal JS function should be removed" # The respondModal div must be gone. assert 'id="respondModal"' not in content, \ "respondModal div should be removed" def test_resolve_uses_direct_status_change_form(self): """The Resolve button POSTs directly to inquiry_change_status (not a modal).""" from django.utils import timezone # Department has responded → "Department has responded" banner shows Resolve. inquiry = self._make_inquiry( sent_to_department=True, department_responded_at=timezone.now(), ) self.client.force_login(self.px_admin) session = self.client.session session["selected_hospital_id"] = str(self.hospital.id) session.save() response = self.client.get(reverse("inquiries:inquiry_detail", kwargs={"pk": inquiry.pk})) assert response.status_code == 200 content = response.content.decode() change_status_url = reverse("inquiries:inquiry_change_status", kwargs={"pk": inquiry.pk}) # The Resolve button must be a real form posting to inquiry_change_status # with status=resolved — not a showRespondModal() trigger. assert f'action="{change_status_url}"' in content, \ "Resolve must POST to inquiry_change_status" assert 'name="status" value="resolved"' in content, \ "Resolve form must include hidden status=resolved input"