HH/apps/core/tests.py
ismail 7369d08012
All checks were successful
Build and Push Docker Image / build (push) Successful in 4m14s
feat: unified reference numbers + feedback modules QA audit
Reference numbers (unified scheme PREFIX-YYYYMM-HOSP-NNNN, e.g. CMP-202606-HHN-0001):
- new ReferenceSequence model + generate_reference() helper (apps/core)
- Complaint/Inquiry/Observation/Appreciation/Suggestion emit unified refs via save()
- prefix-based auto-routing in public track API (CMP/INQ/OBS trackable; APR/SGT internal-only)
- removed legacy CMP-/INQ- generators in ui_views, integrations, px_sources
- migrations: core.0003_referencesequence, appreciation.0006, feedback.0008, observations.0012
- unit tests (format, sanitization, monthly reset, 40-thread concurrency)

QA audit:
- isolated E2E hospital sandbox mirroring HH-N + 10 role users (create_e2e_isolated_env)
- feedback-modules-audit.spec.ts + audit helper (headed, run-to-completion)
- reports/feedback-modules-qa-report.md

Also bundles accumulated in-progress work across complaints, observations,
organizations, templates, and other modules.
2026-06-14 14:29:23 +03:00

139 lines
5.4 KiB
Python

"""
Tests for the unified reference-number generator (apps.core.reference).
Covers: hospital-token sanitization, format, per-module prefix coverage,
sequence monotonicity, None-hospital fallback, and concurrent allocation
uniqueness.
"""
import threading
from datetime import datetime
from unittest.mock import patch
from django.test import TestCase, TransactionTestCase
from apps.core.models import ReferenceSequence
from apps.core.reference import generate_reference, sanitize_hospital_token
from apps.organizations.models import Hospital, Organization
def _make_hospital(code="TEST-HOSP"):
org, _ = Organization.objects.get_or_create(code="TEST-ORG", defaults={"name": "Test Org", "status": "active"})
hospital, _ = Hospital.objects.get_or_create(
code=code,
defaults={"organization": org, "name": f"Test {code}", "status": "active"},
)
return hospital
class SanitizeTokenTest(TestCase):
def test_strips_hyphens_and_uppercases(self):
class H:
def __init__(self, code):
self.code = code
self.assertEqual(sanitize_hospital_token(H("HH-N")), "HHN")
self.assertEqual(sanitize_hospital_token(H("e2e-hosp")), "E2EHOSP")
self.assertEqual(sanitize_hospital_token(H("main-campus-1")), "MAINCAMPUS1")
def test_none_or_empty_hospital(self):
self.assertEqual(sanitize_hospital_token(None), "GEN")
self.assertEqual(sanitize_hospital_token(object()), "GEN")
class Empty:
code = ""
self.assertEqual(sanitize_hospital_token(Empty()), "GEN")
def test_strips_non_alphanumeric(self):
class H:
def __init__(self, code):
self.code = code
self.assertEqual(sanitize_hospital_token(H("NUZHA@2026")), "NUZHA2026")
self.assertEqual(sanitize_hospital_token(H(" spaced ")), "SPACED")
class GenerateReferenceFormatTest(TestCase):
def setUp(self):
self.hospital = _make_hospital("HH-N")
def test_format_and_prefix(self):
with patch("apps.core.reference.datetime") as mock_dt:
mock_dt.now.return_value = datetime(2026, 6, 14)
for prefix in ("CMP", "INQ", "OBS", "APR", "SGT"):
ref = generate_reference(prefix, self.hospital)
self.assertRegex(
ref,
rf"^{prefix}-202606-HHN-0001$",
f"unexpected ref {ref} for prefix {prefix}",
)
def test_sequence_increments_monotonically(self):
refs = [generate_reference("CMP", self.hospital) for _ in range(5)]
self.assertEqual(refs, [f"CMP-202606-HHN-{i:04d}" for i in range(1, 6)])
# all unique
self.assertEqual(len(set(refs)), len(refs))
def test_each_module_has_independent_sequence(self):
with patch("apps.core.reference.datetime") as mock_dt:
mock_dt.now.return_value = datetime(2026, 6, 14)
cmp1 = generate_reference("CMP", self.hospital)
inq1 = generate_reference("INQ", self.hospital)
cmp2 = generate_reference("CMP", self.hospital)
self.assertIn("-0001", cmp1)
self.assertIn("-0001", inq1) # independent sequence
self.assertIn("-0002", cmp2)
def test_per_hospital_isolation(self):
h2 = _make_hospital("HH-A")
with patch("apps.core.reference.datetime") as mock_dt:
mock_dt.now.return_value = datetime(2026, 6, 14)
ref_n = generate_reference("CMP", self.hospital) # HHN
ref_a = generate_reference("CMP", h2) # HHA
self.assertIn("-HHN-", ref_n)
self.assertIn("-HHA-", ref_a)
self.assertIn("-0001", ref_n)
self.assertIn("-0001", ref_a) # separate counter per hospital
def test_none_hospital_fallback(self):
ref = generate_reference("CMP", None)
self.assertRegex(ref, r"^CMP-\d{6}-GEN-0001$")
def test_sequence_resets_per_month(self):
with patch("apps.core.reference.datetime") as mock_dt:
mock_dt.now.return_value = datetime(2026, 6, 14)
generate_reference("CMP", self.hospital)
generate_reference("CMP", self.hospital) # 202606 -> 2
mock_dt.now.return_value = datetime(2026, 7, 1)
july_ref = generate_reference("CMP", self.hospital)
self.assertIn("-202607-HHN-0001", july_ref)
class ConcurrencyTest(TransactionTestCase):
"""Concurrent allocation must never produce duplicate sequence numbers."""
def test_concurrent_allocations_are_unique(self):
hospital = _make_hospital("HH-S")
n = 40
results = [None] * n
barrier = threading.Barrier(n)
def worker(idx):
barrier.wait()
results[idx] = generate_reference("CMP", hospital)
threads = [threading.Thread(target=worker, args=(i,)) for i in range(n)]
for t in threads:
t.start()
for t in threads:
t.join(timeout=30)
self.assertNotIn(None, results, "a worker did not produce a reference")
self.assertEqual(len(set(results)), n, "duplicate references allocated under concurrency")
# numbers should be exactly 1..n
numbers = sorted(int(r.split("-")[-1]) for r in results)
self.assertEqual(numbers, list(range(1, n + 1)))
# one sequence row for this key
self.assertEqual(ReferenceSequence.objects.filter(prefix="CMP", hospital_token="HHS").count(), 1)