docs: publish PDF remediation and Phase 9 qualification evidence
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
import { expect,test } from '../../../../../job-tracker-ui/node_modules/@playwright/test';
|
||||
import fs from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
|
||||
const api='http://localhost:5302/api';
|
||||
for(const [themeId,language] of [['minimal','en'],['technical','nb']] as const){
|
||||
test(`portable fonts preserve long skills, headings and empty sections ${themeId}/${language}`,async({page,context,browser})=>{
|
||||
test.setTimeout(180_000);
|
||||
await context.addInitScript(()=>localStorage.setItem('uiLanguage','en'));
|
||||
await context.route('**/*',route=>{
|
||||
const url=new URL(route.request().url());
|
||||
return ['localhost','127.0.0.1'].includes(url.hostname)||['data:','blob:','about:'].includes(url.protocol)?route.continue():route.abort();
|
||||
});
|
||||
expect((await page.request.post(`${api}/auth/login`,{data:{email:'e2e@example.test',password:'E2ePassword123!'}})).status()).toBe(200);
|
||||
const me=await(await page.request.get(`${api}/auth/me`)).json();
|
||||
const csrf=(await context.storageState()).cookies.find(cookie=>cookie.name==='XSRF-TOKEN')!.value;
|
||||
const headers={'X-CSRF-TOKEN':csrf,'X-Session-Context':me.sessionContext};
|
||||
const skills=Array.from({length:64},(_,i)=>`S${String(i+1).padStart(2,'0')} ${language==='nb'?'Tilgjengelige og pålitelige løsninger for norske arbeidssøkere æøå':'Accessible reliable engineering workflows with verified outcomes'} ${i+1}`);
|
||||
const current=await(await page.request.get(`${api}/career/profile`)).json();
|
||||
expect((await page.request.put(`${api}/career/profile`,{headers,data:{expectedVersion:current.revision,profile:{version:'1',contact:{fullName:'Åse Østby æøå',headline:'R01 Verified portable document heading — reliable accessible engineering'},summary:[],jobs:[],education:[],projects:[],skills}}})).status()).toBe(200);
|
||||
const source=await(await page.request.get(`${api}/career/profile`)).json();
|
||||
expect(source.profile.skills).toEqual(skills);
|
||||
const created=await page.request.post(`${api}/cv/variants`,{headers,data:{name:`Supplemental ${themeId}/${language}`,settings:{themeId,language,skillsStyle:'bullets',sections:[{key:'skills',title:'R02 Long heading for verified professional skills and documented engineering capabilities — æøå'}]}}});
|
||||
expect(created.status()).toBe(200);const variant=await created.json();
|
||||
await page.goto(`/career/builder/${variant.id}`);
|
||||
const frame=page.getByTitle('CV preview',{exact:true});
|
||||
await expect.poll(()=>frame.evaluate(element=>(element as HTMLIFrameElement).contentDocument?.documentElement.dataset.cvPreviewReady)).toBe('true');
|
||||
const measured=await frame.evaluate(element=>{
|
||||
const doc=(element as HTMLIFrameElement).contentDocument!,win=doc.defaultView!,width=Number(doc.documentElement.dataset.cvPreviewWidth),height=Number(doc.documentElement.dataset.cvPreviewHeight);
|
||||
const tokens=[];const walker=doc.createTreeWalker(doc.body,NodeFilter.SHOW_TEXT);let node;
|
||||
while((node=walker.nextNode()))for(const match of(node.textContent||'').matchAll(/\b[SEBPR]\d{2}\b/g)){
|
||||
const range=doc.createRange();range.setStart(node,match.index!);range.setEnd(node,match.index!+match[0].length);const rect=range.getClientRects()[0],style=win.getComputedStyle(node.parentElement!);const index=Math.floor(rect.x/width);
|
||||
tokens.push({token:match[0],x:rect.x-index*width,y:rect.y+index*height,fontSize:style.fontSize,color:style.color});
|
||||
}
|
||||
return{tokens,effectiveMargin:win.getComputedStyle(doc.documentElement).getPropertyValue('--cv-page-margin'),text:doc.body.innerText};
|
||||
});
|
||||
for(const skill of skills)expect(measured.text).toContain(skill);
|
||||
const pages=await page.locator('[data-cv-page]').count();expect(pages).toBeGreaterThan(1);
|
||||
const root=path.join(process.env.CV_FIDELITY_OUTPUT!,`long-skills-${themeId}-${language}`);await fs.mkdir(root,{recursive:true});
|
||||
const rendered=await(await page.request.get(`${api}/cv/variants/${variant.id}/preview`)).json();
|
||||
const download=await page.request.post(`${api}/cv/variants/${variant.id}/export-pdf`,{headers});expect(download.status()).toBe(200);
|
||||
await fs.writeFile(path.join(root,'render.html'),rendered.html);await fs.writeFile(path.join(root,'export.pdf'),await download.body());
|
||||
for(let i=1;i<=pages;i++){
|
||||
const srcDoc=await page.getByTitle(`CV preview page ${i}`,{exact:true}).getAttribute('srcdoc');
|
||||
const capture=await browser.newPage({viewport:{width:794,height:1123}});
|
||||
try{await capture.setContent(srcDoc!);await expect.poll(()=>capture.evaluate(()=>document.documentElement.dataset.cvPreviewReady)).toBe('true');await capture.screenshot({path:path.join(root,`preview-${i}.png`)});}finally{await capture.close();}
|
||||
}
|
||||
await fs.writeFile(path.join(root,'measurements.json'),JSON.stringify({browserVersion:browser.version(),fixtures:[{fixture:path.basename(root),settings:variant.settings,previewPages:pages,measured}]},null,2));
|
||||
const independent=await browser.newContext();
|
||||
try{
|
||||
expect((await independent.request.post(`${api}/auth/login`,{data:{email:'e2e@example.test',password:'E2ePassword123!'}})).status()).toBe(200);
|
||||
const persisted=await(await independent.request.get(`${api}/career/profile`)).json();
|
||||
expect(persisted.profile).toEqual(source.profile);expect(persisted.revision).toBe(source.revision);
|
||||
expect((await(await independent.request.get(`${api}/cv/variants/${variant.id}`)).json()).version).toBe(variant.version);
|
||||
}finally{await independent.close();}
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import { defineConfig } from '../../../../../job-tracker-ui/node_modules/@playwright/test';
|
||||
import base from '../../../../../job-tracker-ui/playwright.config';
|
||||
import path from 'node:path';
|
||||
if (process.env.P9_REQUAL_LINUX_READY !== 'p9-remed-02-fonts') throw new Error('Use the isolated verified Linux renderer lifecycle.');
|
||||
const servers=Array.isArray(base.webServer)?base.webServer:[base.webServer!];
|
||||
export default defineConfig({...base,testDir:__dirname,testMatch:'*.spec.ts',workers:1,retries:0,
|
||||
webServer:[{...servers[1],cwd:path.resolve(__dirname,'../../../../../job-tracker-ui'),reuseExistingServer:false}]});
|
||||
@@ -0,0 +1,38 @@
|
||||
from pathlib import Path
|
||||
import subprocess,json,datetime,re,sys,hashlib
|
||||
ROOT=Path(__file__).resolve().parents[5]
|
||||
PDF="C:/Users/Connor/.cache/codex-runtimes/codex-primary-runtime/dependencies/python/python.exe" if sys.platform=='win32' else sys.executable
|
||||
out=Path(sys.argv[1]).resolve()
|
||||
assert out.is_relative_to(ROOT/".artifacts/p9-remed-02")
|
||||
assert not (out/"result.json").exists(), "Preserve previous evidence."
|
||||
root=out/'linux/fidelity'
|
||||
files=[root/'long-skills-minimal-en/measurements.json',root/'long-skills-technical-nb/measurements.json']
|
||||
records=[json.loads(p.read_text(encoding='utf-8')) for p in files]
|
||||
assert len({r['browserVersion'] for r in records})==1
|
||||
fixtures=[f for r in records for f in r['fixtures']]
|
||||
assert {f['fixture'] for f in fixtures}=={'long-skills-minimal-en','long-skills-technical-nb'}
|
||||
assert len(fixtures)==2
|
||||
(root/'measurements.json').write_text(json.dumps({'browserVersion':records[0]['browserVersion'],'fixtures':fixtures},indent=2,ensure_ascii=False)+'\n',encoding='utf-8')
|
||||
inputs={str(path.resolve()):hashlib.sha256(path.read_bytes()).hexdigest() for path in root.rglob('*') if path.is_file()}
|
||||
inputs[str((ROOT/'scripts/verify-cv-fidelity.py').resolve())]='64f82e622cc11483c33307d987e969f6cf27f82ab6bdbd72c2aeb1bfbceade87'
|
||||
assert all(hashlib.sha256(Path(name).read_bytes()).hexdigest()==digest for name,digest in inputs.items())
|
||||
(out/'oracle-inputs.json').write_text(json.dumps({'inputs':inputs},indent=2)+'\n',encoding='utf-8')
|
||||
with (out/'oracle.log').open('w',encoding='utf-8') as log:
|
||||
result=subprocess.run(['rtk','proxy',PDF,'-B','scripts/verify-cv-fidelity.py',str(root)],cwd=ROOT,stdout=log,stderr=subprocess.STDOUT)
|
||||
record={'oracleExitCode':result.returncode,'status':'PASS' if result.returncode==0 else 'FAIL','scope':'Two supplemental real-MariaDB/Linux long-skill fixtures, separate from original24 and ordinary362.','observedUtc':datetime.datetime.now(datetime.timezone.utc).isoformat()}
|
||||
report=json.loads((root/'fidelity-results.json').read_text(encoding='utf-8'))
|
||||
assert all(hashlib.sha256(Path(name).read_bytes()).hexdigest()==digest for name,digest in inputs.items()),'Oracle input drift'
|
||||
record['inputs']=inputs
|
||||
record['reportSha256']=hashlib.sha256((root/'fidelity-results.json').read_bytes()).hexdigest()
|
||||
assert report['total']==2 and {f['fixture'] for f in report['fixtures']}=={f['fixture'] for f in fixtures}
|
||||
record['cases']=report['fixtures']
|
||||
for case in report['fixtures']:
|
||||
text='\n'.join(p['text'] for p in case['pages'])
|
||||
assert set(re.findall(r'\bS\d{2}\b',text))=={f'S{i:02}' for i in range(1,65)},case['fixture']
|
||||
assert 'Åse' in text and 'Østby' in text,case['fixture']
|
||||
assert text.count('Åse')==1,case['fixture']
|
||||
assert all(value not in text for value in ['file://','http://localhost','document.html']),case['fixture']
|
||||
assert not re.search(r'\b(?:20\d\d[-/.]\d{1,2}[-/.]\d{1,2}|\d{1,2}[-/.]\d{1,2}[-/.](?:20)?\d{2})\b',text),case['fixture']
|
||||
(out/'result.json').write_text(json.dumps(record,indent=2,ensure_ascii=False)+'\n',encoding='utf-8')
|
||||
print('Supplemental long-skills/header-footer proof:',record['status'],flush=True)
|
||||
raise SystemExit(result.returncode)
|
||||
Reference in New Issue
Block a user