220 lines
6.9 KiB
TypeScript
220 lines
6.9 KiB
TypeScript
/* @vitest-environment jsdom */
|
|
|
|
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
|
import userEvent from '@testing-library/user-event';
|
|
import { beforeEach, expect, test, vi } from 'vitest';
|
|
|
|
import {
|
|
getAdminFeatureGateConfig,
|
|
upsertAdminFeatureGateConfig,
|
|
} from '../api/adminApiClient';
|
|
import type { AdminFeatureGateConfigResponse } from '../api/adminApiTypes';
|
|
import { AdminGrayReleaseConfigPage } from './AdminGrayReleaseConfigPage';
|
|
|
|
vi.mock('../api/adminApiClient', () => ({
|
|
formatAdminApiError: vi.fn((error: unknown) =>
|
|
error instanceof Error ? error.message : '请求失败',
|
|
),
|
|
getAdminFeatureGateConfig: vi.fn(),
|
|
isAdminApiError: vi.fn(() => false),
|
|
upsertAdminFeatureGateConfig: vi.fn(),
|
|
}));
|
|
|
|
const configResponse: AdminFeatureGateConfigResponse = {
|
|
gates: [
|
|
{
|
|
gateKey: 'editor.new-toolbar',
|
|
enabled: true,
|
|
rolloutPercent: 25,
|
|
allowUserIds: ['user-1'],
|
|
allowUserTags: ['beta'],
|
|
denyUserIds: ['blocked-1'],
|
|
description: '新版编辑器工具条',
|
|
updatedAt: '2026-07-07T01:00:00Z',
|
|
},
|
|
{
|
|
gateKey: 'image.generator.v2',
|
|
enabled: false,
|
|
rolloutPercent: 5,
|
|
allowUserIds: ['artist-1', 'artist-2'],
|
|
allowUserTags: ['internal', 'trial'],
|
|
denyUserIds: [],
|
|
description: '图片生成链路',
|
|
updatedAt: '2026-07-07T02:00:00Z',
|
|
},
|
|
],
|
|
};
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
vi.mocked(getAdminFeatureGateConfig).mockResolvedValue(configResponse);
|
|
vi.mocked(upsertAdminFeatureGateConfig).mockResolvedValue(configResponse);
|
|
});
|
|
|
|
test('灰度发布页加载并展示 gate 列表', async () => {
|
|
render(
|
|
<AdminGrayReleaseConfigPage token="admin-token" onUnauthorized={vi.fn()} />,
|
|
);
|
|
|
|
expect(
|
|
await screen.findByRole('button', { name: 'editor.new-toolbar' }),
|
|
).toBeTruthy();
|
|
expect(
|
|
screen.getByRole('button', { name: 'image.generator.v2' }),
|
|
).toBeTruthy();
|
|
expect(screen.getByText('25%')).toBeTruthy();
|
|
expect(getAdminFeatureGateConfig).toHaveBeenCalledWith('admin-token');
|
|
});
|
|
|
|
test('灰度发布页可选择已有 gate 编辑', async () => {
|
|
const user = userEvent.setup();
|
|
render(
|
|
<AdminGrayReleaseConfigPage token="admin-token" onUnauthorized={vi.fn()} />,
|
|
);
|
|
|
|
await user.click(
|
|
await screen.findByRole('button', { name: 'image.generator.v2' }),
|
|
);
|
|
|
|
expect((screen.getByLabelText('Gate Key') as HTMLInputElement).value).toBe(
|
|
'image.generator.v2',
|
|
);
|
|
expect((screen.getByLabelText('启用') as HTMLInputElement).checked).toBe(
|
|
false,
|
|
);
|
|
expect((screen.getByLabelText('灰度比例') as HTMLInputElement).value).toBe(
|
|
'5',
|
|
);
|
|
expect(
|
|
(screen.getByLabelText('允许用户 ID') as HTMLTextAreaElement).value,
|
|
).toBe('artist-1\nartist-2');
|
|
expect(
|
|
(screen.getByLabelText('允许用户标签') as HTMLTextAreaElement).value,
|
|
).toBe('internal\ntrial');
|
|
expect(
|
|
(screen.getByLabelText('拒绝用户 ID') as HTMLTextAreaElement).value,
|
|
).toBe('');
|
|
});
|
|
|
|
test('灰度发布页选择新 target 时重置旧 gate 规则', async () => {
|
|
const user = userEvent.setup();
|
|
render(
|
|
<AdminGrayReleaseConfigPage token="admin-token" onUnauthorized={vi.fn()} />,
|
|
);
|
|
|
|
await user.click(
|
|
await screen.findByRole('button', { name: 'editor.new-toolbar' }),
|
|
);
|
|
await user.selectOptions(screen.getByLabelText('Gate Key 前缀'), [
|
|
'image-editor',
|
|
]);
|
|
|
|
expect((screen.getByLabelText('Gate Key') as HTMLInputElement).value).toBe(
|
|
'image-editor:agent-sidebar',
|
|
);
|
|
expect((screen.getByLabelText('启用') as HTMLInputElement).checked).toBe(
|
|
false,
|
|
);
|
|
expect((screen.getByLabelText('灰度比例') as HTMLInputElement).value).toBe(
|
|
'0',
|
|
);
|
|
expect(
|
|
(screen.getByLabelText('允许用户 ID') as HTMLTextAreaElement).value,
|
|
).toBe('');
|
|
expect(
|
|
(screen.getByLabelText('允许用户标签') as HTMLTextAreaElement).value,
|
|
).toBe('');
|
|
expect(
|
|
(screen.getByLabelText('拒绝用户 ID') as HTMLTextAreaElement).value,
|
|
).toBe('');
|
|
expect((screen.getByLabelText('描述') as HTMLTextAreaElement).value).toBe(
|
|
'画布 Agent 入口灰度',
|
|
);
|
|
});
|
|
|
|
test('灰度发布页可通过功能入口生成画布 Agent Gate Key', async () => {
|
|
const user = userEvent.setup();
|
|
render(
|
|
<AdminGrayReleaseConfigPage token="admin-token" onUnauthorized={vi.fn()} />,
|
|
);
|
|
|
|
await screen.findByRole('button', { name: 'editor.new-toolbar' });
|
|
await user.selectOptions(screen.getByLabelText('Gate Key 前缀'), [
|
|
'image-editor',
|
|
]);
|
|
|
|
expect((screen.getByLabelText('Gate Key') as HTMLInputElement).value).toBe(
|
|
'image-editor:agent-sidebar',
|
|
);
|
|
expect(
|
|
(screen.getByLabelText('Gate Key 目标') as HTMLSelectElement).value,
|
|
).toBe('agent-sidebar');
|
|
expect((screen.getByLabelText('描述') as HTMLTextAreaElement).value).toBe(
|
|
'画布 Agent 入口灰度',
|
|
);
|
|
});
|
|
|
|
test('灰度发布页保存时转换数组和百分比', async () => {
|
|
const user = userEvent.setup();
|
|
vi.mocked(upsertAdminFeatureGateConfig).mockResolvedValueOnce({
|
|
gates: [
|
|
...configResponse.gates,
|
|
{
|
|
gateKey: 'homepage.feed-redesign',
|
|
enabled: true,
|
|
rolloutPercent: 42,
|
|
allowUserIds: ['user-a', 'user-b'],
|
|
allowUserTags: ['beta', 'staff'],
|
|
denyUserIds: ['blocked-a'],
|
|
description: '首页信息流',
|
|
updatedAt: '2026-07-07T03:00:00Z',
|
|
},
|
|
],
|
|
});
|
|
render(
|
|
<AdminGrayReleaseConfigPage token="admin-token" onUnauthorized={vi.fn()} />,
|
|
);
|
|
|
|
await screen.findByRole('button', { name: 'editor.new-toolbar' });
|
|
fireEvent.change(screen.getByLabelText('Gate Key'), {
|
|
target: { value: 'homepage.feed-redesign' },
|
|
});
|
|
await user.click(screen.getByLabelText('启用'));
|
|
fireEvent.change(screen.getByLabelText('灰度比例'), {
|
|
target: { value: '42' },
|
|
});
|
|
fireEvent.change(screen.getByLabelText('允许用户 ID'), {
|
|
target: { value: ' user-a \n\n user-b ' },
|
|
});
|
|
fireEvent.change(screen.getByLabelText('允许用户标签'), {
|
|
target: { value: ' beta \n staff ' },
|
|
});
|
|
fireEvent.change(screen.getByLabelText('拒绝用户 ID'), {
|
|
target: { value: ' blocked-a \n ' },
|
|
});
|
|
fireEvent.change(screen.getByLabelText('描述'), {
|
|
target: { value: ' 首页信息流 ' },
|
|
});
|
|
await user.click(screen.getByRole('button', { name: '保存配置' }));
|
|
await user.click(screen.getByRole('button', { name: '确认' }));
|
|
|
|
await waitFor(() => {
|
|
expect(upsertAdminFeatureGateConfig).toHaveBeenCalledWith('admin-token', {
|
|
gateKey: 'homepage.feed-redesign',
|
|
enabled: true,
|
|
rolloutPercent: 42,
|
|
allowUserIds: ['user-a', 'user-b'],
|
|
allowUserTags: ['beta', 'staff'],
|
|
denyUserIds: ['blocked-a'],
|
|
description: '首页信息流',
|
|
});
|
|
});
|
|
});
|
|
|
|
test('灰度发布页无 token 时不请求配置', () => {
|
|
render(<AdminGrayReleaseConfigPage token="" onUnauthorized={vi.fn()} />);
|
|
|
|
expect(getAdminFeatureGateConfig).not.toHaveBeenCalled();
|
|
expect(upsertAdminFeatureGateConfig).not.toHaveBeenCalled();
|
|
});
|