oxedyne/daimond/dev/verify_version.mjs
3.0 KiB, 1 run
created by r2519314175:783, which is this file's identity for as long as the history lasts, whatever it is later renamed to
download · who wrote it · its history
| 1 | // verify_version.mjs — the browser notices when the gateway declares it too old. |
| 2 | // |
| 3 | // Phase-2 compatibility contract, client half: gateway.js sends `X-Daimond-Api` on every call and |
| 4 | // inspects the reply. A 426, or an advertised `x-daimond-min-api` above this build's version, means |
| 5 | // the tab is out of date -- it fires `daimond:stale`, which the updater turns into a forced reload. |
| 6 | // Here we route a real gateway call to each of those two answers and confirm the event fires. The |
| 7 | // loop guard (daimond-forced-from) is pre-set to this build so the assertion does not itself reload |
| 8 | // the page out from under us; the forced-reload behaviour is covered in verify_updates.mjs. |
| 9 | // |
| 10 | // Needs an unlocked identity, because the only unconditional gateway call is bootstrap(). |
| 11 | import { open } from './harness.mjs'; |
| 12 | |
| 13 | const ok = [], bad = []; |
| 14 | const check = (name, pass, detail) => { |
| 15 | (pass ? ok : bad).push(name + (detail ? ' — ' + detail : '')); |
| 16 | console.log((pass ? ' ok ' : ' FAIL ') + name + (detail ? ' — ' + detail : '')); |
| 17 | }; |
| 18 | |
| 19 | const s = await open({ name: 'version', signIn: true, connect: false }); |
| 20 | const { page } = s; |
| 21 | |
| 22 | // Wait for both the updater and the gateway client to be live. |
| 23 | await page.waitForFunction(() => !!window.DaimondUpdater && !!window.DaimondGateway, null, { timeout: 8000 }).catch(() => {}); |
| 24 | |
| 25 | // Pre-set the loop guard to this build, so onStale reaches its "already tried" branch and does NOT |
| 26 | // reload during the test. |
| 27 | await page.evaluate(() => { |
| 28 | var b = window.DaimondUpdater && window.DaimondUpdater.booted(); |
| 29 | try { if (b) sessionStorage.setItem('daimond-forced-from', b); } catch (e) {} |
| 30 | }); |
| 31 | |
| 32 | // Fire bootstrap() (its first call is POST /api/account) against a routed answer, and watch for the |
| 33 | // staleness event. |
| 34 | async function firesStaleWhen(fulfil) { |
| 35 | await page.unroute('**/api/account').catch(() => {}); |
| 36 | await page.route('**/api/account', route => route.fulfill(fulfil)); |
| 37 | return page.evaluate(() => new Promise(resolve => { |
| 38 | window.addEventListener('daimond:stale', () => resolve(true), { once: true }); |
| 39 | try { window.DaimondGateway.bootstrap().catch(() => {}); } catch (e) {} |
| 40 | setTimeout(() => resolve(false), 3500); |
| 41 | })); |
| 42 | } |
| 43 | |
| 44 | try { |
| 45 | const on426 = await firesStaleWhen({ |
| 46 | status: 426, contentType: 'application/json', |
| 47 | body: JSON.stringify({ ok: false, error: 'old', min_api: 2, api: 2 }), |
| 48 | }); |
| 49 | check('a 426 from the gateway fires daimond:stale', on426); |
| 50 | |
| 51 | const onHeader = await firesStaleWhen({ |
| 52 | status: 200, contentType: 'application/json', |
| 53 | headers: { 'x-daimond-min-api': '2', 'x-daimond-api': '2' }, |
| 54 | body: JSON.stringify({ ok: true }), |
| 55 | }); |
| 56 | check('an advertised min-api above this build fires daimond:stale', onHeader); |
| 57 | |
| 58 | const errs = s.errs.filter(e => !/favicon|ERR_|Failed to load resource|401|426|502|Unauthorized/.test(e)); |
| 59 | check('no unexpected console errors', errs.length === 0, errs.slice(0, 3).join(' | ')); |
| 60 | } catch (e) { |
| 61 | check('test harness ran to completion', false, String(e && e.message || e)); |
| 62 | } finally { |
| 63 | await s.close(); |
| 64 | } |
| 65 | |
| 66 | console.log(`\n${ok.length} passed, ${bad.length} failed`); |
| 67 | process.exit(bad.length ? 1 : 0); |