bmc_hub/tests/test_workflow_manual_edit_scope.py

83 lines
5.0 KiB
Python
Raw Permalink Normal View History

"""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'