bmc_hub/tests/test_case_v5_billing_scope.py

72 lines
4.8 KiB
Python
Raw Permalink Normal View History

import shutil
import subprocess
from pathlib import Path
import pytest
@pytest.mark.skipif(not shutil.which('node'), reason='Node required for V5 behavior')
def test_composer_new_automatic_body_does_not_mutate_frozen_unknown_outcome():
source = Path('static/js/case-command-composer.js').read_text()
body = source.split('function body()', 1)[1].split(' function label(', 1)[0]
script = r'''
const assert=require('node:assert/strict');
let frozenBody=null,parsed={comment:'Tid',commands:[{type:'time',minutes:15}]};
function body() BODY
assert.equal(body().billing_mode,'automatic');
const legacy={comment:'Older',commands:[{type:'time',settlement_choice:'card_8'}],internal:true};frozenBody=legacy;
assert.equal(body(),legacy);assert.equal(body().commands[0].settlement_choice,'card_8');assert.equal(body().billing_mode,undefined);
'''.replace('BODY', body)
result = subprocess.run(['node', '-e', script], text=True, capture_output=True)
assert result.returncode == 0, result.stderr
@pytest.mark.skipif(not shutil.which('node'), reason='Node required for V5 behavior')
def test_active_billing_choice_permission_and_selected_method():
source = Path('static/js/time-billing-review.js').read_text()
script = r'''
const assert=require('node:assert/strict'),vm=require('node:vm'),fs=require('node:fs'),window={};
vm.runInNewContext(fs.readFileSync('static/js/time-billing-review.js','utf8'),{window});const review=window.TimeBillingReview;
assert.equal(review.canChoose({workflow_active:true,workflow_can_override:false,status:'pending'}),false);
assert.equal(review.canChoose({workflow_active:true,workflow_can_override:true,status:'pending'}),true);
assert.equal(review.canChoose({workflow_active:true,workflow_can_override:true,status:'billed'}),false);
assert.match(review.displayBadge({billing_method:'draft',selected_billing_method:'invoice'}),/Faktura/);
assert.doesNotMatch(review.displayBadge({billing_method:'draft',selected_billing_method:'invoice'}),/Kladde/);
'''
result = subprocess.run(['node', '-e', script], text=True, capture_output=True)
assert result.returncode == 0, result.stderr
def test_shared_v4_payload_markers_are_conditional_and_manual_only_in_time_tab():
source = Path('app/modules/sag/templates/detail_v4.html').read_text()
manual = source.split('async function createManualTimeV1(event)', 1)[1].split(' async function ', 1)[0]
assert "document.getElementById('caseV5Data') ? {billing_mode:'manual'" in manual
quick = source.split('async function registerQuickTimeFromComment(content)', 1)[1].split(' function resolveCommentAwait', 1)[0]
assert "document.getElementById('caseV5Data') ? {billing_mode: 'automatic'} : {}" in quick
assert 'settlement_choice' not in quick
assert "document.getElementById('caseV5Data')?'&billing_display=true':''" in source
@pytest.mark.skipif(not shutil.which('node'), reason='Node required for V5 behavior')
def test_active_manual_selection_only_saves_audited_decision():
script = r'''
const assert=require('node:assert/strict'),vm=require('node:vm'),fs=require('node:fs'),window={},calls=[];
let host;
const select={value:'invoice',add(){}},textarea={value:''},submit={disabled:false},status={},form={};
const document={createElement:()=>host={listeners:{},querySelector:s=>({select,textarea,form,'[type=submit]':submit,'[role=status]':status}[s]),addEventListener:(name,fn)=>host.listeners[name]=fn,remove(){}},body:{append(){}}};
class Modal{show(){}hide(){host.listeners['hidden.bs.modal']();}dispose(){}}
const fetch=async(url,options={})=>{calls.push({url,options});return{ok:true,json:async()=>url.endsWith('settlement-options')?{prepaid_cards:[{id:8}],agreements:[]}:url.endsWith('workflow-decision')?{current:{method:'invoice',status:'decided',details:{}}}:{id:1}};};
function Option(label,value){this.value=value;}
vm.runInNewContext(fs.readFileSync('static/js/time-billing-review.js','utf8'),{window,document,fetch,Option,bootstrap:{Modal}});
(async()=>{
const promise=window.TimeBillingReview.choose({id:1,workflow_active:true,workflow_can_override:true,status:'pending'});
await new Promise(resolve=>setImmediate(resolve));
select.value='card_8';await form.onsubmit({preventDefault(){}});assert.equal(calls.length,2);
textarea.value='Gyldig kundeaftale';await form.onsubmit({preventDefault(){}});assert.equal(await promise,true);
assert.equal(calls.length,3);assert.match(calls[2].url,/workflow-override$/);assert.equal(calls[2].options.method,'POST');
assert.deepEqual(JSON.parse(calls[2].options.body),{method:'prepaid',reason:'Gyldig kundeaftale',card_id:8});
assert.equal(calls.some(call=>call.url.endsWith('/approve')),false);
})().catch(error=>{console.error(error);process.exitCode=1;});
'''
result = subprocess.run(['node', '-e', script], text=True, capture_output=True)
assert result.returncode == 0, result.stderr