| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354 |
- 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 { publishBoardV3, MANIFEST_PATH } from '../scripts/publish-board-v3-20261112.mjs';
- const sha = bytes => crypto.createHash('sha256').update(bytes).digest('hex');
- function fixture() {
- const base = fs.mkdtempSync(path.join(os.tmpdir(), 'ksf-board-v3-test-'));
- const targetDir = path.join(base, 'site'); fs.mkdirSync(targetDir);
- const manifest = JSON.parse(fs.readFileSync(MANIFEST_PATH)); manifest.homepageSha256 = sha('homepage');
- fs.writeFileSync(path.join(targetDir, 'index.html'), 'homepage');
- for (const file of manifest.files) {
- if (file.allowMissing) continue;
- const target = path.join(targetDir, file.path), bytes = Buffer.from('prior ' + file.path);
- fs.mkdirSync(path.dirname(target), { recursive: true }); fs.writeFileSync(target, bytes);
- file.priorNormalizedSha256 = [sha(bytes)];
- }
- const manifestPath = path.join(base, 'manifest.json'); fs.writeFileSync(manifestPath, JSON.stringify(manifest));
- return { base, targetDir, manifestPath, manifest };
- }
- test('v3发布检查完整浏览器依赖,原子更新后重复执行无变化,首页不变', () => {
- const f = fixture(); try {
- const plan = publishBoardV3(f); assert.equal(plan.readOnly, true); assert.equal(plan.filesChecked, f.manifest.files.length);
- assert.equal(plan.changes.length, f.manifest.files.length);
- assert.equal(fs.existsSync(path.join(f.targetDir, 'skills/ksf-workshop-runtime/scripts/course-deliverables.mjs')), false);
- const result = publishBoardV3({ ...f, apply: true }); assert.equal(result.filesystemVerified, true);
- assert.equal(result.homepageModified, false); assert.equal(result.businessRecordWrites, 0);
- assert.deepEqual(publishBoardV3(f).changes, []); assert.equal(fs.readFileSync(path.join(f.targetDir, 'index.html'), 'utf8'), 'homepage');
- } finally { fs.rmSync(f.base, { recursive: true, force: true }); }
- });
- test('v3发布拒绝未知现场、额外文件和首页变化,写入失败回退已改文件及新增模块', () => {
- const f = fixture(); try {
- const originalManifest = fs.readFileSync(f.manifestPath), prior = new Map();
- for (const file of f.manifest.files) if (!file.allowMissing) prior.set(file.path, fs.readFileSync(path.join(f.targetDir, file.path)));
- assert.throws(() => publishBoardV3({ ...f, apply: true }, { beforeWrite(relative) {
- if (relative === 'src/pages/view-report.js') throw Error('simulated write failure');
- } }), error => error.rollbackComplete === true);
- for (const file of f.manifest.files) {
- const target = path.join(f.targetDir, file.path);
- if (file.allowMissing) assert.equal(fs.existsSync(target), false); else assert.deepEqual(fs.readFileSync(target), prior.get(file.path));
- }
- f.manifest.files.push({ path: 'server/library-api.mjs' }); fs.writeFileSync(f.manifestPath, JSON.stringify(f.manifest));
- assert.throws(() => publishBoardV3({ ...f, apply: true }), { code: 'UNEXPECTED_FILE_LIST' });
- fs.writeFileSync(f.manifestPath, originalManifest);
- const unknown = f.manifest.files.find(file => !file.allowMissing).path;
- fs.writeFileSync(path.join(f.targetDir, unknown), 'concurrent content');
- assert.throws(() => publishBoardV3({ ...f, apply: true }), { code: 'UNKNOWN_TARGET_CONTENT' });
- fs.writeFileSync(path.join(f.targetDir, unknown), prior.get(unknown)); fs.writeFileSync(path.join(f.targetDir, 'index.html'), 'other homepage');
- assert.throws(() => publishBoardV3({ ...f, apply: true }), { code: 'HOMEPAGE_MISMATCH' });
- } finally { fs.rmSync(f.base, { recursive: true, force: true }); }
- });
|