bmc_hub/tests/test_case_time_billing.py

119 lines
7.0 KiB
Python
Raw Permalink Normal View History

import asyncio
import pytest
from fastapi import HTTPException
import importlib
r=importlib.import_module("app.timetracking.backend.router")
@pytest.mark.parametrize('choice,method,card,agreement,status',[
('draft','draft',None,None,'kladde'),('operations','operations',None,None,'kladde'),('development','development',None,None,'kladde'),('invoice','invoice',None,None,'afventer'),
('card_7','prepaid',7,None,'afventer'),('fpa_9','subscription',None,9,'afventer'),
])
def test_billing_selection_validates_and_keeps_selected_link(monkeypatch,choice,method,card,agreement,status):
monkeypatch.setattr(r,'_settlement_options_for_case_entry',lambda entry:{'prepaid_cards':[{'id':7}],'agreements':[{'id':9}]})
result=r._case_billing_choice(186,choice)
assert result=={'method':method,'status':status,'card_id':card,'agreement_id':agreement}
@pytest.mark.parametrize('choice',['card_8','fpa_10','card_bad','unknown'])
def test_invalid_or_foreign_billing_option_is_rejected(monkeypatch,choice):
monkeypatch.setattr(r,'_settlement_options_for_case_entry',lambda entry:{'prepaid_cards':[],'agreements':[]})
with pytest.raises(HTTPException):r._case_billing_choice(186,choice)
def test_draft_cannot_be_self_approved(monkeypatch):
monkeypatch.setattr(r,'execute_query_single',lambda *args:{'id':1,'medarbejder_id':12,'billing_method':'draft'})
monkeypatch.setattr(r,'_can_view_all_team_timers',lambda user:False)
with pytest.raises(HTTPException) as error:
asyncio.run(r.approve_time_entry_v1(1,{}, {'id':12,'username':'test'}))
assert error.value.status_code==403
def test_manual_selection_is_stored_pending_without_consuming_credit(monkeypatch):
monkeypatch.setattr(r,'_resolve_case_customer_id',lambda *args:77)
monkeypatch.setattr(r,'_settlement_options_for_case_entry',lambda entry:{'prepaid_cards':[{'id':7}],'agreements':[]})
captured={}
def insert(sql,params):
assert sql.count('%s')==len(params)
assert 'prepaid_card_id, fixed_price_agreement_id' in sql
captured['params']=params
return [{'id':3}]
monkeypatch.setattr(r,'execute_query',insert)
result=asyncio.run(r.create_manual_time_v1({'sag_id':186,'faktisk_tid_min':15,'settlement_choice':'card_7'},{'id':12,'username':'test'}))
assert result['id']==3
assert captured['params'][9]=='prepaid'
assert captured['params'][15]=='sag_v5'
assert captured['params'][16]=='afventer'
assert captured['params'][-4:-2]==(7,None)
def test_options_show_explicit_zero_customer_rate(monkeypatch):
monkeypatch.setattr(r,'execute_query_single',lambda sql,params: {'customer_id':5} if 'sag_sager' in sql else {'standard_hourly_rate':0})
monkeypatch.setattr(r,'_settlement_options_for_case_entry',lambda entry:{'prepaid_cards':[],'agreements':[]})
assert asyncio.run(r.get_case_billing_options(186))['hourly_rate']==0
def test_live_timer_keeps_draft_choice_and_does_not_consume_credit(monkeypatch):
monkeypatch.setattr(r,'_resolve_case_customer_id',lambda *args:77)
monkeypatch.setattr(r,'_resolve_target_user_id',lambda *args:12)
monkeypatch.setattr(r,'execute_query_single',lambda *args:None)
monkeypatch.setattr(r,'_settlement_options_for_case_entry',lambda entry:{'prepaid_cards':[],'agreements':[]})
def insert(sql,params):
assert sql.count('%s')==len(params)
assert params[13]=='sag_v5'
assert params[14]=='kladde'
assert params[-4:-1]==('draft',None,None)
return [{'id':3}]
monkeypatch.setattr(r,'execute_query',insert)
asyncio.run(r.start_live_timer_v1({'sag_id':186,'settlement_choice':'draft'},{'id':12,'username':'test'}))
def test_approval_preserves_explicit_invoice_instead_of_recommending_prepaid(monkeypatch):
entry={'id':3,'medarbejder_id':12,'billing_method':'invoice','kilde':'sag_v5','entry_type':'manuel','work_type':'support','fakturerbar_tid_min':30}
monkeypatch.setattr(r,'execute_query_single',lambda *args:entry)
monkeypatch.setattr(r,'_settlement_options_for_case_entry',lambda entry:{'recommended':{'method':'prepaid','prepaid_card_id':7},'prepaid_cards':[{'id':7}],'agreements':[]})
def update(sql,params):
assert params[4]=='invoice'
assert params[5] is None
return [{'id':3,'status':'approved'}]
monkeypatch.setattr(r,'execute_query',update)
assert asyncio.run(r.approve_time_entry_v1(3,{}, {'id':12,'username':'test'}))['status']=='approved'
def test_manager_can_review_draft_and_select_invoice(monkeypatch):
entry={'id':3,'medarbejder_id':12,'billing_method':'draft','kilde':'sag_v5','entry_type':'manuel','work_type':'support','fakturerbar_tid_min':30}
monkeypatch.setattr(r,'execute_query_single',lambda *args:entry)
monkeypatch.setattr(r,'_can_view_all_team_timers',lambda user:True)
monkeypatch.setattr(r,'_settlement_options_for_case_entry',lambda entry:{'recommended':{'method':'invoice'},'prepaid_cards':[],'agreements':[]})
monkeypatch.setattr(r,'execute_query',lambda *args:[{'id':3,'status':'approved'}])
assert asyncio.run(r.approve_time_entry_v1(3,{'billing_method':'invoice'}, {'id':20,'username':'chef'}))['status']=='approved'
@pytest.mark.parametrize('method',['operations','development'])
def test_review_categories_require_manager_and_final_choice(monkeypatch,method):
entry={'id':3,'medarbejder_id':12,'billing_method':method,'kilde':'sag_v5','entry_type':'manuel','work_type':'support'}
monkeypatch.setattr(r,'execute_query_single',lambda *args:entry)
monkeypatch.setattr(r,'_can_view_all_team_timers',lambda user:False)
with pytest.raises(HTTPException) as error:
asyncio.run(r.approve_time_entry_v1(3,{'billing_method':'invoice'},{'id':12}))
assert error.value.status_code==403
monkeypatch.setattr(r,'_can_view_all_team_timers',lambda user:True)
with pytest.raises(HTTPException) as error:
asyncio.run(r.approve_time_entry_v1(3,{}, {'id':20}))
assert error.value.status_code==400
@pytest.mark.parametrize('field,value',[('prepaid_card_id',999),('fixed_price_agreement_id',999)])
def test_legacy_case_registration_rejects_other_company_before_writes(monkeypatch,field,value):
monkeypatch.setattr(r,'_settlement_options_for_case_entry',lambda entry:{'prepaid_cards':[],'agreements':[]})
writes=[]
monkeypatch.setattr(r,'execute_query',lambda *args:writes.append(args))
with pytest.raises(HTTPException) as error:
asyncio.run(r.create_internal_time_entry({'sag_id':186,'original_hours':1,'customer_id':999,field:value},{'id':12,'username':'test'}))
assert error.value.status_code in (400,409)
assert writes==[]
def test_case_billing_queries_use_case_company_not_entry_customer(monkeypatch):
monkeypatch.setattr(r,'execute_query_single',lambda *args:{'customer_id':8})
queries=[]
def read(sql,params):queries.append((sql,params));return []
monkeypatch.setattr(r,'execute_query',read)
r._settlement_options_for_case_entry({'sag_id':186,'customer_id':999})
assert len(queries)==2
assert all(params==(8,) and 'customer_id = %s' in sql for sql,params in queries)