8701f43f75
- 删除渲染层传输层:services/clientApi.ts、clientHttp 的 fetchClientHttp 与超时封装整体退场,账户、认证、素材直传、错误上报与游戏分发发布改走 Rust typed command。 - 新增宿主封装:accountHost、editorAssetHost、platformSessionRetry、agentRuntimeErrorDetail 与 features/app-shell/workspacePreferences;渲染层不再读写 access token / refresh cookie,localStorage 只剩测试态旧键迁移 shim。 - 修复 DirectProject 失败文案 v2 映射与「查看详情」按需读取,画布标签面板高度夹紧、框选每帧重算、文档预览入口与场景标题栏 key、资源筛选面板样式。 - 补齐与调整回归:accountHost、agentRuntimeErrorDetail、clientAuthHost、clientHttp、platformSession、platformSessionRetry、resourceFilterPanelStyle、directThreadChatSubscription 等定向用例,并清理测试 harness 中无调用方的残留。
238 lines
7.5 KiB
TypeScript
238 lines
7.5 KiB
TypeScript
// @vitest-environment jsdom
|
|
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
|
|
|
|
import type { GameCreationAppManifest } from '../../../packages/shared/src/contracts/gameCreationApp';
|
|
import type { TauriInvoke } from '../src/app/types';
|
|
import {
|
|
generateGameDistributionCover,
|
|
publishLocalProjectGame,
|
|
readGameCoverGenerationPrice,
|
|
readGamePublishAvailability,
|
|
suggestGameDistributionPublishMetadata,
|
|
} from '../src/services/gameDistributionPublish';
|
|
|
|
const MANIFEST = {
|
|
projectId: 'local-proj-1',
|
|
name: '星轨防线',
|
|
goal: '守住轨道城',
|
|
} as unknown as GameCreationAppManifest;
|
|
|
|
const PUBLISH_RESULT = {
|
|
gameId: 'game_1',
|
|
versionId: 'gamever_1',
|
|
versionNumber: 1,
|
|
status: 'pending_review',
|
|
packageSha256: 'a'.repeat(64),
|
|
packageSizeBytes: 1024,
|
|
fileCount: 1,
|
|
};
|
|
|
|
type InvokeCall = {
|
|
command: string;
|
|
args: Record<string, unknown> | undefined;
|
|
};
|
|
|
|
function installNativeInvoke(
|
|
implementation: (command: string, args?: Record<string, unknown>) => unknown,
|
|
) {
|
|
const calls: InvokeCall[] = [];
|
|
const invoke = vi.fn(
|
|
async (command: string, args?: Record<string, unknown>) => {
|
|
calls.push({ command, args });
|
|
return implementation(command, args);
|
|
},
|
|
) as unknown as TauriInvoke;
|
|
return { invoke, calls };
|
|
}
|
|
|
|
beforeEach(() => {
|
|
window.localStorage.clear();
|
|
});
|
|
|
|
afterEach(() => {
|
|
vi.useRealTimers();
|
|
delete (window as unknown as { __TAURI__?: unknown }).__TAURI__;
|
|
});
|
|
|
|
describe('Rust 发布 facade', () => {
|
|
test('发布只通过一个 native command 传递本地路径、资料和幂等键', async () => {
|
|
const { invoke, calls } = installNativeInvoke((command) => {
|
|
if (command === 'publish_local_project_game') return PUBLISH_RESULT;
|
|
throw new Error(`未预期的命令:${command}`);
|
|
});
|
|
|
|
const result = await publishLocalProjectGame({
|
|
invoke,
|
|
projectPath: '/tmp/project',
|
|
packageRelativePath: 'exports/playtest-package-1.zip',
|
|
manifest: MANIFEST,
|
|
metadata: {
|
|
coverAssetId: 'asset_cover',
|
|
screenshots: ['asset_shot_1', 'asset_shot_2'],
|
|
},
|
|
idempotencyKey: 'agc-publish-test',
|
|
});
|
|
|
|
expect(result).toEqual(PUBLISH_RESULT);
|
|
expect(calls).toHaveLength(1);
|
|
expect(calls[0]?.command).toBe('publish_local_project_game');
|
|
expect(calls[0]?.args).toMatchObject({
|
|
projectPath: '/tmp/project',
|
|
packageRelativePath: 'exports/playtest-package-1.zip',
|
|
idempotencyKey: 'agc-publish-test',
|
|
metadata: {
|
|
localProjectId: 'local-proj-1',
|
|
coverAssetId: 'asset_cover',
|
|
screenshots: ['asset_shot_1', 'asset_shot_2'],
|
|
},
|
|
});
|
|
expect(calls[0]?.args).not.toHaveProperty('accessToken');
|
|
expect(calls[0]?.args).not.toHaveProperty('apiBaseUrl');
|
|
expect(calls[0]?.args).not.toHaveProperty('packageBytes');
|
|
});
|
|
|
|
test('缺少本地项目标识时在发起 native command 前失败关闭', async () => {
|
|
const invoke = vi.fn() as unknown as TauriInvoke;
|
|
await expect(
|
|
publishLocalProjectGame({
|
|
invoke,
|
|
projectPath: '/tmp/project',
|
|
packageRelativePath: 'exports/playtest-package-1.zip',
|
|
manifest: { ...MANIFEST, projectId: ' ' } as GameCreationAppManifest,
|
|
metadata: { coverAssetId: 'asset_cover' },
|
|
}),
|
|
).rejects.toThrow('发布需要本地项目标识');
|
|
expect(invoke).not.toHaveBeenCalled();
|
|
});
|
|
|
|
test('缺少封面时在创建 native 发布任务前失败关闭', async () => {
|
|
const invoke = vi.fn() as unknown as TauriInvoke;
|
|
await expect(
|
|
publishLocalProjectGame({
|
|
invoke,
|
|
projectPath: '/tmp/project',
|
|
packageRelativePath: 'exports/playtest-package-1.zip',
|
|
manifest: MANIFEST,
|
|
metadata: { coverAssetId: ' ' },
|
|
}),
|
|
).rejects.toThrow('请先选择游戏封面');
|
|
expect(invoke).not.toHaveBeenCalled();
|
|
});
|
|
|
|
test('截图超过 6 张时在 native command 前失败关闭', async () => {
|
|
const invoke = vi.fn() as unknown as TauriInvoke;
|
|
await expect(
|
|
publishLocalProjectGame({
|
|
invoke,
|
|
projectPath: '/tmp/project',
|
|
packageRelativePath: 'exports/playtest-package-1.zip',
|
|
manifest: MANIFEST,
|
|
metadata: {
|
|
coverAssetId: 'asset_cover',
|
|
screenshots: Array.from(
|
|
{ length: 7 },
|
|
(_, index) => `asset_${index}`,
|
|
),
|
|
},
|
|
}),
|
|
).rejects.toThrow('游戏截图最多 6 张');
|
|
expect(invoke).not.toHaveBeenCalled();
|
|
});
|
|
|
|
test('发布灰度、资料建议和价格均通过 native command', async () => {
|
|
const { invoke, calls } = installNativeInvoke((command) => {
|
|
if (command === 'read_game_publish_availability') return true;
|
|
if (command === 'suggest_game_distribution_publish_metadata') {
|
|
return { summary: '驾驶炮台守住轨道城', category: '策略' };
|
|
}
|
|
if (command === 'read_game_cover_generation_price') return 5;
|
|
throw new Error(`未预期的命令:${command}`);
|
|
});
|
|
|
|
await expect(readGamePublishAvailability(invoke)).resolves.toBe(true);
|
|
await expect(
|
|
suggestGameDistributionPublishMetadata(
|
|
{
|
|
name: '星轨防线',
|
|
goal: '守住轨道城',
|
|
context:
|
|
'当前任务与状态:原型已完成;现有素材:image:assets/hero.png',
|
|
},
|
|
invoke,
|
|
),
|
|
).resolves.toEqual({ summary: '驾驶炮台守住轨道城', category: '策略' });
|
|
await expect(
|
|
readGameCoverGenerationPrice(
|
|
{ model: 'gpt-image-2', imageSize: '2K' },
|
|
invoke,
|
|
),
|
|
).resolves.toBe(5);
|
|
|
|
expect(calls).toEqual([
|
|
{ command: 'read_game_publish_availability', args: undefined },
|
|
{
|
|
command: 'suggest_game_distribution_publish_metadata',
|
|
args: {
|
|
name: '星轨防线',
|
|
goal: '守住轨道城',
|
|
context:
|
|
'当前任务与状态:原型已完成;现有素材:image:assets/hero.png',
|
|
},
|
|
},
|
|
{
|
|
command: 'read_game_cover_generation_price',
|
|
args: { model: 'gpt-image-2', imageSize: '2K' },
|
|
},
|
|
]);
|
|
});
|
|
|
|
test('封面生成由 Rust 等待队列后一次性返回已登记素材', async () => {
|
|
const { invoke, calls } = installNativeInvoke((command) => {
|
|
if (command === 'generate_game_distribution_cover') {
|
|
return {
|
|
assetObjectId: 'asset_generated_cover',
|
|
previewUrl: 'https://assets.example.com/generated-cover.png',
|
|
taskId: 'task_cover_1',
|
|
model: 'gpt-image-2',
|
|
};
|
|
}
|
|
throw new Error(`未预期的命令:${command}`);
|
|
});
|
|
|
|
await expect(
|
|
generateGameDistributionCover(
|
|
{
|
|
prompt: '为《星轨防线》生成游戏封面',
|
|
model: 'gpt-image-2',
|
|
aspectRatio: '16:9',
|
|
imageSize: '2K',
|
|
},
|
|
invoke,
|
|
),
|
|
).resolves.toEqual({
|
|
assetObjectId: 'asset_generated_cover',
|
|
previewUrl: 'https://assets.example.com/generated-cover.png',
|
|
taskId: 'task_cover_1',
|
|
model: 'gpt-image-2',
|
|
});
|
|
expect(calls).toEqual([
|
|
{
|
|
command: 'generate_game_distribution_cover',
|
|
args: {
|
|
prompt: '为《星轨防线》生成游戏封面',
|
|
model: 'gpt-image-2',
|
|
aspectRatio: '16:9',
|
|
imageSize: '2K',
|
|
assetLabel: '游戏封面',
|
|
},
|
|
},
|
|
]);
|
|
});
|
|
|
|
test('缺少 native 宿主时发布网络 facade 失败关闭', async () => {
|
|
await expect(readGamePublishAvailability()).rejects.toThrow(
|
|
'需要在 Tauri App 内执行发布操作',
|
|
);
|
|
});
|
|
});
|