Files
codegraph/scripts/validation/cloudflare-official-drupal.cjs

37 lines
9.7 KiB
JavaScript

// Real local Worker/operator -> browser -> compiled installer -> pinned Drupal corpus.
const fs=require('node:fs'),path=require('node:path'),os=require('node:os'),assert=require('node:assert/strict'),cp=require('node:child_process'),crypto=require('node:crypto');
const {DatabaseSync}=require('node:sqlite');
const {chromium}=require(process.env.PLAYWRIGHT_MODULE||'playwright');
const {CodeGraph}=require('../../dist');
const {ExtensionManager}=require('../../dist/plugins/manager');
const {startExtensionBridge}=require('../../dist/plugins/bridge');
Object.assign(process.env,{CODEGRAPH_TELEMETRY:'0',CODEGRAPH_PARSE_WORKERS:'2',CODEGRAPH_RESOLVE_WORKERS:'0'});
const root=path.resolve(__dirname,'../..'),out=path.resolve(process.env.OFFICIAL_BROWSER_OUTPUT||'.qa/official/browser');fs.mkdirSync(out,{recursive:true});
const lab=fs.mkdtempSync(path.join(os.tmpdir(),'cg-official-browser-')),corpus=path.resolve(process.env.PATHAUTO_CORPUS||'../validation/pathauto'),pin='b97aadf47a37cff25f7d6105c3dade6778fccb47';
const project=path.join(lab,'pathauto'),other=path.join(lab,'untouched'),state=path.join(lab,'registry'),artifact=path.join(root,'dist/extensions/drupal.cgext'),bytes=fs.readFileSync(artifact),checks=[],commands=[];
fs.mkdirSync(project);fs.mkdirSync(other);fs.writeFileSync(path.join(other,'example.php'),'<?php function untouched() {}');
cp.execFileSync('tar',['-x','-C',project],{input:cp.execFileSync('git',['-C',corpus,'archive',pin],{maxBuffer:64*1024*1024})});
const extensionVersion=JSON.parse(bytes).package.version;
const hash=b=>crypto.createHash('sha256').update(b).digest('hex');
function snapshot(){const db=new DatabaseSync(path.join(project,'.codegraph/codegraph.db'),{readOnly:true});try{const nodes=db.prepare('SELECT * FROM nodes ORDER BY id').all().map(({updated_at,...n})=>n),edges=db.prepare('SELECT source,target,kind,metadata,line,col,provenance FROM edges ORDER BY source,target,kind,line,col,metadata').all();return {nodes,edges,pluginEdges:edges.filter(e=>JSON.parse(e.metadata||'{}').synthesizedBy==='drupal'),hash:hash(JSON.stringify({nodes,edges})),config:fs.existsSync(path.join(project,'codegraph.json'))?fs.readFileSync(path.join(project,'codegraph.json'),'utf8'):null};}finally{db.close();}}
function verify(data){const definitions=[['/admin/config/search/path/settings','pathauto.routing.yml','buildForm','src/Form/PathautoSettingsForm.php','Drupal route handler'],['/admin/config/search/path/update_bulk','pathauto.routing.yml','buildForm','src/Form/PathautoBulkUpdateForm.php','Drupal route handler'],['getPunctuationCharacters','src/AliasCleaner.php','pathautoPunctuationCharsAlter','tests/modules/pathauto_custom_punctuation_test/src/Hook/PathautoCustomPunctuationTestHooks.php','Drupal hook pathauto_punctuation_chars_alter'],['PathautoGenerator','src/PathautoGenerator.php','AliasCleaner','src/AliasCleaner.php','Drupal service injection']];for(const [from,file,to,target,label]of definitions){const sources=new Set(data.nodes.filter(n=>n.name===from&&n.file_path===file).map(n=>n.id)),targets=new Set(data.nodes.filter(n=>n.name===to&&n.file_path===target).map(n=>n.id));assert.ok(data.pluginEdges.some(e=>sources.has(e.source)&&targets.has(e.target)&&JSON.parse(e.metadata).label===label),'Missing '+label+': '+from);}return definitions;}
function pass(name){checks.push(name);fs.writeFileSync(path.join(out,'progress.json'),JSON.stringify({checks,commands},null,2));console.log('PASS',name);}
async function cli(args){const command=[path.join(root,'marketplace/cloudflare/official-cli.mjs'),...args];const r=await new Promise((resolve,reject)=>{const c=cp.spawn(process.execPath,command,{timeout:60000});let log='';c.stdout.on('data',b=>log+=b);c.stderr.on('data',b=>log+=b);c.on('error',reject);c.on('close',exit=>resolve({exit,log}));});const log='operator-'+(commands.length+1)+'.log';fs.writeFileSync(path.join(out,log),r.log);commands.push({command:[process.execPath,...command],exit:r.exit,log,sha256:hash(r.log)});assert.equal(r.exit,0,r.log);return r.log;}
let server,bridge,browser,page;
(async()=>{try{
const {startLocal}=await import('../../marketplace/cloudflare/local.mjs');server=await startLocal({state});await server.close();server=null;
const target=path.join(lab,'target.json'),plan=path.join(lab,'plan.json');fs.writeFileSync(target,await cli(['inspect-local',state]));await cli(['plan',target,artifact,plan]);await cli(['apply',plan,'--confirm-plan-sha256',hash(fs.readFileSync(plan))]);
server=await startLocal({state});const origin='http://127.0.0.1:'+server.port;const catalog=await(await fetch(origin+'/api/extensions')).json();assert.equal(catalog.length,1);assert.equal(catalog[0].official,true);assert.equal(catalog[0].publisherId,'codegraph');assert.equal(catalog[0].integrity,hash(bytes));pass('reviewed operator CLI imports actual team Drupal artifact into real Worker D1/R2');
const g=await CodeGraph.init(project);try{const r=await g.indexAll();assert.equal(r.filesErrored,0);}finally{g.close();}assert.equal(snapshot().pluginEdges.length,0);
bridge=await startExtensionBridge([other,project],origin);browser=await chromium.launch({executablePath:process.env.CHROME_PATH||'/opt/google/chrome/chrome',headless:true,args:['--no-sandbox']});page=await browser.newPage({viewport:{width:1440,height:1000}});page.setDefaultTimeout(60000);let popups=0;page.on('popup',()=>popups++);const pageErrors=[];page.on('pageerror',e=>pageErrors.push(e.message));
await page.goto(bridge.connectionUrl);await page.getByRole('button',{name:'Official',exact:true}).click();await page.locator('.extension-card .official').waitFor();assert.equal(await page.locator('.extension-card').count(),1);assert.match(await page.locator('.extension-card .byline').innerText(),/CodeGraph.*OFFICIAL/s);pass('official catalog filter displays trusted CodeGraph Drupal identity');
const popupReady=page.waitForEvent('popup');await page.locator('#connect').click();const popup=await popupReady;await popup.getByRole('button',{name:'Allow connection'}).click();await page.getByText('CodeGraph connected',{exact:true}).waitFor();await page.locator('#project').selectOption('1');await page.getByRole('link',{name:'Drupal',exact:true}).click();await page.locator('.compatibility').filter({hasText:'Selected '+extensionVersion}).waitFor();assert.equal(await page.locator('.metadata dd.integrity').first().innerText(),'codegraph');await page.getByRole('button',{name:'Install extension'}).click();await page.waitForFunction(()=>state.snapshot?.jobId===1&&!state.snapshot.busy);assert.equal(await page.evaluate(()=>state.snapshot.error),undefined);assert.equal(popups,1);const installed=snapshot(),flows=verify(installed);assert.ok(!CodeGraph.isInitialized(other));pass('one connection and one Install refresh only selected pinned Drupal project; four real route hook and DI edges verified');
fs.writeFileSync(path.join(out,'graph-proof.json'),JSON.stringify({corpus:'pathauto',revision:pin,artifactSHA:hash(bytes),nodes:installed.nodes.length,edges:installed.edges.length,pluginEdges:installed.pluginEdges.length,hash:installed.hash,expected:flows},null,2));await page.screenshot({path:path.join(out,'desktop-installed.png'),fullPage:true});
const manager=new ExtensionManager(project),key='packages/sha256/'+hash(bytes);await server.bucket.put(key,Buffer.from('tampered'));await assert.rejects(()=>manager.installFromRegistry({registry:origin,id:'drupal',version:extensionVersion,replaces:['drupal']}));assert.equal(snapshot().hash,installed.hash);assert.equal(snapshot().config,installed.config);await server.bucket.put(key,bytes);pass('tampered registry artifact fails installation without changing existing graph or config');
const incompatible=JSON.parse(bytes);incompatible.package.codegraph.engines='>=9';await assert.rejects(()=>manager.install({bytes:Buffer.from(JSON.stringify(incompatible)),replaces:['drupal']}),/requires/);await assert.rejects(()=>manager.install({bytes:Buffer.from('{}'),replaces:['drupal']}));assert.equal(snapshot().hash,installed.hash);assert.equal(snapshot().config,installed.config);pass('incompatible and malformed installs preserve previously working Drupal graph');
await page.getByRole('button',{name:'Disable',exact:true}).click();await page.getByRole('button',{name:'Enable',exact:true}).waitFor();assert.equal(snapshot().pluginEdges.length,0);pass('browser disable removes actual extension contributions');
await page.getByRole('button',{name:'Enable',exact:true}).click();await page.getByRole('button',{name:'Disable',exact:true}).waitFor();verify(snapshot());await page.setViewportSize({width:390,height:844});await page.screenshot({path:path.join(out,'mobile-official.png'),fullPage:true});pass('browser enable restores expected graph and mobile official details');
await page.getByRole('button',{name:'Remove',exact:true}).click();await page.getByText('Extension removed and graph refreshed',{exact:true}).first().waitFor();assert.equal(snapshot().pluginEdges.length,0);assert.equal(new ExtensionManager(project).list().length,0);assert.ok(!CodeGraph.isInitialized(other));assert.deepEqual(pageErrors,[]);pass('browser removal clears extension config and SQLite contributions; no page errors');
fs.writeFileSync(path.join(out,'result.json'),JSON.stringify({revision:cp.execFileSync('git',['rev-parse','HEAD'],{encoding:'utf8'}).trim(),dirty:!!cp.execFileSync('git',['status','--porcelain'],{encoding:'utf8'}).trim(),platform:process.platform,browser:browser.version(),corpus:{name:'pathauto',revision:pin},checks,commands,success:true},null,2));
}catch(error){if(page)await page.screenshot({path:path.join(out,'failure.png'),fullPage:true}).catch(()=>{});fs.writeFileSync(path.join(out,'failure.json'),JSON.stringify({checks,commands,error:String(error.stack)},null,2));throw error;}finally{await browser?.close();await bridge?.close();await server?.close();fs.rmSync(lab,{recursive:true,force:true});}})().catch(e=>{console.error(e);process.exitCode=1;});