Files
ClassHelper/scripts/verify-update-download.mjs
T
LoveChengke 71cc8eb605 fix(update): 修复云端安装交接并完善发布验收
修复隐藏缓存目录下载和响应收尾,使用原生助手校验安装包并确认交接。
补齐真实静默升级、便携版及中文路径验收,并按现行设备和科目授权契约更新回归。
服务端安装时生成独立密钥,覆盖升级保留既有配置;补齐安装包副本验证说明。
2026-10-31 17:07:17 +08:00

97 lines
4.5 KiB
JavaScript

/** 使用实际更新路由和 Express 文件发送,聚焦隐藏路径、Range 与文件失效后的错误收尾。 */
import assert from 'node:assert/strict';
import fs from 'node:fs';
import path from 'node:path';
import { createRequire } from 'node:module';
import { fileURLToPath } from 'node:url';
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
const dir = path.join(root, '.cache', 'update-download-tests', String(Date.now()), '.hidden');
fs.mkdirSync(dir, { recursive: true });
const file = path.join(dir, 'client-setup.exe');
const bytes = Buffer.alloc(4096, 0x5a);
fs.writeFileSync(file, bytes);
globalThis.__updateDownloadFixture = { file, fileName: path.basename(file) };
const clientRequire = createRequire(path.join(root, 'packages/desktop-client/package.json'));
const serverRequire = createRequire(path.join(root, 'packages/server/package.json'));
const express = serverRequire('express');
const pass = 'const pass=()=> (_req,_res,next)=>next();';
const mocks = {
authz:
'export const assertClassDevice=()=>{}; export const assertPermission=async()=>{}; export const classIdsFor=async()=>[];',
auth:
pass +
'export const authenticate=pass,requireAnyPermission=pass,requirePermission=pass,requireAdmin=pass,requirePermissionOrClassDevice=pass; export const getAuthUser=()=>({role:"CLASS_DEVICE",sub:"fixture"}); export const hasAuthPermission=()=>false;',
validate:
pass +
'export const validateBadRequest=pass;export const validatedBody=req=>req.body;export const validatedParams=req=>req.params;',
releases:
'export const resolveReleaseFile=async()=>globalThis.__updateDownloadFixture;export const listReleases=async()=>[];export const fetchReleaseFromGithub=async()=>null;export const registerRelease=async()=>null;export const deleteRelease=async()=>null;',
update: 'export const checkForUpdates=async()=>null;',
progress: 'export const getReleaseProgress=()=>({});export const setReleaseProgress=()=>{};',
};
const output = path.join(dir, 'route.cjs');
await clientRequire('esbuild').build({
entryPoints: [path.join(root, 'packages/server/src/modules/update/update.module.ts')],
outfile: output,
bundle: true,
platform: 'node',
format: 'cjs',
logLevel: 'silent',
plugins: [
{
name: '隔离身份与元数据',
setup(build) {
build.onResolve({ filter: /^express$/ }, () => ({
path: serverRequire.resolve('express'),
external: true,
}));
const rules = [
[/authz\/index\.js$/, 'authz'],
[/middleware\/auth\.js$/, 'auth'],
[/middleware\/validate\.js$/, 'validate'],
[/client-releases\.service\.js$/, 'releases'],
[/\/update\.service\.js$/, 'update'],
[/release-progress\.js$/, 'progress'],
];
build.onResolve({ filter: /.*/ }, (args) => {
const rule = rules.find(([pattern]) => pattern.test(args.path));
return rule ? { path: rule[1], namespace: 'fixture' } : undefined;
});
build.onLoad({ filter: /.*/, namespace: 'fixture' }, (args) => ({
contents: mocks[args.path],
loader: 'js',
}));
},
},
],
});
const { updateModule } = clientRequire(output);
const app = express();
app.use('/api/update', updateModule.router);
app.use((error, _req, res, _next) =>
res.status(error.status ?? 500).json({ code: error.code, message: error.message }),
);
const server = app.listen(0, '127.0.0.1');
await new Promise((resolve) => server.once('listening', resolve));
const url = `http://127.0.0.1:${server.address().port}/api/update/client/download/fixture`;
try {
const downloaded = await fetch(url, { signal: AbortSignal.timeout(2000) });
assert.equal(downloaded.status, 200);
assert.deepEqual(Buffer.from(await downloaded.arrayBuffer()), bytes);
console.log('✓ 实际更新路由可发送隐藏目录中的安装包');
const range = await fetch(url, { headers: { range: 'bytes=4-11' }, signal: AbortSignal.timeout(2000) });
assert.equal(range.status, 206);
assert.equal((await range.arrayBuffer()).byteLength, 8);
console.log('✓ 安装包 Range 请求保持有效');
fs.renameSync(file, file + '.backup');
const missing = await fetch(url, { signal: AbortSignal.timeout(2000) });
assert.equal(missing.status, 404);
assert.equal((await missing.json()).code, 'NOT_FOUND');
console.log('✓ 解析元数据后文件消失会结束错误响应,不再挂起');
} finally {
server.closeAllConnections();
await new Promise((resolve) => server.close(resolve));
delete globalThis.__updateDownloadFixture;
}