Per the workflow realignment, the department owns inquiry resolution end-to-end. The PX-side respond modal and its 'Response to Patient' tile button are removed; the 'Resolve' button in the 'Department has responded' banner now POSTs directly to inquiry_change_status (with a confirm dialog), which already enforces the contact_status gate. The inquiry_respond view and URL are kept for backward compatibility but no UI surfaces them. Two regression tests lock in the new behavior: - showRespondModal JS function and respondModal div are gone - Resolve form posts directly to inquiry_change_status with status=resolved
149 lines
6.7 KiB
Python
149 lines
6.7 KiB
Python
"""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"
|