test: restore project verification suite

This commit is contained in:
boos
2026-06-06 03:35:29 +08:00
parent e4630c4cb6
commit fae0f607aa
12 changed files with 55 additions and 46 deletions
+2
View File
@@ -6,6 +6,7 @@ export const USER_PROFILE_STORAGE_KEY = 'user_profile';
export const ADMIN_PROFILE_STORAGE_KEY = 'admin_profile';
export const LEGACY_USER_PROFILE_STORAGE_KEY = USER_PROFILE_STORAGE_KEY;
export const FIRST_USE_DOC_REMINDER_KEY = 'boosapi_first_use_docs_reminder_seen_v1';
export const LEGACY_FIRST_USE_DOC_REMINDER_KEY = 'metapi_first_use_docs_reminder_seen_v1';
type StorageLike = {
getItem?: (key: string) => string | null;
@@ -28,4 +29,5 @@ export function clearAppInstallationState(storage?: StorageLike | null): void {
target.removeItem(USER_PROFILE_STORAGE_KEY);
target.removeItem(ADMIN_PROFILE_STORAGE_KEY);
target.removeItem(FIRST_USE_DOC_REMINDER_KEY);
target.removeItem(LEGACY_FIRST_USE_DOC_REMINDER_KEY);
}
+7 -6
View File
@@ -8,11 +8,12 @@ export function useIsMobile(breakpoint = MOBILE_BREAKPOINT) {
useEffect(() => {
if (typeof window === 'undefined') return;
const targetWindow = window;
const query = breakpoint === MOBILE_BREAKPOINT
? MOBILE_MEDIA_QUERY
: `(max-width: ${breakpoint}px)`;
const media = typeof window.matchMedia === 'function' ? window.matchMedia(query) : null;
const update = () => setIsMobile(media ? media.matches : window.innerWidth <= breakpoint);
const media = typeof targetWindow.matchMedia === 'function' ? targetWindow.matchMedia(query) : null;
const update = () => setIsMobile(media ? media.matches : targetWindow.innerWidth <= breakpoint);
update();
if (media && typeof media.addEventListener === 'function') {
@@ -24,14 +25,14 @@ export function useIsMobile(breakpoint = MOBILE_BREAKPOINT) {
};
}
const addResizeListener = typeof window.addEventListener === 'function';
const removeResizeListener = typeof window.removeEventListener === 'function';
const addResizeListener = typeof targetWindow.addEventListener === 'function';
const removeResizeListener = typeof targetWindow.removeEventListener === 'function';
if (!addResizeListener) return;
window.addEventListener('resize', update);
targetWindow.addEventListener('resize', update);
return () => {
if (removeResizeListener) {
window.removeEventListener('resize', update);
targetWindow.removeEventListener('resize', update);
}
};
}, [breakpoint]);
+5
View File
@@ -13,6 +13,7 @@ const { apiMock } = vi.hoisted(() => ({
getAccounts: vi.fn(),
getAccountsSnapshot: vi.fn(),
getAccountTokens: vi.fn(),
getSites: vi.fn(),
getDownstreamApiKeyOverview: vi.fn(),
getDownstreamApiKeyTrend: vi.fn(),
createDownstreamApiKey: vi.fn(),
@@ -166,6 +167,10 @@ beforeEach(() => {
},
},
]);
apiMock.getSites.mockResolvedValue([
{ id: 201, name: '站点A', status: 'active' },
{ id: 202, name: '站点B', status: 'active' },
]);
apiMock.getAccountTokens.mockResolvedValue([
{
id: 301,
+6 -6
View File
@@ -214,7 +214,7 @@ describe('ImportExport', () => {
success: true,
config: {
enabled: true,
fileUrl: 'https://dav.example.com/backups/metapi.json',
fileUrl: 'https://dav.example.com/backups/boosapi.json',
username: 'alice',
exportType: 'all',
autoSyncEnabled: true,
@@ -231,7 +231,7 @@ describe('ImportExport', () => {
success: true,
config: {
enabled: true,
fileUrl: 'https://dav.example.com/backups/metapi.json',
fileUrl: 'https://dav.example.com/backups/boosapi.json',
username: 'alice',
exportType: 'all',
autoSyncEnabled: true,
@@ -246,7 +246,7 @@ describe('ImportExport', () => {
});
apiMock.exportBackupToWebdav.mockResolvedValue({
success: true,
fileUrl: 'https://dav.example.com/backups/metapi.json',
fileUrl: 'https://dav.example.com/backups/boosapi.json',
exportType: 'all',
});
apiMock.importBackupFromWebdav.mockResolvedValue({
@@ -459,7 +459,7 @@ describe('ImportExport', () => {
const fileUrlInput = root!.root.findAll((node) => (
node.type === 'input'
&& node.props.placeholder === 'https://dav.example.com/backups/metapi.json'
&& node.props.placeholder === 'https://dav.example.com/backups/boosapi.json'
)).at(-1);
const cronInput = root!.root.findAll((node) => (
node.type === 'input'
@@ -507,7 +507,7 @@ describe('ImportExport', () => {
const fileUrlInput = root!.root.findAll((node) => (
node.type === 'input'
&& node.props.placeholder === 'https://dav.example.com/backups/metapi.json'
&& node.props.placeholder === 'https://dav.example.com/backups/boosapi.json'
)).at(-1);
expect(fileUrlInput).toBeTruthy();
@@ -538,7 +538,7 @@ describe('ImportExport', () => {
success: true,
config: {
enabled: true,
fileUrl: 'https://dav.example.com/backups/metapi.json',
fileUrl: 'https://dav.example.com/backups/boosapi.json',
username: 'alice',
exportType: 'all',
autoSyncEnabled: true,
@@ -789,7 +789,7 @@ describe('Models marketplace text', () => {
expect(String(latencyBadge.props.className || '')).toContain('badge-muted');
expect(collectText(latencyBadge)).toContain('延迟');
expect(collectText(latencyBadge)).toContain('—');
expect(collectText(latencyBadge)).toContain('暂无');
expect(collectText(root!.root)).not.toContain('680ms');
} finally {
root?.unmount();
@@ -520,7 +520,7 @@ describe('ProxyLogs server-driven page', () => {
});
await flushMicrotasks();
expect(globalThis.localStorage.setItem).toHaveBeenCalledWith('metapi.proxyLogs.debugTracePanelExpanded', 'false');
expect(globalThis.localStorage.setItem).toHaveBeenCalledWith('boosapi.proxyLogs.debugTracePanelExpanded', 'false');
await act(async () => {
root.unmount();
@@ -547,7 +547,7 @@ describe('ProxyLogs server-driven page', () => {
&& node.props['data-debug-trace-panel-body'] === true
));
expect(globalThis.localStorage.getItem).toHaveBeenCalledWith('metapi.proxyLogs.debugTracePanelExpanded');
expect(globalThis.localStorage.getItem).toHaveBeenCalledWith('boosapi.proxyLogs.debugTracePanelExpanded');
expect(restoredToggleButton.props['aria-expanded']).toBe(false);
expect(String(restoredPanelBody.props.className || '')).not.toContain('is-open');
} finally {
@@ -8,7 +8,7 @@ describe('Dashboard mobile layout', () => {
expect(source).toContain('import { useIsMobile } from "../components/useIsMobile.js";');
expect(source).toContain('const isMobile = useIsMobile()');
expect(source).toContain('gridTemplateColumns: isMobile ? "1fr" : "1fr 1fr"');
expect(source).toContain('className="dashboard-chart-grid"');
expect(source).toContain('gridTemplateColumns: isMobile ? "1fr" : "1fr 300px"');
});
});
@@ -90,7 +90,7 @@ describe('modelTesterSession', () => {
name: 'draft.pdf',
mimeType: 'application/pdf',
dataUrl: 'data:application/pdf;base64,JVBERi0x',
fileId: 'file-metapi-draft',
fileId: 'file-boosapi-draft',
status: 'uploaded',
errorMessage: null,
},
@@ -243,7 +243,7 @@ describe('modelTesterSession', () => {
it('creates user conversation messages that preserve uploaded file references', () => {
const message = createConversationUserMessage('请总结附件', [
{
fileId: 'file-metapi-123',
fileId: 'file-boosapi-123',
filename: 'paper.pdf',
mimeType: 'application/pdf',
},
@@ -254,7 +254,7 @@ describe('modelTesterSession', () => {
expect(message.parts).toEqual([
{
type: 'input_file',
fileId: 'file-metapi-123',
fileId: 'file-boosapi-123',
filename: 'paper.pdf',
mimeType: 'application/pdf',
},
@@ -295,7 +295,7 @@ describe('modelTesterSession', () => {
parts: [
{
type: 'input_file',
fileId: 'file-metapi-456',
fileId: 'file-boosapi-456',
filename: 'appendix.txt',
mimeType: 'text/plain',
},
@@ -304,7 +304,7 @@ describe('modelTesterSession', () => {
expect(files).toEqual([
{
fileId: 'file-metapi-456',
fileId: 'file-boosapi-456',
filename: 'appendix.txt',
mimeType: 'text/plain',
},
@@ -322,7 +322,7 @@ describe('modelTesterSession', () => {
const files = await resolveConversationReplayFiles?.(
[
{
fileId: 'file-metapi-123',
fileId: 'file-boosapi-123',
filename: 'brief.pdf',
mimeType: 'application/pdf',
},
@@ -331,10 +331,10 @@ describe('modelTesterSession', () => {
loader,
);
expect(loader).toHaveBeenCalledWith('file-metapi-123');
expect(loader).toHaveBeenCalledWith('file-boosapi-123');
expect(files).toEqual([
{
fileId: 'file-metapi-123',
fileId: 'file-boosapi-123',
filename: 'brief.pdf',
mimeType: 'application/pdf',
data: 'data:application/pdf;base64,JVBERi0x',
@@ -564,7 +564,7 @@ describe('modelTesterSession', () => {
parts: [
{
type: 'input_file' as const,
fileId: 'file-metapi-123',
fileId: 'file-boosapi-123',
filename: 'brief.pdf',
mimeType: 'application/pdf',
data: 'data:application/pdf;base64,JVBERi0x',
@@ -592,7 +592,7 @@ describe('modelTesterSession', () => {
{
type: 'file',
file: {
file_id: 'file-metapi-123',
file_id: 'file-boosapi-123',
filename: 'brief.pdf',
mime_type: 'application/pdf',
},
@@ -625,7 +625,7 @@ describe('modelTesterSession', () => {
{ type: 'input_text', text: 'reuse native file id' },
{
type: 'input_file',
file_id: 'file-metapi-123',
file_id: 'file-boosapi-123',
filename: 'brief.pdf',
},
],
@@ -105,7 +105,7 @@ describe('Settings model availability probe confirmation', () => {
const toggleLabel = root.root.find((node) => (
node.type === 'label'
&& collectText(node).includes('允许 metapi 后台主动批量测活')
&& collectText(node).includes('允许 BoosAPI 后台主动批量测活')
));
const toggle = toggleLabel.findByType('input');
expect(toggle.props.checked).toBe(false);
@@ -109,7 +109,7 @@ describe('Settings proxy transport', () => {
const websocketToggleLabel = root.root.find((node) => (
node.type === 'label'
&& collectText(node).includes('允许 metapi 到 Codex 上游使用 WebSocket')
&& collectText(node).includes('允许 BoosAPI 到 Codex 上游使用 WebSocket')
));
const websocketToggle = websocketToggleLabel.findByType('input');
expect(websocketToggle.props.checked).toBe(false);
+16 -15
View File
@@ -264,19 +264,19 @@ describe('Sites create redirect', () => {
const platformSelect = selects.at(-1);
expect(platformSelect?.props.options).toEqual(
expect.arrayContaining([
expect.objectContaining({ label: '阿里云 CodingPlan / OpenAI' }),
expect.objectContaining({ label: '阿里云 CodingPlan / Claude' }),
expect.objectContaining({ label: '智谱 Coding Plan / OpenAI' }),
expect.objectContaining({ label: '智谱 Coding Plan / Claude' }),
expect.objectContaining({ label: 'DeepSeek / OpenAI' }),
expect.objectContaining({ label: 'DeepSeek / Claude' }),
expect.objectContaining({ label: 'Moonshot(Kimi) / OpenAI' }),
expect.objectContaining({ label: 'Moonshot(Kimi) / Claude' }),
expect.objectContaining({ label: 'MiniMax / OpenAI' }),
expect.objectContaining({ label: 'MiniMax / Claude' }),
expect.objectContaining({ label: 'ModelScope / OpenAI' }),
expect.objectContaining({ label: 'ModelScope / Claude' }),
expect.objectContaining({ label: '豆包 Coding Plan / OpenAI' }),
expect.objectContaining({ label: '阿里云 Coding Plan (OpenAI)' }),
expect.objectContaining({ label: '阿里云 Coding Plan (Claude)' }),
expect.objectContaining({ label: '智谱清言 Coding Plan (OpenAI)' }),
expect.objectContaining({ label: '智谱清言 Coding Plan (Claude)' }),
expect.objectContaining({ label: 'DeepSeek (OpenAI)' }),
expect.objectContaining({ label: 'DeepSeek (Claude)' }),
expect.objectContaining({ label: 'Moonshot (OpenAI)' }),
expect.objectContaining({ label: 'Moonshot (Claude)' }),
expect.objectContaining({ label: 'MiniMax (OpenAI)' }),
expect.objectContaining({ label: 'MiniMax (Claude)' }),
expect.objectContaining({ label: 'ModelScope (OpenAI)' }),
expect.objectContaining({ label: 'ModelScope (Claude)' }),
expect.objectContaining({ label: '豆包 Coding Plan (OpenAI)' }),
]),
);
} finally {
@@ -366,11 +366,12 @@ describe('Sites create redirect', () => {
await flushMicrotasks();
expect(urlInput.props.value).toBe('https://open.bigmodel.cn/api/coding/paas/v4');
expect(JSON.stringify(root.toJSON())).toContain('智谱 Coding Plan / OpenAI');
expect(JSON.stringify(root.toJSON())).toContain('智谱清言 Coding Plan (OpenAI)');
const presetAlerts = root.root.findAll((node) => (
typeof node.props.className === 'string'
&& node.props.className.includes('alert alert-info')
&& collectText(node).includes('已应用官方预设')
));
expect(presetAlerts).toHaveLength(1);
expect(collectText(presetAlerts[0]!)).toContain('已应用官方预设');
@@ -645,7 +646,7 @@ describe('Sites create redirect', () => {
));
expect(platformSelect?.props.value).toBe('preset:deepseek-openai');
expect(collectText(presetAlert)).toContain('已应用官方预设');
expect(collectText(presetAlert)).toContain('DeepSeek / OpenAI');
expect(collectText(presetAlert)).toContain('DeepSeek (OpenAI)');
} finally {
root?.unmount();
}