Files
Genarrative/apps/ai-game-creator-shell/tests/gameDistributionPublish.test.ts
T
kdletters 328ac31844 恢复游戏分发完整实现(特性分支)
- 主站:游戏广场、详情、在线游玩、网页发布与作者中心,以及共享契约与客户端服务
- 后端:module-game-distribution 领域层、SpacetimeDB 表/迁移/绑定、spacetime-client facade、api-server 路由与发行网关
- 后台:游戏审核页(待审列表、通过/拒绝、安全下架)
- AGC:发布面板、本地导出包读取命令与发布服务,含默认跳过的真实链路测试
- 运维:发行来源 nginx 模板与门禁、game-distribution:publish 灰度发布开关、OSS PutObject 受控重试
- 文档:主规范、里程碑与实施计划、决策日志与踩坑记录
2026-09-20 20:49:42 +08:00

119 lines
3.7 KiB
TypeScript

// @vitest-environment jsdom
import { beforeEach, expect, test, vi } from 'vitest';
const fetchClientHttp = vi.fn();
vi.mock('../src/services/clientHttp', () => ({
AGC_DEVELOPMENT_API_BASE_URL: 'https://dev.genarrative.world',
fetchClientHttp: (...args: unknown[]) => fetchClientHttp(...args),
getClientServerBaseUrl: () => 'https://dev.genarrative.world',
readClientHttpResponseText: (response: Response) => response.text(),
}));
vi.mock('../src/services/errorReporting', () => ({
captureClientError: vi.fn(),
}));
import type { GameCreationAppManifest } from '../../../packages/shared/src/contracts/gameCreationApp';
import { publishLocalProjectGame } from '../src/services/gameDistributionPublish';
const MANIFEST = {
projectId: 'local-proj-1',
name: '星轨防线',
goal: '守住轨道城',
} as unknown as GameCreationAppManifest;
function jsonResponse(payload: unknown) {
return new Response(
JSON.stringify({
ok: true,
data: payload,
error: null,
meta: { apiVersion: 'v1' },
}),
{
status: 200,
headers: { 'Content-Type': 'application/json' },
},
);
}
beforeEach(() => {
fetchClientHttp.mockReset();
window.localStorage.clear();
});
test('发布时携带本地项目标识,让重复发布复用同一个平台游戏', async () => {
fetchClientHttp
.mockResolvedValueOnce(
jsonResponse({ id: 'game_1', publicationRevision: 0 }),
)
.mockResolvedValueOnce(
jsonResponse({
gameId: 'game_1',
versionId: 'gamever_1',
versionNumber: 1,
status: 'awaiting_upload',
}),
)
.mockResolvedValueOnce(
jsonResponse({ versionId: 'gamever_1', status: 'uploaded' }),
)
.mockResolvedValueOnce(
jsonResponse({ version: { status: 'pending_review' } }),
);
const result = await publishLocalProjectGame({
invoke: (async (command: string) => {
expect(command).toBe('read_local_project_export_package');
return {
packageRelativePath: 'exports/playtest-package-1.zip',
packageBytes: [1, 2, 3],
packageSha256: 'a'.repeat(64),
packageSizeBytes: 3,
files: [{ path: 'index.html', sizeBytes: 3, sha256: 'a'.repeat(64) }],
};
}) as never,
projectPath: '/tmp/project',
packageRelativePath: 'exports/playtest-package-1.zip',
manifest: MANIFEST,
});
const createGameCall = fetchClientHttp.mock.calls[0];
expect(createGameCall?.[0]).toBe('/api/game-distribution/games');
const createGameBody = JSON.parse(
String((createGameCall?.[1] as RequestInit).body),
);
expect(createGameBody.localProjectId).toBe('local-proj-1');
const createVersionCall = fetchClientHttp.mock.calls[1];
expect(createVersionCall?.[0]).toBe(
'/api/game-distribution/games/game_1/versions',
);
expect(
JSON.parse(String((createVersionCall?.[1] as RequestInit).body))
.localProjectId,
).toBe('local-proj-1');
expect(result.gameId).toBe('game_1');
expect(result.versionId).toBe('gamever_1');
});
test('缺少本地项目标识时在发起请求前失败关闭', async () => {
await expect(
publishLocalProjectGame({
invoke: (async () => ({
packageRelativePath: 'exports/playtest-package-1.zip',
packageBytes: [1],
packageSha256: 'a'.repeat(64),
packageSizeBytes: 1,
files: [{ path: 'index.html', sizeBytes: 1, sha256: 'a'.repeat(64) }],
})) as never,
projectPath: '/tmp/project',
packageRelativePath: 'exports/playtest-package-1.zip',
manifest: { ...MANIFEST, projectId: ' ' } as GameCreationAppManifest,
}),
).rejects.toThrow('发布需要本地项目标识');
expect(fetchClientHttp).not.toHaveBeenCalled();
});