Files
Genarrative/apps/ai-game-creator-shell/tests/platformSession.test.ts
T
kdletters 8701f43f75 AGC 渲染层网络与状态下沉 Rust,并修复画布交互与失败文案
- 删除渲染层传输层: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 中无调用方的残留。
2026-09-28 14:15:02 +08:00

188 lines
5.5 KiB
TypeScript

// @vitest-environment node
/**
* 平台会话代次的语义。
*
* 渲染层只持有这一个派生计数,用来识别换号/登出后到达的迟到结果。它必须在
* 「同一账号续期」和「同一身份重复采纳(StrictMode 双次 hydrate)」时保持不变——否则
* 在途生成会被自己的续期判成旧账号请求;而换账号、换 origin、登出又必须立刻推进。
*/
import { beforeEach, expect, test, vi } from 'vitest';
const refreshClientAuthSession = vi.hoisted(() => vi.fn());
vi.mock('../src/services/clientAuth', () => ({
refreshClientAuthSession,
subscribeClientAuthState: () => () => {},
}));
import {
beginPlatformSessionTransition,
commitAuthenticatedPlatformSession,
currentPlatformSessionGeneration,
requestPlatformSessionRefresh,
resetPlatformSessionForTests,
subscribePlatformSessionGeneration,
} from '../src/services/platformSession';
const userA = {
id: 'user-a',
publicUserCode: 'tn-a',
displayName: '账号 A',
avatarUrl: null,
phoneNumber: null,
phoneNumberMasked: null,
loginMethod: 'password',
bindingStatus: 'active',
wechatBound: false,
wechatDisplayName: null,
wechatAccount: null,
};
const userB = {
...userA,
id: 'user-b',
publicUserCode: 'tn-b',
displayName: '账号 B',
};
beforeEach(() => {
refreshClientAuthSession.mockReset();
resetPlatformSessionForTests();
});
test('同一身份重复采纳不推进代次,换 origin 或换账号才推进', async () => {
const observed: number[] = [];
const unsubscribe = subscribePlatformSessionGeneration((generation) => {
observed.push(generation);
});
const first = await commitAuthenticatedPlatformSession(
userA,
currentPlatformSessionGeneration(),
'https://a.example.com',
);
expect(first).toBe(1);
// StrictMode 双次 hydrate 或同一账号重新登录:代次必须原地不动。
const again = await commitAuthenticatedPlatformSession(
userA,
currentPlatformSessionGeneration(),
'https://a.example.com',
);
expect(again).toBe(first);
const otherOrigin = await commitAuthenticatedPlatformSession(
userA,
currentPlatformSessionGeneration(),
'https://b.example.com',
);
expect(otherOrigin).toBe(2);
const otherUser = await commitAuthenticatedPlatformSession(
userB,
currentPlatformSessionGeneration(),
'https://b.example.com',
);
expect(otherUser).toBe(3);
expect(observed).toEqual([1, 2, 3]);
unsubscribe();
});
test('期望代次已过期时不采纳身份', async () => {
await commitAuthenticatedPlatformSession(
userA,
currentPlatformSessionGeneration(),
'https://a.example.com',
);
await expect(
commitAuthenticatedPlatformSession(userB, 0, 'https://a.example.com'),
).resolves.toBeNull();
expect(currentPlatformSessionGeneration()).toBe(1);
});
test('登录切换与登出立刻推进代次', () => {
const before = currentPlatformSessionGeneration();
const next = beginPlatformSessionTransition();
expect(next).toBe(before + 1);
expect(currentPlatformSessionGeneration()).toBe(next);
});
test('同一账号续期不推进代次,并发续期只发一次请求', async () => {
await commitAuthenticatedPlatformSession(
userA,
currentPlatformSessionGeneration(),
'https://a.example.com',
);
const generationBefore = currentPlatformSessionGeneration();
refreshClientAuthSession.mockResolvedValue({
status: 'refreshed',
user: userA,
});
const [first, second] = await Promise.all([
requestPlatformSessionRefresh(),
requestPlatformSessionRefresh(),
]);
expect(refreshClientAuthSession).toHaveBeenCalledTimes(1);
expect(first).toEqual({
status: 'refreshed',
user: userA,
generation: generationBefore,
});
expect(second).toEqual(first);
expect(currentPlatformSessionGeneration()).toBe(generationBefore);
});
test('续期发现未登录时采纳空身份并给出权威失败', async () => {
await commitAuthenticatedPlatformSession(
userA,
currentPlatformSessionGeneration(),
'https://a.example.com',
);
const generationBefore = currentPlatformSessionGeneration();
refreshClientAuthSession.mockResolvedValue({ status: 'unauthenticated' });
const result = await requestPlatformSessionRefresh();
expect(result.status).toBe('failed');
if (result.status === 'failed') {
expect(result.authoritative).toBe(true);
expect(String(result.error)).toContain('登录已失效');
}
expect(currentPlatformSessionGeneration()).toBe(generationBefore + 1);
});
test('续期期间换号时返回 stale,不覆盖新身份', async () => {
await commitAuthenticatedPlatformSession(
userA,
currentPlatformSessionGeneration(),
'https://a.example.com',
);
refreshClientAuthSession.mockImplementation(async () => {
beginPlatformSessionTransition();
return { status: 'refreshed', user: userA };
});
await expect(requestPlatformSessionRefresh()).resolves.toEqual({
status: 'stale',
});
});
test('网络类续期失败保持会话且不标记为权威失败', async () => {
await commitAuthenticatedPlatformSession(
userA,
currentPlatformSessionGeneration(),
'https://a.example.com',
);
const generationBefore = currentPlatformSessionGeneration();
refreshClientAuthSession.mockRejectedValue(new Error('网络不可达'));
const result = await requestPlatformSessionRefresh();
expect(result.status).toBe('failed');
if (result.status === 'failed') {
expect(result.authoritative).toBe(false);
}
expect(currentPlatformSessionGeneration()).toBe(generationBefore);
});