Files
Investidor-V2/tools/portal-internal-designation-browser.cjs
nala 287e991486
tests / ci (push) Waiting to run
oportunidades 7
2026-10-13 11:40:29 +02:00

101 lines
14 KiB
JavaScript

const fs=require('node:fs'),path=require('node:path'),assert=require('node:assert/strict'),{spawn,spawnSync}=require('node:child_process');
const {chromium}=require('C:/Users/macbook/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/node_modules/playwright');
const R=path.resolve(__dirname,'..'),P=path.join(R,'storage/app/private/investmoz-v2/portal-audit');
const plan=JSON.parse(fs.readFileSync(path.join(P,'internal-designation-plan.json'),'utf8')),S=plan.source;
assert(S.startsWith(P+path.sep+'onboarding-qa-internal-')&&plan.target.port===55441);
const PHP='C:/laragon/bin/php/php-8.5.11-Win32-vs17-x64/php.exe',fixture=path.join(S,'tools/internal-fixture.php');
if(!fs.existsSync(path.join(S,'storage/app/private/onboarding-actors-local.json'))){const seeded=spawnSync(PHP,[fixture,'seed'],{cwd:S,windowsHide:true,encoding:'utf8'});assert.equal(seeded.status,0,'Synthetic fixture initialization failed');}
const actors=JSON.parse(fs.readFileSync(path.join(S,'storage/app/private/onboarding-actors-local.json'),'utf8'));
const evidence={at:new Date().toISOString(),target:'http://127.0.0.1:18118',QA_only:true,database:plan.target.database,port:55441,active_writes:0,secrets_recorded:false,criteria:[],page_errors:[]};
const envFile=path.join(S,'.env'),initialEnv=fs.readFileSync(envFile,'utf8');
const log=fs.openSync(path.join(S,'storage/logs/internal-http.log'),'a');
const server=spawn(PHP,['-S','127.0.0.1:18118','-t',path.join(S,'public'),path.join(S,'tools/internal-router.php')],{cwd:S,windowsHide:true,stdio:['ignore',log,log]});
let browser,page,context;const pause=ms=>new Promise(r=>setTimeout(r,ms));
const resumePromoter=process.argv.includes('--resume-promoter');
const checkLabels=process.argv.includes('--check-labels');
if(checkLabels){const previous=JSON.parse(fs.readFileSync(path.join(P,'internal-designation-browser-scope-before-labels.json'),'utf8'));assert.equal(previous.result,'passed');evidence.criteria=[...previous.criteria];evidence.reused_proof='internal-designation-browser-scope-before-labels.json';}
if(resumePromoter){const previous=JSON.parse(fs.readFileSync(path.join(P,'internal-designation-browser.json'),'utf8'));assert(previous.stage.startsWith('internal_promoter_')&&previous.criteria.includes('assignments_recovered_in_new_normal_MFA_session'));evidence.criteria=[...previous.criteria];evidence.reused_proof='internal-designation-browser-administration-passed-before-promoter.json';if(!fs.existsSync(path.join(P,evidence.reused_proof)))fs.copyFileSync(path.join(P,'internal-designation-browser.json'),path.join(P,evidence.reused_proof));}
async function login(which){
context=await browser.newContext();page=await context.newPage();page.on('pageerror',()=>evidence.page_errors.push('pageerror'));
const actor=actors[which];await page.goto(evidence.target+'/login');await page.locator('input[name="email"]').fill(actor.email);await page.locator('input[name="password"]').fill(actor.password);
await page.getByRole('button',{name:'Log in',exact:true}).click();await page.waitForURL('**/two-factor-challenge');
const otp=spawnSync(PHP,[fixture,'otp',which],{cwd:S,windowsHide:true,encoding:'utf8'});assert.equal(otp.status,0);assert(/^\d{6}$/.test(otp.stdout));
await page.locator('input[autocomplete="one-time-code"]').fill(otp.stdout);await page.getByRole('button',{name:'Continue',exact:true}).click();
await page.waitForFunction(()=>!['/login','/two-factor-challenge'].includes(location.pathname));
const landing=await page.goto(evidence.target+'/dashboard',{waitUntil:'domcontentloaded'});assert.equal(landing.status(),200);assert.equal(new URL(page.url()).pathname,'/dashboard');
}
async function persistence(){const r=spawnSync(PHP,[fixture,'verify'],{cwd:S,windowsHide:true,encoding:'utf8'});assert.equal(r.status,0);return JSON.parse(r.stdout);}
(async()=>{try{
for(let i=0;i<30;i++){try{if((await fetch(evidence.target+'/up')).ok)break;}catch{}await pause(250);}
await pause(31000-Date.now()%30000);browser=await chromium.launch({headless:true,executablePath:'C:/Users/macbook/AppData/Local/ms-playwright/chromium-1243/chrome-win64/chrome.exe'});
if(checkLabels){
evidence.stage='internal_assignment_six_categories_and_labels';await login('holder');await page.getByRole('link',{name:'Gestão de perfis',exact:true}).first().click();await page.getByRole('heading',{name:'Gestão de perfis',exact:true}).waitFor();
const section=page.getByRole('region',{name:'Designação interna',exact:true});assert.equal(await section.getByRole('checkbox').count(),7);
assert.deepEqual(await section.getByLabel('Função interna',{exact:true}).locator('option').allTextContents(),['Promotor','Validador','Publicador']);
assert.equal(await section.getByLabel('concession',{exact:true}).count(),0);assert.equal(await section.getByLabel('financing',{exact:true}).count(),0);
await page.screenshot({path:path.join(P,'internal-designation-administration-qa.png'),fullPage:true});evidence.criteria.push('internal_form_only_six_approved_categories_and_three_named_roles');
evidence.persistence=await persistence();assert.equal(evidence.persistence.assignments.length,3);assert.equal(evidence.persistence.audit_role_granted,3);assert.equal(evidence.persistence.memberships,0);assert.equal(evidence.persistence.external_requests,0);assert.equal(evidence.persistence.operational_enabled,false);
evidence.result='passed';evidence.code_revision=plan.delta_sha256;return;
}
let data,operator;
if(!resumePromoter){
evidence.stage='holder_login';await login('holder');
await page.getByRole('link',{name:'Gestão de perfis',exact:true}).first().click();await page.waitForURL('**/perfis/gestao');
evidence.criteria.push('normal_MFA_dashboard_to_administration');
evidence.stage='operator_registration';let section=page.getByRole('region',{name:'Organização operadora',exact:true});
await section.getByRole('button',{name:'Guardar organização operadora',exact:true}).click();await section.getByRole('status').filter({hasText:/Organização operadora registada|Organização existente recuperada/}).waitFor();
await section.getByRole('button',{name:'Guardar organização operadora',exact:true}).click();await section.getByRole('status').getByText('Cadastro recuperado',{exact:false}).waitFor();
evidence.criteria.push('operator_master_registered_and_same_attempt_repeated_without_duplicate');
data=await persistence();operator=data.organizations.find(o=>o.external_id==='400993114');assert(operator);
evidence.stage='internal_roles';section=page.getByRole('region',{name:'Designação interna',exact:true});
for(const which of ['promoter','validator','publisher']){
const recovered=(await persistence()).assignments.find(a=>a.user_id===actors[which].id&&a.role===which);
if(recovered){await page.getByText('Conta '+actors[which].id+': '+which+' — atribuído',{exact:false}).waitFor();evidence.criteria.push('prior_'+which+'_assignment_recovered_visibly');continue;}
await section.getByLabel('Conta da pessoa designada',{exact:true}).fill(String(actors[which].id));await section.getByLabel('Função interna',{exact:true}).selectOption(which);
await section.getByLabel('Vínculo à organização operadora',{exact:true}).selectOption(operator.id);
for(const type of ['investment_project','real_estate','land_rights','business_sale','business_partnership','franchise'])await section.getByLabel(type,{exact:true}).check();
await section.getByLabel('Confirmo a designação interna expressa pelo responsável da organização operadora.',{exact:true}).check();
await section.getByLabel('Fundamento da designação interna',{exact:true}).fill('Synthetic QA internal owner designation for portal staff; external promoter documents do not apply.');
await section.getByRole('button',{name:'Atribuir função interna com auditoria',exact:true}).click();await page.getByText('Conta '+actors[which].id+': '+which+' — atribuído',{exact:false}).waitFor();
await section.getByRole('button',{name:'Atribuir função interna com auditoria',exact:true}).click();await section.getByRole('status').getByText('Designação interna atribuída e auditada.',{exact:true}).waitFor();
if(which!=='publisher')await section.getByRole('button',{name:'Preparar outra atribuição',exact:true}).click();
}
evidence.criteria.push('three_roles_assigned_by_owner_without_external_documents_or_company_memberships');
await page.reload();for(const which of ['promoter','validator','publisher'])await page.getByText('Conta '+actors[which].id+': '+which+' — atribuído',{exact:false}).waitFor();
await page.screenshot({path:path.join(P,'internal-designation-administration-qa.png'),fullPage:true});
assert.equal((await page.goto(evidence.target+'/minhas-oportunidades')).status(),404);await context.close();
evidence.criteria.push('assignments_recovered_after_reload_while_workflow_is_closed');
await pause(31000-Date.now()%30000);await login('holder');await page.goto(evidence.target+'/perfis/gestao');
for(const which of ['promoter','validator','publisher'])await page.getByText('Conta '+actors[which].id+': '+which+' — atribuído',{exact:false}).waitFor();
evidence.criteria.push('assignments_recovered_in_new_normal_MFA_session');await context.close();
}else{data=await persistence();operator=data.organizations.find(o=>o.external_id==='400993114');assert(operator);assert.equal(data.assignments.length,3);}
// Enable only in the synthetic QA tree to verify the newly affected promoter entry.
assert(initialEnv.includes('PORTAL_PROFILE_ACCESS_ENABLED=false'));
assert(initialEnv.includes('PORTAL_OPPORTUNITY_SPECIALIZED_DRAFTS_ENABLED=false')&&initialEnv.includes('PORTAL_OPPORTUNITY_DASHBOARD_ACCESS_ENABLED=false'));
fs.writeFileSync(envFile,initialEnv.replace('PORTAL_PROFILE_ACCESS_ENABLED=false','PORTAL_PROFILE_ACCESS_ENABLED=true').replace('PORTAL_OPPORTUNITY_SPECIALIZED_DRAFTS_ENABLED=false','PORTAL_OPPORTUNITY_SPECIALIZED_DRAFTS_ENABLED=true').replace('PORTAL_OPPORTUNITY_DASHBOARD_ACCESS_ENABLED=false','PORTAL_OPPORTUNITY_DASHBOARD_ACCESS_ENABLED=true'));
evidence.workflow_QA_configuration={profiles:true,dashboard:true,specialized_drafts:true,only_during_synthetic_QA_demo:true};
evidence.stage='internal_promoter_dashboard';await login('promoter');
page.on('response',async response=>{if(response.request().method()==='POST'&&new URL(response.url()).pathname==='/minhas-oportunidades/drafts'){evidence.draft_save_status=response.status();const body=await response.json().catch(()=>({}));evidence.draft_save_errors=body.errors??null;}});
evidence.stage='internal_promoter_create';
await page.getByRole('link',{name:'Nova oportunidade',exact:true}).first().click();await page.waitForFunction(()=>location.pathname==='/minhas-oportunidades/create');
const selector=page.getByLabel('Promotor real e categoria autorizada',{exact:true});assert.equal(await selector.inputValue(),'');
const actual=data.organizations.find(o=>o.name==='Synthetic real promoter QA');await selector.selectOption(actual.id+'|investment_project');
await page.getByLabel('Título oficial',{exact:true}).fill('Synthetic internal operator draft — actual promoter preserved');
await page.getByLabel('Motivo da alteração',{exact:true}).fill('Synthetic QA creation by internal portal staff; actual promoter identity preserved.');
evidence.stage='internal_promoter_save';await page.getByRole('button',{name:'Guardar rascunho',exact:true}).click();
await page.waitForFunction(()=>location.pathname.startsWith('/minhas-oportunidades/')&&location.pathname.endsWith('/edit'));
evidence.draft_id=Number(new URL(page.url()).pathname.split('/')[2]);await page.reload({waitUntil:'domcontentloaded'});assert.equal(await page.getByLabel('Título oficial',{exact:true}).inputValue(),'Synthetic internal operator draft — actual promoter preserved');
await page.screenshot({path:path.join(P,'internal-designation-real-promoter-draft-qa.png'),fullPage:true});
evidence.criteria.push('internal_promoter_dashboard_create_save_reload_with_explicit_actual_promoter');
await context.close();fs.writeFileSync(envFile,initialEnv);evidence.persistence=await persistence();
assert.equal(evidence.persistence.assignments.length,3);assert.equal(evidence.persistence.audit_role_granted,3);assert.equal(evidence.persistence.organization_registrations,1);
assert.equal(evidence.persistence.memberships,0);assert.equal(evidence.persistence.external_requests,0);assert.equal(evidence.persistence.operational_enabled,false);
const record=evidence.persistence.opportunities.find(r=>r.id===evidence.draft_id);assert(record);assert.equal(record.organization_id,actual.id);
assert.equal(JSON.parse(record.workflow_context).operator_organization_id,operator.id);assert.notEqual(actual.id,operator.id);
evidence.criteria.push('actual_promoter_operator_and_creator_recovered_separately_and_workflow_returned_OFF');
assert.equal(evidence.page_errors.length,0);evidence.result='passed';
}catch(e){evidence.result='stopped';evidence.error_type=e.name;evidence.error_message=String(e.message).slice(0,500);process.exitCode=1;
if(page){evidence.final_path=new URL(page.url()).pathname;evidence.visible_headings=await page.getByRole('heading').allTextContents();if(evidence.final_path.startsWith('/minhas-oportunidades')){evidence.visible_feedback=await page.locator('[role="status"], [role="alert"]').allTextContents();await page.screenshot({path:path.join(P,'internal-designation-promoter-stopped-qa.png'),fullPage:true});}}
if(page&&new URL(page.url()).pathname.startsWith('/perfis/')){evidence.visible_feedback=await page.locator('main [role="status"]').allTextContents();await page.screenshot({path:path.join(P,'internal-designation-stopped-qa.png'),fullPage:true});}
}finally{fs.writeFileSync(envFile,initialEnv);if(browser)await browser.close();server.kill();fs.closeSync(log);fs.writeFileSync(path.join(P,'internal-designation-browser.json'),JSON.stringify(evidence,null,2));console.log(JSON.stringify(evidence));}})();