- 新增许可证服务器 (license-server/) - 新增 Agent 部署模块 (flnxi-agent/) - 添加许可证验证和激活页面 - 添加 Harbor 镜像同步脚本 - 更新素材系统支持许可证验证
192 lines
6.4 KiB
TypeScript
192 lines
6.4 KiB
TypeScript
/**
|
||
* 许可证激活 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);
|
||
});
|
||
});
|
||
});
|