Files
ycode/scripts/native-probe-contract.test.mjs
T

124 lines
7.4 KiB
JavaScript

/** Offline VM contracts only: no Electron, browser, layout or native input is executed. */
import assert from 'node:assert/strict'
import fs from 'node:fs'
import path from 'node:path'
import vm from 'node:vm'
import test from 'node:test'
const source = fs.readFileSync(new URL('./verify-b04-image-native.cjs', import.meta.url), 'utf8')
const flush = async () => { for (let i = 0; i < 12; i++) await Promise.resolve() }
const deferred = () => { let resolve, reject; const promise = new Promise((a, b) => { resolve = a; reject = b }); return { promise, resolve, reject } }
const validReport = () => ({ samples: [{ height: 232, textLength: 35, imageHeight: 180 }], errors: [] })
function harness({ report = validReport(), holdReady = false, holdLoad = false, holdFrames = false, frameError } = {}) {
const ready = deferred(), load = deferred(), frames = deferred()
const state = { writes: [], exits: [], events: {}, timers: new Map(), windows: [], logs: [] }
let id = 0
const app = { setPath: (key, value) => { state.profile = { key, value } }, whenReady: () => ready.promise, exit: code => state.exits.push(code) }
class BrowserWindow {
constructor(options) {
state.windows.push(options); state.window = this; this.destroyed = false
this.webContents = { on: (name, callback) => { state.events[name] = callback }, executeJavaScript: async code => {
state.rendererCode = code
if (frameError) throw Error(frameError)
if (holdFrames) return frames.promise
return report
} }
}
loadFile(file) { state.loaded = file; return holdLoad ? load.promise : Promise.resolve() }
isDestroyed() { return this.destroyed }
destroy() { this.destroyed = true }
}
const modules = {
electron: { app, BrowserWindow, session: { defaultSession: { webRequest: { onBeforeRequest: callback => { state.network = callback } } } } },
'node:path': path, 'node:os': { tmpdir: () => '/mock-temp' },
'node:fs': { existsSync: () => true, mkdtempSync: prefix => prefix + 'isolated', writeFileSync: (file, body) => state.writes.push({ file, report: JSON.parse(body) }) }
}
vm.runInNewContext(source, { require: name => { assert.ok(name in modules); return modules[name] }, process: { argv: ['electron', 'probe.cjs', '/mock-fixture'] },
console: { log: value => state.logs.push(value) }, setTimeout: (callback, delay) => { state.timers.set(++id, { callback, delay }); return id }, clearTimeout: key => state.timers.delete(key) })
if (!holdReady) ready.resolve()
return { state, ready, load, frames, expire: () => { const timer = [...state.timers.values()][0]; assert.equal(timer.delay, 20000); timer.callback() } }
}
function result(h, status, pattern) {
assert.equal(h.state.writes.length, 1)
const report = h.state.writes[0].report
assert.equal(report.result, status)
assert.deepEqual(h.state.exits, [status === 'PASS' ? 0 : 1])
if (pattern) assert.match(report.error, pattern)
assert.match(report.limits, /not the installed application/)
assert.equal(h.state.timers.size, 0)
return report
}
test('B04 mock host preserves security, frame scheduling, deadline and report', async () => {
const h = harness(); await flush()
const report = result(h, 'PASS')
assert.equal(report.phase, 'checking-native-report')
const options = h.state.windows[0]
assert.equal(options.show, true)
assert.equal(options.webPreferences.backgroundThrottling, false)
assert.equal(options.webPreferences.sandbox, true)
assert.equal(options.webPreferences.contextIsolation, true)
assert.equal(options.webPreferences.nodeIntegration, false)
assert.equal(h.state.profile.key, 'userData')
assert.match(h.state.profile.value, /ycode-b04-native-/)
assert.equal(h.state.window.destroyed, true)
for (const [url, cancel] of [['https://example.invalid/image', true], ['file:///fixture.js', false], ['data:image/png;base64,AA', false], ['blob:local', false]]) {
let actual; h.state.network({ url }, value => { actual = value.cancel }); assert.equal(actual, cancel)
}
assert.match(h.state.rendererCode, /window\.__b04Error/)
assert.match(h.state.rendererCode, /window\.__b04Report/)
})
for (const [label, report, message] of [
['zero layout', { samples: [{ height: 0, textLength: 0 }] }, /No nonzero/],
['large text', { samples: [{ height: 232, textLength: 1001 }] }, /text\/height spike/],
['large layout', { samples: [{ height: 2001, textLength: 35 }] }, /text\/height spike/],
['missing samples', {}, /some|samples/]
]) test(`B04 mock rejects ${label}`, async () => { const h = harness({ report }); await flush(); result(h, 'FAIL', message) })
test('B04 mock propagates fixture rejection', async () => { const h = harness({ frameError: 'synthetic no URI text assertion' }); await flush(); result(h, 'FAIL', /no URI text assertion/) })
for (const [option, phase] of [['holdReady', 'waiting-for-app'], ['holdLoad', 'loading-fixture'], ['holdFrames', 'waiting-for-native-frames']]) {
test(`B04 mock deadline covers ${phase}`, async () => {
const h = harness({ [option]: true }); await flush(); h.expire()
const report = result(h, 'FAIL', /timed out after 20s/); assert.equal(report.phase, phase)
if (option === 'holdReady') h.ready.resolve()
if (option === 'holdLoad') h.load.resolve()
if (option === 'holdFrames') h.frames.resolve(validReport())
await flush(); assert.equal(h.state.writes.length, 1)
if (option === 'holdReady') assert.equal(h.state.windows.length, 0)
})
}
for (const [event, args, message] of [
['render-process-gone', [{}, { reason: 'crashed' }], /Renderer gone: crashed/],
['did-fail-load', [{}, -6, 'FILE_NOT_FOUND'], /Page load failed: -6 FILE_NOT_FOUND/]
]) test(`B04 mock records ${event} once`, async () => {
const h = harness({ holdLoad: true }); await flush(); h.state.events[event](...args); h.load.reject(Error('later rejection')); await flush(); result(h, 'FAIL', message)
})
test('B04 mock fails renderer console errors', async () => {
const h = harness({ holdFrames: true }); await flush()
h.state.events['console-message']({ level: 'error', message: 'synthetic renderer error' })
h.frames.resolve(validReport()); await flush(); result(h, 'FAIL', /synthetic renderer error/)
})
test('B04 fixture retains real rAF sampling and decode assertions (source contract only)', () => {
const fixture = fs.readFileSync(new URL('./b04-image-lifecycle-fixture.tsx', import.meta.url), 'utf8')
assert.match(fixture, /requestAnimationFrame\(\(\) => \{sample\(/)
assert.match(fixture, /await folds\(true\)/)
assert.match(fixture, /naturalWidth === 320/)
assert.match(fixture, /imageHeight > 0/)
assert.match(fixture, /no URI text/)
})
for (const scenario of ['success', 'fixture-error', 'pending']) test(`B04 renderer polling contract: ${scenario} (mock window)`, async () => {
const h = harness(); await flush()
const report = validReport(), window = {}, timers = []
if (scenario === 'success') window.__b04Report = report
if (scenario === 'fixture-error') { window.__b04Error = 'synthetic fixture assertion'; window.__b04Report = report }
const promise = vm.runInNewContext(h.state.rendererCode, { window, setTimeout: (callback, delay) => timers.push({ callback, delay }) })
if (scenario === 'fixture-error') await assert.rejects(promise, /synthetic fixture assertion/)
else {
if (scenario === 'pending') {
assert.equal(timers.length, 1); assert.equal(timers[0].delay, 25)
window.__b04Report = report; timers[0].callback()
}
assert.equal(await promise, report)
}
})