// Synthetic publication check. Uses only Node standard-library modules. const http = require('node:http'); const fs = require('node:fs'); const path = require('node:path'); const crypto = require('node:crypto'); const assert = require('node:assert/strict'); const output = path.resolve(process.argv[2] || path.join(__dirname, 'result')); const sha = value => crypto.createHash('sha256').update(value).digest('hex'); const now = () => new Date().toISOString(); const approved = {version:'weekly-report-v2',title:'Export a weekly report',instruction:'Select Last seven days before exporting.',link:{href:'/help/date-range/',text:'Date-range help'},media:{label:'A report limited to seven days',svg:'Last seven days'}}; const old = {...approved,version:'weekly-report-v1',instruction:'Select All dates before exporting.'}; const css = 'body{margin:0;background:#fafaf8;color:#182433;font:18px/1.6 system-ui,sans-serif}main{max-width:700px;margin:36px auto;padding:0 18px}aside{font-size:14px;color:#536173;border-bottom:1px solid #ccd4db;padding-bottom:12px}h1{font-size:32px;line-height:1.2}figure{max-width:360px;margin:24px 0}svg{width:100%;height:auto}a{color:#125b94}pre{white-space:pre-wrap;overflow-wrap:anywhere;background:#edf0f4;padding:12px;font:13px/1.5 monospace}'; const render = (record) => `${record.title}

${record.title}

${record.instruction}

${record.link.text}

${record.media.svg}
\n`; const approvedHtml = render(approved); let cachedBody = render(old); const requests = []; const events = []; const server = http.createServer((req,res) => { requests.push({at:now(),method:req.method,path:req.url,authorization_present:!!req.headers.authorization,cookie_present:!!req.headers.cookie}); const send = (status,body,type='text/html; charset=utf-8',headers={}) => {res.writeHead(status,{'Content-Type':type,...headers});res.end(body)}; if(req.method==='GET' && req.url==='/mock-cms/posts/42') return send(200,JSON.stringify({fixture_only:true,provider:'synthetic-local-cms',id:42,status:'publish',link:'/weekly-report/',version:approved.version}),'application/json'); if(req.method==='GET' && req.url==='/preview/42') return send(200,approvedHtml); if(req.method==='GET' && req.url==='/weekly-report/') { const hit=cachedBody!==null; if(!hit) cachedBody=approvedHtml; return send(200,cachedBody,'text/html; charset=utf-8',{'Cache-Control':'public, max-age=60','X-Fixture-Cache':hit?'HIT':'MISS'}); } if(req.method==='POST' && req.url==='/fixture/invalidate') { cachedBody=null; events.push({at:now(),action:'Clear only this local fixture cache',path:'/weekly-report/'}); return send(200,JSON.stringify({fixture_only:true,cleared:'/weekly-report/'}),'application/json'); } if(req.method==='GET' && req.url==='/help/date-range/') return send(200,'Use Last seven days for this synthetic weekly report.','text/plain; charset=utf-8'); send(404,'Not found','text/plain; charset=utf-8'); }); function request(url,method='GET') { return new Promise((resolve,reject) => { const started=now(); const req=http.request(url,{method,headers:{Accept:'*/*'},agent:false},res=>{ const chunks=[]; res.on('data',chunk=>chunks.push(chunk)); res.on('end',()=>{const body=Buffer.concat(chunks).toString('utf8');resolve({started_at:started,completed_at:now(),url,method,status:res.statusCode,headers:res.headers,body,body_sha256:sha(body)})}); }); req.on('error',reject);req.end(); }); } function inspect(html,base) { // A deliberately small parser for this fixed fixture, not a general HTML verifier. const title=html.match(/([^<]*)<\/title>/)?.[1]; const heading=html.match(/<h1>([^<]*)<\/h1>/)?.[1]; const instruction=html.match(/<p id="instruction">([^<]*)<\/p>/)?.[1]; const link=html.match(/<a href="([^"]*)">([^<]*)<\/a>/); const media=html.match(/<svg[\s\S]*?<\/svg>/)?.[0]; return {title,heading,instruction,link:{href:link?.[1],text:link?.[2],resolved_url:link?new URL(link[1],base).href:null},media:{label:media?.match(/aria-label="([^"]*)"/)?.[1],sha256:media?sha(media):null}}; } function compare(reference,candidate) { return {title:reference.title===candidate.title&&reference.heading===candidate.heading,instruction:reference.instruction===candidate.instruction,link:JSON.stringify(reference.link)===JSON.stringify(candidate.link),media:JSON.stringify(reference.media)===JSON.stringify(candidate.media)}; } (async()=>{ await new Promise((resolve,reject)=>{server.once('error',reject);server.listen(0,'127.0.0.1',resolve)}); try { const base=`http://127.0.0.1:${server.address().port}`; const publicUrl=base+'/weekly-report/'; const cms=await request(base+'/mock-cms/posts/42'); const preview=await request(base+'/preview/42'); const before=await request(publicUrl); const link=await request(base+approved.link.href); const invalidation=await request(base+'/fixture/invalidate','POST'); const after=await request(publicUrl); const sourceSnapshot=inspect(approvedHtml,publicUrl); const checks={preview:compare(sourceSnapshot,inspect(preview.body,publicUrl)),public_before:compare(sourceSnapshot,inspect(before.body,publicUrl)),public_after:compare(sourceSnapshot,inspect(after.body,publicUrl))}; assert.deepEqual(checks.preview,{title:true,instruction:true,link:true,media:true}); assert.deepEqual(checks.public_before,{title:true,instruction:false,link:true,media:true}); assert.deepEqual(checks.public_after,{title:true,instruction:true,link:true,media:true}); assert.equal(before.url,after.url); assert.equal(before.status,200);assert.equal(after.status,200);assert.equal(link.status,200); assert.equal(before.headers['x-fixture-cache'],'HIT');assert.equal(after.headers['x-fixture-cache'],'MISS'); assert.equal(requests.some(req=>req.authorization_present||req.cookie_present),false); assert.equal(JSON.parse(cms.body).status,'publish'); fs.mkdirSync(output,{recursive:true}); const escape=text=>text.replaceAll('&','&').replaceAll('<','<').replaceAll('>','>'); const sourcePreview=preview.body.replace('<article>',`<details><summary>Approved source, ${approved.version}</summary><pre>${escape(JSON.stringify(approved,null,2))}</pre></details><aside>Saved local CMS preview response; approved source above.</aside><article>`); const files={'source-preview.html':sourcePreview,'public-before.html':before.body,'public-after.html':after.body}; const fileHashes={}; for(const [name,body] of Object.entries(files)){fs.writeFileSync(path.join(output,name),body);fileHashes[name]=sha(body)} const result={article_id:'A08',captured_at:now(),node_version:process.version,scope:'Synthetic local HTTP fixture. No WordPress request, external publishing, real CDN or browser operation.',approved_source:approved,approved_html_sha256:sha(approvedHtml),source_snapshot:sourceSnapshot,cms_response:cms,preview_response:preview,public_before:before,link_response:link,cache_invalidation:invalidation,public_after:after,request_log:requests,cache_events:events,checks,verification:{same_public_url:before.url===after.url,public_before_http:before.status,public_after_http:after.status,requests_without_authentication:requests.every(req=>!req.authorization_present&&!req.cookie_present),public_after_equals_approved_html:after.body===approvedHtml,public_after_equals_preview:after.body===preview.body,link_destination_http:link.status,assertions:'pass'},file_sha256:fileHashes,script_sha256:sha(fs.readFileSync(__filename)),limits:['The cache is a deliberately initialized in-memory body cache, not a real CMS/CDN implementation or complete HTTP cache.','Outdated editorial content is not the same as an expired HTTP freshness lifetime.','Invalidation uses an explicit fixture-only POST; no browser refresh or query parameter is represented as a universal purge.','Public access means a loopback HTTP read without Authorization or Cookie. No internet, geographic, bot, membership or browser rendering check was performed.','Media comparison covers identical inline SVG content and its label; no separate image host was tested.','The fixed-shape parser is valid for these controlled inputs only.','Saved public HTML is exact response content; its help link requires the transient fixture server, which stops after the run. HTTP link success is retained in this receipt.','Times, loopback port and response Date headers vary on rerun; substantive checks and static HTML hashes are stable.']}; fs.writeFileSync(path.join(output,'result.json'),JSON.stringify(result,null,2)+'\n'); console.log(JSON.stringify({assertions:'pass',output,checks,public_url:publicUrl,requests:requests.length})); } finally {await new Promise(resolve=>server.close(resolve))} })().catch(error=>{console.error(error);process.exitCode=1});