WuYongJie ba98bf22cc feat: 添加许可证系统、Agent部署模块和Harbor镜像管理
- 新增许可证服务器 (license-server/)
- 新增 Agent 部署模块 (flnxi-agent/)
- 添加许可证验证和激活页面
- 添加 Harbor 镜像同步脚本
- 更新素材系统支持许可证验证
2025-12-15 21:19:46 +08:00

192 lines
6.4 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.

/**
* 许可证激活 API 路由测试
* 测试 app/api/license/activate/route.ts
*/
import { NextRequest } from 'next/server';
import { POST } from '../route';
// Mock lib/license 模块
jest.mock('@/lib/license', () => ({
activate: jest.fn(),
}));
import { activate } from '@/lib/license';
const mockActivate = activate as jest.MockedFunction<typeof activate>;
describe('POST /api/license/activate', () => {
beforeEach(() => {
jest.clearAllMocks();
});
describe('请求验证', () => {
it('缺少 licenseKey 时应返回 400 错误', async () => {
const request = new NextRequest('http://localhost/api/license/activate', {
method: 'POST',
body: JSON.stringify({}),
headers: { 'Content-Type': 'application/json' },
});
const response = await POST(request);
const data = await response.json();
expect(response.status).toBe(400);
expect(data.success).toBe(false);
expect(data.error).toBe('请输入许可证密钥');
});
it('licenseKey 为空字符串时应返回 400 错误', async () => {
const request = new NextRequest('http://localhost/api/license/activate', {
method: 'POST',
body: JSON.stringify({ licenseKey: '' }),
headers: { 'Content-Type': 'application/json' },
});
const response = await POST(request);
const data = await response.json();
expect(response.status).toBe(400);
expect(data.success).toBe(false);
});
});
describe('激活成功', () => {
it('激活成功时应返回 success: true', async () => {
mockActivate.mockResolvedValueOnce({ success: true });
const request = new NextRequest('http://localhost/api/license/activate', {
method: 'POST',
body: JSON.stringify({ licenseKey: 'ABCD-1234-EFGH-5678' }),
headers: { 'Content-Type': 'application/json' },
});
const response = await POST(request);
const data = await response.json();
expect(response.status).toBe(200);
expect(data.success).toBe(true);
expect(mockActivate).toHaveBeenCalledWith('ABCD-1234-EFGH-5678');
});
it('激活成功时应设置 license_activated cookie', async () => {
mockActivate.mockResolvedValueOnce({ success: true });
const request = new NextRequest('http://localhost/api/license/activate', {
method: 'POST',
body: JSON.stringify({ licenseKey: 'ABCD-1234-EFGH-5678' }),
headers: { 'Content-Type': 'application/json' },
});
const response = await POST(request);
const setCookieHeader = response.headers.get('set-cookie');
expect(setCookieHeader).toBeTruthy();
expect(setCookieHeader).toContain('license_activated=true');
expect(setCookieHeader).toContain('HttpOnly');
expect(setCookieHeader).toContain('Path=/');
// SameSite 值可能是 Lax 或 lax(大小写不敏感)
expect(setCookieHeader?.toLowerCase()).toContain('samesite=lax');
});
it('cookie 应设置 1 年有效期', async () => {
mockActivate.mockResolvedValueOnce({ success: true });
const request = new NextRequest('http://localhost/api/license/activate', {
method: 'POST',
body: JSON.stringify({ licenseKey: 'ABCD-1234-EFGH-5678' }),
headers: { 'Content-Type': 'application/json' },
});
const response = await POST(request);
const setCookieHeader = response.headers.get('set-cookie');
// 1年 = 365 * 24 * 60 * 60 = 31536000 秒
expect(setCookieHeader).toContain('Max-Age=31536000');
});
});
describe('激活失败', () => {
it('许可证无效时应返回 400 错误', async () => {
mockActivate.mockResolvedValueOnce({ success: false, error: '许可证无效' });
const request = new NextRequest('http://localhost/api/license/activate', {
method: 'POST',
body: JSON.stringify({ licenseKey: 'INVALID-KEY-1234' }),
headers: { 'Content-Type': 'application/json' },
});
const response = await POST(request);
const data = await response.json();
expect(response.status).toBe(400);
expect(data.success).toBe(false);
expect(data.error).toBe('许可证无效');
});
it('许可证已被使用时应返回相应错误', async () => {
mockActivate.mockResolvedValueOnce({ success: false, error: '许可证已被其他设备激活' });
const request = new NextRequest('http://localhost/api/license/activate', {
method: 'POST',
body: JSON.stringify({ licenseKey: 'USED-KEY-1234-5678' }),
headers: { 'Content-Type': 'application/json' },
});
const response = await POST(request);
const data = await response.json();
expect(response.status).toBe(400);
expect(data.success).toBe(false);
expect(data.error).toBe('许可证已被其他设备激活');
});
it('激活失败时不应设置 cookie', async () => {
mockActivate.mockResolvedValueOnce({ success: false, error: '许可证无效' });
const request = new NextRequest('http://localhost/api/license/activate', {
method: 'POST',
body: JSON.stringify({ licenseKey: 'INVALID-KEY-1234' }),
headers: { 'Content-Type': 'application/json' },
});
const response = await POST(request);
const setCookieHeader = response.headers.get('set-cookie');
expect(setCookieHeader).toBeNull();
});
});
describe('服务器错误处理', () => {
it('activate 函数抛出异常时应返回 500 错误', async () => {
mockActivate.mockRejectedValueOnce(new Error('数据库连接失败'));
const request = new NextRequest('http://localhost/api/license/activate', {
method: 'POST',
body: JSON.stringify({ licenseKey: 'ABCD-1234-EFGH-5678' }),
headers: { 'Content-Type': 'application/json' },
});
const response = await POST(request);
const data = await response.json();
expect(response.status).toBe(500);
expect(data.success).toBe(false);
expect(data.error).toBe('服务器错误');
});
it('JSON 解析错误时应返回 500 错误', async () => {
const request = new NextRequest('http://localhost/api/license/activate', {
method: 'POST',
body: 'invalid json',
headers: { 'Content-Type': 'application/json' },
});
const response = await POST(request);
const data = await response.json();
expect(response.status).toBe(500);
expect(data.success).toBe(false);
});
});
});