flnxi-Wu/app/activate/__tests__/page.test.tsx
2026-05-28 21:49:16 +08:00

533 lines
18 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* @jest-environment jsdom
*/
/**
* 激活页面测试
* 测试许可证激活和临时授权申请功能
*/
import { render, screen, fireEvent, waitFor, act } from '@testing-library/react';
import ActivatePage from '../page';
// Mock next/navigation
const mockPush = jest.fn();
jest.mock('next/navigation', () => ({
useRouter: () => ({
push: mockPush,
}),
}));
// Mock fetch
global.fetch = jest.fn();
const createJsonResponse = (data: any, init?: { ok?: boolean; status?: number; statusText?: string }) => ({
ok: init?.ok ?? true,
status: init?.status ?? 200,
statusText: init?.statusText ?? 'OK',
json: () => Promise.resolve(data),
text: () => Promise.resolve(JSON.stringify(data)),
});
const mockFetchRoutes = (routes: Record<string, any>) => {
const routesWithDefaults: Record<string, any> = {
'/api/system-settings': createJsonResponse({
siteName: 'FLNXI素材库',
loginLogoUrl: '',
}),
...routes,
};
(global.fetch as jest.Mock).mockImplementation((input: RequestInfo | URL) => {
const url =
typeof input === 'string'
? input
: input instanceof URL
? input.toString()
: input.url;
const handler = routesWithDefaults[url] ?? routesWithDefaults['*'];
if (handler instanceof Error) {
return Promise.reject(handler);
}
if (!handler) {
throw new Error(`Unhandled fetch request: ${url}`);
}
return Promise.resolve(typeof handler === 'function' ? handler() : handler);
});
};
const renderPage = async () => {
await act(async () => {
render(<ActivatePage />);
});
await screen.findByText('FLNXI素材库');
};
describe('ActivatePage', () => {
beforeEach(() => {
jest.clearAllMocks();
(global.fetch as jest.Mock).mockReset();
delete process.env.NEXT_PUBLIC_ENABLE_FREE_TRIAL_ACTIVATION;
mockFetchRoutes({
'/api/license/check': createJsonResponse({ activated: false }),
'*': createJsonResponse({ success: true }),
});
});
describe('渲染测试', () => {
it('应该正确渲染页面标题和表单', async () => {
await renderPage();
expect(screen.getByText('FLNXI素材库')).toBeInTheDocument();
expect(screen.getByText('请输入交付给您的许可证密钥')).toBeInTheDocument();
expect(screen.getByPlaceholderText('XXXX-XXXX-XXXX-XXXX')).toBeInTheDocument();
expect(screen.getByText('激活许可证')).toBeInTheDocument();
expect(screen.queryByRole('heading', { name: '申请临时授权' })).not.toBeInTheDocument();
});
it('默认不显示临时授权说明', async () => {
await renderPage();
expect(screen.queryByText('临时授权会按当前授权服务规则生成受限许可证')).not.toBeInTheDocument();
});
it('应该显示联系信息', async () => {
await renderPage();
expect(screen.getByText('如需许可证,请联系系统交付人员或管理员')).toBeInTheDocument();
});
});
describe('许可证密钥格式化', () => {
it('应该自动将输入转换为大写', async () => {
await renderPage();
const input = screen.getByPlaceholderText('XXXX-XXXX-XXXX-XXXX') as HTMLInputElement;
fireEvent.change(input, { target: { value: 'abcd' } });
expect(input.value).toBe('ABCD');
});
it('应该自动添加横杠分隔符', async () => {
await renderPage();
const input = screen.getByPlaceholderText('XXXX-XXXX-XXXX-XXXX') as HTMLInputElement;
fireEvent.change(input, { target: { value: 'abcd1234' } });
expect(input.value).toBe('ABCD-1234');
});
it('应该过滤非字母数字字符', async () => {
await renderPage();
const input = screen.getByPlaceholderText('XXXX-XXXX-XXXX-XXXX') as HTMLInputElement;
fireEvent.change(input, { target: { value: 'ab!@#cd12' } });
expect(input.value).toBe('ABCD-12');
});
it('应该限制最大长度为19个字符(含横杠)', async () => {
await renderPage();
const input = screen.getByPlaceholderText('XXXX-XXXX-XXXX-XXXX') as HTMLInputElement;
fireEvent.change(input, { target: { value: 'ABCD1234EFGH5678EXTRA' } });
expect(input.value).toBe('ABCD-1234-EFGH-5678');
});
});
describe('激活按钮状态', () => {
it('密钥不完整时激活按钮应该禁用', async () => {
await renderPage();
const button = screen.getByText('激活许可证');
expect(button).toBeDisabled();
});
it('密钥完整时激活按钮应该启用', async () => {
await renderPage();
const input = screen.getByPlaceholderText('XXXX-XXXX-XXXX-XXXX');
fireEvent.change(input, { target: { value: 'ABCD1234EFGH5678' } });
const button = screen.getByText('激活许可证');
expect(button).not.toBeDisabled();
});
});
describe('许可证激活', () => {
it('激活成功后应该显示成功页面', async () => {
mockFetchRoutes({
'/api/license/check': createJsonResponse({ activated: false }),
'/api/license/activate': createJsonResponse({
success: true,
initialAdmin: {
email: 'admin@local.flnxi',
username: 'admin',
password: 'Generated-Password-123',
mustChangePassword: true,
},
}),
});
await renderPage();
const input = screen.getByPlaceholderText('XXXX-XXXX-XXXX-XXXX');
fireEvent.change(input, { target: { value: 'ABCD1234EFGH5678' } });
const button = screen.getByText('激活许可证');
await act(async () => {
fireEvent.click(button);
});
await waitFor(() => {
expect(screen.getByText('激活成功')).toBeInTheDocument();
expect(screen.getByText('系统已准备就绪')).toBeInTheDocument();
expect(screen.getByText('初始管理员账号')).toBeInTheDocument();
expect(screen.getByText('admin@local.flnxi')).toBeInTheDocument();
expect(screen.getByText('Generated-Password-123')).toBeInTheDocument();
expect(screen.getByText('前往登录')).toBeInTheDocument();
});
});
it('点击前往登录按钮应该跳转到登录页', async () => {
mockFetchRoutes({
'/api/license/check': createJsonResponse({ activated: false }),
'/api/license/activate': createJsonResponse({
success: true,
initialAdmin: {
email: 'admin@local.flnxi',
username: 'admin',
password: 'Generated-Password-123',
mustChangePassword: true,
},
}),
});
await renderPage();
const input = screen.getByPlaceholderText('XXXX-XXXX-XXXX-XXXX');
fireEvent.change(input, { target: { value: 'ABCD1234EFGH5678' } });
const activateButton = screen.getByText('激活许可证');
await act(async () => {
fireEvent.click(activateButton);
});
await waitFor(() => {
expect(screen.getByText('前往登录')).toBeInTheDocument();
});
const loginButton = screen.getByText('前往登录');
await act(async () => {
fireEvent.click(loginButton);
});
expect(mockPush).toHaveBeenCalledWith('/login');
});
it('激活失败应该显示错误信息', async () => {
mockFetchRoutes({
'/api/license/check': createJsonResponse({ activated: false }),
'/api/license/activate': createJsonResponse({ success: false, error: '许可证无效' }),
});
await renderPage();
const input = screen.getByPlaceholderText('XXXX-XXXX-XXXX-XXXX');
fireEvent.change(input, { target: { value: 'ABCD1234EFGH5678' } });
const button = screen.getByText('激活许可证');
await act(async () => {
fireEvent.click(button);
});
await waitFor(() => {
expect(screen.getByText('许可证无效')).toBeInTheDocument();
});
});
it('网络错误应该显示网络错误提示', async () => {
mockFetchRoutes({
'/api/license/check': createJsonResponse({ activated: false }),
'/api/license/activate': new Error('Network error'),
});
await renderPage();
const input = screen.getByPlaceholderText('XXXX-XXXX-XXXX-XXXX');
fireEvent.change(input, { target: { value: 'ABCD1234EFGH5678' } });
const button = screen.getByText('激活许可证');
await act(async () => {
fireEvent.click(button);
});
await waitFor(() => {
expect(screen.getByText('网络错误,请检查网络连接')).toBeInTheDocument();
});
});
it('激活时应该显示加载状态', async () => {
let resolvePromise: (value: unknown) => void;
mockFetchRoutes({
'/api/license/check': createJsonResponse({ activated: false }),
'/api/license/activate': () =>
new Promise((resolve) => {
resolvePromise = resolve;
}),
});
await renderPage();
const input = screen.getByPlaceholderText('XXXX-XXXX-XXXX-XXXX');
fireEvent.change(input, { target: { value: 'ABCD1234EFGH5678' } });
const button = screen.getByText('激活许可证');
await act(async () => {
fireEvent.click(button);
});
expect(screen.getByText('激活中...')).toBeInTheDocument();
// 清理
await act(async () => {
resolvePromise!(createJsonResponse({
success: true,
initialAdmin: {
email: 'admin@local.flnxi',
username: 'admin',
password: 'Generated-Password-123',
mustChangePassword: true,
},
}));
});
});
});
describe('临时授权申请(显式开关开启时)', () => {
beforeEach(() => {
process.env.NEXT_PUBLIC_ENABLE_FREE_TRIAL_ACTIVATION = 'true';
});
it('点击临时授权按钮应该显示交付单位输入弹窗', async () => {
await renderPage();
const button = screen.getByText('申请临时授权');
await act(async () => {
fireEvent.click(button);
});
expect(screen.getByRole('heading', { name: '申请临时授权' })).toBeInTheDocument();
expect(screen.getByPlaceholderText('请输入单位或团队名称')).toBeInTheDocument();
expect(screen.getByText('确认申请')).toBeInTheDocument();
expect(screen.getByText('取消')).toBeInTheDocument();
});
it('未输入客户名称时确认申请按钮应该禁用', async () => {
await renderPage();
const freeTrialButton = screen.getByText('申请临时授权');
await act(async () => {
fireEvent.click(freeTrialButton);
});
const confirmButton = screen.getByText('确认申请');
expect(confirmButton).toBeDisabled();
});
it('只输入客户名称未输入联系方式时确认申请按钮应该禁用', async () => {
await renderPage();
const freeTrialButton = screen.getByText('申请临时授权');
await act(async () => {
fireEvent.click(freeTrialButton);
});
const nameInput = screen.getByPlaceholderText('请输入单位或团队名称');
fireEvent.change(nameInput, { target: { value: '测试公司' } });
const confirmButton = screen.getByText('确认申请');
expect(confirmButton).toBeDisabled();
});
it('输入客户名称和联系方式后确认申请按钮应该启用', async () => {
await renderPage();
const freeTrialButton = screen.getByText('申请临时授权');
await act(async () => {
fireEvent.click(freeTrialButton);
});
const nameInput = screen.getByPlaceholderText('请输入单位或团队名称');
const contactInput = screen.getByPlaceholderText('手机号/微信/邮箱');
fireEvent.change(nameInput, { target: { value: '测试公司' } });
fireEvent.change(contactInput, { target: { value: '13800138000' } });
const confirmButton = screen.getByText('确认申请');
expect(confirmButton).not.toBeDisabled();
});
it('点击取消按钮应该关闭弹窗', async () => {
await renderPage();
const freeTrialButton = screen.getByText('申请临时授权');
await act(async () => {
fireEvent.click(freeTrialButton);
});
expect(screen.getByRole('heading', { name: '申请临时授权' })).toBeInTheDocument();
const cancelButton = screen.getByText('取消');
await act(async () => {
fireEvent.click(cancelButton);
});
expect(screen.queryByRole('heading', { name: '申请临时授权' })).not.toBeInTheDocument();
});
it('临时授权申请成功后应该显示成功页面', async () => {
mockFetchRoutes({
'/api/license/check': createJsonResponse({ activated: false }),
'/api/license/activate-free': createJsonResponse({
success: true,
initialAdmin: {
email: 'admin@local.flnxi',
username: 'admin',
password: 'Generated-Password-123',
mustChangePassword: true,
},
}),
});
await renderPage();
// 点击临时授权按钮打开弹窗
const freeTrialButton = screen.getByText('申请临时授权');
await act(async () => {
fireEvent.click(freeTrialButton);
});
// 输入客户名称和联系方式
const nameInput = screen.getByPlaceholderText('请输入单位或团队名称');
const contactInput = screen.getByPlaceholderText('手机号/微信/邮箱');
fireEvent.change(nameInput, { target: { value: '测试公司' } });
fireEvent.change(contactInput, { target: { value: '13800138000' } });
// 点击确认申请
const confirmButton = screen.getByText('确认申请');
await act(async () => {
fireEvent.click(confirmButton);
});
await waitFor(() => {
expect(screen.getByText('激活成功')).toBeInTheDocument();
expect(screen.getByText('前往登录')).toBeInTheDocument();
});
expect(global.fetch).toHaveBeenCalledWith('/api/license/activate-free', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ customerName: '测试公司', contactInfo: '13800138000' }),
});
});
it('临时授权申请失败应该显示错误信息', async () => {
mockFetchRoutes({
'/api/license/check': createJsonResponse({ activated: false }),
'/api/license/activate-free': createJsonResponse({ success: false, error: '临时授权已过期' }),
});
await renderPage();
// 点击临时授权按钮打开弹窗
const freeTrialButton = screen.getByText('申请临时授权');
await act(async () => {
fireEvent.click(freeTrialButton);
});
// 输入客户名称和联系方式
const nameInput = screen.getByPlaceholderText('请输入单位或团队名称');
const contactInput = screen.getByPlaceholderText('手机号/微信/邮箱');
fireEvent.change(nameInput, { target: { value: '测试公司' } });
fireEvent.change(contactInput, { target: { value: '13800138000' } });
// 点击确认申请
const confirmButton = screen.getByText('确认申请');
await act(async () => {
fireEvent.click(confirmButton);
});
await waitFor(() => {
expect(screen.getByText('临时授权已过期')).toBeInTheDocument();
});
});
it('网络错误应该显示网络错误提示', async () => {
mockFetchRoutes({
'/api/license/check': createJsonResponse({ activated: false }),
'/api/license/activate-free': new Error('Network error'),
});
await renderPage();
// 点击临时授权按钮打开弹窗
const freeTrialButton = screen.getByText('申请临时授权');
await act(async () => {
fireEvent.click(freeTrialButton);
});
// 输入客户名称和联系方式
const nameInput = screen.getByPlaceholderText('请输入单位或团队名称');
const contactInput = screen.getByPlaceholderText('手机号/微信/邮箱');
fireEvent.change(nameInput, { target: { value: '测试公司' } });
fireEvent.change(contactInput, { target: { value: '13800138000' } });
// 点击确认申请
const confirmButton = screen.getByText('确认申请');
await act(async () => {
fireEvent.click(confirmButton);
});
await waitFor(() => {
expect(screen.getByText('网络错误,请检查网络连接')).toBeInTheDocument();
});
});
it('应该显示联系方式输入框', async () => {
await renderPage();
const freeTrialButton = screen.getByText('申请临时授权');
await act(async () => {
fireEvent.click(freeTrialButton);
});
expect(screen.getByPlaceholderText('手机号/微信/邮箱')).toBeInTheDocument();
expect(screen.getByText('联系方式')).toBeInTheDocument();
});
});
describe('API 调用', () => {
it('激活时应该发送正确的请求', async () => {
mockFetchRoutes({
'/api/license/check': createJsonResponse({ activated: false }),
'/api/license/activate': createJsonResponse({
success: true,
initialAdmin: {
email: 'admin@local.flnxi',
username: 'admin',
password: 'Generated-Password-123',
mustChangePassword: true,
},
}),
});
await renderPage();
const input = screen.getByPlaceholderText('XXXX-XXXX-XXXX-XXXX');
fireEvent.change(input, { target: { value: 'ABCD1234EFGH5678' } });
const button = screen.getByText('激活许可证');
await act(async () => {
fireEvent.click(button);
});
await waitFor(() => {
expect(global.fetch).toHaveBeenCalledWith('/api/license/activate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ licenseKey: 'ABCD-1234-EFGH-5678' }),
});
});
});
});
});