bmc_hub/tests/test_contact_lifecycle_api.py

124 lines
4.8 KiB
Python
Raw Normal View History

2026-11-03 09:08:51 +01:00
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]