from datetime import datetime,timezone from unittest.mock import MagicMock import pytest from fastapi import HTTPException from pydantic import ValidationError from app.timetracking.backend import quick_time as q BASE=dict(request_id='10000000-0000-4000-8000-000000000001',mode='registered',description='Internt møde',minutes=30,starts_at=datetime(2026,10,7,10,tzinfo=timezone.utc),user_ids=[1]) @pytest.mark.parametrize('changes',[{'minutes':0},{'description':' '},{'starts_at':datetime(2026,10,7)},{'user_ids':[]},{'priority':'invalid'}]) def test_input_validation(changes): with pytest.raises(ValidationError):q.QuickTimeInput(**(BASE|changes)) def test_no_customer_or_case_required(): payload=q.QuickTimeInput(**BASE) assert payload.customer_id is None and payload.contact_id is None assert 'sag_id' not in payload.model_dump() def test_foreign_employee_permission_rejected(monkeypatch): monkeypatch.setattr(q,'can_register',lambda *a:False) with pytest.raises(HTTPException) as error:q.save(q.QuickTimeInput(**BASE),{'id':2}) assert error.value.status_code==403 def test_convert_requires_plan(monkeypatch): monkeypatch.setattr(q,'can_register',lambda *a:True);monkeypatch.setattr(q,'can_plan',lambda *a:True) with pytest.raises(HTTPException):q.save(q.QuickTimeInput(**(BASE|{'mode':'convert'})),{'id':1}) def test_idempotent_replay_returns_result(monkeypatch): payload=q.QuickTimeInput(**BASE);conn=MagicMock();cur=conn.cursor.return_value.__enter__.return_value cur.fetchone.return_value={'actor_id':1,'payload':payload.model_dump(mode='json'),'result':{'ids':[12]}} monkeypatch.setattr(q,'can_register',lambda *a:True);monkeypatch.setattr(q,'get_db_connection',lambda:conn);monkeypatch.setattr(q,'release_db_connection',lambda c:None) assert q.save(payload,{'id':1})=={'ids':[12]} conn.commit.assert_not_called() assert all('INSERT' not in call.args[0] for call in cur.execute.call_args_list)