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

60 lines
13 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Changed UUID consumers only. Existing A/B and C37 acceptance is preserved.
const fs=require('node:fs'),path=require('node:path'),assert=require('node:assert/strict'),crypto=require('node:crypto');
const {chromium}=require('C:/Users/macbook/.cache/codex-runtimes/codex-primary-runtime/dependencies/node/node_modules/playwright');
const ROOT=path.resolve(__dirname,'..'),DIR=path.join(ROOT,'storage/app/private/investmoz-v2/portal-audit');
const plan=JSON.parse(fs.readFileSync(path.join(DIR,'portal-stability-latest.json'))),BASE=plan.consumer_environment.base,TARGET=plan.consumer_environment.target.database+':55441';
const actors=JSON.parse(fs.readFileSync(path.join(DIR,'profiles-v1-actors-private.json')));
actors.geo=JSON.parse(fs.readFileSync(path.join(DIR,'i0-qa-actor-private.json')));
const receipt=path.join(DIR,'portal-stability-extra-chromium.json');
const proof={at:new Date().toISOString(),origin:BASE,target:TARGET,checks:[],operations:[],errors:[],active_writes:0,AB_C37_not_repeated:true};
const resume=process.argv.includes('--resume');
if(resume){const prior=JSON.parse(fs.readFileSync(receipt));assert.equal(prior.target,TARGET);assert.equal(prior.errors.length,0);assert.equal(prior.checks.length,7);assert(prior.new_opportunity_id&&prior.new_partnership_path);fs.copyFileSync(receipt,receipt.replace('.json','-before-consent-retry.json'));Object.assign(proof,prior,{at:new Date().toISOString(),previous_harness_missing_consent:prior.failure});delete proof.failure;delete proof.result;}
const remaining=process.argv.includes('--remaining');
if(remaining){const prior=JSON.parse(fs.readFileSync(receipt));assert.equal(prior.target,TARGET);assert.equal(prior.errors.length,0);assert.equal(prior.checks.length,15);fs.copyFileSync(receipt,receipt.replace('.json','-before-geometry-memory-correction.json'));Object.assign(proof,prior,{at:new Date().toISOString(),previous_missing_geometry_exemplar:prior.failure});delete proof.failure;delete proof.result;}
const save=()=>fs.writeFileSync(receipt,JSON.stringify(proof,null,2));
const redact=e=>Object.values(actors).reduce((s,a)=>['password','totp_secret','secret'].reduce((v,k)=>a[k]?v.replaceAll(a[k],'[REDACTED]'):v,s),String(e.message||e)).split('\nCall log:')[0].replace(/\b\d{6}\b/g,'[OTP OMITTED]').slice(0,1200);
function otp(secret){let bits=0,v=0;const b=[],abc='ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';for(const c of secret.toUpperCase().replace(/=+$/,'')){v=(v<<5)|abc.indexOf(c);bits+=5;if(bits>=8){b.push((v>>>(bits-8))&255);bits-=8;}}const n=Buffer.alloc(8);n.writeBigUInt64BE(BigInt(Math.floor(Date.now()/30000)));const d=crypto.createHmac('sha1',Buffer.from(b)).update(n).digest(),o=d[19]&15;return String((d.readUInt32BE(o)&0x7fffffff)%1000000).padStart(6,'0');}
(async()=>{const browser=await chromium.launch({headless:true,executablePath:'C:/Users/macbook/AppData/Local/ms-playwright/chromium-1243/chrome-win64/chrome.exe',args:['--host-resolver-rules=MAP portal-investidor.test 127.0.0.1','--no-proxy-server']});let ctx,page,role;
async function actor(name){if(ctx)await ctx.close();role=name;ctx=await browser.newContext({ignoreHTTPSErrors:false,viewport:{width:1440,height:1000}});
await ctx.addInitScript(()=>Object.defineProperty(Crypto.prototype,'randomUUID',{value:undefined,configurable:true}));
await ctx.route('**/*',r=>new URL(r.request().url()).origin===BASE?r.continue():r.abort());page=await ctx.newPage();page.setDefaultTimeout(35000);
page.on('pageerror',e=>{proof.errors.push({role,path:new URL(page.url()).pathname,message:redact(e)});save();});
page.on('request',r=>{if(!['POST','PUT'].includes(r.method())||['/login','/two-factor-challenge'].includes(new URL(r.url()).pathname))return;
let data;try{data=r.postDataJSON();}catch{return;}for(const key of ['client_key','creation_key','idempotency_key'])if(data?.[key]){
assert.match(data[key],/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i);
proof.operations.push({role,path:new URL(r.url()).pathname,method:r.method(),key_field:key,key_sha256:crypto.createHash('sha256').update(data[key]).digest('hex')});save();}});
await open('/login');const a=actors[name];await page.locator('#email').fill(a.email);await page.locator('#password').fill(a.password);await page.locator('[data-test="login-button"]').click();
await page.waitForURL(u=>u.pathname!=='/login');if(new URL(page.url()).pathname==='/two-factor-challenge'){await page.waitForTimeout(30500-(Date.now()%30000));await page.locator('input[name="code"]').fill(otp(a.totp_secret??a.secret));await page.getByRole('button',{name:'Continue',exact:true}).click();await page.waitForURL(u=>!['/login','/two-factor-challenge'].includes(u.pathname));}}
async function open(route){const r=await page.goto(BASE+route,{waitUntil:'networkidle'});assert.equal(r.headers()['x-investmoz-qa-target'],TARGET);return r;}
async function observed(component,route,expected=200){const r=await open(route);assert.equal(r.status(),expected);await page.locator('h1').first().waitFor();assert(await page.evaluate(()=>isSecureContext&&typeof crypto.randomUUID==='undefined'&&typeof crypto.getRandomValues==='function'));
const row={component,role,route,status:r.status(),uuid_api_absent:true,visible_heading:(await page.locator('h1').first().innerText()).slice(0,150),errors:proof.errors.length};proof.checks.push(row);save();return row;}
async function recover(route,label){await open(route);await page.reload({waitUntil:'networkidle'});assert(await page.locator('h1').first().isVisible());proof.checks.push({criterion:label,role,route,refresh_recovery:true});save();}
try{
const resumeExtra=process.argv.includes('--resume-extra');
if(resumeExtra){const prior=JSON.parse(fs.readFileSync(receipt));assert.equal(prior.checks.length,2);Object.assign(proof,prior);delete proof.failure;delete proof.result;}
if(!resumeExtra){
await actor('geo');await open('/management/territory-geometry-import?level=1');
assert(await page.getByText(/Scan: limpo e atual/).isVisible());await page.getByLabel('Fundamento da aplicação ou reversão').fill('Synthetic isolated QA stability: replay and reversal preserve retained geometries');await page.getByLabel('Confirmei as correspondências.').check();
const apply=page.getByRole('button',{name:'Aplicar correspondências',exact:true});await apply.click();await page.getByRole('status').filter({hasText:'Criadas 0; reutilizadas 11; pendentes 0.'}).waitFor();await apply.click();await page.getByRole('status').filter({hasText:'Criadas 0; reutilizadas 11; pendentes 0.'}).waitFor();
assert.equal(proof.operations.at(-1).key_sha256,proof.operations.at(-2).key_sha256);await page.getByRole('button',{name:'Reverter apenas geometrias criadas pelo lote',exact:true}).click();await page.getByRole('status').filter({hasText:'Revertidas 0; preservadas 11'}).waitFor();
proof.checks.push({component:'territory-geometry-import',criterion:'preview/apply/replay/reverse preserves all eleven existing province geometries; publication separate',passed:true});save();
await page.getByLabel('Nível',{exact:true}).selectOption('2');await page.getByText(/Esperado: 159/).waitFor();await page.getByRole('button',{name:'Atualizar pré-visualização',exact:true}).click();await page.getByText(/Esperado: 159/).waitFor();proof.checks.push({criterion:'level switch and preview refresh without randomUUID',passed:true});save();
}
await actor('promoter');await open('/minhas-oportunidades/53/resources');
let resourceKeys=[];page.on('request',r=>{if(r.method()==='POST'&&new URL(r.url()).pathname.includes('/53/resources/')){const key=r.postData()?.match(/name="creation_key"\r\n\r\n([0-9a-f-]+)/)?.[1];if(key){assert.match(key,/^[0-9a-f-]{36}$/);resourceKeys.push(crypto.createHash('sha256').update(key).digest('hex'));}}});
let interrupted=false;await page.route('**/53/resources/locations',async r=>{if(r.request().method()==='POST'&&!interrupted){interrupted=true;await r.abort('failed');}else await r.continue();});
await page.getByLabel('Título',{exact:true}).fill('Synthetic stability recoverable location');await page.getByLabel('Motivo (20–1000 caracteres)').fill('Synthetic QA UUID location save: no territorial classification inferred');await page.getByRole('button',{name:'Guardar recurso',exact:true}).click();await page.getByRole('status').filter({hasText:/Failed to fetch/}).waitFor();
await page.getByRole('button',{name:'Guardar recurso',exact:true}).click();await page.getByText('Synthetic stability recoverable location · v1 · rascunho',{exact:true}).waitFor();assert.equal(resourceKeys[0],resourceKeys[1]);
await recover('/minhas-oportunidades/53/resources','incomplete location preserved after validation/retry and refresh');proof.checks.push({component:'opportunity-resources',key_preserved_on_retry:true,passed:true});save();
await page.getByRole('button',{name:'Ver / editar / restaurar',exact:true}).last().click();await page.getByLabel('Título',{exact:true}).fill('Synthetic stability recoverable location edited');await page.getByRole('button',{name:'Guardar recurso',exact:true}).click();await page.getByText('Synthetic stability recoverable location edited · v2 · rascunho',{exact:true}).waitFor();proof.checks.push({criterion:'resource selection rotates key and edited version visible',passed:true});save();
await open('/balcao-virtual/painel');await page.getByText('Preparar acompanhamento de um projecto',{exact:true}).click();await page.getByLabel('Título do projecto',{exact:true}).fill('Synthetic UUID direct Balcao dossier');await page.getByLabel('Autorizo reutilizar os dados seleccionados').check();await page.locator('form').getByRole('button').first().click();await page.waitForURL(/\/balcao-virtual\/dossiers\/[a-f0-9-]+$/);const directCase=new URL(page.url()).pathname;proof.new_direct_case=directCase;
await page.getByRole('button',{name:'Preparar rascunho de pedido',exact:true}).click();await page.waitForURL(/\/balcao-virtual\/pedidos\/[a-f0-9-]+$/);proof.new_direct_request=new URL(page.url()).pathname;proof.checks.push({component:'desk-panel + desk-case',criterion:'both direct creation actions saved and retrieved',passed:true});await recover(proof.new_direct_request,'direct desk request refreshed');
await open('/parcerias/salas/1');page.on('response',r=>{const u=new URL(r.url());if(u.pathname==='/parcerias/salas/1/mensagens'){proof.room_responses??=[];proof.room_responses.push({status:r.status()});save();}});
for(let n=1;n<=2;n++){const text='Independent UUID stability room message '+n;await page.getByLabel('Mensagem privada',{exact:true}).fill(text);const response=page.waitForResponse(r=>new URL(r.url()).pathname==='/parcerias/salas/1/mensagens'&&r.request().method()==='POST');await page.getByRole('button',{name:'Enviar mensagem',exact:true}).click();const r=await response;assert(r.status()<400,'Room POST failed: '+r.status());await page.getByText(text,{exact:true}).waitFor();await page.getByLabel('Mensagem privada',{exact:true}).toBeEmpty?.();await page.waitForFunction(()=>document.querySelector('textarea')?.value==='');}
await recover('/parcerias/salas/1','two independently keyed room messages persisted');proof.checks.push({component:'partnership-room',passed:true});save();
await actor('outsider');for(const route of [directCase,proof.new_direct_request,'/investimentos/d8ac9133-65b6-4ed4-964e-e230bc656a02','/management/territory-geometry-import','/management/profile-install']){const r=await open(route);assert([403,404].includes(r.status()));proof.checks.push({criterion:'other account cannot access private or privileged consumer',role,route,status:r.status(),passed:true});save();}
if(ctx)await ctx.close();ctx=await browser.newContext({ignoreHTTPSErrors:false});await ctx.addInitScript(()=>Object.defineProperty(Crypto.prototype,'randomUUID',{value:undefined,configurable:true}));page=await ctx.newPage();page.setDefaultTimeout(35000);await open('/simulador-fiscal');
const post=page.waitForResponse(r=>new URL(r.url()).pathname==='/api/v1/simulation-drafts'&&r.request().method()==='POST');await page.getByLabel('Nível de detalhe (obrigatório)',{exact:true}).selectOption('simple');const response=await post;assert(response.status()<300,'Anonymous autosave failed: '+response.status());const json=await response.json();proof.anonymous_draft_id=json.data.id;const k=response.request().headers()['idempotency-key'];assert.match(k,/^[0-9a-f-]{36}$/);proof.checks.push({component:'simulation-form',criterion:'anonymous autosave generates compatible header key and recovers own draft',key_sha256:crypto.createHash('sha256').update(k).digest('hex'),passed:true});await page.reload({waitUntil:'networkidle'});assert.equal(await page.getByLabel('Nível de detalhe (obrigatório)',{exact:true}).inputValue(),'simple');save();
assert.equal(proof.errors.length,0);proof.result='passed';save();console.log(JSON.stringify({result:proof.result,checks:proof.checks.length,operations:proof.operations.length,active_writes:0}));
}catch(e){proof.result='failed';proof.failure=redact(e);save();console.log(JSON.stringify({result:'failed',failure:proof.failure,checks:proof.checks.length,active_writes:0}));process.exitCode=1;}finally{if(ctx)await ctx.close();await browser.close();}})();