Files
ComfyAssets-ComfyUI_PromptM…/tests/vendor/vendor-viewerjs.test.mjs
Vito cf0cef3f25 feat: vendor Viewer.js reproducibly and automate release updates (#170)
Refs #169. Pin and verify pristine distribution assets, upgrade to 1.15.2, and add tested maintainer tooling plus desktop and touch browser checks.
2026-10-16 04:56:25 -07:00

108 lines
5.6 KiB
JavaScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { mkdtempSync, mkdirSync, writeFileSync, readFileSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { createHash } from 'node:crypto';
import { syncViewer } from '../../tools/vendor/vendor-viewerjs.mjs';
import { updateViewer } from '../../tools/vendor/update-viewerjs.mjs';
function fixture(t) {
const root = mkdtempSync(join(tmpdir(), 'pm-vendor-'));
t.after(() => rmSync(root, { recursive: true, force: true }));
const tooling = join(root, 'tools/vendor');
const dependency = join(tooling, 'node_modules/viewerjs');
mkdirSync(join(dependency, 'dist'), { recursive: true });
writeFileSync(join(tooling, 'package.json'), JSON.stringify({ devDependencies: { viewerjs: '1.15.2' } }));
writeFileSync(join(tooling, 'package-lock.json'), JSON.stringify({ packages: {
'node_modules/viewerjs': { version: '1.15.2', resolved: 'https://registry.npmjs.org/viewerjs/-/viewerjs-1.15.2.tgz', integrity: 'sha512-fixture' },
} }));
writeFileSync(join(dependency, 'package.json'), JSON.stringify({ name: 'viewerjs', version: '1.15.2', license: 'MIT' }));
for (const name of ['viewer.min.js', 'viewer.min.css']) {
writeFileSync(join(dependency, 'dist', name), `/*! Viewer.js v1.15.2 */\n${name}`);
}
writeFileSync(join(dependency, 'LICENSE'), 'The MIT License (MIT)\nCopyright Chen Fengyuan\n');
return { root, tooling, dependency, destination: join(root, 'web/lib/viewerjs') };
}
test('sync copies pristine assets and license and records locked provenance deterministically', t => {
const f = fixture(t);
syncViewer({ root: f.root });
const first = readFileSync(join(f.destination, 'provenance.json'), 'utf8');
const record = JSON.parse(first);
assert.equal(record.version, '1.15.2');
assert.equal(record.integrity, 'sha512-fixture');
for (const [target, source] of Object.entries({ 'viewer.min.js': 'dist/viewer.min.js', 'viewer.min.css': 'dist/viewer.min.css', LICENSE: 'LICENSE' })) {
const original = readFileSync(join(f.dependency, source));
assert.deepEqual(readFileSync(join(f.destination, target)), original);
assert.equal(record.files[target].sha256, createHash('sha256').update(original).digest('hex'));
}
syncViewer({ root: f.root });
assert.equal(readFileSync(join(f.destination, 'provenance.json'), 'utf8'), first);
syncViewer({ root: f.root, check: true });
});
test('check rejects drift without overwriting local changes', t => {
const f = fixture(t);
syncViewer({ root: f.root });
writeFileSync(join(f.destination, 'viewer.min.js'), 'local edit');
assert.throws(() => syncViewer({ root: f.root, check: true }), /viewer.min.js/);
assert.equal(readFileSync(join(f.destination, 'viewer.min.js'), 'utf8'), 'local edit');
rmSync(join(f.destination, 'LICENSE'));
assert.throws(() => syncViewer({ root: f.root, check: true }), /LICENSE/);
});
test('sync refuses mismatched installed versions and unpinned dependencies', t => {
const f = fixture(t);
writeFileSync(join(f.dependency, 'package.json'), JSON.stringify({ name: 'viewerjs', version: '1.11.6', license: 'MIT' }));
assert.throws(() => syncViewer({ root: f.root }), /version/i);
writeFileSync(join(f.tooling, 'package.json'), JSON.stringify({ devDependencies: { viewerjs: '^1.15.2' } }));
assert.throws(() => syncViewer({ root: f.root }), /exact|pin/i);
});
test('sync validates every upstream asset before writing anything', t => {
const f = fixture(t);
writeFileSync(join(f.dependency, 'dist/viewer.min.css'), '/*! Viewer.js v1.11.6 */');
assert.throws(() => syncViewer({ root: f.root }), /version|banner/i);
assert.throws(() => readFileSync(join(f.destination, 'viewer.min.js')), /ENOENT/);
});
test('updater resolves the stable release and installs it exactly with lifecycle scripts disabled', t => {
const f = fixture(t);
const calls = [];
let synced = false;
const run = (args, options) => {
calls.push({ args, options });
return args[0] === 'view' ? '"1.15.3"' : '';
};
assert.equal(updateViewer({ root: f.root, run, sync: () => { synced = true; } }), '1.15.3');
assert.deepEqual(calls[1].args, ['install', '--save-dev', '--save-exact', '--ignore-scripts', 'viewerjs@1.15.3']);
assert.equal(calls[1].options.cwd, f.tooling);
assert.equal(synced, true);
});
test('updater is a no-op at the current release and rejects invalid version arguments', t => {
const f = fixture(t);
const run = () => { throw new Error('must not install'); };
assert.equal(updateViewer({ root: f.root, version: '1.15.2', run }), null);
for (const version of ['latest', '1.15.3-beta.1', '1.15.3;anything', '--global']) {
assert.throws(() => updateViewer({ root: f.root, version, run }), /version/i);
}
});
test('failed installation never copies vendor assets', t => {
const f = fixture(t);
let synced = false;
assert.throws(() => updateViewer({ root: f.root, version: '1.15.3', run: () => { throw new Error('offline'); }, sync: () => { synced = true; } }), /offline/);
assert.equal(synced, false);
});
test('updater accepts a single registry result array and rejects ambiguous metadata', t => {
const f = fixture(t);
const calls = [];
const run = args => { calls.push(args); return '["1.15.2"]'; };
assert.equal(updateViewer({ root: f.root, run }), null);
assert.equal(calls.length, 1);
assert.throws(() => updateViewer({ root: f.root, run: () => '["1.15.2", "1.15.3"]' }), /version/i);
});