All checks were successful
Build and Push Docker Image / build (push) Successful in 4m14s
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.
139 lines
5.4 KiB
Python
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)
|