bmc_hub/tests/test_contact_lifecycle_api.py
2026-11-03 09:08:51 +01:00

124 lines
4.8 KiB
Python

from contextlib import nullcontext
from datetime import date
import pytest
from fastapi import HTTPException
from pydantic import ValidationError
from app.contacts.backend import lifecycle as api
from app.contacts.backend import lifecycle_service as service
USER = {'id': 5, 'is_superadmin': True}
def event(**overrides):
return dict(id=9, contact_id=1, customer_id=2, event_type='onboarding',
event_date=date(2026, 11, 17), lead_days=14, auto_create=False,
template_id=None, responsible_user_id=None, parent_case_id=None,
case_id=None, status='draft', last_error=None, **overrides)
@pytest.fixture
def setup_api(monkeypatch):
monkeypatch.setattr(api, '_ready', lambda: None)
monkeypatch.setattr(api, '_contact', lambda _: None)
monkeypatch.setattr(api, '_membership', lambda *args: None)
monkeypatch.setattr(api, 'atomic_transaction', nullcontext)
monkeypatch.setattr(api, 'hidden_case_ids', lambda _: set())
monkeypatch.setattr(service, 'find_parent_candidates', lambda *args: [])
def test_default_automation_is_disabled_and_lead_is_fourteen():
request = api.LifecycleCreate(customer_id=2, event_type='onboarding', event_date='2026-11-17')
assert request.auto_create is False
assert request.lead_days == 14
with pytest.raises(ValidationError):
api.LifecycleCreate(customer_id=2, event_type='offboarding', event_date='2026-11-17', lead_days=-1)
def test_missing_migration_is_explicit_503(monkeypatch):
monkeypatch.setattr(api, 'execute_query_single', lambda *args: {'relation': None})
with pytest.raises(HTTPException) as error:
api._ready()
assert error.value.status_code == 503
def test_customer_must_belong_to_contact(monkeypatch):
monkeypatch.setattr(api, 'execute_query_single', lambda *args: None)
with pytest.raises(HTTPException) as error:
api._membership(1, 90)
assert error.value.status_code == 400
def test_enabling_automation_requires_case_creation(setup_api):
row = event()
row['auto_create'] = True
with pytest.raises(HTTPException) as error:
api._validate(row, {'id': 5, 'permissions': ['cases.edit']})
assert error.value.status_code == 403
def test_template_requires_run_permission(setup_api):
row = event()
row['template_id'] = 4
with pytest.raises(HTTPException) as error:
api._validate(row, {'id': 5, 'permissions': ['cases.edit']})
assert error.value.status_code == 403
def test_ambiguous_parent_saves_waiting_for_choice(setup_api, monkeypatch):
monkeypatch.setattr(service, 'find_parent_candidates', lambda *args: [{'id': 10}, {'id': 11}])
writes = []
def query(sql, params):
writes.append((sql, params))
row = event()
row.update(auto_create=True, status='needs_parent')
return row
monkeypatch.setattr(api, 'execute_query_single', query)
result = api.create_lifecycle(1, api.LifecycleCreate(
customer_id=2, event_type='onboarding', event_date='2026-11-17', auto_create=True), USER)
assert result['status'] == 'needs_parent'
assert writes[0][1][-2] == 'needs_parent'
def test_preview_excludes_confidential_candidates(setup_api, monkeypatch):
monkeypatch.setattr(service, 'find_parent_candidates', lambda *args: [{'id': 10}, {'id': 11}])
monkeypatch.setattr(api, 'hidden_case_ids', lambda _: {11})
result = api.preview_lifecycle(1, 2, 'onboarding', date(2026, 11, 17), 14, USER)
assert result['candidates'] == [{'id': 10}]
assert result['parent_case_id'] == 10
assert result['needs_parent'] is False
def test_date_change_updates_same_case_without_insert(setup_api, monkeypatch):
row = event()
row.update(case_id=45, status='created')
monkeypatch.setattr(api, '_find_event', lambda *args, **kwargs: row)
monkeypatch.setattr(api, 'require_case', lambda *args: None)
calls = []
monkeypatch.setattr(service, 'update_created_case_date', lambda *args:
calls.append(args) or {'status': 'created', 'last_error': None})
writes = []
def query(sql, params):
writes.append(sql)
return dict(row, event_date=date(2026, 11, 20))
monkeypatch.setattr(api, 'execute_query_single', query)
result = api.update_lifecycle(1, 9, api.LifecycleUpdate(event_date='2026-11-20'), USER)
assert result['case_id'] == 45
assert calls[0][1] == date(2026, 11, 17)
assert all('INSERT' not in sql for sql in writes)
def test_cancel_preserves_case_and_disables_automation(setup_api, monkeypatch):
monkeypatch.setattr(api, '_find_event', lambda *args, **kwargs: event())
writes = []
def query(sql, params):
writes.append(sql)
return event()
monkeypatch.setattr(api, 'execute_query_single', query)
api.cancel_lifecycle(1, 9, USER)
assert 'auto_create=FALSE' in writes[0]
assert 'DELETE' not in writes[0]
assert 'case_id=' not in writes[0]