Files
qiming/qiming-vite-plugin-design-mode/test/core/serverMiddleware.test.ts
2026-06-01 13:43:09 +08:00

297 lines
9.5 KiB
TypeScript

import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import * as fs from 'fs';
import * as path from 'path';
import { createServerMiddleware } from '../../packages/plugin/src/core/serverMiddleware';
import type { DesignModeOptions } from '../../packages/plugin/src/types';
// Mock fs 模块
vi.mock('fs', async () => {
const actualFs = await vi.importActual<typeof import('fs')>('fs');
const mockFs = {
...actualFs,
promises: {
...actualFs.promises,
access: vi.fn(),
readFile: vi.fn(),
writeFile: vi.fn(),
stat: vi.fn(),
},
};
return {
...mockFs,
default: mockFs,
};
});
describe('serverMiddleware', () => {
const mockOptions: Required<DesignModeOptions> = {
enabled: true,
enableInProduction: false,
attributePrefix: 'data-source',
verbose: false,
exclude: ['node_modules'],
include: ['**/*.{js,jsx,ts,tsx}'],
};
const mockRootDir = '/test/project';
let mockReq: any;
let mockRes: any;
beforeEach(() => {
// 重置 mock
vi.clearAllMocks();
// 创建模拟的请求对象
mockReq = {
url: '',
method: 'GET',
on: vi.fn(),
};
// 创建模拟的响应对象
mockRes = {
statusCode: 200,
headers: {},
end: vi.fn(),
setHeader: vi.fn(),
};
});
afterEach(() => {
vi.restoreAllMocks();
});
describe('createServerMiddleware', () => {
it('应该创建中间件函数', () => {
const middleware = createServerMiddleware(mockOptions, mockRootDir);
expect(typeof middleware).toBe('function');
});
it('应该处理健康检查请求', async () => {
mockReq.url = '/health';
const middleware = createServerMiddleware(mockOptions, mockRootDir);
await middleware(mockReq, mockRes);
expect(mockRes.statusCode).toBe(200);
expect(mockRes.end).toHaveBeenCalled();
const responseData = JSON.parse(mockRes.end.mock.calls[0][0]);
expect(responseData.status).toBe('ok');
expect(responseData.plugin).toBe('@xagi/vite-plugin-design-mode');
expect(responseData.timestamp).toBeDefined();
});
it('应该处理获取源码请求 - 成功', async () => {
const elementId = 'src/App.tsx:4:5_div_test';
mockReq.url = `/get-source?elementId=${encodeURIComponent(elementId)}`;
const mockFileContent = 'import React from "react";\n\nfunction App() {\n return <div>Test</div>;\n}';
vi.mocked(fs.promises.access).mockResolvedValue(undefined);
vi.mocked(fs.promises.stat).mockResolvedValue({ mtime: new Date() } as any);
vi.mocked(fs.promises.readFile).mockResolvedValue(mockFileContent);
const middleware = createServerMiddleware(mockOptions, mockRootDir);
await middleware(mockReq, mockRes);
expect(mockRes.statusCode).toBe(200);
expect(fs.promises.readFile).toHaveBeenCalled();
const responseData = JSON.parse(mockRes.end.mock.calls[0][0]);
expect(responseData.sourceInfo).toBeDefined();
expect(responseData.sourceInfo.fileName).toBe('src/App.tsx');
expect(responseData.sourceInfo.lineNumber).toBe(4);
expect(responseData.sourceInfo.columnNumber).toBe(5);
});
it('应该处理获取源码请求 - 缺少elementId参数', async () => {
mockReq.url = '/get-source';
const middleware = createServerMiddleware(mockOptions, mockRootDir);
await middleware(mockReq, mockRes);
expect(mockRes.statusCode).toBe(400);
const responseData = JSON.parse(mockRes.end.mock.calls[0][0]);
expect(responseData.error).toContain('Missing elementId');
});
it('应该处理获取源码请求 - 无效的elementId格式', async () => {
mockReq.url = '/get-source?elementId=invalid';
const middleware = createServerMiddleware(mockOptions, mockRootDir);
await middleware(mockReq, mockRes);
expect(mockRes.statusCode).toBe(400);
const responseData = JSON.parse(mockRes.end.mock.calls[0][0]);
expect(responseData.error).toContain('Invalid elementId');
});
it('应该处理修改源码请求 - 成功', async () => {
const elementId = 'src/App.tsx:4:5_div_test';
mockReq.url = '/modify-source';
mockReq.method = 'POST';
const mockFileContent = 'import React from "react";\n\nfunction App() {\n return <div className="old">Test</div>;\n}';
const mockUpdatedContent = 'import React from "react";\n\nfunction App() {\n return <div className="new">Test</div>;\n}';
vi.mocked(fs.promises.access).mockResolvedValue(undefined);
vi.mocked(fs.promises.readFile).mockResolvedValue(mockFileContent);
vi.mocked(fs.promises.writeFile).mockResolvedValue(undefined);
// 模拟请求体
const requestBody = JSON.stringify({
elementId,
newStyles: 'new',
oldStyles: 'old',
});
let dataCallback: (chunk: any) => void;
let endCallback: () => void;
mockReq.on = vi.fn((event: string, callback: any) => {
if (event === 'data') {
dataCallback = callback;
} else if (event === 'end') {
endCallback = callback;
}
});
const middleware = createServerMiddleware(mockOptions, mockRootDir);
const middlewarePromise = middleware(mockReq, mockRes);
// 模拟数据流
if (dataCallback!) {
dataCallback(Buffer.from(requestBody));
}
if (endCallback!) {
endCallback();
}
await middlewarePromise;
await new Promise(resolve => setTimeout(resolve, 0));
expect(mockRes.statusCode).toBe(200);
const responseData = JSON.parse(mockRes.end.mock.calls[0][0]);
expect(responseData.success).toBe(true);
});
it('应该处理修改源码请求 - 方法不允许', async () => {
mockReq.url = '/modify-source';
mockReq.method = 'GET';
const middleware = createServerMiddleware(mockOptions, mockRootDir);
await middleware(mockReq, mockRes);
expect(mockRes.statusCode).toBe(405);
const responseData = JSON.parse(mockRes.end.mock.calls[0][0]);
expect(responseData.error).toContain('Method not allowed');
});
it('应该处理修改源码请求 - 缺少必需参数', async () => {
mockReq.url = '/modify-source';
mockReq.method = 'POST';
const requestBody = JSON.stringify({});
let dataCallback: (chunk: any) => void;
let endCallback: () => void;
mockReq.on = vi.fn((event: string, callback: any) => {
if (event === 'data') {
dataCallback = callback;
} else if (event === 'end') {
endCallback = callback;
}
});
const middleware = createServerMiddleware(mockOptions, mockRootDir);
const middlewarePromise = middleware(mockReq, mockRes);
if (dataCallback!) {
dataCallback(Buffer.from(requestBody));
}
if (endCallback!) {
endCallback();
}
await middlewarePromise;
expect(mockRes.statusCode).toBe(400);
const responseData = JSON.parse(mockRes.end.mock.calls[0][0]);
expect(responseData.error).toContain('Missing required parameters');
});
it('应该处理404请求', async () => {
mockReq.url = '/unknown';
const middleware = createServerMiddleware(mockOptions, mockRootDir);
await middleware(mockReq, mockRes);
expect(mockRes.statusCode).toBe(404);
const responseData = JSON.parse(mockRes.end.mock.calls[0][0]);
expect(responseData.error).toBe('Not found');
});
it('应该处理文件读取错误', async () => {
const elementId = 'src/App.tsx:4:5_div_test';
mockReq.url = `/get-source?elementId=${encodeURIComponent(elementId)}`;
vi.mocked(fs.promises.access).mockResolvedValue(undefined);
vi.mocked(fs.promises.readFile).mockRejectedValue(new Error('File not found'));
const middleware = createServerMiddleware(mockOptions, mockRootDir);
await middleware(mockReq, mockRes);
expect(mockRes.statusCode).toBe(500);
const responseData = JSON.parse(mockRes.end.mock.calls[0][0]);
expect(responseData.error).toBe('File not found');
});
it('应该处理文件写入错误', async () => {
const elementId = 'src/App.tsx:4:5_div_test';
mockReq.url = '/modify-source';
mockReq.method = 'POST';
const mockFileContent = 'import React from "react";\n\nfunction App() {\n return <div>Test</div>;\n}';
vi.mocked(fs.promises.access).mockResolvedValue(undefined);
vi.mocked(fs.promises.readFile).mockResolvedValue(mockFileContent);
vi.mocked(fs.promises.writeFile).mockRejectedValue(new Error('Permission denied'));
const requestBody = JSON.stringify({
elementId,
newStyles: 'new',
});
let dataCallback: (chunk: any) => void;
let endCallback: () => void;
mockReq.on = vi.fn((event: string, callback: any) => {
if (event === 'data') {
dataCallback = callback;
} else if (event === 'end') {
endCallback = callback;
}
});
const middleware = createServerMiddleware(mockOptions, mockRootDir);
const middlewarePromise = middleware(mockReq, mockRes);
if (dataCallback!) {
dataCallback(Buffer.from(requestBody));
}
if (endCallback!) {
endCallback();
}
await middlewarePromise;
await new Promise(resolve => setTimeout(resolve, 0));
expect(mockRes.statusCode).toBe(500);
const responseData = JSON.parse(mockRes.end.mock.calls[0][0]);
expect(responseData.error).toBe('Permission denied');
});
});
});