Files
ACORE/apps/rro/tests.py
T
lokses 70e5a77b04 Harden portal access and hiring, add RRO portal CRUD.
Remove the is_staff permission bridge, tighten intern credential handling with one-time PDFs and random seed passwords, and let staff manage cash registers from the portal.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-10 08:54:41 +03:00

312 lines
13 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from datetime import date
from django.contrib.auth import get_user_model
from django.core.exceptions import ValidationError
from django.db import IntegrityError, transaction
from django.test import TestCase
from core.models import Directory, DirectoryType
from apps.locations.models import Pharmacy
from apps.rro.models import CashRegister
Employee = get_user_model()
class CashRegisterModelTests(TestCase):
@classmethod
def setUpTestData(cls):
org_type = DirectoryType.objects.create(code='organization', name='Организация')
region_type = DirectoryType.objects.create(code='region', name='Регион')
status_type, _ = DirectoryType.objects.get_or_create(
code='rro_status', defaults={'name': 'Статус РРО/ПРРО', 'hierarchy_type': 'strict'},
)
cls.status_active, _ = Directory.objects.get_or_create(
directory_type=status_type, code='active',
defaults={'name': 'Активный', 'is_active': True},
)
cls.region = Directory.objects.create(directory_type=region_type, code='od', name='Одесса')
cls.org = Directory.objects.create(directory_type=org_type, code='akc', name='АКС')
cls.pharmacy = Pharmacy.objects.create(
organization=cls.org, location_region=cls.region,
pharmacy_number=42, location_type='pharmacy',
street='Тестовая', house_number='1',
)
cls.pharmacy2 = Pharmacy.objects.create(
organization=cls.org, location_region=cls.region,
pharmacy_number=43, location_type='pharmacy',
street='Другая', house_number='2',
)
def _reg(self, **kw):
defaults = {
'pharmacy': self.pharmacy,
'kind': CashRegister.Kind.PRRO,
'fiscal_number': kw.pop('fiscal_number', f'FN-{CashRegister.objects.count()+1}'),
'status': self.status_active,
}
defaults.update(kw)
return CashRegister(**defaults)
def test_seed_statuses_exist(self):
codes = set(
Directory.objects.filter(directory_type__code='rro_status')
.values_list('code', flat=True)
)
self.assertTrue({'active', 'reserve', 'repair', 'decommissioned'} <= codes)
def test_unique_fiscal_number(self):
self._reg(fiscal_number='FN-UNIQUE').save()
with self.assertRaises(IntegrityError):
with transaction.atomic():
self._reg(fiscal_number='FN-UNIQUE', pharmacy=self.pharmacy2).save()
def test_only_one_primary_per_pharmacy(self):
a = self._reg(fiscal_number='FN-P1', is_primary=True)
a.full_clean()
a.save()
b = self._reg(fiscal_number='FN-P2', is_primary=True)
with self.assertRaises(ValidationError):
b.full_clean()
def test_primary_ok_on_different_pharmacies(self):
a = self._reg(fiscal_number='FN-A', is_primary=True)
a.full_clean()
a.save()
b = self._reg(fiscal_number='FN-B', pharmacy=self.pharmacy2, is_primary=True)
b.full_clean()
b.save()
class CashRegisterPortalTests(TestCase):
@classmethod
def setUpTestData(cls):
org_type = DirectoryType.objects.create(code='organization', name='Организация')
region_type = DirectoryType.objects.create(code='region', name='Регион')
status_type, _ = DirectoryType.objects.get_or_create(
code='rro_status', defaults={'name': 'Статус РРО/ПРРО', 'hierarchy_type': 'strict'},
)
cls.status_active, _ = Directory.objects.get_or_create(
directory_type=status_type, code='active',
defaults={'name': 'Активный', 'is_active': True},
)
region = Directory.objects.create(directory_type=region_type, code='kv', name='Киев')
org = Directory.objects.create(directory_type=org_type, code='org', name='ТестОрг')
cls.pharmacy = Pharmacy.objects.create(
organization=org, location_region=region,
pharmacy_number=7, location_type='pharmacy',
street='Главная', house_number='7',
)
CashRegister.objects.create(
pharmacy=cls.pharmacy, kind=CashRegister.Kind.RRO,
fiscal_number='FN-PORTAL-1', status=cls.status_active,
model_name='Datecs FP-700', is_primary=True,
)
cls.user = Employee.objects.create(
username='rro.staff',
email='rro.staff@company.local',
first_name='РРО',
last_name='Тест',
hire_date=date(2026, 1, 1),
user_location_type='office',
is_staff=True,
mattermost_sync_enabled=False,
ad_sync_enabled=False,
totp_enabled=True,
)
def _login(self):
self.client.force_login(self.user)
session = self.client.session
session['2fa_verified'] = True
session.save()
def test_rro_list_page(self):
self._login()
resp = self.client.get('/rro/', HTTP_HX_REQUEST='true')
self.assertEqual(resp.status_code, 200)
self.assertContains(resp, 'FN-PORTAL-1')
def test_pharmacy_detail_rro_tab(self):
self._login()
resp = self.client.get(f'/pharmacies/{self.pharmacy.pk}/', HTTP_HX_REQUEST='true')
self.assertEqual(resp.status_code, 200)
self.assertContains(resp, 'FN-PORTAL-1')
self.assertContains(resp, 'ph-panel-rro')
def test_pharmacy_detail_hides_rro_for_other_pharmacy_user(self):
other = Pharmacy.objects.create(
organization=self.pharmacy.organization,
location_region=self.pharmacy.location_region,
pharmacy_number=99, location_type='pharmacy',
street='Чужа', house_number='9',
)
stranger = Employee.objects.create(
username='rro.stranger',
email='rro.stranger@company.local',
first_name='Інша',
last_name='Аптека',
hire_date=date(2026, 1, 1),
user_location_type='pharmacy',
user_pharmacy=other,
is_staff=False,
mattermost_sync_enabled=False,
ad_sync_enabled=False,
)
self.client.force_login(stranger)
session = self.client.session
session['2fa_verified'] = True
session.save()
resp = self.client.get(f'/pharmacies/{self.pharmacy.pk}/', HTTP_HX_REQUEST='true')
self.assertEqual(resp.status_code, 200)
self.assertNotContains(resp, 'FN-PORTAL-1')
self.assertNotContains(resp, 'ph-panel-rro')
self.assertFalse(resp.context['can_view_ops'])
self.assertEqual(resp.context['staff'], [])
self.assertEqual(resp.context['equipment'], [])
self.assertEqual(resp.context['internet_contracts'], [])
self.assertEqual(resp.context['utility_readings'], [])
self.assertNotContains(resp, 'ph-panel-equipment')
self.assertNotContains(resp, 'ph-panel-internet')
self.assertNotContains(resp, 'ph-panel-utilities')
class CashRegisterCrudTests(TestCase):
@classmethod
def setUpTestData(cls):
org_type = DirectoryType.objects.create(code='organization', name='Организация')
region_type = DirectoryType.objects.create(code='region', name='Регион')
status_type, _ = DirectoryType.objects.get_or_create(
code='rro_status', defaults={'name': 'Статус РРО/ПРРО', 'hierarchy_type': 'strict'},
)
cls.status_active, _ = Directory.objects.get_or_create(
directory_type=status_type, code='active',
defaults={'name': 'Активный', 'is_active': True},
)
cls.status_repair, _ = Directory.objects.get_or_create(
directory_type=status_type, code='repair',
defaults={'name': 'В ремонте', 'is_active': True},
)
region = Directory.objects.create(directory_type=region_type, code='od2', name='Одесса2')
org = Directory.objects.create(directory_type=org_type, code='org2', name='Орг2')
cls.pharmacy = Pharmacy.objects.create(
organization=org, location_region=region,
pharmacy_number=11, location_type='pharmacy',
street='CRUD', house_number='1',
)
cls.staff = Employee.objects.create(
username='rro.crud.staff',
email='rro.crud.staff@company.local',
first_name='CRUD',
last_name='Staff',
hire_date=date(2026, 1, 1),
user_location_type='office',
is_staff=True,
mattermost_sync_enabled=False,
ad_sync_enabled=False,
totp_enabled=True,
)
cls.regular = Employee.objects.create(
username='rro.crud.user',
email='rro.crud.user@company.local',
first_name='CRUD',
last_name='User',
hire_date=date(2026, 1, 1),
user_location_type='pharmacy',
user_pharmacy=cls.pharmacy,
is_staff=False,
mattermost_sync_enabled=False,
ad_sync_enabled=False,
)
def _login(self, user):
self.client.force_login(user)
session = self.client.session
session['2fa_verified'] = True
session.save()
def _payload(self, **extra):
data = {
'pharmacy': self.pharmacy.pk,
'kind': CashRegister.Kind.PRRO,
'fiscal_number': 'FN-CRUD-NEW',
'status': self.status_active.pk,
'model_name': 'Checkbox',
'provider': 'checkbox',
}
data.update(extra)
return data
def test_staff_create(self):
self._login(self.staff)
resp = self.client.post('/rro/create/', self._payload(), HTTP_HX_REQUEST='true')
self.assertEqual(resp.status_code, 200)
self.assertTrue(CashRegister.objects.filter(fiscal_number='FN-CRUD-NEW').exists())
self.assertContains(resp, 'Кассовый аппарат добавлен')
def test_non_staff_create_forbidden(self):
self._login(self.regular)
resp = self.client.post('/rro/create/', self._payload(fiscal_number='FN-FORBIDDEN'))
self.assertEqual(resp.status_code, 403)
self.assertFalse(CashRegister.objects.filter(fiscal_number='FN-FORBIDDEN').exists())
def test_staff_update(self):
reg = CashRegister.objects.create(
pharmacy=self.pharmacy, kind=CashRegister.Kind.RRO,
fiscal_number='FN-UPD', status=self.status_active,
)
self._login(self.staff)
resp = self.client.post(
f'/rro/{reg.pk}/update/',
self._payload(
fiscal_number='FN-UPD-2',
kind=CashRegister.Kind.RRO,
status=self.status_repair.pk,
provider='',
),
HTTP_HX_REQUEST='true',
)
self.assertEqual(resp.status_code, 200)
reg.refresh_from_db()
self.assertEqual(reg.fiscal_number, 'FN-UPD-2')
self.assertEqual(reg.status_id, self.status_repair.pk)
def test_duplicate_fiscal_shows_form_error(self):
CashRegister.objects.create(
pharmacy=self.pharmacy, kind=CashRegister.Kind.PRRO,
fiscal_number='FN-DUP', status=self.status_active,
)
self._login(self.staff)
resp = self.client.post(
'/rro/create/',
self._payload(fiscal_number='FN-DUP'),
HTTP_HX_REQUEST='true',
)
self.assertEqual(resp.status_code, 200)
self.assertEqual(CashRegister.objects.filter(fiscal_number='FN-DUP').count(), 1)
self.assertTrue(resp.context['rro_form_errors'])
def test_primary_conflict_shows_form_error(self):
CashRegister.objects.create(
pharmacy=self.pharmacy, kind=CashRegister.Kind.PRRO,
fiscal_number='FN-PRIM-1', status=self.status_active, is_primary=True,
)
self._login(self.staff)
resp = self.client.post(
'/rro/create/',
self._payload(fiscal_number='FN-PRIM-2', is_primary='1'),
HTTP_HX_REQUEST='true',
)
self.assertEqual(resp.status_code, 200)
self.assertFalse(CashRegister.objects.filter(fiscal_number='FN-PRIM-2').exists())
self.assertIn('is_primary', resp.context['rro_form_errors'])
def test_new_query_opens_create_panel(self):
self._login(self.staff)
resp = self.client.get(
f'/rro/?pharmacy={self.pharmacy.pk}&new=1',
HTTP_HX_REQUEST='true',
)
self.assertEqual(resp.status_code, 200)
self.assertTrue(resp.context['show_create_panel'])
self.assertEqual(str(resp.context['prefill_pharmacy']), str(self.pharmacy.pk))