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

43 lines
7.8 KiB
JavaScript
Raw 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-action-diagnosis.json');
if(fs.existsSync(receipt))fs.copyFileSync(receipt,receipt.replace('.json','-attempt-'+Date.now()+'.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{
await actor('promoter');await open('/minhas-oportunidades/53/resources');
if(!process.argv.includes('--room-only')){
proof.resource_before=await page.locator('article').allTextContents();
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.getByLabel('Motivo (20–1000 caracteres)').fill('Synthetic QA edit: preserve existing location and increase its version');
const response=page.waitForResponse(r=>r.request().method()==='POST'&&new URL(r.url()).pathname.includes('/53/resources/'));await page.getByRole('button',{name:'Guardar recurso',exact:true}).click();const r=await response;proof.resource_edit_response={status:r.status(),body:await r.json()};save();await page.reload({waitUntil:'networkidle'});proof.resource_visible=await page.locator('article').allTextContents();save();
}
await open('/parcerias/salas/1');for(let n=1;n<=2;n++){const text='Isolated trace for consecutive QA room messages '+n;await page.getByLabel('Mensagem privada',{exact:true}).fill(text);const result=page.waitForResponse(r=>r.request().method()==='POST'&&new URL(r.url()).pathname==='/parcerias/salas/1/mensagens');await page.getByRole('button',{name:'Enviar mensagem',exact:true}).click();const r=await result;const sent=r.request().postDataJSON();proof.checks.push({room_send:n,status:r.status(),body_length:sent.body.length,body_matches_entered:sent.body===text});save();await page.waitForTimeout(1000);proof.checks.at(-1).visible=await page.getByText(text,{exact:true}).count();proof.checks.at(-1).errors=await page.getByRole('alert').allTextContents();save();}
await page.reload({waitUntil:'networkidle'});proof.room_recovery=await page.locator('article').allTextContents();proof.result='diagnosed';save();console.log(JSON.stringify({result:proof.result,resource_status:proof.resource_edit_response?.status,room:proof.checks}));
}catch(e){proof.result='failed';proof.failure=redact(e);if(page&&!page.isClosed())proof.visible_status=await page.getByRole('status').allTextContents();save();console.log(JSON.stringify({result:'failed',failure:proof.failure}));process.exitCode=1;}finally{if(ctx)await ctx.close();await browser.close();}})();