/** * 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'); } }); }); });