126 lines
6.0 KiB
Python
126 lines
6.0 KiB
Python
|
|
import pytest
|
||
|
|
from fastapi import HTTPException
|
||
|
|
from pydantic import ValidationError
|
||
|
|
from app.modules.sag.backend import create_support as support
|
||
|
|
|
||
|
|
|
||
|
|
def test_similarity_handles_danish_case_and_punctuation():
|
||
|
|
assert support.title_similarity('NETVÆRK: fejl!', 'netværk fejl') == 1
|
||
|
|
assert support.title_similarity('', '') == 0
|
||
|
|
assert support.title_similarity('Router virker ikke', 'Router virker ikke hos kunde') > .6
|
||
|
|
|
||
|
|
|
||
|
|
def test_template_rejects_forbidden_relationships_and_invalid_numbers():
|
||
|
|
with pytest.raises(ValidationError):
|
||
|
|
support.TemplateValues(customer_id=12)
|
||
|
|
with pytest.raises(ValidationError):
|
||
|
|
support.PipelineDefaults(probability=101)
|
||
|
|
with pytest.raises(ValidationError):
|
||
|
|
support.PipelineDefaults(amount=float('nan'))
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.mark.parametrize('value', [None, {}, [True], [0], [-1], ['1']])
|
||
|
|
def test_relationship_ids_are_strict(value):
|
||
|
|
with pytest.raises(HTTPException):
|
||
|
|
support.ids(value, 'ids')
|
||
|
|
|
||
|
|
|
||
|
|
def test_duplicates_rank_and_limit(monkeypatch):
|
||
|
|
monkeypatch.setattr(support, 'closed_statuses', lambda: ['lukket'])
|
||
|
|
def query(sql, params):
|
||
|
|
assert 'deleted_at IS NULL' in sql
|
||
|
|
assert params == (7, ['lukket'])
|
||
|
|
return [{'id': i, 'titel': 'Netværk fejl'} for i in range(8)] + [{'id': 9, 'titel': 'xyz'}]
|
||
|
|
monkeypatch.setattr(support, 'execute_query', query)
|
||
|
|
assert [r['id'] for r in support.duplicates(7, 'Netværk fejl')] == [7, 6, 5, 4, 3]
|
||
|
|
|
||
|
|
|
||
|
|
def test_workload_empty_and_total(monkeypatch):
|
||
|
|
monkeypatch.setattr(support, 'closed_statuses', lambda: ['closed'])
|
||
|
|
monkeypatch.setattr(support, 'execute_query', lambda *args: [])
|
||
|
|
assert support.workload(7) == {'total': 0, 'items': []}
|
||
|
|
monkeypatch.setattr(support, 'execute_query', lambda *args: [{'id': 4, 'total': 25}])
|
||
|
|
assert support.workload(7)['total'] == 25
|
||
|
|
|
||
|
|
|
||
|
|
def test_contact_open_cases_returns_only_active_cases_and_first_five(monkeypatch):
|
||
|
|
monkeypatch.setattr(support, 'closed_statuses', lambda: ['lukket'])
|
||
|
|
def query(sql, params):
|
||
|
|
assert 'FROM sag_kontakter' in sql
|
||
|
|
assert 'position<=5' in sql
|
||
|
|
assert params == ([3], ['lukket'])
|
||
|
|
return [
|
||
|
|
{'contact_id': 3, 'id': index, 'titel': f'Sag {index}', 'status': 'åben',
|
||
|
|
'deadline': None, 'ansvarlig_navn': 'Ada', 'total': 7}
|
||
|
|
for index in range(1, 6)
|
||
|
|
]
|
||
|
|
monkeypatch.setattr(support, 'execute_query', query)
|
||
|
|
result = support.contacts_open_cases([3])
|
||
|
|
assert result[3]['total'] == 7
|
||
|
|
assert [item['id'] for item in result[3]['items']] == [1, 2, 3, 4, 5]
|
||
|
|
|
||
|
|
|
||
|
|
def test_associations_deduplicate_and_single_group_last_wins():
|
||
|
|
class Cursor:
|
||
|
|
def __init__(self): self.calls = []
|
||
|
|
def execute(self, sql, args): self.calls.append((sql, args))
|
||
|
|
def fetchall(self):
|
||
|
|
if 'hardware_assets' in self.calls[-1][0]: return [{'id': 4}]
|
||
|
|
return [{'id': i, 'name': str(i), 'tag_group_id': 5, 'behavior': 'single'} for i in [8, 9]]
|
||
|
|
def fetchone(self): return {'action_type': 'open_task_template_modal', 'action_config': {}}
|
||
|
|
cursor = Cursor()
|
||
|
|
actions = support.attach_create_relations(cursor, 20, {'hardware_ids': [4, 4], 'tag_ids': [8, 9]}, 7)
|
||
|
|
inserts = [args for sql, args in cursor.calls if 'INSERT INTO entity_tags' in sql]
|
||
|
|
assert inserts == [(20, 9, 7)]
|
||
|
|
assert len([sql for sql, _ in cursor.calls if 'INSERT INTO sag_hardware' in sql]) == 1
|
||
|
|
assert actions[0]['entity_id'] == 20
|
||
|
|
|
||
|
|
|
||
|
|
def test_missing_hardware_fails_before_association_insert():
|
||
|
|
class Cursor:
|
||
|
|
def execute(self, sql, args): assert 'SELECT' in sql
|
||
|
|
def fetchall(self): return []
|
||
|
|
with pytest.raises(HTTPException):
|
||
|
|
support.attach_create_relations(Cursor(), 20, {'hardware_ids': [999]}, 7)
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.mark.parametrize('missing_hardware', [False, True])
|
||
|
|
def test_ticket_creation_commits_all_optional_data_or_rolls_back(monkeypatch, missing_hardware):
|
||
|
|
import asyncio
|
||
|
|
from app.modules.sag.backend import router as cases
|
||
|
|
class Cursor:
|
||
|
|
def __init__(self): self.calls = []
|
||
|
|
def __enter__(self): return self
|
||
|
|
def __exit__(self, *_): pass
|
||
|
|
def execute(self, sql, args): self.calls.append((sql, args))
|
||
|
|
def fetchone(self):
|
||
|
|
if 'INSERT INTO sag_sager' in self.calls[-1][0]: return {'id': 20}
|
||
|
|
return None
|
||
|
|
def fetchall(self):
|
||
|
|
if 'hardware_assets' in self.calls[-1][0]: return [] if missing_hardware else [{'id': 4}]
|
||
|
|
return [{'id': 9, 'name': 'Test', 'tag_group_id': None, 'behavior': None}]
|
||
|
|
class Connection:
|
||
|
|
def __init__(self): self.cur = Cursor(); self.committed = False; self.rolled_back = False
|
||
|
|
def cursor(self, **kwargs): return self.cur
|
||
|
|
def commit(self): self.committed = True
|
||
|
|
def rollback(self): self.rolled_back = True
|
||
|
|
conn = Connection()
|
||
|
|
monkeypatch.setattr(cases, 'get_db_connection', lambda: conn)
|
||
|
|
monkeypatch.setattr(cases, 'release_db_connection', lambda c: None)
|
||
|
|
monkeypatch.setattr(cases, '_normalize_case_status', lambda v: 'åben')
|
||
|
|
monkeypatch.setattr(cases, '_get_user_id_from_request', lambda r: 7)
|
||
|
|
monkeypatch.setattr(cases, '_validate_user_id', lambda v: None)
|
||
|
|
monkeypatch.setattr(cases, '_validate_group_id', lambda v: None)
|
||
|
|
monkeypatch.setattr(cases, 'table_has_column', lambda *a: True)
|
||
|
|
data = {'titel': 'Test', 'customer_id': 3, 'type': 'ticket', 'pipeline': {'amount': 123}, 'hardware_ids': [4], 'tag_ids': [9], 'order_items': [{'description': 'Router', 'amount': 50}]}
|
||
|
|
if missing_hardware:
|
||
|
|
with pytest.raises(HTTPException): asyncio.run(cases.create_sag(None, data))
|
||
|
|
assert conn.rolled_back and not conn.committed
|
||
|
|
else:
|
||
|
|
result = asyncio.run(cases.create_sag(None, data))
|
||
|
|
assert result['id'] == 20 and conn.committed and not conn.rolled_back
|
||
|
|
sql = '\n'.join(query for query, _ in conn.cur.calls)
|
||
|
|
assert all(table in sql for table in ['sag_sager','sag_salgsvarer','sag_hardware','entity_tags'])
|
||
|
|
case_args = next(args for query, args in conn.cur.calls if 'INSERT INTO sag_sager' in query)
|
||
|
|
assert 123.0 in case_args
|