Files
ACORE/apps/locations/tests.py
T
lokses 01f0c6f002 Carbonio CE: хранение пароля ящика аптеки (шифрованно)
Вариант А — пароль ящика хранится обратимо-зашифрованным, чтобы показать
заведующему для входа:

- Pharmacy.carbonio_password (EncryptedCharField) — шифруется в БД
- sync_service при создании ящика генерирует пароль, отдаёт его в Carbonio
  и сохраняет свою зашифрованную копию; на update-пути пароль не трогается
- Пароль виден в админке аптеки (readonly, расшифрованный) — вкладка Carbonio
- 2 теста (мок CarbonioService): пароль сохранён, совпадает с отданным в
  Carbonio, в БД лежит шифртекстом; update не перетирает пароль

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-24 10:29:40 +03:00

138 lines
6.9 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 unittest import mock
from django.core.exceptions import ValidationError
from django.db import IntegrityError, connection, transaction
from django.test import TestCase
from core.models import Directory, DirectoryType
from apps.locations.models import Pharmacy
class CarbonioEmailTests(TestCase):
"""Генерация адреса Carbonio (<префикс организации><номер>@домен),
ручной override и проверка уникальности."""
@classmethod
def setUpTestData(cls):
org_type = DirectoryType.objects.create(code='organization', name='Организация')
region_type = DirectoryType.objects.create(code='region', name='Регион')
cls.region = Directory.objects.create(directory_type=region_type, code='od', name='Одесса')
cls.aks = Directory.objects.create(
directory_type=org_type, code='akc', name='АКС', extra_data={'mail_prefix': 'a'})
cls.tira = Directory.objects.create(
directory_type=org_type, code='tira', name='Тира', extra_data={'mail_prefix': 't'})
cls.noprefix = Directory.objects.create(
directory_type=org_type, code='xx', name='Без префикса', extra_data={})
def _pharmacy(self, org, number, **kw):
return Pharmacy.objects.create(
organization=org, location_region=self.region,
pharmacy_number=number, location_type='pharmacy', **kw)
def test_build_email_format(self):
self.assertEqual(self._pharmacy(self.aks, 5).build_carbonio_email(), 'a5@aks-apteka.com.ua')
def test_build_email_uses_org_prefix(self):
self.assertEqual(self._pharmacy(self.tira, 5).build_carbonio_email(), 't5@aks-apteka.com.ua')
def test_build_email_empty_without_prefix(self):
self.assertEqual(self._pharmacy(self.noprefix, 5).build_carbonio_email(), '')
def test_autogenerate_on_save_when_enabled(self):
p = self._pharmacy(self.aks, 7, carbonio_sync_enabled=True)
self.assertEqual(p.carbonio_email, 'a7@aks-apteka.com.ua')
def test_no_autogenerate_when_disabled(self):
self.assertEqual(self._pharmacy(self.aks, 8).carbonio_email, '')
def test_manual_override_respected(self):
p = self._pharmacy(self.aks, 9, carbonio_sync_enabled=True,
carbonio_email='custom@aks-apteka.com.ua')
self.assertEqual(p.carbonio_email, 'custom@aks-apteka.com.ua')
def test_uniqueness_rejected_in_clean(self):
self._pharmacy(self.aks, 10, carbonio_sync_enabled=True) # a10@
p2 = self._pharmacy(self.tira, 11)
p2.carbonio_email = 'a10@aks-apteka.com.ua'
with self.assertRaises(ValidationError):
p2.clean()
def test_uniqueness_enforced_by_db(self):
self._pharmacy(self.aks, 12, carbonio_sync_enabled=True) # a12@
with self.assertRaises(IntegrityError):
with transaction.atomic():
self._pharmacy(self.tira, 13, carbonio_email='a12@aks-apteka.com.ua')
def test_autogenerate_skips_on_collision(self):
# ручной a15@ занят другой аптекой -> автоген для АКС №15 оставляет пусто
self._pharmacy(self.tira, 99, carbonio_sync_enabled=True,
carbonio_email='a15@aks-apteka.com.ua')
p = self._pharmacy(self.aks, 15, carbonio_sync_enabled=True)
self.assertEqual(p.carbonio_email, '')
def test_blank_emails_do_not_clash(self):
# несколько аптек без адреса — частичный уникальный индекс их не блокирует
self._pharmacy(self.aks, 16)
self._pharmacy(self.tira, 17)
self.assertEqual(Pharmacy.objects.filter(carbonio_email='').count(), 2)
class CarbonioPasswordSyncTests(TestCase):
"""sync_pharmacy_to_carbonio с замоканным CarbonioService: пароль ящика
сохраняется у нас зашифрованным и совпадает с отданным в Carbonio."""
@classmethod
def setUpTestData(cls):
org_type = DirectoryType.objects.create(code='organization', name='Организация')
region_type = DirectoryType.objects.create(code='region', name='Регион')
cls.region = Directory.objects.create(directory_type=region_type, code='od', name='Одесса')
cls.aks = Directory.objects.create(
directory_type=org_type, code='akc', name='АКС', extra_data={'mail_prefix': 'a'})
def _pharmacy(self, number, **kw):
return Pharmacy.objects.create(
organization=self.aks, location_region=self.region,
pharmacy_number=number, location_type='pharmacy', **kw)
def test_create_stores_encrypted_password(self):
from apps.mail_connector.services.sync_service import sync_pharmacy_to_carbonio
p = self._pharmacy(20) # синхронизация выключена, адрес пуст
fake = mock.Mock()
fake.get_account.return_value = None # аккаунта ещё нет → create
fake.create_account.return_value = 'acc-123'
with mock.patch(
'apps.mail_connector.services.sync_service.CarbonioService',
return_value=fake):
self.assertTrue(sync_pharmacy_to_carbonio(p))
p.refresh_from_db()
self.assertEqual(p.carbonio_email, 'a20@aks-apteka.com.ua')
self.assertEqual(p.carbonio_account_id, 'acc-123')
self.assertEqual(p.carbonio_sync_status, 'synced')
self.assertTrue(p.carbonio_password)
# тот же пароль ушёл в Carbonio, что и сохранён у нас
self.assertEqual(fake.create_account.call_args.args[1], p.carbonio_password)
# в БД пароль лежит зашифрованным
table, pk = Pharmacy._meta.db_table, Pharmacy._meta.pk.column
with connection.cursor() as cur:
cur.execute(f'SELECT carbonio_password FROM {table} WHERE {pk}=%s', [p.pk])
raw = cur.fetchone()[0]
self.assertTrue(raw.startswith('gAAAAA'))
def test_update_path_does_not_touch_password(self):
from apps.mail_connector.services.sync_service import sync_pharmacy_to_carbonio
p = self._pharmacy(21, carbonio_email='a21@aks-apteka.com.ua')
fake = mock.Mock()
fake.get_account.return_value = {'id': 'acc-existing'} # аккаунт уже есть → update
with mock.patch(
'apps.mail_connector.services.sync_service.CarbonioService',
return_value=fake):
self.assertTrue(sync_pharmacy_to_carbonio(p))
p.refresh_from_db()
self.assertEqual(p.carbonio_account_id, 'acc-existing')
self.assertEqual(p.carbonio_password, '') # на update пароль не трогаем
fake.create_account.assert_not_called()
fake.set_account_status.assert_called_once_with('acc-existing', 'active')