import shutil import subprocess import pytest @pytest.mark.skipif(not shutil.which('node'), reason='Node required for CRM panel behavior') @pytest.mark.parametrize('scenario', ['readonly', 'shadow', 'save', 'forbidden', 'read_failure', 'missing_schema']) def test_customer_workflow_panel_isolated_permissions_and_effective_status(scenario): script = r''' const assert=require('node:assert/strict'),vm=require('node:vm'),fs=require('node:fs'); const scenario=SCENARIO,elements={},calls=[]; for(const name of ['Card','Enabled','Write','Reason','Save','Effective','Global','Preference','Message']){ elements['customerWorkflow'+name]={hidden:name==='Card'||name==='Write',disabled:true,checked:false,value:'',textContent:'',dataset:{customerId:'24'},classList:{toggle(){}},listeners:{},addEventListener(type,fn){this.listeners[type]=fn;},focus(){}}; } const document={readyState:'complete',getElementById:id=>elements[id]}; let data={customer_id:24,enabled:true,inherited:false,effective_enabled:false,global_enabled:false,mode:'active',can_manage:scenario==='save'||scenario==='shadow'}; if(scenario==='shadow')data={...data,global_enabled:true,mode:'shadow'}; if(scenario==='save')data={...data,enabled:false,global_enabled:true}; if(scenario==='missing_schema')data={...data,can_manage:true,schema_ready:false}; const fetch=async(url,options)=>{calls.push({url,options});if(scenario==='read_failure')throw Error('Offline');if(scenario==='forbidden')return{ok:false,status:403,json:async()=>({detail:'No access'})};if(options.method==='PUT')data={...data,...JSON.parse(options.body),effective_enabled:true};return{ok:true,json:async()=>data};}; vm.runInNewContext(fs.readFileSync('static/js/customer-workflow.js','utf8'),{document,fetch,console}); (async()=>{ await new Promise(resolve=>setImmediate(resolve)); assert.equal(calls[0].url,'/api/v1/workflows/customers/24/activation'); const card=elements.customerWorkflowCard; if(scenario==='forbidden'){assert.equal(card.hidden,true);return;} assert.equal(card.hidden,false); if(scenario==='read_failure'){assert.equal(elements.customerWorkflowEnabled.disabled,true);assert.match(elements.customerWorkflowMessage.textContent,/Offline/);return;} assert.equal(elements.customerWorkflowEffective.textContent,'Ikke aktiv'); if(scenario==='readonly'||scenario==='missing_schema'){ assert.equal(elements.customerWorkflowWrite.hidden,true);assert.equal(elements.customerWorkflowEnabled.disabled,true); await elements.customerWorkflowSave.listeners.click();assert.equal(calls.length,1); assert.match(elements.customerWorkflowGlobal.textContent,/slukket/); if(scenario==='missing_schema')assert.match(elements.customerWorkflowMessage.textContent,/workflowopdatering/); }else if(scenario==='shadow'){ assert.match(elements.customerWorkflowGlobal.textContent,/skyggetilstand/);assert.equal(elements.customerWorkflowEnabled.checked,true); }else{ elements.customerWorkflowEnabled.checked=true;elements.customerWorkflowReason.value='Nye regler'; await elements.customerWorkflowSave.listeners.click(); assert.deepEqual(JSON.parse(calls[1].options.body),{enabled:true,reason:'Nye regler'}); assert.equal(calls[1].options.method,'PUT');assert.equal(elements.customerWorkflowEffective.textContent,'Aktiv for kunden'); } })().catch(error=>{console.error(error);process.exitCode=1;}); '''.replace('SCENARIO', repr(scenario)) result = subprocess.run(['node', '-e', script], text=True, capture_output=True) assert result.returncode == 0, result.stderr