df39d5a16f
Project CI / AI game creator shell Rust shard 1/4 (push) Has been cancelled
Project CI / AI game creator shell Rust shard 2/4 (push) Has been cancelled
Project CI / AI game creator shell Rust shard 3/4 (push) Has been cancelled
Project CI / AI game creator shell Rust shard 4/4 (push) Has been cancelled
Project CI / AI game creator shell Rust smoke (push) Has been cancelled
Project CI / AI game creator shell Rust crates (push) Has been cancelled
Project CI / Backend tests (push) Has been cancelled
Project CI / Native shell tests (push) Has been cancelled
Project CI / Frontend tests (push) Has been cancelled
Project CI / Repository checks (push) Has been cancelled
Project CI / AI game creator shell web tests (push) Has been cancelled
客户端 debug 保留服务器选择,正式包按发布渠道连接服务 修复错误报告时间解析与超时路由脱敏 重做后台错误详情面板并补齐定向测试 同步渠道、诊断与后台展示文档
124 lines
4.4 KiB
TypeScript
124 lines
4.4 KiB
TypeScript
/** @vitest-environment jsdom */
|
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
import {
|
|
getStoredAuthAccessToken as getApiAccessToken,
|
|
requestClientApi,
|
|
} from '../src/services/clientApi';
|
|
import {
|
|
clearStoredAuthAccessToken,
|
|
getStoredAuthAccessToken,
|
|
setStoredAuthAccessToken,
|
|
} from '../src/services/clientAuth';
|
|
import { AGC_DEVELOPMENT_API_BASE_URL } from '../src/services/clientHttp';
|
|
|
|
vi.mock('@tauri-apps/plugin-http', () => ({ fetch: vi.fn() }));
|
|
vi.mock('../src/services/errorReporting', () => ({
|
|
captureClientError: vi.fn(),
|
|
}));
|
|
|
|
const tokenKey = 'genarrative.auth.access-token.v1';
|
|
const originKey = 'genarrative.auth.access-token-origin.v1';
|
|
const selectionKey = 'genarrative.client.server-selection.v1';
|
|
|
|
describe('AGC platform credential origin', () => {
|
|
afterEach(() => {
|
|
window.localStorage.clear();
|
|
vi.restoreAllMocks();
|
|
vi.unstubAllEnvs();
|
|
});
|
|
|
|
it('preserves a credential already marked as dev', () => {
|
|
window.localStorage.setItem(tokenKey, 'existing-dev-token');
|
|
window.localStorage.setItem(originKey, AGC_DEVELOPMENT_API_BASE_URL);
|
|
|
|
expect(getStoredAuthAccessToken()).toBe('existing-dev-token');
|
|
expect(getApiAccessToken()).toBe('existing-dev-token');
|
|
expect(window.localStorage.getItem(originKey)).toBe(
|
|
AGC_DEVELOPMENT_API_BASE_URL,
|
|
);
|
|
});
|
|
|
|
it.each([
|
|
JSON.stringify({ preset: 'dev', customBaseUrl: '' }),
|
|
JSON.stringify({
|
|
preset: 'custom',
|
|
customBaseUrl: `${AGC_DEVELOPMENT_API_BASE_URL}/`,
|
|
}),
|
|
JSON.stringify({ preset: 'release', customBaseUrl: '' }),
|
|
JSON.stringify({ preset: 'custom', customBaseUrl: 'https://example.com' }),
|
|
JSON.stringify({
|
|
preset: 'custom',
|
|
customBaseUrl: 'http://localhost:8082',
|
|
}),
|
|
JSON.stringify({ preset: 'unknown', customBaseUrl: '' }),
|
|
'invalid-json',
|
|
'null',
|
|
])(
|
|
'never infers a legacy credential origin from a saved preference: %s',
|
|
async (selection) => {
|
|
window.localStorage.setItem(tokenKey, 'other-server-token');
|
|
window.localStorage.setItem(selectionKey, selection);
|
|
vi.stubEnv('MODE', 'production');
|
|
const fetchMock = vi
|
|
.spyOn(globalThis, 'fetch')
|
|
.mockResolvedValue(
|
|
new Response(JSON.stringify({ result: true }), { status: 200 }),
|
|
);
|
|
|
|
await requestClientApi('/api/profile/dashboard', {}, '读取失败');
|
|
|
|
expect(fetchMock).toHaveBeenCalledTimes(1);
|
|
const [url, init] = fetchMock.mock.calls[0];
|
|
expect(url).toBe(`${AGC_DEVELOPMENT_API_BASE_URL}/api/profile/dashboard`);
|
|
expect(new Headers(init?.headers).get('Authorization')).toBeNull();
|
|
expect(window.localStorage.getItem(tokenKey)).toBeNull();
|
|
},
|
|
);
|
|
|
|
it.each([true, false])(
|
|
'clears an unmarked credential without a preference (development=%s)',
|
|
(development) => {
|
|
// Vitest 0.34 stores stubbed env values as strings; use a falsy value for DEV=false.
|
|
vi.stubEnv('DEV', development ? 'true' : '');
|
|
window.localStorage.setItem(tokenKey, 'legacy-token');
|
|
|
|
expect(getStoredAuthAccessToken()).toBe('');
|
|
},
|
|
);
|
|
|
|
it('does not relabel a credential that already belongs to another origin', () => {
|
|
window.localStorage.setItem(tokenKey, 'other-origin-token');
|
|
window.localStorage.setItem(originKey, 'https://www.genarrative.world');
|
|
window.localStorage.setItem(
|
|
selectionKey,
|
|
JSON.stringify({ preset: 'dev' }),
|
|
);
|
|
|
|
expect(getApiAccessToken()).toBe('');
|
|
expect(window.localStorage.getItem(tokenKey)).toBeNull();
|
|
expect(window.localStorage.getItem(originKey)).toBeNull();
|
|
});
|
|
|
|
it('stores credentials with their origin and keeps debug server preferences', () => {
|
|
setStoredAuthAccessToken('new-token');
|
|
window.localStorage.setItem(
|
|
selectionKey,
|
|
JSON.stringify({ preset: 'release' }),
|
|
);
|
|
|
|
expect(getStoredAuthAccessToken(AGC_DEVELOPMENT_API_BASE_URL)).toBe(
|
|
'new-token',
|
|
);
|
|
expect(getStoredAuthAccessToken('https://www.genarrative.world')).toBe('');
|
|
setStoredAuthAccessToken('release-token', 'https://www.genarrative.world');
|
|
expect(getStoredAuthAccessToken('https://www.genarrative.world')).toBe(
|
|
'release-token',
|
|
);
|
|
expect(window.localStorage.getItem(selectionKey)).toContain('release');
|
|
clearStoredAuthAccessToken();
|
|
expect(window.localStorage.getItem(tokenKey)).toBeNull();
|
|
expect(window.localStorage.getItem(originKey)).toBeNull();
|
|
});
|
|
});
|