fix(update): 修复云端安装交接并完善发布验收
修复隐藏缓存目录下载和响应收尾,使用原生助手校验安装包并确认交接。 补齐真实静默升级、便携版及中文路径验收,并按现行设备和科目授权契约更新回归。 服务端安装时生成独立密钥,覆盖升级保留既有配置;补齐安装包副本验证说明。
This commit is contained in:
1 parent
66be3a63b1
commit
71cc8eb605
19 files changed
+1424
-91
No files matched your search
@@ -0,0 +1,96 @@
|
||||
/** 使用实际更新路由和 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;
|
||||
}
|
||||
Reference in new issue
Block a user