from contextlib import contextmanager import asyncio import pytest from fastapi import HTTPException from app.services import email_sender_create as service def test_suggestions_use_sender_and_newest_signature_only(): draft = service.sender_suggestions({ 'sender_name': 'Anna Jensen', 'sender_email': 'Anna.Jensen@firma.dk', 'body_text': '''Hej\n\nAnna Jensen\nFirma ApS\nStilling: Projektleder\nMobil: +45 20 30 40 50\nGade 12\n8000 Aarhus C\nCVR: 12345678\n\nFra: Gammel Person\nGammelt A/S\nCVR: 87654321''', 'body_html': '', }) assert draft['contact']['email'] == 'anna.jensen@firma.dk' assert draft['contact']['first_name'] == 'Anna' assert draft['contact']['last_name'] == 'Jensen' assert draft['contact']['mobile'] == '+45 20 30 40 50' assert draft['company'] == { 'name': 'Firma ApS', 'cvr_number': '12345678', 'address': 'Gade 12', 'postal_code': '8000', 'city': 'Aarhus C', } assert 'Gammelt A/S' not in draft['evidence'] def test_suggestions_do_not_invent_company_from_email_domain(): draft = service.sender_suggestions({ 'sender_name': 'Anna Jensen', 'sender_email': 'anna@ukendt-example.dk', 'body_text': 'Hej\nMvh Anna', 'body_html': '', }) assert draft['company'].get('name', '') == '' assert draft['contact']['email'] == 'anna@ukendt-example.dk' @pytest.fixture def db(monkeypatch): state = {'writes': [], 'mail': {'id': 7, 'customer_id': None, 'supplier_id': None}, 'previous': None, 'company': {'id': 12, 'name': 'Firma ApS'}, 'contacts': [], 'contact': None} @contextmanager def transaction(): try: yield state['committed'] = True except Exception: state['rolled_back'] = True raise def one(sql, params): if 'FROM email_messages' in sql: return state['mail'] if 'FROM email_activity_log' in sql: return state['previous'] if 'pg_advisory_xact_lock' in sql: return {'locked': None} if 'FROM customers' in sql or 'FROM vendors' in sql: return state['company'] if 'FROM contacts WHERE id=' in sql: return state['contact'] or {'id': params[0]} if 'INSERT INTO contacts' in sql: return {'id': 31} raise AssertionError(sql) def many(sql, params): if 'FROM contacts WHERE LOWER' in sql: return state['contacts'] if 'FROM customers' in sql or 'FROM vendors' in sql: return [] raise AssertionError(sql) monkeypatch.setattr(service, 'atomic_transaction', transaction) monkeypatch.setattr(service, 'execute_query_single', one) monkeypatch.setattr(service, 'execute_query', many) monkeypatch.setattr(service, 'execute_update', lambda sql, params: state['writes'].append((sql, params))) return state def test_existing_company_and_contact_are_related_without_overwrite(db): payload = service.SenderCreateRequest( kind='customer', company={'existing_id': 12}, contact={'existing_id': 30, 'first_name': '', 'last_name': '', 'email': ''}, ) result = asyncio.run(service.create_sender(7, payload, user_id=4)) assert result['company_id'] == 12 and result['contact_id'] == 30 assert result['reused_contact'] is True and db['committed'] assert any('INSERT INTO contact_companies' in sql for sql, _ in db['writes']) assert not any('INSERT INTO contacts' in sql for sql, _ in db['writes']) assert any('UPDATE email_messages SET customer_id' in sql for sql, _ in db['writes']) def test_duplicate_company_blocks_all_writes_and_rolls_back(db, monkeypatch): db['company'] = None monkeypatch.setattr(service, 'execute_query', lambda sql, params: [{'id': 99, 'name': 'Firma ApS'}] if 'FROM customers' in sql else []) payload = service.SenderCreateRequest( kind='customer', company={'name': 'Firma ApS'}, contact={'first_name': 'Anna', 'last_name': 'Jensen', 'email': 'anna@firma.dk'}, ) with pytest.raises(HTTPException) as error: asyncio.run(service.create_sender(7, payload)) assert error.value.status_code == 409 assert db['rolled_back'] and not db['writes'] def test_failure_after_relation_rolls_back_whole_command(db, monkeypatch): calls = 0 def fail_on_mail_update(sql, params): nonlocal calls calls += 1 if 'UPDATE email_messages' in sql: raise RuntimeError('database failed') db['writes'].append((sql, params)) monkeypatch.setattr(service, 'execute_update', fail_on_mail_update) payload = service.SenderCreateRequest( kind='customer', company={'existing_id': 12}, contact={'existing_id': 30, 'first_name': '', 'last_name': '', 'email': ''}, ) with pytest.raises(RuntimeError): asyncio.run(service.create_sender(7, payload)) assert calls == 2 and db['rolled_back'] and not db.get('committed')