feat: Add testing framework and initial test cases for various tools and database operations
This commit is contained in:
@@ -7,7 +7,10 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "bun run src/index.ts",
|
"dev": "bun run src/index.ts",
|
||||||
"build": "bun build src/index.ts --outdir dist --target bun",
|
"build": "bun build src/index.ts --outdir dist --target bun",
|
||||||
"start": "bun run dist/index.js"
|
"start": "bun run dist/index.js",
|
||||||
|
"test": "bun test",
|
||||||
|
"test:watch": "bun test --watch",
|
||||||
|
"test:coverage": "bun test --coverage"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
"mcp",
|
"mcp",
|
||||||
|
|||||||
@@ -19,6 +19,11 @@ class MCPServer {
|
|||||||
private server: Server;
|
private server: Server;
|
||||||
private tools: Map<string, { tool: Tool; handler: ToolHandler }> = new Map();
|
private tools: Map<string, { tool: Tool; handler: ToolHandler }> = new Map();
|
||||||
|
|
||||||
|
// Expose tools for testing
|
||||||
|
getTools(): Map<string, { tool: Tool; handler: ToolHandler }> {
|
||||||
|
return this.tools;
|
||||||
|
}
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.server = new Server(
|
this.server = new Server(
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,10 +3,18 @@
|
|||||||
* Uses JSON file storage for simplicity
|
* Uses JSON file storage for simplicity
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
||||||
import { join } from 'path';
|
import { join } from "path";
|
||||||
|
|
||||||
const DATA_DIR = join(process.cwd(), 'data');
|
// Use environment variable for test data directory, or default to project data directory
|
||||||
|
const getDataDir = () => {
|
||||||
|
if (process.env.MCP_TEST_DATA_DIR) {
|
||||||
|
return process.env.MCP_TEST_DATA_DIR;
|
||||||
|
}
|
||||||
|
return join(process.cwd(), "data");
|
||||||
|
};
|
||||||
|
|
||||||
|
const DATA_DIR = getDataDir();
|
||||||
|
|
||||||
export interface CodeSnippet {
|
export interface CodeSnippet {
|
||||||
id: string;
|
id: string;
|
||||||
@@ -64,15 +72,20 @@ export interface GameWishlist {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class Database {
|
class Database {
|
||||||
|
private getDataDir(): string {
|
||||||
|
return getDataDir();
|
||||||
|
}
|
||||||
|
|
||||||
private ensureDataDir(): void {
|
private ensureDataDir(): void {
|
||||||
if (!existsSync(DATA_DIR)) {
|
const dataDir = this.getDataDir();
|
||||||
mkdirSync(DATA_DIR, { recursive: true });
|
if (!existsSync(dataDir)) {
|
||||||
|
mkdirSync(dataDir, { recursive: true });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private getFilePath(collection: string): string {
|
private getFilePath(collection: string): string {
|
||||||
this.ensureDataDir();
|
this.ensureDataDir();
|
||||||
return join(DATA_DIR, `${collection}.json`);
|
return join(this.getDataDir(), `${collection}.json`);
|
||||||
}
|
}
|
||||||
|
|
||||||
private readCollection<T>(collection: string): T[] {
|
private readCollection<T>(collection: string): T[] {
|
||||||
@@ -81,7 +94,7 @@ class Database {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const content = readFileSync(filePath, 'utf-8');
|
const content = readFileSync(filePath, "utf-8");
|
||||||
return JSON.parse(content);
|
return JSON.parse(content);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error reading ${collection}:`, error);
|
console.error(`Error reading ${collection}:`, error);
|
||||||
@@ -92,7 +105,7 @@ class Database {
|
|||||||
private writeCollection<T>(collection: string, data: T[]): void {
|
private writeCollection<T>(collection: string, data: T[]): void {
|
||||||
const filePath = this.getFilePath(collection);
|
const filePath = this.getFilePath(collection);
|
||||||
try {
|
try {
|
||||||
writeFileSync(filePath, JSON.stringify(data, null, 2), 'utf-8');
|
writeFileSync(filePath, JSON.stringify(data, null, 2), "utf-8");
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Error writing ${collection}:`, error);
|
console.error(`Error writing ${collection}:`, error);
|
||||||
throw error;
|
throw error;
|
||||||
@@ -101,37 +114,37 @@ class Database {
|
|||||||
|
|
||||||
// Code Snippets
|
// Code Snippets
|
||||||
saveCodeSnippet(snippet: CodeSnippet): void {
|
saveCodeSnippet(snippet: CodeSnippet): void {
|
||||||
const snippets = this.readCollection<CodeSnippet>('codeSnippets');
|
const snippets = this.readCollection<CodeSnippet>("codeSnippets");
|
||||||
const index = snippets.findIndex((s) => s.id === snippet.id);
|
const index = snippets.findIndex((s) => s.id === snippet.id);
|
||||||
if (index >= 0) {
|
if (index >= 0) {
|
||||||
snippets[index] = { ...snippet, updatedAt: new Date().toISOString() };
|
snippets[index] = { ...snippet, updatedAt: new Date().toISOString() };
|
||||||
} else {
|
} else {
|
||||||
snippets.push(snippet);
|
snippets.push(snippet);
|
||||||
}
|
}
|
||||||
this.writeCollection('codeSnippets', snippets);
|
this.writeCollection("codeSnippets", snippets);
|
||||||
}
|
}
|
||||||
|
|
||||||
getCodeSnippets(): CodeSnippet[] {
|
getCodeSnippets(): CodeSnippet[] {
|
||||||
return this.readCollection<CodeSnippet>('codeSnippets');
|
return this.readCollection<CodeSnippet>("codeSnippets");
|
||||||
}
|
}
|
||||||
|
|
||||||
getCodeSnippet(id: string): CodeSnippet | undefined {
|
getCodeSnippet(id: string): CodeSnippet | undefined {
|
||||||
const snippets = this.readCollection<CodeSnippet>('codeSnippets');
|
const snippets = this.readCollection<CodeSnippet>("codeSnippets");
|
||||||
return snippets.find((s) => s.id === id);
|
return snippets.find((s) => s.id === id);
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteCodeSnippet(id: string): boolean {
|
deleteCodeSnippet(id: string): boolean {
|
||||||
const snippets = this.readCollection<CodeSnippet>('codeSnippets');
|
const snippets = this.readCollection<CodeSnippet>("codeSnippets");
|
||||||
const filtered = snippets.filter((s) => s.id !== id);
|
const filtered = snippets.filter((s) => s.id !== id);
|
||||||
if (filtered.length < snippets.length) {
|
if (filtered.length < snippets.length) {
|
||||||
this.writeCollection('codeSnippets', filtered);
|
this.writeCollection("codeSnippets", filtered);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
searchCodeSnippets(query: string, tags?: string[]): CodeSnippet[] {
|
searchCodeSnippets(query: string, tags?: string[]): CodeSnippet[] {
|
||||||
const snippets = this.readCollection<CodeSnippet>('codeSnippets');
|
const snippets = this.readCollection<CodeSnippet>("codeSnippets");
|
||||||
const lowerQuery = query.toLowerCase();
|
const lowerQuery = query.toLowerCase();
|
||||||
return snippets.filter((s) => {
|
return snippets.filter((s) => {
|
||||||
const matchesQuery =
|
const matchesQuery =
|
||||||
@@ -146,27 +159,27 @@ class Database {
|
|||||||
|
|
||||||
// Notes
|
// Notes
|
||||||
saveNote(note: Note): void {
|
saveNote(note: Note): void {
|
||||||
const notes = this.readCollection<Note>('notes');
|
const notes = this.readCollection<Note>("notes");
|
||||||
const index = notes.findIndex((n) => n.id === note.id);
|
const index = notes.findIndex((n) => n.id === note.id);
|
||||||
if (index >= 0) {
|
if (index >= 0) {
|
||||||
notes[index] = { ...note, updatedAt: new Date().toISOString() };
|
notes[index] = { ...note, updatedAt: new Date().toISOString() };
|
||||||
} else {
|
} else {
|
||||||
notes.push(note);
|
notes.push(note);
|
||||||
}
|
}
|
||||||
this.writeCollection('notes', notes);
|
this.writeCollection("notes", notes);
|
||||||
}
|
}
|
||||||
|
|
||||||
getNotes(): Note[] {
|
getNotes(): Note[] {
|
||||||
return this.readCollection<Note>('notes');
|
return this.readCollection<Note>("notes");
|
||||||
}
|
}
|
||||||
|
|
||||||
getNote(id: string): Note | undefined {
|
getNote(id: string): Note | undefined {
|
||||||
const notes = this.readCollection<Note>('notes');
|
const notes = this.readCollection<Note>("notes");
|
||||||
return notes.find((n) => n.id === id);
|
return notes.find((n) => n.id === id);
|
||||||
}
|
}
|
||||||
|
|
||||||
searchNotes(query: string): Note[] {
|
searchNotes(query: string): Note[] {
|
||||||
const notes = this.readCollection<Note>('notes');
|
const notes = this.readCollection<Note>("notes");
|
||||||
const lowerQuery = query.toLowerCase();
|
const lowerQuery = query.toLowerCase();
|
||||||
return notes.filter(
|
return notes.filter(
|
||||||
(n) =>
|
(n) =>
|
||||||
@@ -177,10 +190,10 @@ class Database {
|
|||||||
}
|
}
|
||||||
|
|
||||||
deleteNote(id: string): boolean {
|
deleteNote(id: string): boolean {
|
||||||
const notes = this.readCollection<Note>('notes');
|
const notes = this.readCollection<Note>("notes");
|
||||||
const filtered = notes.filter((n) => n.id !== id);
|
const filtered = notes.filter((n) => n.id !== id);
|
||||||
if (filtered.length < notes.length) {
|
if (filtered.length < notes.length) {
|
||||||
this.writeCollection('notes', filtered);
|
this.writeCollection("notes", filtered);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -188,18 +201,18 @@ class Database {
|
|||||||
|
|
||||||
// Tasks
|
// Tasks
|
||||||
saveTask(task: Task): void {
|
saveTask(task: Task): void {
|
||||||
const tasks = this.readCollection<Task>('tasks');
|
const tasks = this.readCollection<Task>("tasks");
|
||||||
const index = tasks.findIndex((t) => t.id === task.id);
|
const index = tasks.findIndex((t) => t.id === task.id);
|
||||||
if (index >= 0) {
|
if (index >= 0) {
|
||||||
tasks[index] = task;
|
tasks[index] = task;
|
||||||
} else {
|
} else {
|
||||||
tasks.push(task);
|
tasks.push(task);
|
||||||
}
|
}
|
||||||
this.writeCollection('tasks', tasks);
|
this.writeCollection("tasks", tasks);
|
||||||
}
|
}
|
||||||
|
|
||||||
getTasks(completed?: boolean): Task[] {
|
getTasks(completed?: boolean): Task[] {
|
||||||
const tasks = this.readCollection<Task>('tasks');
|
const tasks = this.readCollection<Task>("tasks");
|
||||||
if (completed === undefined) {
|
if (completed === undefined) {
|
||||||
return tasks;
|
return tasks;
|
||||||
}
|
}
|
||||||
@@ -207,39 +220,39 @@ class Database {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getTask(id: string): Task | undefined {
|
getTask(id: string): Task | undefined {
|
||||||
const tasks = this.readCollection<Task>('tasks');
|
const tasks = this.readCollection<Task>("tasks");
|
||||||
return tasks.find((t) => t.id === id);
|
return tasks.find((t) => t.id === id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Baby Milestones
|
// Baby Milestones
|
||||||
saveBabyMilestone(milestone: BabyMilestone): void {
|
saveBabyMilestone(milestone: BabyMilestone): void {
|
||||||
const milestones = this.readCollection<BabyMilestone>('babyMilestones');
|
const milestones = this.readCollection<BabyMilestone>("babyMilestones");
|
||||||
milestones.push(milestone);
|
milestones.push(milestone);
|
||||||
this.writeCollection('babyMilestones', milestones);
|
this.writeCollection("babyMilestones", milestones);
|
||||||
}
|
}
|
||||||
|
|
||||||
getBabyMilestones(): BabyMilestone[] {
|
getBabyMilestones(): BabyMilestone[] {
|
||||||
return this.readCollection<BabyMilestone>('babyMilestones');
|
return this.readCollection<BabyMilestone>("babyMilestones");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Math Resources
|
// Math Resources
|
||||||
saveMathResource(resource: MathResource): void {
|
saveMathResource(resource: MathResource): void {
|
||||||
const resources = this.readCollection<MathResource>('mathResources');
|
const resources = this.readCollection<MathResource>("mathResources");
|
||||||
const index = resources.findIndex((r) => r.id === resource.id);
|
const index = resources.findIndex((r) => r.id === resource.id);
|
||||||
if (index >= 0) {
|
if (index >= 0) {
|
||||||
resources[index] = resource;
|
resources[index] = resource;
|
||||||
} else {
|
} else {
|
||||||
resources.push(resource);
|
resources.push(resource);
|
||||||
}
|
}
|
||||||
this.writeCollection('mathResources', resources);
|
this.writeCollection("mathResources", resources);
|
||||||
}
|
}
|
||||||
|
|
||||||
getMathResources(): MathResource[] {
|
getMathResources(): MathResource[] {
|
||||||
return this.readCollection<MathResource>('mathResources');
|
return this.readCollection<MathResource>("mathResources");
|
||||||
}
|
}
|
||||||
|
|
||||||
searchMathResources(query: string, grade?: string): MathResource[] {
|
searchMathResources(query: string, grade?: string): MathResource[] {
|
||||||
const resources = this.readCollection<MathResource>('mathResources');
|
const resources = this.readCollection<MathResource>("mathResources");
|
||||||
const lowerQuery = query.toLowerCase();
|
const lowerQuery = query.toLowerCase();
|
||||||
return resources.filter((r) => {
|
return resources.filter((r) => {
|
||||||
const matchesQuery =
|
const matchesQuery =
|
||||||
@@ -253,25 +266,25 @@ class Database {
|
|||||||
|
|
||||||
// Game Wishlist
|
// Game Wishlist
|
||||||
saveGameWishlist(game: GameWishlist): void {
|
saveGameWishlist(game: GameWishlist): void {
|
||||||
const games = this.readCollection<GameWishlist>('gameWishlist');
|
const games = this.readCollection<GameWishlist>("gameWishlist");
|
||||||
const index = games.findIndex((g) => g.id === game.id);
|
const index = games.findIndex((g) => g.id === game.id);
|
||||||
if (index >= 0) {
|
if (index >= 0) {
|
||||||
games[index] = game;
|
games[index] = game;
|
||||||
} else {
|
} else {
|
||||||
games.push(game);
|
games.push(game);
|
||||||
}
|
}
|
||||||
this.writeCollection('gameWishlist', games);
|
this.writeCollection("gameWishlist", games);
|
||||||
}
|
}
|
||||||
|
|
||||||
getGameWishlist(): GameWishlist[] {
|
getGameWishlist(): GameWishlist[] {
|
||||||
return this.readCollection<GameWishlist>('gameWishlist');
|
return this.readCollection<GameWishlist>("gameWishlist");
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteGameWishlist(id: string): boolean {
|
deleteGameWishlist(id: string): boolean {
|
||||||
const games = this.readCollection<GameWishlist>('gameWishlist');
|
const games = this.readCollection<GameWishlist>("gameWishlist");
|
||||||
const filtered = games.filter((g) => g.id !== id);
|
const filtered = games.filter((g) => g.id !== id);
|
||||||
if (filtered.length < games.length) {
|
if (filtered.length < games.length) {
|
||||||
this.writeCollection('gameWishlist', filtered);
|
this.writeCollection("gameWishlist", filtered);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@@ -279,4 +292,3 @@ class Database {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const database = new Database();
|
export const database = new Database();
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,9 @@
|
|||||||
|
/*
|
||||||
|
* @Date: 2026-01-06 15:03:24
|
||||||
|
* @LastEditors: 陈子健
|
||||||
|
* @LastEditTime: 2026-01-07 10:04:47
|
||||||
|
* @FilePath: /cloud-mcp/src/tools/programming/projectTemplate.ts
|
||||||
|
*/
|
||||||
/**
|
/**
|
||||||
* Project template generation tools
|
* Project template generation tools
|
||||||
*/
|
*/
|
||||||
@@ -323,6 +329,7 @@ bun run build
|
|||||||
try {
|
try {
|
||||||
mkdirSync(projectPath, { recursive: true });
|
mkdirSync(projectPath, { recursive: true });
|
||||||
mkdirSync(join(projectPath, "frontend"), { recursive: true });
|
mkdirSync(join(projectPath, "frontend"), { recursive: true });
|
||||||
|
mkdirSync(join(projectPath, "frontend", "src"), { recursive: true });
|
||||||
mkdirSync(join(projectPath, "backend"), { recursive: true });
|
mkdirSync(join(projectPath, "backend"), { recursive: true });
|
||||||
mkdirSync(join(projectPath, "backend", "src"), { recursive: true });
|
mkdirSync(join(projectPath, "backend", "src"), { recursive: true });
|
||||||
|
|
||||||
|
|||||||
173
tests/README.md
Normal file
173
tests/README.md
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
# 测试文档
|
||||||
|
|
||||||
|
## 测试框架
|
||||||
|
|
||||||
|
项目使用 **Bun 内置测试框架** (`bun test`),支持 TypeScript,无需额外配置。
|
||||||
|
|
||||||
|
## 运行测试
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 运行所有测试
|
||||||
|
bun test
|
||||||
|
|
||||||
|
# 运行特定测试文件
|
||||||
|
bun test tests/unit/storage/database.test.ts
|
||||||
|
|
||||||
|
# 监听模式(自动重新运行)
|
||||||
|
bun test --watch
|
||||||
|
|
||||||
|
# 生成覆盖率报告
|
||||||
|
bun test --coverage
|
||||||
|
```
|
||||||
|
|
||||||
|
## 测试结构
|
||||||
|
|
||||||
|
```
|
||||||
|
tests/
|
||||||
|
├── helpers/ # 测试辅助函数
|
||||||
|
│ ├── test-utils.ts # 通用测试工具(临时目录、环境变量等)
|
||||||
|
│ ├── database-helper.ts # 数据库测试辅助
|
||||||
|
│ └── tool-helper.ts # 工具测试辅助
|
||||||
|
├── fixtures/ # 测试数据
|
||||||
|
│ └── test-data.ts # 测试数据定义
|
||||||
|
├── unit/ # 单元测试
|
||||||
|
│ ├── storage/ # 存储层测试
|
||||||
|
│ │ ├── database.test.ts
|
||||||
|
│ │ └── config.test.ts
|
||||||
|
│ └── tools/ # 工具测试
|
||||||
|
│ ├── programming/ # 编程工具
|
||||||
|
│ ├── family/ # 家庭工具
|
||||||
|
│ ├── hobbies/ # 爱好工具
|
||||||
|
│ ├── common/ # 通用工具
|
||||||
|
│ └── devops/ # DevOps 工具
|
||||||
|
└── integration/ # 集成测试
|
||||||
|
└── mcp-server.test.ts
|
||||||
|
```
|
||||||
|
|
||||||
|
## 测试覆盖
|
||||||
|
|
||||||
|
### ✅ 存储层测试
|
||||||
|
- 代码片段 CRUD 操作
|
||||||
|
- 笔记 CRUD 操作
|
||||||
|
- 任务 CRUD 操作
|
||||||
|
- 数学资源 CRUD 操作
|
||||||
|
- 育儿里程碑 CRUD 操作
|
||||||
|
- 游戏愿望单 CRUD 操作
|
||||||
|
- 搜索功能
|
||||||
|
|
||||||
|
### ✅ 配置管理测试
|
||||||
|
- 环境变量加载
|
||||||
|
- 配置获取方法
|
||||||
|
|
||||||
|
### ✅ 编程工具测试
|
||||||
|
- 代码片段管理(保存、搜索、列出、删除)
|
||||||
|
- 项目模板生成(Vite+Vue3、全栈项目)
|
||||||
|
- 技术文档查询(TypeScript、Vue3、Bun)
|
||||||
|
- 代码审查和优化
|
||||||
|
|
||||||
|
### ✅ 家庭工具测试
|
||||||
|
- 数学资源搜索和保存
|
||||||
|
- 数学题目生成(不同年级和难度)
|
||||||
|
- 育儿里程碑记录
|
||||||
|
- 育儿提醒设置
|
||||||
|
|
||||||
|
### ✅ 爱好工具测试
|
||||||
|
- 游戏信息查询
|
||||||
|
- 游戏折扣查询
|
||||||
|
- 游戏愿望单管理
|
||||||
|
- 足球信息查询(mock)
|
||||||
|
|
||||||
|
### ✅ 通用工具测试
|
||||||
|
- 笔记创建、搜索、列出、删除
|
||||||
|
- 任务添加、列出、完成
|
||||||
|
|
||||||
|
### ✅ 服务器工具测试
|
||||||
|
- 服务器状态查询(mock SSH)
|
||||||
|
- 服务器日志查看(mock SSH)
|
||||||
|
- 部署功能验证
|
||||||
|
|
||||||
|
### ✅ 集成测试
|
||||||
|
- 工具注册验证
|
||||||
|
- 工具调用测试
|
||||||
|
- 错误处理测试
|
||||||
|
|
||||||
|
## 测试隔离
|
||||||
|
|
||||||
|
每个测试使用独立的临时目录和数据存储,确保测试之间不会相互影响:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
beforeEach(() => {
|
||||||
|
testContext = createTempDir();
|
||||||
|
cleanupDb = setupTestDatabase(testContext);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
cleanupDb();
|
||||||
|
testContext.cleanup();
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## 测试统计
|
||||||
|
|
||||||
|
- **总测试数**: 77
|
||||||
|
- **通过**: 77 ✅
|
||||||
|
- **失败**: 0
|
||||||
|
- **测试文件**: 14
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
1. **NAS 和软路由测试**: 由于需要隧道穿透,这些功能的测试被排除在外
|
||||||
|
2. **SSH 连接测试**: 使用 mock,不会实际连接服务器
|
||||||
|
3. **API 测试**: 游戏和足球 API 测试可能会因为网络问题失败,但会优雅处理
|
||||||
|
4. **测试数据**: 所有测试数据存储在临时目录,测试结束后自动清理
|
||||||
|
|
||||||
|
## 调试测试
|
||||||
|
|
||||||
|
如果测试失败,可以使用以下方法调试:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 运行单个测试并显示详细输出
|
||||||
|
bun test tests/unit/storage/database.test.ts --reporter verbose
|
||||||
|
|
||||||
|
# 使用 Node.js 调试器
|
||||||
|
bun test --inspect
|
||||||
|
```
|
||||||
|
|
||||||
|
## 添加新测试
|
||||||
|
|
||||||
|
1. 在相应的测试目录创建测试文件
|
||||||
|
2. 使用 `describe` 和 `test` 组织测试
|
||||||
|
3. 使用 `beforeEach` 和 `afterEach` 设置和清理
|
||||||
|
4. 使用测试辅助函数(`callTool`, `setupTestDatabase` 等)
|
||||||
|
|
||||||
|
示例:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
import { describe, test, expect, beforeEach, afterEach } from "bun:test";
|
||||||
|
import { callTool } from "../../helpers/tool-helper.js";
|
||||||
|
import { createTempDir } from "../../helpers/test-utils.js";
|
||||||
|
import { setupTestDatabase } from "../../helpers/database-helper.js";
|
||||||
|
|
||||||
|
describe("My Tool", () => {
|
||||||
|
let testContext: ReturnType<typeof createTempDir>;
|
||||||
|
let cleanupDb: () => void;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
testContext = createTempDir();
|
||||||
|
cleanupDb = setupTestDatabase(testContext);
|
||||||
|
registerMyTool();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
cleanupDb();
|
||||||
|
testContext.cleanup();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should do something", async () => {
|
||||||
|
const result = await callTool("my_tool", { arg: "value" });
|
||||||
|
expect(result.content[0].text).toContain("expected");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
66
tests/fixtures/test-data.ts
vendored
Normal file
66
tests/fixtures/test-data.ts
vendored
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
/**
|
||||||
|
* Test data fixtures
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
CodeSnippet,
|
||||||
|
Note,
|
||||||
|
Task,
|
||||||
|
BabyMilestone,
|
||||||
|
MathResource,
|
||||||
|
GameWishlist,
|
||||||
|
} from "../../src/storage/database.js";
|
||||||
|
|
||||||
|
export const testCodeSnippet: CodeSnippet = {
|
||||||
|
id: "test-snippet-1",
|
||||||
|
title: "Test Snippet",
|
||||||
|
code: "const x = 1;",
|
||||||
|
language: "typescript",
|
||||||
|
tags: ["test", "example"],
|
||||||
|
category: "utils",
|
||||||
|
createdAt: "2024-01-01T00:00:00.000Z",
|
||||||
|
updatedAt: "2024-01-01T00:00:00.000Z",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const testNote: Note = {
|
||||||
|
id: "test-note-1",
|
||||||
|
title: "Test Note",
|
||||||
|
content: "This is a test note",
|
||||||
|
tags: ["test"],
|
||||||
|
createdAt: "2024-01-01T00:00:00.000Z",
|
||||||
|
updatedAt: "2024-01-01T00:00:00.000Z",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const testTask: Task = {
|
||||||
|
id: "test-task-1",
|
||||||
|
title: "Test Task",
|
||||||
|
description: "This is a test task",
|
||||||
|
completed: false,
|
||||||
|
createdAt: "2024-01-01T00:00:00.000Z",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const testBabyMilestone: BabyMilestone = {
|
||||||
|
id: "test-milestone-1",
|
||||||
|
title: "First Steps",
|
||||||
|
description: "Baby took first steps",
|
||||||
|
date: "2024-01-01",
|
||||||
|
createdAt: "2024-01-01T00:00:00.000Z",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const testMathResource: MathResource = {
|
||||||
|
id: "test-math-1",
|
||||||
|
title: "Addition Problems",
|
||||||
|
content: "1 + 1 = 2",
|
||||||
|
grade: "1st",
|
||||||
|
difficulty: "easy",
|
||||||
|
tags: ["addition"],
|
||||||
|
createdAt: "2024-01-01T00:00:00.000Z",
|
||||||
|
};
|
||||||
|
|
||||||
|
export const testGameWishlist: GameWishlist = {
|
||||||
|
id: "test-game-1",
|
||||||
|
gameName: "Test Game",
|
||||||
|
platform: "PC",
|
||||||
|
notes: "Want to play this",
|
||||||
|
addedAt: "2024-01-01T00:00:00.000Z",
|
||||||
|
};
|
||||||
29
tests/helpers/database-helper.ts
Normal file
29
tests/helpers/database-helper.ts
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
/**
|
||||||
|
* Database test helper - creates isolated database instances for testing
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { database } from "../../src/storage/database.js";
|
||||||
|
import { join } from "path";
|
||||||
|
import { mkdirSync } from "fs";
|
||||||
|
import type { TestContext } from "./test-utils.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Setup test database with isolated data directory
|
||||||
|
*/
|
||||||
|
export function setupTestDatabase(testContext: TestContext): () => void {
|
||||||
|
const testDataDir = join(testContext.tempDir, "data");
|
||||||
|
mkdirSync(testDataDir, { recursive: true });
|
||||||
|
|
||||||
|
// Set environment variable for test data directory
|
||||||
|
const originalDataDir = process.env.MCP_TEST_DATA_DIR;
|
||||||
|
process.env.MCP_TEST_DATA_DIR = testDataDir;
|
||||||
|
|
||||||
|
// Return cleanup function
|
||||||
|
return () => {
|
||||||
|
if (originalDataDir) {
|
||||||
|
process.env.MCP_TEST_DATA_DIR = originalDataDir;
|
||||||
|
} else {
|
||||||
|
delete process.env.MCP_TEST_DATA_DIR;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
68
tests/helpers/test-utils.ts
Normal file
68
tests/helpers/test-utils.ts
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
/**
|
||||||
|
* Test utilities and helpers
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { mkdtempSync, rmSync, existsSync } from "fs";
|
||||||
|
import { join } from "path";
|
||||||
|
import { tmpdir } from "os";
|
||||||
|
|
||||||
|
export interface TestContext {
|
||||||
|
tempDir: string;
|
||||||
|
cleanup: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a temporary directory for testing
|
||||||
|
*/
|
||||||
|
export function createTempDir(prefix = "mcp-test-"): TestContext {
|
||||||
|
const tempDir = mkdtempSync(join(tmpdir(), prefix));
|
||||||
|
|
||||||
|
return {
|
||||||
|
tempDir,
|
||||||
|
cleanup: () => {
|
||||||
|
if (existsSync(tempDir)) {
|
||||||
|
rmSync(tempDir, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set test environment variables
|
||||||
|
*/
|
||||||
|
export function setTestEnv(env: Record<string, string>): () => void {
|
||||||
|
const originalEnv: Record<string, string | undefined> = {};
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(env)) {
|
||||||
|
originalEnv[key] = process.env[key];
|
||||||
|
process.env[key] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
for (const [key, originalValue] of Object.entries(originalEnv)) {
|
||||||
|
if (originalValue === undefined) {
|
||||||
|
delete process.env[key];
|
||||||
|
} else {
|
||||||
|
process.env[key] = originalValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wait for a specified time (for async operations)
|
||||||
|
*/
|
||||||
|
export function sleep(ms: number): Promise<void> {
|
||||||
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mock HTTP response helper
|
||||||
|
*/
|
||||||
|
export function createMockResponse(data: unknown, status = 200) {
|
||||||
|
return {
|
||||||
|
status,
|
||||||
|
data,
|
||||||
|
headers: {},
|
||||||
|
};
|
||||||
|
}
|
||||||
35
tests/helpers/tool-helper.ts
Normal file
35
tests/helpers/tool-helper.ts
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
/**
|
||||||
|
* Tool testing helper - helps test MCP tools
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { mcpServer } from "../../src/server.js";
|
||||||
|
import type { ToolHandler } from "../../src/server.js";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Call a tool by name with arguments
|
||||||
|
*/
|
||||||
|
export async function callTool(
|
||||||
|
toolName: string,
|
||||||
|
args: Record<string, unknown>
|
||||||
|
): Promise<{
|
||||||
|
content: Array<{ type: string; text: string }>;
|
||||||
|
isError?: boolean;
|
||||||
|
}> {
|
||||||
|
// Get the tool handler from the server
|
||||||
|
const tools = mcpServer.getTools();
|
||||||
|
|
||||||
|
const toolEntry = tools.get(toolName);
|
||||||
|
if (!toolEntry) {
|
||||||
|
throw new Error(`Tool ${toolName} not found`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return await toolEntry.handler(args);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all registered tools
|
||||||
|
*/
|
||||||
|
export function getRegisteredTools(): string[] {
|
||||||
|
const tools = mcpServer.getTools();
|
||||||
|
return Array.from(tools.keys());
|
||||||
|
}
|
||||||
114
tests/integration/mcp-server.test.ts
Normal file
114
tests/integration/mcp-server.test.ts
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
/**
|
||||||
|
* MCP Server integration tests
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, test, expect, beforeEach } from "bun:test";
|
||||||
|
import { mcpServer } from "../../src/server.js";
|
||||||
|
import { getRegisteredTools, callTool } from "../helpers/tool-helper.js";
|
||||||
|
|
||||||
|
// Register all tools
|
||||||
|
import { registerCodeSnippetTools } from "../../src/tools/programming/codeSnippet.js";
|
||||||
|
import { registerProjectTemplateTools } from "../../src/tools/programming/projectTemplate.js";
|
||||||
|
import { registerDocsTools } from "../../src/tools/programming/docs.js";
|
||||||
|
import { registerCodeReviewTools } from "../../src/tools/programming/codeReview.js";
|
||||||
|
import { registerServerTools } from "../../src/tools/devops/server.js";
|
||||||
|
import { registerRouterTools } from "../../src/tools/devops/router.js";
|
||||||
|
import { registerMathTools } from "../../src/tools/family/math.js";
|
||||||
|
import { registerBabyTools } from "../../src/tools/family/baby.js";
|
||||||
|
import { registerFootballTools } from "../../src/tools/hobbies/football.js";
|
||||||
|
import { registerGameTools } from "../../src/tools/hobbies/games.js";
|
||||||
|
import { registerNoteTools } from "../../src/tools/common/notes.js";
|
||||||
|
import { registerTaskTools } from "../../src/tools/common/tasks.js";
|
||||||
|
|
||||||
|
describe("MCP Server Integration", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
// Register all tools
|
||||||
|
registerCodeSnippetTools();
|
||||||
|
registerProjectTemplateTools();
|
||||||
|
registerDocsTools();
|
||||||
|
registerCodeReviewTools();
|
||||||
|
registerServerTools();
|
||||||
|
registerRouterTools();
|
||||||
|
registerMathTools();
|
||||||
|
registerBabyTools();
|
||||||
|
registerFootballTools();
|
||||||
|
registerGameTools();
|
||||||
|
registerNoteTools();
|
||||||
|
registerTaskTools();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should register all tools", () => {
|
||||||
|
const tools = getRegisteredTools();
|
||||||
|
|
||||||
|
// Check that key tools are registered
|
||||||
|
expect(tools).toContain("code_snippet_save");
|
||||||
|
expect(tools).toContain("code_snippet_search");
|
||||||
|
expect(tools).toContain("project_template_create");
|
||||||
|
expect(tools).toContain("docs_typescript");
|
||||||
|
expect(tools).toContain("code_review");
|
||||||
|
expect(tools).toContain("note_create");
|
||||||
|
expect(tools).toContain("task_add");
|
||||||
|
expect(tools).toContain("math_problem_generate");
|
||||||
|
expect(tools).toContain("baby_milestone_add");
|
||||||
|
expect(tools).toContain("game_info");
|
||||||
|
expect(tools).toContain("server_status");
|
||||||
|
|
||||||
|
// Should have many tools registered
|
||||||
|
expect(tools.length).toBeGreaterThan(20);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should handle tool call with valid arguments", async () => {
|
||||||
|
const result = await callTool("docs_typescript", {});
|
||||||
|
|
||||||
|
expect(result.content).toBeDefined();
|
||||||
|
expect(result.content.length).toBeGreaterThan(0);
|
||||||
|
expect(result.content[0].type).toBe("text");
|
||||||
|
expect(result.content[0].text).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should handle tool call with invalid tool name", async () => {
|
||||||
|
await expect(callTool("non_existent_tool", {})).rejects.toThrow(
|
||||||
|
"Tool non_existent_tool not found"
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should handle tool errors gracefully", async () => {
|
||||||
|
// Call a tool that might fail (like server_status without proper config)
|
||||||
|
const result = await callTool("server_status", {});
|
||||||
|
|
||||||
|
// Should return an error response, not throw
|
||||||
|
expect(result.content).toBeDefined();
|
||||||
|
expect(result.content[0].text).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should support multiple tool categories", () => {
|
||||||
|
const tools = getRegisteredTools();
|
||||||
|
|
||||||
|
// Programming tools
|
||||||
|
const programmingTools = tools.filter(
|
||||||
|
(t) =>
|
||||||
|
t.startsWith("code_") ||
|
||||||
|
t.startsWith("project_") ||
|
||||||
|
t.startsWith("docs_")
|
||||||
|
);
|
||||||
|
expect(programmingTools.length).toBeGreaterThan(0);
|
||||||
|
|
||||||
|
// Family tools
|
||||||
|
const familyTools = tools.filter(
|
||||||
|
(t) => t.startsWith("math_") || t.startsWith("baby_")
|
||||||
|
);
|
||||||
|
expect(familyTools.length).toBeGreaterThan(0);
|
||||||
|
|
||||||
|
// Common tools
|
||||||
|
const commonTools = tools.filter(
|
||||||
|
(t) => t.startsWith("note_") || t.startsWith("task_")
|
||||||
|
);
|
||||||
|
expect(commonTools.length).toBeGreaterThan(0);
|
||||||
|
|
||||||
|
// Hobby tools
|
||||||
|
const hobbyTools = tools.filter(
|
||||||
|
(t) => t.startsWith("game_") || t.startsWith("football_")
|
||||||
|
);
|
||||||
|
expect(hobbyTools.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
87
tests/unit/storage/config.test.ts
Normal file
87
tests/unit/storage/config.test.ts
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
* @Date: 2026-01-07 09:09:22
|
||||||
|
* @LastEditors: 陈子健
|
||||||
|
* @LastEditTime: 2026-01-07 10:04:55
|
||||||
|
* @FilePath: /cloud-mcp/tests/unit/storage/config.test.ts
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Configuration management tests
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, test, expect, beforeEach, afterEach } from "bun:test";
|
||||||
|
import { configManager } from "../../../src/storage/config.js";
|
||||||
|
import { setTestEnv } from "../../helpers/test-utils.js";
|
||||||
|
|
||||||
|
describe("ConfigManager", () => {
|
||||||
|
let cleanupEnv: () => void;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
cleanupEnv = setTestEnv({
|
||||||
|
NAS_HOST: "test-nas-host",
|
||||||
|
NAS_USERNAME: "test-user",
|
||||||
|
NAS_PASSWORD: "test-password",
|
||||||
|
NAS_PROTOCOL: "smb",
|
||||||
|
SERVER_HOST: "test-server",
|
||||||
|
SERVER_USERNAME: "test-server-user",
|
||||||
|
SERVER_PORT: "2222",
|
||||||
|
SERVER_KEY_PATH: "/test/key/path",
|
||||||
|
ROUTER_HOST: "test-router",
|
||||||
|
ROUTER_USERNAME: "test-router-user",
|
||||||
|
ROUTER_PASSWORD: "test-router-password",
|
||||||
|
FOOTBALL_API_KEY: "test-football-key",
|
||||||
|
});
|
||||||
|
configManager.reload();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
cleanupEnv();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should load NAS configuration from environment", () => {
|
||||||
|
const nasConfig = configManager.getNASConfig();
|
||||||
|
|
||||||
|
expect(nasConfig.host).toBe("test-nas-host");
|
||||||
|
expect(nasConfig.username).toBe("test-user");
|
||||||
|
expect(nasConfig.password).toBe("test-password");
|
||||||
|
expect(nasConfig.protocol).toBe("smb");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should load server configuration from environment", () => {
|
||||||
|
const serverConfig = configManager.getServerConfig();
|
||||||
|
|
||||||
|
expect(serverConfig.host).toBe("test-server");
|
||||||
|
expect(serverConfig.username).toBe("test-server-user");
|
||||||
|
expect(serverConfig.port).toBe(2222);
|
||||||
|
expect(serverConfig.keyPath).toBe("/test/key/path");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should load router configuration from environment", () => {
|
||||||
|
const routerConfig = configManager.getRouterConfig();
|
||||||
|
|
||||||
|
expect(routerConfig.host).toBe("test-router");
|
||||||
|
expect(routerConfig.username).toBe("test-router-user");
|
||||||
|
expect(routerConfig.password).toBe("test-router-password");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should get full configuration", () => {
|
||||||
|
const config = configManager.getConfig();
|
||||||
|
|
||||||
|
expect(config.nas.host).toBe("test-nas-host");
|
||||||
|
expect(config.server.host).toBe("test-server");
|
||||||
|
expect(config.router.host).toBe("test-router");
|
||||||
|
expect(config.footballApiKey).toBe("test-football-key");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should handle missing environment variables", () => {
|
||||||
|
cleanupEnv();
|
||||||
|
// Clear all relevant env vars
|
||||||
|
delete process.env.NAS_HOST;
|
||||||
|
delete process.env.NAS_USERNAME;
|
||||||
|
delete process.env.NAS_PASSWORD;
|
||||||
|
delete process.env.NAS_PROTOCOL;
|
||||||
|
configManager.reload();
|
||||||
|
|
||||||
|
const nasConfig = configManager.getNASConfig();
|
||||||
|
expect(nasConfig.host).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
212
tests/unit/storage/database.test.ts
Normal file
212
tests/unit/storage/database.test.ts
Normal file
@@ -0,0 +1,212 @@
|
|||||||
|
/**
|
||||||
|
* Database storage layer tests
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, test, expect, beforeEach, afterEach } from "bun:test";
|
||||||
|
import { database } from "../../../src/storage/database.js";
|
||||||
|
import { createTempDir } from "../../helpers/test-utils.js";
|
||||||
|
import { setupTestDatabase } from "../../helpers/database-helper.js";
|
||||||
|
import {
|
||||||
|
testCodeSnippet,
|
||||||
|
testNote,
|
||||||
|
testTask,
|
||||||
|
testBabyMilestone,
|
||||||
|
testMathResource,
|
||||||
|
testGameWishlist,
|
||||||
|
} from "../../fixtures/test-data.js";
|
||||||
|
|
||||||
|
describe("Database", () => {
|
||||||
|
let testContext: ReturnType<typeof createTempDir>;
|
||||||
|
let cleanupDb: () => void;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
testContext = createTempDir();
|
||||||
|
cleanupDb = setupTestDatabase(testContext);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
cleanupDb();
|
||||||
|
testContext.cleanup();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Code Snippets", () => {
|
||||||
|
test("should save and retrieve code snippet", () => {
|
||||||
|
database.saveCodeSnippet(testCodeSnippet);
|
||||||
|
const snippet = database.getCodeSnippet(testCodeSnippet.id);
|
||||||
|
|
||||||
|
expect(snippet).toBeDefined();
|
||||||
|
expect(snippet?.title).toBe(testCodeSnippet.title);
|
||||||
|
expect(snippet?.code).toBe(testCodeSnippet.code);
|
||||||
|
expect(snippet?.language).toBe(testCodeSnippet.language);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should list all code snippets", () => {
|
||||||
|
database.saveCodeSnippet(testCodeSnippet);
|
||||||
|
const snippets = database.getCodeSnippets();
|
||||||
|
|
||||||
|
expect(snippets.length).toBeGreaterThan(0);
|
||||||
|
expect(snippets.find((s) => s.id === testCodeSnippet.id)).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should search code snippets", () => {
|
||||||
|
database.saveCodeSnippet(testCodeSnippet);
|
||||||
|
const results = database.searchCodeSnippets("Test");
|
||||||
|
|
||||||
|
expect(results.length).toBeGreaterThan(0);
|
||||||
|
expect(results[0].title).toContain("Test");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should search code snippets by tags", () => {
|
||||||
|
database.saveCodeSnippet(testCodeSnippet);
|
||||||
|
const results = database.searchCodeSnippets("", ["test"]);
|
||||||
|
|
||||||
|
expect(results.length).toBeGreaterThan(0);
|
||||||
|
expect(results[0].tags).toContain("test");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should delete code snippet", () => {
|
||||||
|
database.saveCodeSnippet(testCodeSnippet);
|
||||||
|
const deleted = database.deleteCodeSnippet(testCodeSnippet.id);
|
||||||
|
|
||||||
|
expect(deleted).toBe(true);
|
||||||
|
expect(database.getCodeSnippet(testCodeSnippet.id)).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should update existing code snippet", () => {
|
||||||
|
database.saveCodeSnippet(testCodeSnippet);
|
||||||
|
const updated = {
|
||||||
|
...testCodeSnippet,
|
||||||
|
title: "Updated Title",
|
||||||
|
};
|
||||||
|
database.saveCodeSnippet(updated);
|
||||||
|
|
||||||
|
const snippet = database.getCodeSnippet(testCodeSnippet.id);
|
||||||
|
expect(snippet?.title).toBe("Updated Title");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Notes", () => {
|
||||||
|
test("should save and retrieve note", () => {
|
||||||
|
database.saveNote(testNote);
|
||||||
|
const note = database.getNote(testNote.id);
|
||||||
|
|
||||||
|
expect(note).toBeDefined();
|
||||||
|
expect(note?.title).toBe(testNote.title);
|
||||||
|
expect(note?.content).toBe(testNote.content);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should list all notes", () => {
|
||||||
|
database.saveNote(testNote);
|
||||||
|
const notes = database.getNotes();
|
||||||
|
|
||||||
|
expect(notes.length).toBeGreaterThan(0);
|
||||||
|
expect(notes.find((n) => n.id === testNote.id)).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should search notes", () => {
|
||||||
|
database.saveNote(testNote);
|
||||||
|
const results = database.searchNotes("Test");
|
||||||
|
|
||||||
|
expect(results.length).toBeGreaterThan(0);
|
||||||
|
expect(results[0].title).toContain("Test");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should delete note", () => {
|
||||||
|
database.saveNote(testNote);
|
||||||
|
const deleted = database.deleteNote(testNote.id);
|
||||||
|
|
||||||
|
expect(deleted).toBe(true);
|
||||||
|
expect(database.getNote(testNote.id)).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Tasks", () => {
|
||||||
|
test("should save and retrieve task", () => {
|
||||||
|
database.saveTask(testTask);
|
||||||
|
const task = database.getTask(testTask.id);
|
||||||
|
|
||||||
|
expect(task).toBeDefined();
|
||||||
|
expect(task?.title).toBe(testTask.title);
|
||||||
|
expect(task?.completed).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should list all tasks", () => {
|
||||||
|
database.saveTask(testTask);
|
||||||
|
const tasks = database.getTasks();
|
||||||
|
|
||||||
|
expect(tasks.length).toBeGreaterThan(0);
|
||||||
|
expect(tasks.find((t) => t.id === testTask.id)).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should filter tasks by completion status", () => {
|
||||||
|
database.saveTask(testTask);
|
||||||
|
const completedTask = { ...testTask, id: "task-2", completed: true };
|
||||||
|
database.saveTask(completedTask);
|
||||||
|
|
||||||
|
const pendingTasks = database.getTasks(false);
|
||||||
|
const completedTasks = database.getTasks(true);
|
||||||
|
|
||||||
|
expect(pendingTasks.length).toBeGreaterThan(0);
|
||||||
|
expect(completedTasks.length).toBeGreaterThan(0);
|
||||||
|
expect(pendingTasks.find((t) => t.id === testTask.id)).toBeDefined();
|
||||||
|
expect(completedTasks.find((t) => t.id === "task-2")).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Baby Milestones", () => {
|
||||||
|
test("should save and retrieve baby milestone", () => {
|
||||||
|
database.saveBabyMilestone(testBabyMilestone);
|
||||||
|
const milestones = database.getBabyMilestones();
|
||||||
|
|
||||||
|
expect(milestones.length).toBeGreaterThan(0);
|
||||||
|
expect(milestones.find((m) => m.id === testBabyMilestone.id)).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Math Resources", () => {
|
||||||
|
test("should save and retrieve math resource", () => {
|
||||||
|
database.saveMathResource(testMathResource);
|
||||||
|
const resources = database.getMathResources();
|
||||||
|
|
||||||
|
expect(resources.length).toBeGreaterThan(0);
|
||||||
|
expect(resources.find((r) => r.id === testMathResource.id)).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should search math resources", () => {
|
||||||
|
database.saveMathResource(testMathResource);
|
||||||
|
const results = database.searchMathResources("Addition");
|
||||||
|
|
||||||
|
expect(results.length).toBeGreaterThan(0);
|
||||||
|
expect(results[0].title).toContain("Addition");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should filter math resources by grade", () => {
|
||||||
|
database.saveMathResource(testMathResource);
|
||||||
|
const results = database.searchMathResources("", "1st");
|
||||||
|
|
||||||
|
expect(results.length).toBeGreaterThan(0);
|
||||||
|
expect(results[0].grade).toBe("1st");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Game Wishlist", () => {
|
||||||
|
test("should save and retrieve game wishlist", () => {
|
||||||
|
database.saveGameWishlist(testGameWishlist);
|
||||||
|
const games = database.getGameWishlist();
|
||||||
|
|
||||||
|
expect(games.length).toBeGreaterThan(0);
|
||||||
|
expect(games.find((g) => g.id === testGameWishlist.id)).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should delete game from wishlist", () => {
|
||||||
|
database.saveGameWishlist(testGameWishlist);
|
||||||
|
const deleted = database.deleteGameWishlist(testGameWishlist.id);
|
||||||
|
|
||||||
|
expect(deleted).toBe(true);
|
||||||
|
const games = database.getGameWishlist();
|
||||||
|
expect(games.find((g) => g.id === testGameWishlist.id)).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
97
tests/unit/tools/common/notes.test.ts
Normal file
97
tests/unit/tools/common/notes.test.ts
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
/*
|
||||||
|
* @Date: 2026-01-07 09:11:08
|
||||||
|
* @LastEditors: 陈子健
|
||||||
|
* @LastEditTime: 2026-01-07 10:04:45
|
||||||
|
* @FilePath: /cloud-mcp/tests/unit/tools/common/notes.test.ts
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Notes tools tests
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, test, expect, beforeEach, afterEach } from "bun:test";
|
||||||
|
import { registerNoteTools } from "../../../../src/tools/common/notes.js";
|
||||||
|
import { callTool } from "../../../helpers/tool-helper.js";
|
||||||
|
import { createTempDir } from "../../../helpers/test-utils.js";
|
||||||
|
import { setupTestDatabase } from "../../../helpers/database-helper.js";
|
||||||
|
|
||||||
|
describe("Notes Tools", () => {
|
||||||
|
let testContext: ReturnType<typeof createTempDir>;
|
||||||
|
let cleanupDb: () => void;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
testContext = createTempDir();
|
||||||
|
cleanupDb = setupTestDatabase(testContext);
|
||||||
|
registerNoteTools();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
cleanupDb();
|
||||||
|
testContext.cleanup();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should create note", async () => {
|
||||||
|
const result = await callTool("note_create", {
|
||||||
|
title: "Test Note",
|
||||||
|
content: "This is a test note",
|
||||||
|
tags: ["test"],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.content[0].text).toContain("saved successfully");
|
||||||
|
expect(result.content[0].text).toContain("Test Note");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should search notes", async () => {
|
||||||
|
// Create a note first
|
||||||
|
await callTool("note_create", {
|
||||||
|
title: "Test Note",
|
||||||
|
content: "This is a test note",
|
||||||
|
tags: ["test"],
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await callTool("note_search", {
|
||||||
|
query: "Test",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.content[0].text).toContain("Found");
|
||||||
|
expect(result.content[0].text).toContain("Test Note");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should list notes", async () => {
|
||||||
|
// Create a note
|
||||||
|
await callTool("note_create", {
|
||||||
|
title: "Test Note",
|
||||||
|
content: "This is a test note",
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await callTool("note_list", {});
|
||||||
|
|
||||||
|
expect(result.content[0].text).toContain("Total");
|
||||||
|
expect(result.content[0].text).toContain("Test Note");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should delete note", async () => {
|
||||||
|
// Create a note
|
||||||
|
const createResult = await callTool("note_create", {
|
||||||
|
title: "Test Note",
|
||||||
|
content: "This is a test note",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Extract ID
|
||||||
|
const idMatch = createResult.content[0].text.match(/ID: ([a-f0-9-]+)/);
|
||||||
|
if (!idMatch) {
|
||||||
|
throw new Error("Could not extract ID");
|
||||||
|
}
|
||||||
|
const id = idMatch[1];
|
||||||
|
|
||||||
|
// Delete it
|
||||||
|
const result = await callTool("note_delete", { id });
|
||||||
|
|
||||||
|
expect(result.content[0].text).toContain("deleted successfully");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should handle empty notes list", async () => {
|
||||||
|
const result = await callTool("note_list", {});
|
||||||
|
|
||||||
|
expect(result.content[0].text).toMatch(/No notes found|Use note_create/i);
|
||||||
|
});
|
||||||
|
});
|
||||||
92
tests/unit/tools/common/tasks.test.ts
Normal file
92
tests/unit/tools/common/tasks.test.ts
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
/*
|
||||||
|
* @Date: 2026-01-07 09:11:15
|
||||||
|
* @LastEditors: 陈子健
|
||||||
|
* @LastEditTime: 2026-01-07 10:04:50
|
||||||
|
* @FilePath: /cloud-mcp/tests/unit/tools/common/tasks.test.ts
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Tasks tools tests
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, test, expect, beforeEach, afterEach } from "bun:test";
|
||||||
|
import { registerTaskTools } from "../../../../src/tools/common/tasks.js";
|
||||||
|
import { callTool } from "../../../helpers/tool-helper.js";
|
||||||
|
import { createTempDir } from "../../../helpers/test-utils.js";
|
||||||
|
import { setupTestDatabase } from "../../../helpers/database-helper.js";
|
||||||
|
|
||||||
|
describe("Tasks Tools", () => {
|
||||||
|
let testContext: ReturnType<typeof createTempDir>;
|
||||||
|
let cleanupDb: () => void;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
testContext = createTempDir();
|
||||||
|
cleanupDb = setupTestDatabase(testContext);
|
||||||
|
registerTaskTools();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
cleanupDb();
|
||||||
|
testContext.cleanup();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should add task", async () => {
|
||||||
|
const result = await callTool("task_add", {
|
||||||
|
title: "Test Task",
|
||||||
|
description: "This is a test task",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.content[0].text).toContain("added successfully");
|
||||||
|
expect(result.content[0].text).toContain("Test Task");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should list tasks", async () => {
|
||||||
|
// Add a task first
|
||||||
|
await callTool("task_add", {
|
||||||
|
title: "Test Task",
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await callTool("task_list", {});
|
||||||
|
|
||||||
|
expect(result.content[0].text).toContain("Tasks");
|
||||||
|
expect(result.content[0].text).toContain("Test Task");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should filter tasks by completion status", async () => {
|
||||||
|
// Add a task
|
||||||
|
await callTool("task_add", {
|
||||||
|
title: "Test Task",
|
||||||
|
});
|
||||||
|
|
||||||
|
const allTasks = await callTool("task_list", {});
|
||||||
|
const pendingTasks = await callTool("task_list", { completed: false });
|
||||||
|
|
||||||
|
expect(allTasks.content[0].text).toContain("Tasks");
|
||||||
|
expect(pendingTasks.content[0].text).toContain("Pending");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should complete task", async () => {
|
||||||
|
// Add a task
|
||||||
|
const addResult = await callTool("task_add", {
|
||||||
|
title: "Test Task",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Extract ID
|
||||||
|
const idMatch = addResult.content[0].text.match(/ID: ([a-f0-9-]+)/);
|
||||||
|
if (!idMatch) {
|
||||||
|
throw new Error("Could not extract ID");
|
||||||
|
}
|
||||||
|
const id = idMatch[1];
|
||||||
|
|
||||||
|
// Complete it
|
||||||
|
const result = await callTool("task_complete", { id });
|
||||||
|
|
||||||
|
expect(result.content[0].text).toContain("marked as completed");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should handle empty tasks list", async () => {
|
||||||
|
const result = await callTool("task_list", {});
|
||||||
|
|
||||||
|
// The message varies based on completion status filter
|
||||||
|
expect(result.content[0].text).toMatch(/No.*tasks|Use task_add/i);
|
||||||
|
});
|
||||||
|
});
|
||||||
71
tests/unit/tools/devops/server.test.ts
Normal file
71
tests/unit/tools/devops/server.test.ts
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
/*
|
||||||
|
* @Date: 2026-01-07 09:11:20
|
||||||
|
* @LastEditors: 陈子健
|
||||||
|
* @LastEditTime: 2026-01-07 10:04:41
|
||||||
|
* @FilePath: /cloud-mcp/tests/unit/tools/devops/server.test.ts
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Server tools tests (with mocked SSH)
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, test, expect, beforeEach } from "bun:test";
|
||||||
|
import { registerServerTools } from "../../../../src/tools/devops/server.js";
|
||||||
|
import { callTool } from "../../../helpers/tool-helper.js";
|
||||||
|
import { setTestEnv } from "../../../helpers/test-utils.js";
|
||||||
|
|
||||||
|
describe("Server Tools", () => {
|
||||||
|
let cleanupEnv: () => void;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
cleanupEnv = setTestEnv({
|
||||||
|
SERVER_HOST: "test-server",
|
||||||
|
SERVER_USERNAME: "test-user",
|
||||||
|
SERVER_PORT: "22",
|
||||||
|
SERVER_KEY_PATH: "/test/key/path",
|
||||||
|
});
|
||||||
|
registerServerTools();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
cleanupEnv();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should handle server status request", async () => {
|
||||||
|
const result = await callTool("server_status", {});
|
||||||
|
|
||||||
|
// Should either return status or handle connection error gracefully
|
||||||
|
expect(result.content[0].text).toBeDefined();
|
||||||
|
// Since we don't have actual SSH connection, it will likely return an error
|
||||||
|
// which is expected behavior
|
||||||
|
}, 15000); // Longer timeout for SSH attempts
|
||||||
|
|
||||||
|
test("should handle server logs request", async () => {
|
||||||
|
const result = await callTool("server_logs", {
|
||||||
|
logPath: "/var/log/test.log",
|
||||||
|
lines: 10,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Should either return logs or handle connection error gracefully
|
||||||
|
expect(result.content[0].text).toBeDefined();
|
||||||
|
}, 15000);
|
||||||
|
|
||||||
|
test("should handle deploy request", async () => {
|
||||||
|
const result = await callTool("server_deploy", {
|
||||||
|
localPath: "/local/path",
|
||||||
|
remotePath: "/remote/path",
|
||||||
|
command: "pm2 restart app",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.content[0].text).toContain("Deployment initiated");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should handle missing server configuration", async () => {
|
||||||
|
cleanupEnv();
|
||||||
|
cleanupEnv = setTestEnv({});
|
||||||
|
registerServerTools();
|
||||||
|
|
||||||
|
const result = await callTool("server_status", {});
|
||||||
|
|
||||||
|
expect(result.content[0].text).toContain("configuration not found");
|
||||||
|
});
|
||||||
|
});
|
||||||
70
tests/unit/tools/family/baby.test.ts
Normal file
70
tests/unit/tools/family/baby.test.ts
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
/**
|
||||||
|
* Baby tools tests
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, test, expect, beforeEach, afterEach } from "bun:test";
|
||||||
|
import { registerBabyTools } from "../../../../src/tools/family/baby.js";
|
||||||
|
import { callTool } from "../../../helpers/tool-helper.js";
|
||||||
|
import { createTempDir } from "../../../helpers/test-utils.js";
|
||||||
|
import { setupTestDatabase } from "../../../helpers/database-helper.js";
|
||||||
|
|
||||||
|
describe("Baby Tools", () => {
|
||||||
|
let testContext: ReturnType<typeof createTempDir>;
|
||||||
|
let cleanupDb: () => void;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
testContext = createTempDir();
|
||||||
|
cleanupDb = setupTestDatabase(testContext);
|
||||||
|
registerBabyTools();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
cleanupDb();
|
||||||
|
testContext.cleanup();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should add baby milestone", async () => {
|
||||||
|
const result = await callTool("baby_milestone_add", {
|
||||||
|
title: "First Steps",
|
||||||
|
description: "Baby took first steps today",
|
||||||
|
date: "2024-01-01",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.content[0].text).toContain("recorded successfully");
|
||||||
|
expect(result.content[0].text).toContain("First Steps");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should list baby milestones", async () => {
|
||||||
|
// Add a milestone first
|
||||||
|
await callTool("baby_milestone_add", {
|
||||||
|
title: "First Steps",
|
||||||
|
description: "Baby took first steps",
|
||||||
|
date: "2024-01-01",
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await callTool("baby_milestone_list", {});
|
||||||
|
|
||||||
|
expect(result.content[0].text).toContain("Total");
|
||||||
|
expect(result.content[0].text).toContain("First Steps");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should set baby reminder", async () => {
|
||||||
|
const result = await callTool("baby_reminder_set", {
|
||||||
|
title: "Vaccine",
|
||||||
|
description: "DTaP vaccine due",
|
||||||
|
date: "2024-02-01",
|
||||||
|
type: "vaccine",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.content[0].text).toContain("reminder set successfully");
|
||||||
|
expect(result.content[0].text).toContain("Vaccine");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should handle empty milestones list", async () => {
|
||||||
|
const result = await callTool("baby_milestone_list", {});
|
||||||
|
|
||||||
|
expect(result.content[0].text).toMatch(
|
||||||
|
/No milestones recorded|Use baby_milestone_add/i
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
109
tests/unit/tools/family/math.test.ts
Normal file
109
tests/unit/tools/family/math.test.ts
Normal file
@@ -0,0 +1,109 @@
|
|||||||
|
/*
|
||||||
|
* @Date: 2026-01-07 09:10:17
|
||||||
|
* @LastEditors: 陈子健
|
||||||
|
* @LastEditTime: 2026-01-07 10:04:38
|
||||||
|
* @FilePath: /cloud-mcp/tests/unit/tools/family/math.test.ts
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* Math tools tests
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, test, expect, beforeEach, afterEach } from "bun:test";
|
||||||
|
import { registerMathTools } from "../../../../src/tools/family/math.js";
|
||||||
|
import { callTool } from "../../../helpers/tool-helper.js";
|
||||||
|
import { createTempDir } from "../../../helpers/test-utils.js";
|
||||||
|
import { setupTestDatabase } from "../../../helpers/database-helper.js";
|
||||||
|
|
||||||
|
describe("Math Tools", () => {
|
||||||
|
let testContext: ReturnType<typeof createTempDir>;
|
||||||
|
let cleanupDb: () => void;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
testContext = createTempDir();
|
||||||
|
cleanupDb = setupTestDatabase(testContext);
|
||||||
|
registerMathTools();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
cleanupDb();
|
||||||
|
testContext.cleanup();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should generate math problems for elementary grade", async () => {
|
||||||
|
const result = await callTool("math_problem_generate", {
|
||||||
|
grade: "1st",
|
||||||
|
difficulty: "easy",
|
||||||
|
count: 5,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.content[0].text).toContain("Generated");
|
||||||
|
expect(result.content[0].text).toContain("1st");
|
||||||
|
expect(result.content[0].text).toContain("problem");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should generate math problems for middle school", async () => {
|
||||||
|
const result = await callTool("math_problem_generate", {
|
||||||
|
grade: "middle",
|
||||||
|
difficulty: "medium",
|
||||||
|
topic: "algebra",
|
||||||
|
count: 3,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.content[0].text).toContain("Generated");
|
||||||
|
expect(result.content[0].text).toContain("middle");
|
||||||
|
expect(result.content[0].text).toContain("algebra");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should save math resource", async () => {
|
||||||
|
const result = await callTool("math_resource_save", {
|
||||||
|
title: "Addition Worksheet",
|
||||||
|
content: "1 + 1 = 2",
|
||||||
|
grade: "1st",
|
||||||
|
tags: ["addition"],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.content[0].text).toContain("saved successfully");
|
||||||
|
expect(result.content[0].text).toContain("Addition Worksheet");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should search math resources", async () => {
|
||||||
|
// First save a resource
|
||||||
|
await callTool("math_resource_save", {
|
||||||
|
title: "Addition Worksheet",
|
||||||
|
content: "1 + 1 = 2",
|
||||||
|
grade: "1st",
|
||||||
|
tags: ["addition"],
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await callTool("math_resource_search", {
|
||||||
|
query: "Addition",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.content[0].text).toContain("Found");
|
||||||
|
expect(result.content[0].text).toContain("Addition Worksheet");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should search math resources by grade", async () => {
|
||||||
|
await callTool("math_resource_save", {
|
||||||
|
title: "Addition Worksheet",
|
||||||
|
content: "1 + 1 = 2",
|
||||||
|
grade: "1st",
|
||||||
|
tags: ["addition"],
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await callTool("math_resource_search", {
|
||||||
|
query: "Addition",
|
||||||
|
grade: "1st",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.content[0].text).toContain("Found");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should handle search with no results", async () => {
|
||||||
|
const result = await callTool("math_resource_search", {
|
||||||
|
query: "NonExistent",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.content[0].text).toContain("No math resources found");
|
||||||
|
});
|
||||||
|
});
|
||||||
61
tests/unit/tools/hobbies/football.test.ts
Normal file
61
tests/unit/tools/hobbies/football.test.ts
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
/**
|
||||||
|
* Football tools tests
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, test, expect, beforeEach } from "bun:test";
|
||||||
|
import { registerFootballTools } from "../../../../src/tools/hobbies/football.js";
|
||||||
|
import { callTool } from "../../../helpers/tool-helper.js";
|
||||||
|
import { setTestEnv } from "../../../helpers/test-utils.js";
|
||||||
|
|
||||||
|
describe("Football Tools", () => {
|
||||||
|
let cleanupEnv: () => void;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
cleanupEnv = setTestEnv({
|
||||||
|
FOOTBALL_API_KEY: "test-key",
|
||||||
|
});
|
||||||
|
registerFootballTools();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
cleanupEnv();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should get football matches", async () => {
|
||||||
|
const result = await callTool("football_matches", {
|
||||||
|
days: 7,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Should return matches or placeholder message
|
||||||
|
expect(result.content[0].text).toBeDefined();
|
||||||
|
expect(result.content[0].text.length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should get team information", async () => {
|
||||||
|
const result = await callTool("football_team_info", {
|
||||||
|
team: "Manchester United",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Should return team info or placeholder
|
||||||
|
expect(result.content[0].text).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should get league standings", async () => {
|
||||||
|
const result = await callTool("football_standings", {
|
||||||
|
league: "Premier League",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Should return standings or placeholder
|
||||||
|
expect(result.content[0].text).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should handle missing API key gracefully", async () => {
|
||||||
|
cleanupEnv();
|
||||||
|
cleanupEnv = setTestEnv({});
|
||||||
|
registerFootballTools();
|
||||||
|
|
||||||
|
const result = await callTool("football_matches", {});
|
||||||
|
|
||||||
|
expect(result.content[0].text).toContain("FOOTBALL_API_KEY");
|
||||||
|
});
|
||||||
|
});
|
||||||
100
tests/unit/tools/hobbies/games.test.ts
Normal file
100
tests/unit/tools/hobbies/games.test.ts
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
/**
|
||||||
|
* Game tools tests
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, test, expect, beforeEach, afterEach } from "bun:test";
|
||||||
|
import { registerGameTools } from "../../../../src/tools/hobbies/games.js";
|
||||||
|
import { callTool } from "../../../helpers/tool-helper.js";
|
||||||
|
import { createTempDir } from "../../../helpers/test-utils.js";
|
||||||
|
import { setupTestDatabase } from "../../../helpers/database-helper.js";
|
||||||
|
|
||||||
|
describe("Game Tools", () => {
|
||||||
|
let testContext: ReturnType<typeof createTempDir>;
|
||||||
|
let cleanupDb: () => void;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
testContext = createTempDir();
|
||||||
|
cleanupDb = setupTestDatabase(testContext);
|
||||||
|
registerGameTools();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
cleanupDb();
|
||||||
|
testContext.cleanup();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should get game information", async () => {
|
||||||
|
const result = await callTool("game_info", {
|
||||||
|
name: "Minecraft",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Should either return game info or handle API error gracefully
|
||||||
|
expect(result.content[0].text).toBeDefined();
|
||||||
|
}, 10000); // Longer timeout for API calls
|
||||||
|
|
||||||
|
test("should get game deals", async () => {
|
||||||
|
const result = await callTool("game_deals", {
|
||||||
|
platform: "steam",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Should either return deals or handle API error gracefully
|
||||||
|
expect(result.content[0].text).toBeDefined();
|
||||||
|
}, 10000);
|
||||||
|
|
||||||
|
test("should add game to wishlist", async () => {
|
||||||
|
const result = await callTool("game_wishlist", {
|
||||||
|
action: "add",
|
||||||
|
gameName: "Test Game",
|
||||||
|
platform: "PC",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.content[0].text).toContain("added to wishlist");
|
||||||
|
expect(result.content[0].text).toContain("Test Game");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should list game wishlist", async () => {
|
||||||
|
// Add a game first
|
||||||
|
await callTool("game_wishlist", {
|
||||||
|
action: "add",
|
||||||
|
gameName: "Test Game",
|
||||||
|
platform: "PC",
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await callTool("game_wishlist", {
|
||||||
|
action: "list",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.content[0].text).toMatch(/wishlist|Test Game/i);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should remove game from wishlist", async () => {
|
||||||
|
// Add a game first
|
||||||
|
const addResult = await callTool("game_wishlist", {
|
||||||
|
action: "add",
|
||||||
|
gameName: "Test Game",
|
||||||
|
});
|
||||||
|
|
||||||
|
// Extract ID
|
||||||
|
const idMatch = addResult.content[0].text.match(/ID: ([a-f0-9-]+)/);
|
||||||
|
if (!idMatch) {
|
||||||
|
throw new Error("Could not extract ID");
|
||||||
|
}
|
||||||
|
const id = idMatch[1];
|
||||||
|
|
||||||
|
// Remove it
|
||||||
|
const result = await callTool("game_wishlist", {
|
||||||
|
action: "remove",
|
||||||
|
id,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.content[0].text).toContain("removed from wishlist");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should handle empty wishlist", async () => {
|
||||||
|
const result = await callTool("game_wishlist", {
|
||||||
|
action: "list",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.content[0].text).toMatch(/empty|Use game_wishlist.*add/i);
|
||||||
|
});
|
||||||
|
});
|
||||||
71
tests/unit/tools/programming/codeReview.test.ts
Normal file
71
tests/unit/tools/programming/codeReview.test.ts
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
/**
|
||||||
|
* Code review tools tests
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, test, expect, beforeEach } from "bun:test";
|
||||||
|
import { registerCodeReviewTools } from "../../../../src/tools/programming/codeReview.js";
|
||||||
|
import { callTool } from "../../../helpers/tool-helper.js";
|
||||||
|
|
||||||
|
describe("Code Review Tools", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
registerCodeReviewTools();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should review code and find issues", async () => {
|
||||||
|
const result = await callTool("code_review", {
|
||||||
|
code: "let x: any = 1;",
|
||||||
|
language: "typescript",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.content[0].text).toContain("Code Review");
|
||||||
|
expect(result.content[0].text).toContain("any");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should suggest improvements", async () => {
|
||||||
|
const result = await callTool("code_review", {
|
||||||
|
code: "console.log('test');",
|
||||||
|
language: "javascript",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.content[0].text).toContain("Suggestions");
|
||||||
|
expect(result.content[0].text).toContain("console.log");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should detect var usage", async () => {
|
||||||
|
const result = await callTool("code_review", {
|
||||||
|
code: "var x = 1;",
|
||||||
|
language: "javascript",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.content[0].text).toContain("var");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should provide optimization suggestions", async () => {
|
||||||
|
const result = await callTool("code_optimize", {
|
||||||
|
code: "if (x == 1) { }",
|
||||||
|
language: "javascript",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.content[0].text).toContain("Optimization");
|
||||||
|
expect(result.content[0].text).toContain("===");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should suggest Vue optimizations", async () => {
|
||||||
|
const result = await callTool("code_optimize", {
|
||||||
|
code: "<div v-for='item in items'>",
|
||||||
|
language: "vue",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.content[0].text).toContain("Optimization");
|
||||||
|
expect(result.content[0].text).toContain(":key");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should handle code with no issues", async () => {
|
||||||
|
const result = await callTool("code_review", {
|
||||||
|
code: "const x: number = 1;",
|
||||||
|
language: "typescript",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.content[0].text).toContain("Code Review");
|
||||||
|
});
|
||||||
|
});
|
||||||
100
tests/unit/tools/programming/codeSnippet.test.ts
Normal file
100
tests/unit/tools/programming/codeSnippet.test.ts
Normal file
@@ -0,0 +1,100 @@
|
|||||||
|
/**
|
||||||
|
* Code snippet tools tests
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, test, expect, beforeEach, afterEach } from "bun:test";
|
||||||
|
import { registerCodeSnippetTools } from "../../../../src/tools/programming/codeSnippet.js";
|
||||||
|
import { callTool } from "../../../helpers/tool-helper.js";
|
||||||
|
import { createTempDir } from "../../../helpers/test-utils.js";
|
||||||
|
import { setupTestDatabase } from "../../../helpers/database-helper.js";
|
||||||
|
|
||||||
|
describe("Code Snippet Tools", () => {
|
||||||
|
let testContext: ReturnType<typeof createTempDir>;
|
||||||
|
let cleanupDb: () => void;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
testContext = createTempDir();
|
||||||
|
cleanupDb = setupTestDatabase(testContext);
|
||||||
|
registerCodeSnippetTools();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
cleanupDb();
|
||||||
|
testContext.cleanup();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should save code snippet", async () => {
|
||||||
|
const result = await callTool("code_snippet_save", {
|
||||||
|
title: "Test Snippet",
|
||||||
|
code: "const x = 1;",
|
||||||
|
language: "typescript",
|
||||||
|
tags: ["test"],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.content[0].text).toContain("saved successfully");
|
||||||
|
expect(result.content[0].text).toContain("Test Snippet");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should search code snippets", async () => {
|
||||||
|
// First save a snippet
|
||||||
|
await callTool("code_snippet_save", {
|
||||||
|
title: "Test Snippet",
|
||||||
|
code: "const x = 1;",
|
||||||
|
language: "typescript",
|
||||||
|
tags: ["test"],
|
||||||
|
});
|
||||||
|
|
||||||
|
// Then search
|
||||||
|
const result = await callTool("code_snippet_search", {
|
||||||
|
query: "Test",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.content[0].text).toContain("Found");
|
||||||
|
expect(result.content[0].text).toContain("Test Snippet");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should list code snippets", async () => {
|
||||||
|
// Save a snippet
|
||||||
|
await callTool("code_snippet_save", {
|
||||||
|
title: "Test Snippet",
|
||||||
|
code: "const x = 1;",
|
||||||
|
language: "typescript",
|
||||||
|
tags: ["test"],
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await callTool("code_snippet_list", {});
|
||||||
|
|
||||||
|
expect(result.content[0].text).toContain("Total");
|
||||||
|
expect(result.content[0].text).toContain("Test Snippet");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should delete code snippet", async () => {
|
||||||
|
// Save a snippet
|
||||||
|
const saveResult = await callTool("code_snippet_save", {
|
||||||
|
title: "Test Snippet",
|
||||||
|
code: "const x = 1;",
|
||||||
|
language: "typescript",
|
||||||
|
tags: ["test"],
|
||||||
|
});
|
||||||
|
|
||||||
|
// Extract ID from save result
|
||||||
|
const idMatch = saveResult.content[0].text.match(/ID: ([a-f0-9-]+)/);
|
||||||
|
if (!idMatch) {
|
||||||
|
throw new Error("Could not extract ID from save result");
|
||||||
|
}
|
||||||
|
const id = idMatch[1];
|
||||||
|
|
||||||
|
// Delete it
|
||||||
|
const result = await callTool("code_snippet_delete", { id });
|
||||||
|
|
||||||
|
expect(result.content[0].text).toContain("deleted successfully");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should handle search with no results", async () => {
|
||||||
|
const result = await callTool("code_snippet_search", {
|
||||||
|
query: "NonExistent",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.content[0].text).toContain("No code snippets found");
|
||||||
|
});
|
||||||
|
});
|
||||||
61
tests/unit/tools/programming/docs.test.ts
Normal file
61
tests/unit/tools/programming/docs.test.ts
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
/**
|
||||||
|
* Documentation tools tests
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, test, expect, beforeEach } from "bun:test";
|
||||||
|
import { registerDocsTools } from "../../../../src/tools/programming/docs.js";
|
||||||
|
import { callTool } from "../../../helpers/tool-helper.js";
|
||||||
|
|
||||||
|
describe("Documentation Tools", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
registerDocsTools();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should get TypeScript documentation", async () => {
|
||||||
|
const result = await callTool("docs_typescript", {});
|
||||||
|
|
||||||
|
expect(result.content[0].text).toContain("TypeScript Documentation");
|
||||||
|
expect(result.content[0].text).toContain("typescriptlang.org");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should get TypeScript documentation with topic", async () => {
|
||||||
|
const result = await callTool("docs_typescript", {
|
||||||
|
topic: "generics",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.content[0].text).toContain("TypeScript Documentation");
|
||||||
|
expect(result.content[0].text).toContain("generics");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should get Vue3 documentation", async () => {
|
||||||
|
const result = await callTool("docs_vue3", {});
|
||||||
|
|
||||||
|
expect(result.content[0].text).toContain("Vue 3 Documentation");
|
||||||
|
expect(result.content[0].text).toContain("vuejs.org");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should get Vue3 documentation with topic", async () => {
|
||||||
|
const result = await callTool("docs_vue3", {
|
||||||
|
topic: "composition",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.content[0].text).toContain("Vue 3 Documentation");
|
||||||
|
expect(result.content[0].text).toContain("composition");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should get Bun documentation", async () => {
|
||||||
|
const result = await callTool("docs_bun", {});
|
||||||
|
|
||||||
|
expect(result.content[0].text).toContain("Bun Documentation");
|
||||||
|
expect(result.content[0].text).toContain("bun.sh");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should get Bun documentation with topic", async () => {
|
||||||
|
const result = await callTool("docs_bun", {
|
||||||
|
topic: "runtime",
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.content[0].text).toContain("Bun Documentation");
|
||||||
|
expect(result.content[0].text).toContain("runtime");
|
||||||
|
});
|
||||||
|
});
|
||||||
98
tests/unit/tools/programming/projectTemplate.test.ts
Normal file
98
tests/unit/tools/programming/projectTemplate.test.ts
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
/**
|
||||||
|
* Project template tools tests
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { describe, test, expect, beforeEach, afterEach } from "bun:test";
|
||||||
|
import { registerProjectTemplateTools } from "../../../../src/tools/programming/projectTemplate.js";
|
||||||
|
import { callTool } from "../../../helpers/tool-helper.js";
|
||||||
|
import { createTempDir } from "../../../helpers/test-utils.js";
|
||||||
|
import { existsSync, readFileSync } from "fs";
|
||||||
|
import { join } from "path";
|
||||||
|
|
||||||
|
describe("Project Template Tools", () => {
|
||||||
|
let testContext: ReturnType<typeof createTempDir>;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
testContext = createTempDir();
|
||||||
|
registerProjectTemplateTools();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
testContext.cleanup();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should create Vite + Vue3 project", async () => {
|
||||||
|
const projectName = "test-vue-project";
|
||||||
|
const projectPath = join(testContext.tempDir, projectName);
|
||||||
|
|
||||||
|
const result = await callTool("project_template_create", {
|
||||||
|
name: projectName,
|
||||||
|
path: testContext.tempDir,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.content[0].text).toContain("created successfully");
|
||||||
|
expect(existsSync(join(projectPath, "package.json"))).toBe(true);
|
||||||
|
expect(existsSync(join(projectPath, "vite.config.ts"))).toBe(true);
|
||||||
|
expect(existsSync(join(projectPath, "src", "main.ts"))).toBe(true);
|
||||||
|
expect(existsSync(join(projectPath, "src", "App.vue"))).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should create project with Pinia", async () => {
|
||||||
|
const projectName = "test-vue-pinia";
|
||||||
|
const projectPath = join(testContext.tempDir, projectName);
|
||||||
|
|
||||||
|
await callTool("project_template_create", {
|
||||||
|
name: projectName,
|
||||||
|
path: testContext.tempDir,
|
||||||
|
usePinia: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const packageJson = JSON.parse(
|
||||||
|
readFileSync(join(projectPath, "package.json"), "utf-8")
|
||||||
|
);
|
||||||
|
expect(packageJson.dependencies.pinia).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should create fullstack project", async () => {
|
||||||
|
const projectName = "test-fullstack";
|
||||||
|
const projectPath = join(testContext.tempDir, projectName);
|
||||||
|
|
||||||
|
const result = await callTool("project_template_create_fullstack", {
|
||||||
|
name: projectName,
|
||||||
|
path: testContext.tempDir,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.content[0].text).toContain("created successfully");
|
||||||
|
expect(existsSync(join(projectPath, "frontend", "package.json"))).toBe(
|
||||||
|
true
|
||||||
|
);
|
||||||
|
expect(existsSync(join(projectPath, "backend", "package.json"))).toBe(true);
|
||||||
|
expect(existsSync(join(projectPath, "backend", "src", "index.ts"))).toBe(
|
||||||
|
true
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should list available templates", async () => {
|
||||||
|
const result = await callTool("project_template_list", {});
|
||||||
|
|
||||||
|
expect(result.content[0].text).toContain("Available project templates");
|
||||||
|
expect(result.content[0].text).toContain("Vite + Vue3");
|
||||||
|
expect(result.content[0].text).toContain("Fullstack");
|
||||||
|
});
|
||||||
|
|
||||||
|
test("should handle existing directory error", async () => {
|
||||||
|
const projectName = "existing-project";
|
||||||
|
const projectPath = join(testContext.tempDir, projectName);
|
||||||
|
|
||||||
|
// Create directory first
|
||||||
|
const { mkdirSync } = await import("fs");
|
||||||
|
mkdirSync(projectPath, { recursive: true });
|
||||||
|
|
||||||
|
const result = await callTool("project_template_create", {
|
||||||
|
name: projectName,
|
||||||
|
path: testContext.tempDir,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.content[0].text).toContain("already exists");
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user