import test from 'node:test' import assert from 'node:assert/strict' import { readFileSync, writeFileSync, mkdtempSync, rmSync } from 'node:fs' import { join, resolve } from 'node:path' import { tmpdir } from 'node:os' import { spawnSync } from 'node:child_process' import { pathToFileURL } from 'node:url' test('actual image smoke marker uses the old/new public list API and never recreates missing persisted data', () => { const script = readFileSync(resolve('scripts/ci/image-smoke.sh'), 'utf8') const program = script.match(/<<'MARKER'\r?\n([\s\S]*?)\r?\nMARKER/)[1] const directory = mkdtempSync(join(tmpdir(), 'evofw-marker-')) const marker = join(directory, 'marker.mjs'), mock = join(directory, 'fetch.mjs'), state = join(directory, 'state.json') writeFileSync(marker, program) writeFileSync(mock, ` import assert from 'node:assert/strict' import { existsSync, readFileSync, writeFileSync } from 'node:fs' globalThis.fetch = async (url, options = {}) => { const state = existsSync(process.env.MARKER_STATE) ? JSON.parse(readFileSync(process.env.MARKER_STATE, 'utf8')) : { creates: 0, entity: null } let status = 200, body if (options.method === 'POST') { assert.equal(url, 'http://127.0.0.1:8080/api/v1/lists') const payload = JSON.parse(options.body) state.creates++ state.entity = { id: 'persisted-list-id', name: payload.name, type: payload.type, entries: payload.entries, entry_count: payload.entries.length } writeFileSync(process.env.MARKER_STATE, JSON.stringify(state)) status = Number(process.env.CREATE_STATUS) body = { id: state.entity.id, name: state.entity.name, type: state.entity.type } } else { assert.equal(url, 'http://127.0.0.1:8080/api/v1/lists/persisted-list-id') status = state.entity ? 200 : 404 body = state.entity || { error: 'NOT_FOUND' } } return { ok: status >= 200 && status < 300, status, json: async () => body } } `) const run = (mode, id, status) => spawnSync(process.execPath, ['--import', pathToFileURL(mock).href, marker, mode, id, 'unique-smoke-marker'], { encoding: 'utf8', env: { ...process.env, MARKER_STATE: state, CREATE_STATUS: String(status) } }) try { // Previous aa47584 returns 200 for POST /lists; newer implementations may use 201. for (const status of [200, 201]) { rmSync(state, { force: true }) const created = run('write', '', status) assert.equal(created.status, 0, created.stderr) assert.equal(created.stdout, 'persisted-list-id') for (const phase of ['read', 'write', 'read', 'read']) { const verified = run(phase, created.stdout, status) assert.equal(verified.status, 0, verified.stderr) assert.equal(verified.stdout, created.stdout) } const saved = JSON.parse(readFileSync(state, 'utf8')) assert.equal(saved.creates, 1, 'Upgrade/restart/restore verification must never create replacement data') saved.entity.entries = []; saved.entity.entry_count = 0 writeFileSync(state, JSON.stringify(saved)) assert.notEqual(run('read', created.stdout, status).status, 0, 'Lost marker entries must fail smoke') saved.entity = null writeFileSync(state, JSON.stringify(saved)) assert.notEqual(run('read', created.stdout, status).status, 0, 'Missing marker must fail smoke') assert.equal(JSON.parse(readFileSync(state, 'utf8')).creates, 1) } } finally { rmSync(directory, { recursive: true, force: true }) } })