c2c5e1ced5
- 发行包上限 100→200 MiB、展开总量 250→500 MiB,整包路由请求体上限继续从包上限派生;发行静态资源进程内缓存预算提到 256 MiB - 反代放行量同步放宽到 210 MiB:Nginx 三份模板的 client_max_body_size、Pingora 网关默认值与 env 样例、路由对照矩阵 - platform-oss 新增内部对象追加写 append_internal_object(_with_retry),以 OSS 返回的 next-append-position 作为权威已收字节 - api-server 新增 upload-state / chunk / complete / reset 四条分片路由,抽出共享收口 confirm_validated_package;偏移不符返回 409 与权威偏移,校验失败删除半包并落 upload_failed - AGC 新增原生上传器 game_package_upload.rs(内容寻址暂存、分片续传、受控重试、进度事件)与 prepare / upload 两条命令,退役整包回传命令 - 渲染进程改为 prepare → 创建游戏 → 创建版本 → 原生分片上传 → 送审,LocalProjectExportPackagePayload 整包类型退役 - 同时修正 live 用例无法指向本地栈的两处基础设施问题:平台基址按传入 URL 选择,桥接层把 jsdom realm 的 Headers / Blob / FormData 降级成 Node 原生值 - 测试:platform-oss 74、api-server game_distribution 23、AGC 原生 4、发布相关前端 20;live 用例补真实栈「中断 → 续传 → 确认」断言(分片偏移序列 [0, 8388608]) - 文档:玩法创作主规范的上传合同、运维与 Pingora 文档、决策记录、发行里程碑口径,以及新增的续传里程碑与实施计划
64 lines
2.1 KiB
TypeScript
64 lines
2.1 KiB
TypeScript
/* @vitest-environment jsdom */
|
|
|
|
import JSZip from 'jszip';
|
|
import { describe, expect, it } from 'vitest';
|
|
|
|
import { GAME_PACKAGE_MAX_BYTES, prepareGamePackage } from './gameZipPackage';
|
|
|
|
async function buildZip(files: Record<string, string>) {
|
|
const zip = new JSZip();
|
|
for (const [name, content] of Object.entries(files)) {
|
|
zip.file(name, content);
|
|
}
|
|
const bytes = await zip.generateAsync({ type: 'uint8array' });
|
|
return new File([bytes as unknown as BlobPart], 'game.zip', {
|
|
type: 'application/zip',
|
|
});
|
|
}
|
|
|
|
describe('prepareGamePackage', () => {
|
|
it('读取根 index.html、统计文件数并给出稳定摘要', async () => {
|
|
const file = await buildZip({
|
|
'index.html': '<html></html>',
|
|
'assets/app.js': 'console.log(1)',
|
|
});
|
|
const prepared = await prepareGamePackage(file);
|
|
expect(prepared.fileCount).toBe(2);
|
|
expect(prepared.totalBytes).toBe(file.size);
|
|
expect(prepared.sha256).toMatch(/^[0-9a-f]{64}$/u);
|
|
const again = await prepareGamePackage(file);
|
|
expect(again.sha256).toBe(prepared.sha256);
|
|
});
|
|
|
|
it('缺少根 index.html 时失败关闭', async () => {
|
|
const file = await buildZip({ 'game/index.html': '<html></html>' });
|
|
await expect(prepareGamePackage(file)).rejects.toThrow(
|
|
'发行包根目录必须包含 index.html',
|
|
);
|
|
});
|
|
|
|
it('拒绝空文件与非 ZIP 内容', async () => {
|
|
await expect(
|
|
prepareGamePackage(
|
|
new File([], 'empty.zip', { type: 'application/zip' }),
|
|
),
|
|
).rejects.toThrow('请选择非空的发行包 ZIP');
|
|
|
|
await expect(
|
|
prepareGamePackage(
|
|
new File(['not a zip' as unknown as BlobPart], 'bad.zip', {
|
|
type: 'application/zip',
|
|
}),
|
|
),
|
|
).rejects.toThrow('发行包不是有效的 ZIP');
|
|
});
|
|
|
|
it('超过上限时在读取字节之前就失败关闭', async () => {
|
|
// 只伪造体积,不真的分配 200 MiB;预检必须在读字节之前拦下。
|
|
const oversized = { size: GAME_PACKAGE_MAX_BYTES + 1 } as unknown as File;
|
|
await expect(prepareGamePackage(oversized)).rejects.toThrow(
|
|
'发行包不能超过 200 MiB',
|
|
);
|
|
});
|
|
});
|