c44527a890
- 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 个测试
264 lines
8.0 KiB
TypeScript
264 lines
8.0 KiB
TypeScript
/**
|
|
* 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');
|
|
}
|
|
});
|
|
});
|
|
});
|