test(server): 添加 routes 模块单元测试
- agents.test.ts: Agent presets CRUD API (18 tests) - tools.test.ts: Tool registration & execution (10 tests) - files.test.ts: File browser API with path security (24 tests) - commands.test.ts: Slash commands CRUD & execution (20 tests) - hooks.test.ts: Hooks configuration management (20 tests) 覆盖率从 29.59% 提升到 60.35%,共 287 个测试
This commit is contained in:
@@ -0,0 +1,263 @@
|
||||
/**
|
||||
* Files Route 测试
|
||||
*
|
||||
* 测试文件浏览器 REST API 端点
|
||||
*/
|
||||
|
||||
import { describe, it, expect, beforeEach, vi, afterEach } from 'vitest';
|
||||
import { Hono } from 'hono';
|
||||
import {
|
||||
filesRouter,
|
||||
setWorkingDirectory,
|
||||
getWorkingDirectory,
|
||||
} from '../../../src/routes/files.js';
|
||||
import { join } from 'node:path';
|
||||
|
||||
// Create test app
|
||||
const app = new Hono();
|
||||
app.route('/files', filesRouter);
|
||||
|
||||
// Save original working directory
|
||||
const originalWorkdir = process.cwd();
|
||||
|
||||
describe('Files Route', () => {
|
||||
beforeEach(() => {
|
||||
// Reset to current working directory
|
||||
setWorkingDirectory(originalWorkdir);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// Restore original working directory
|
||||
setWorkingDirectory(originalWorkdir);
|
||||
});
|
||||
|
||||
describe('setWorkingDirectory / getWorkingDirectory', () => {
|
||||
it('设置工作目录', () => {
|
||||
setWorkingDirectory('/tmp');
|
||||
expect(getWorkingDirectory()).toBe('/tmp');
|
||||
});
|
||||
|
||||
it('获取当前工作目录', () => {
|
||||
expect(getWorkingDirectory()).toBeDefined();
|
||||
expect(typeof getWorkingDirectory()).toBe('string');
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /files - 获取工作目录信息', () => {
|
||||
it('返回工作目录信息', async () => {
|
||||
const res = await app.request('/files');
|
||||
const json = await res.json();
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
expect(json.success).toBe(true);
|
||||
expect(json.data.workingDirectory).toBeDefined();
|
||||
expect(json.data.separator).toBe('/');
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /files/list - 列出目录内容', () => {
|
||||
it('列出当前目录', async () => {
|
||||
const res = await app.request('/files/list');
|
||||
const json = await res.json();
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
expect(json.success).toBe(true);
|
||||
expect(Array.isArray(json.data.files)).toBe(true);
|
||||
});
|
||||
|
||||
it('列出指定目录', async () => {
|
||||
const res = await app.request('/files/list?path=.');
|
||||
const json = await res.json();
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
expect(json.success).toBe(true);
|
||||
expect(json.data.path).toBe('.');
|
||||
});
|
||||
|
||||
it('包含隐藏文件', async () => {
|
||||
const res = await app.request('/files/list?path=.&hidden=true');
|
||||
const json = await res.json();
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
expect(json.success).toBe(true);
|
||||
});
|
||||
|
||||
it('路径在工作目录外返回 403', async () => {
|
||||
const res = await app.request('/files/list?path=../../../../../../etc');
|
||||
const json = await res.json();
|
||||
|
||||
expect(res.status).toBe(403);
|
||||
expect(json.success).toBe(false);
|
||||
expect(json.error).toContain('Access denied');
|
||||
});
|
||||
|
||||
it('非目录返回 400', async () => {
|
||||
// package.json 是一个文件,不是目录
|
||||
const res = await app.request('/files/list?path=package.json');
|
||||
const json = await res.json();
|
||||
|
||||
expect(res.status).toBe(400);
|
||||
expect(json.success).toBe(false);
|
||||
expect(json.error).toContain('Not a directory');
|
||||
});
|
||||
|
||||
it('文件信息包含正确字段', async () => {
|
||||
const res = await app.request('/files/list?path=.');
|
||||
const json = await res.json();
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
if (json.data.files.length > 0) {
|
||||
const file = json.data.files[0];
|
||||
expect(file).toHaveProperty('name');
|
||||
expect(file).toHaveProperty('path');
|
||||
expect(file).toHaveProperty('type');
|
||||
expect(file).toHaveProperty('size');
|
||||
expect(file).toHaveProperty('modified');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /files/read - 读取文件内容', () => {
|
||||
it('缺少路径参数返回 400', async () => {
|
||||
const res = await app.request('/files/read');
|
||||
const json = await res.json();
|
||||
|
||||
expect(res.status).toBe(400);
|
||||
expect(json.success).toBe(false);
|
||||
expect(json.error).toContain('Path is required');
|
||||
});
|
||||
|
||||
it('读取文本文件', async () => {
|
||||
const res = await app.request('/files/read?path=package.json');
|
||||
const json = await res.json();
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
expect(json.success).toBe(true);
|
||||
expect(json.data.encoding).toBe('utf-8');
|
||||
expect(json.data.content).toContain('"name"');
|
||||
});
|
||||
|
||||
it('路径在工作目录外返回 403', async () => {
|
||||
const res = await app.request('/files/read?path=../../../../../../etc/passwd');
|
||||
const json = await res.json();
|
||||
|
||||
expect(res.status).toBe(403);
|
||||
expect(json.success).toBe(false);
|
||||
});
|
||||
|
||||
it('读取目录返回 400', async () => {
|
||||
const res = await app.request('/files/read?path=src');
|
||||
const json = await res.json();
|
||||
|
||||
expect(res.status).toBe(400);
|
||||
expect(json.error).toContain('Cannot read directory');
|
||||
});
|
||||
|
||||
it('返回文件元信息', async () => {
|
||||
const res = await app.request('/files/read?path=package.json');
|
||||
const json = await res.json();
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
expect(json.data).toHaveProperty('path');
|
||||
expect(json.data).toHaveProperty('name');
|
||||
expect(json.data).toHaveProperty('type');
|
||||
expect(json.data).toHaveProperty('size');
|
||||
expect(json.data).toHaveProperty('modified');
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /files/stat - 获取文件信息', () => {
|
||||
it('缺少路径参数返回 400', async () => {
|
||||
const res = await app.request('/files/stat');
|
||||
const json = await res.json();
|
||||
|
||||
expect(res.status).toBe(400);
|
||||
expect(json.success).toBe(false);
|
||||
expect(json.error).toContain('Path is required');
|
||||
});
|
||||
|
||||
it('获取文件信息', async () => {
|
||||
const res = await app.request('/files/stat?path=package.json');
|
||||
const json = await res.json();
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
expect(json.success).toBe(true);
|
||||
expect(json.data.type).toBe('file');
|
||||
expect(json.data.name).toBe('package.json');
|
||||
});
|
||||
|
||||
it('获取目录信息', async () => {
|
||||
const res = await app.request('/files/stat?path=src');
|
||||
const json = await res.json();
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
expect(json.success).toBe(true);
|
||||
expect(json.data.type).toBe('directory');
|
||||
});
|
||||
|
||||
it('路径在工作目录外返回 403', async () => {
|
||||
const res = await app.request('/files/stat?path=../../../../../../etc/passwd');
|
||||
const json = await res.json();
|
||||
|
||||
expect(res.status).toBe(403);
|
||||
expect(json.success).toBe(false);
|
||||
});
|
||||
|
||||
it('文件不存在返回 500', async () => {
|
||||
const res = await app.request('/files/stat?path=non-existent-file-12345.txt');
|
||||
const json = await res.json();
|
||||
|
||||
expect(res.status).toBe(500);
|
||||
expect(json.success).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /files/tree - 获取目录树', () => {
|
||||
it('获取目录树', async () => {
|
||||
const res = await app.request('/files/tree?path=.&depth=1');
|
||||
const json = await res.json();
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
expect(json.success).toBe(true);
|
||||
expect(json.data.path).toBe('.');
|
||||
expect(Array.isArray(json.data.tree)).toBe(true);
|
||||
});
|
||||
|
||||
it('限制深度', async () => {
|
||||
const res = await app.request('/files/tree?path=.&depth=2');
|
||||
const json = await res.json();
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
expect(json.success).toBe(true);
|
||||
});
|
||||
|
||||
it('显示隐藏文件', async () => {
|
||||
const res = await app.request('/files/tree?path=.&depth=1&hidden=true');
|
||||
const json = await res.json();
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
expect(json.success).toBe(true);
|
||||
});
|
||||
|
||||
it('路径在工作目录外返回 403', async () => {
|
||||
const res = await app.request('/files/tree?path=../../../../../../etc');
|
||||
const json = await res.json();
|
||||
|
||||
expect(res.status).toBe(403);
|
||||
expect(json.success).toBe(false);
|
||||
});
|
||||
|
||||
it('树节点包含正确字段', async () => {
|
||||
const res = await app.request('/files/tree?path=.&depth=1');
|
||||
const json = await res.json();
|
||||
|
||||
expect(res.status).toBe(200);
|
||||
if (json.data.tree.length > 0) {
|
||||
const node = json.data.tree[0];
|
||||
expect(node).toHaveProperty('name');
|
||||
expect(node).toHaveProperty('path');
|
||||
expect(node).toHaveProperty('type');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user