60 lines
13 KiB
JavaScript
60 lines
13 KiB
JavaScript
// 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();}})();
|