| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677 |
- import fs from 'node:fs';
- import os from 'node:os';
- import path from 'node:path';
- import crypto from 'node:crypto';
- import assert from 'node:assert/strict';
- import test from 'node:test';
- import { publishBoardUi, MANIFEST_PATH, UI_FILES } from '../scripts/publish-board-ui-20261112.mjs';
- const sha = bytes => crypto.createHash('sha256').update(bytes).digest('hex');
- function fixture() {
- const base = fs.mkdtempSync(path.join(os.tmpdir(), 'ksf-board-ui-test-'));
- const targetDir = path.join(base, 'site');
- fs.mkdirSync(targetDir);
- fs.writeFileSync(path.join(targetDir, 'index.html'), 'fixture homepage');
- const manifest = JSON.parse(fs.readFileSync(MANIFEST_PATH));
- manifest.homepageSha256 = sha('fixture homepage');
- for (const file of manifest.files) {
- // Exercise publisher behavior with current source, independently of the archived release hashes.
- const source = fs.readFileSync(file.path);
- file.sha256 = sha(Buffer.from(source.toString('utf8').replaceAll('\r\n', '\n')));
- if (file.allowMissing) continue;
- const target = path.join(targetDir, file.path), bytes = Buffer.from('fixture prior: ' + file.path);
- fs.mkdirSync(path.dirname(target), { recursive: true });
- fs.writeFileSync(target, bytes);
- file.priorNormalizedSha256 = [sha(bytes)];
- }
- const reportPath = path.join(targetDir, 'src/pages/view-report.js');
- fs.writeFileSync(reportPath, 'independent report update');
- const manifestPath = path.join(base, 'manifest.json');
- fs.writeFileSync(manifestPath, JSON.stringify(manifest));
- return { base, targetDir, manifestPath, reportPath, manifest };
- }
- test('six-file UI release applies and is idempotent while preserving an independent report update', () => {
- const f = fixture();
- try {
- const plan = publishBoardUi(f);
- assert.equal(plan.readOnly, true);
- assert.deepEqual(plan.changes, UI_FILES);
- assert.equal(fs.existsSync(path.join(f.targetDir, 'src/components/kf-member-picker.js')), false);
- const result = publishBoardUi({ ...f, apply: true });
- assert.equal(result.filesystemVerified, true);
- assert.equal(result.filesChecked, 6);
- assert.equal(result.homepageModified, false);
- assert.deepEqual(publishBoardUi(f).changes, []);
- assert.equal(fs.readFileSync(f.reportPath, 'utf8'), 'independent report update');
- assert.equal(fs.readFileSync(path.join(f.targetDir, 'index.html'), 'utf8'), 'fixture homepage');
- } finally { fs.rmSync(f.base, { recursive: true, force: true }); }
- });
- test('UI release refuses an unexpected file list and an unknown baseline before writing', () => {
- const f = fixture();
- try {
- const original = fs.readFileSync(f.manifestPath);
- f.manifest.files.push({ path: 'src/pages/view-report.js' });
- fs.writeFileSync(f.manifestPath, JSON.stringify(f.manifest));
- assert.throws(() => publishBoardUi({ ...f, apply: true }), { code: 'UNEXPECTED_FILE_LIST' });
- fs.writeFileSync(f.manifestPath, original);
- fs.writeFileSync(path.join(f.targetDir, UI_FILES[0]), 'unknown concurrent update');
- assert.throws(() => publishBoardUi({ ...f, apply: true }), { code: 'UNKNOWN_TARGET_CONTENT' });
- assert.equal(fs.existsSync(path.join(f.targetDir, 'src/components/kf-member-picker.js')), false);
- assert.equal(fs.readFileSync(f.reportPath, 'utf8'), 'independent report update');
- } finally { fs.rmSync(f.base, { recursive: true, force: true }); }
- });
- test('UI release rolls back both replaced files and its newly added component on a write failure', () => {
- const f = fixture();
- try {
- const original = fs.readFileSync(path.join(f.targetDir, UI_FILES[0]));
- assert.throws(() => publishBoardUi({ ...f, apply: true }, { beforeWrite(file) {
- if (file === 'src/pages/view-workbench.js') throw Error('fixture failure');
- } }), error => error.rollbackComplete === true);
- assert.deepEqual(fs.readFileSync(path.join(f.targetDir, UI_FILES[0])), original);
- assert.equal(fs.existsSync(path.join(f.targetDir, 'src/components/kf-member-picker.js')), false);
- assert.equal(fs.readFileSync(f.reportPath, 'utf8'), 'independent report update');
- } finally { fs.rmSync(f.base, { recursive: true, force: true }); }
- });
|