72 lines
4.8 KiB
Python
72 lines
4.8 KiB
Python
|
|
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
|