showcase-preparation.test.mjs (4891B)
1 import test from 'node:test'; 2 import assert from 'node:assert/strict'; 3 import {execFileSync} from 'node:child_process'; 4 import {fileURLToPath} from 'node:url'; 5 import {readFile} from 'node:fs/promises'; 6 7 test('showcase staging rejects immutable/duplicate conflicts before discovery or assets',()=>{ 8 const site=fileURLToPath(new URL('../',import.meta.url)); 9 const output=execFileSync('python3',['-c',String.raw` 10 import importlib.util,json,tempfile 11 from pathlib import Path 12 site=Path(__import__('sys').argv[1]) 13 spec=importlib.util.spec_from_file_location('showcase',site/'prepare-showcase.py') 14 m=importlib.util.module_from_spec(spec);spec.loader.exec_module(m) 15 index=json.loads((site/'packages.json').read_text()) 16 releases=[next(r for r in reversed(index['releases']) if r['name']==name) for name in ['tricu-graphs','parser-demo','lexer-demo']] 17 demo=[e for e in json.loads((site/'catalogue.json').read_text()) if e['id'] in ['parser','lexer']] 18 goldens=[json.loads((site/(e['id']+'-demo.json')).read_text()) for e in demo] 19 artifacts={r['hash']:site/'bundles'/(r['hash']+'.arboricx') for r in releases} 20 with tempfile.TemporaryDirectory() as tmp: 21 root=Path(tmp);(root/'bundles').mkdir() 22 (root/'catalogue.json').write_text('[]\n') 23 bad=dict(releases[0],hash='0'*64) 24 for rows in [[bad],[releases[0],releases[0]]]: 25 before=json.dumps(dict(schema=1,releases=rows)) 26 (root/'packages.json').write_text(before) 27 try:m.stage(root,artifacts,releases,demo,goldens) 28 except ValueError:pass 29 else:raise AssertionError('conflicting index accepted') 30 assert (root/'packages.json').read_text()==before 31 assert (root/'catalogue.json').read_text()=='[]\n' 32 assert not list((root/'bundles').iterdir()) 33 # A conflict on the second demo must not discover the first or copy assets. 34 (root/'packages.json').write_text(json.dumps(dict(schema=1,releases=[]))) 35 conflict=json.dumps([dict(demo[1],title='changed')]);(root/'catalogue.json').write_text(conflict) 36 try:m.stage(root,artifacts,releases,demo,goldens) 37 except ValueError:pass 38 else:raise AssertionError('conflicting second demo accepted') 39 assert (root/'catalogue.json').read_text()==conflict 40 assert not json.loads((root/'packages.json').read_text())['releases'] 41 assert not list((root/'bundles').iterdir()) 42 (root/'catalogue.json').write_text('[]') 43 m.stage(root,artifacts,releases,demo,goldens) 44 assert json.loads((root/'catalogue.json').read_text())==demo 45 assert len(json.loads((root/'packages.json').read_text())['releases'])==3 46 for pin,path in artifacts.items():assert (root/'bundles'/(pin+'.arboricx')).read_bytes()==path.read_bytes() 47 m.stage(root,artifacts,releases,demo,goldens) # Exact retry is idempotent. 48 assert len(json.loads((root/'packages.json').read_text())['releases'])==3 49 # A reviewed new version discovers new bytes without changing the old pin. 50 import hashlib 51 data=b'new independently accepted test asset';pin=hashlib.sha256(data).hexdigest() 52 path=Path(tmp)/'new.arboricx';path.write_bytes(data) 53 next_release=dict(releases[0],version='new-test-version',hash=pin) 54 m.stage(root,{pin:path},[next_release],demo,goldens) 55 rows=json.loads((root/'packages.json').read_text())['releases'] 56 assert rows[:3]==releases and rows[-1]==next_release 57 assert (root/'bundles'/(pin+'.arboricx')).read_bytes()==data 58 # A changed native catalogue selection requires a fresh version of its own 59 # already indexed package, not merely an unrelated accepted release. 60 updated=dict(demo[0],hash=pin) 61 vectors=[dict(goldens[0],hash=pin),goldens[1]] 62 before=(root/'catalogue.json').read_bytes() 63 for invalid in [[],[dict(releases[1],hash=pin)], 64 [dict(releases[1],name='unrelated',version='new-demo',hash=pin)]]: 65 try:m.stage(root,{pin:path},invalid,[updated,demo[1]],vectors) 66 except ValueError:pass 67 else:raise AssertionError('unversioned or unrelated demo upgrade accepted') 68 assert (root/'catalogue.json').read_bytes()==before 69 upgrade=dict(releases[1],version='new-demo',hash=pin) 70 m.stage(root,{pin:path},[upgrade],[updated,demo[1]],vectors) 71 assert json.loads((root/'catalogue.json').read_text())==[updated,demo[1]] 72 assert json.loads((root/'packages.json').read_text())['releases']==releases+[next_release,upgrade] 73 m.stage(root,{pin:path},[upgrade],[updated,demo[1]],vectors) 74 print('accepted') 75 `,site],{encoding:'utf8',timeout:15000,maxBuffer:100000}); 76 assert.equal(output.trim(),'accepted'); 77 }); 78 79 test('core preparation preserves separately accepted native demo editability',async()=>{ 80 const prepare=await readFile(new URL('../prepare.py',import.meta.url),'utf8'); 81 const index=await readFile(new URL('../prepare-index.py',import.meta.url),'utf8'); 82 assert.match(prepare,/catalogue\.extend\(entry for entry in previous if entry\.get\('preparation'\) == 'native'/); 83 assert.match(index,/if entry\.get\('preparation'\) == 'native':[\s\S]*?continue/); 84 assert.match(index,/Native demo has no accepted release/); 85 });