83 lines
5.0 KiB
Python
83 lines
5.0 KiB
Python
"""Time-tab financial edits must use the audited workflow override path."""
|
|
import asyncio
|
|
from importlib import import_module
|
|
import pytest
|
|
from fastapi import HTTPException
|
|
from app.core.config import settings
|
|
from app.workflows import integration,settlement
|
|
from app.workflows import decisions,classification
|
|
|
|
router=import_module('app.timetracking.backend.router')
|
|
|
|
@pytest.fixture
|
|
def active(monkeypatch):
|
|
monkeypatch.setattr(settings,'WORKFLOWS_ENABLED',True)
|
|
monkeypatch.setattr(settings,'WORKFLOWS_MODE','active')
|
|
monkeypatch.setattr(integration,'active_for_customer',lambda customer:customer==24)
|
|
monkeypatch.setattr(settlement,'workflow_customer',lambda _:24)
|
|
|
|
@pytest.mark.parametrize('payload',[{'billing_method':'prepaid'},{'prepaid_card_id':9},{'fixed_price_agreement_id':8}])
|
|
def test_raw_patch_does_not_change_active_workflow_financial_selection(monkeypatch,active,payload):
|
|
original={'id':1,'vtiger_id':None,'billing_method':'invoice','status':'pending'}
|
|
monkeypatch.setattr(router,'execute_query_single',lambda *_:original)
|
|
monkeypatch.setattr(router,'execute_query',lambda *_:pytest.fail('No direct financial selection write permitted'))
|
|
monkeypatch.setattr(router,'_assert_prepaid_entry_editable',lambda _:None)
|
|
try:
|
|
result=asyncio.run(router.patch_time_entry_v1(1,payload,current_user={'id':4,'is_superadmin':True}))
|
|
except HTTPException as exc:
|
|
assert exc.status_code==409
|
|
else:
|
|
assert result['billing_method']=='invoice'
|
|
|
|
def test_legacy_wizard_cannot_change_billable_without_audited_override(monkeypatch,active):
|
|
monkeypatch.setattr(router,'execute_query_single',lambda *_:{'id':1,'vtiger_id':None,'billing_method':'invoice','status':'pending'})
|
|
monkeypatch.setattr(router,'execute_query',lambda *_:pytest.fail('Active financial edit must not reach UPDATE'))
|
|
payload=router.TModuleWizardEditRequest(billable=False)
|
|
with pytest.raises(HTTPException) as error:
|
|
asyncio.run(router.update_entry_details(1,payload))
|
|
assert error.value.status_code==409
|
|
|
|
def test_legacy_reset_cannot_reopen_active_workflow_time(monkeypatch,active):
|
|
monkeypatch.setattr(router,'execute_query_single',lambda *_:{'id':1,'vtiger_id':None,'billing_method':'invoice','status':'approved'})
|
|
monkeypatch.setattr(router.wizard,'reset_to_pending',lambda *args,**kwargs:pytest.fail('Workflow time cannot use legacy reset'))
|
|
with pytest.raises(HTTPException) as error:
|
|
asyncio.run(router.reset_to_pending(1,reason='Old wizard',user_id=4))
|
|
assert error.value.status_code==409
|
|
|
|
def test_legacy_bulk_approval_cannot_skip_workflow_decision(monkeypatch,active):
|
|
monkeypatch.setattr(router.wizard,'get_case_entries',lambda *args,**kwargs:[{'id':1,'vtiger_id':None}])
|
|
monkeypatch.setattr(router.wizard,'approve_case_entries',lambda *args,**kwargs:pytest.fail('No active workflow bulk approval through legacy wizard'))
|
|
with pytest.raises(HTTPException) as error:
|
|
asyncio.run(router.approve_all_case_entries(9,user_id=4))
|
|
assert error.value.status_code==409
|
|
|
|
@pytest.mark.parametrize('workflows_enabled,vtiger_id',[(False,None),(True,'legacy-vtiger')])
|
|
def test_wizard_edit_preserves_nonworkflow_and_imported_behavior(monkeypatch,active,workflows_enabled,vtiger_id):
|
|
monkeypatch.setattr(settings,'WORKFLOWS_ENABLED',workflows_enabled)
|
|
monkeypatch.setattr(router,'execute_query_single',lambda *_:{'id':1,'vtiger_id':vtiger_id,'billing_method':'invoice','status':'pending'})
|
|
writes=[]
|
|
monkeypatch.setattr(router,'execute_query',lambda sql,params:writes.append((sql,params)))
|
|
result=asyncio.run(router.update_entry_details(1,router.TModuleWizardEditRequest(billable=False)))
|
|
assert result['id']==1
|
|
assert len(writes)==1 and 'billable = %s' in writes[0][0]
|
|
|
|
def test_manual_override_records_actor_reason_and_previous_decision(monkeypatch):
|
|
from contextlib import contextmanager
|
|
captured={}
|
|
@contextmanager
|
|
def transaction(): yield object()
|
|
monkeypatch.setattr(decisions.service,'transaction',transaction)
|
|
monkeypatch.setattr(decisions,'_locked_time',lambda *args:({'workflow_revision':5},{'id':2}))
|
|
monkeypatch.setattr(decisions.service,'load_time_context',lambda *args:{'time_id':1,'customer_id':'24','workflow_revision':5})
|
|
monkeypatch.setattr(decisions,'active_for_customer',lambda _:True)
|
|
monkeypatch.setattr(classification,'enrich_time_context',lambda cursor,context:context)
|
|
def save(cursor,time_id,revision,result,manual=False):
|
|
captured.update(result=result,manual=manual,time_id=time_id,revision=revision)
|
|
return result
|
|
monkeypatch.setattr(decisions,'_save',save)
|
|
result=decisions.override(1,decisions.OverrideRequest(method='invoice',reason=' Aftale med kunden '),user={'id':4})
|
|
assert captured['manual'] is True and captured['revision']==5
|
|
assert result['actor_id']==4 and result['previous_decision_id']==2
|
|
assert result['reason']=='manual_override' and result['override_reason']=='Aftale med kunden'
|
|
assert result['method']=='invoice'
|