Skip to main content
Glama
lis186

Taiwan Holiday MCP Server

by lis186
build-and-package.test.ts12 kB
import { spawn, ChildProcess } from 'child_process'; import { promises as fs } from 'fs'; import { join } from 'path'; import { platform } from 'os'; describe('建置與打包完整測試', () => { const projectRoot = process.cwd(); const distPath = join(projectRoot, 'dist'); describe('T5.2.1: 建置腳本測試', () => { test('應該正確清理輸出目錄', async () => { // 建立臨時測試目錄而不是清理實際的 dist 目錄 const tempDistPath = join(projectRoot, 'temp-dist-test'); // 建立測試目錄和檔案 await fs.mkdir(tempDistPath, { recursive: true }); await fs.writeFile(join(tempDistPath, 'test-file.txt'), 'test'); // 執行清理命令(使用 shx rm -rf) await new Promise<void>((resolve, reject) => { const cleanProcess = spawn('npx', ['shx', 'rm', '-rf', tempDistPath], { cwd: projectRoot, stdio: 'pipe' }); cleanProcess.on('close', (code) => { if (code === 0) { resolve(); } else { reject(new Error(`清理失敗,退出代碼: ${code}`)); } }); }); // 檢查目錄是否被清理 try { await fs.access(tempDistPath); fail('測試目錄應該被清理'); } catch (error) { // 預期的錯誤,目錄不存在 expect(error).toBeDefined(); } }); test('應該生成所有必要的檔案', async () => { // 檢查必要檔案(不重新建置,使用全域建置的結果) const requiredFiles = [ 'index.js', 'index.d.ts', 'index.js.map', 'index.d.ts.map', 'server.js', 'server.d.ts', 'holiday-service.js', 'holiday-service.d.ts', 'types.js', 'types.d.ts', 'utils/date-parser.js', 'utils/date-parser.d.ts' ]; for (const file of requiredFiles) { const filePath = join(distPath, file); await expect(fs.access(filePath)).resolves.toBeUndefined(); } }); test('應該設定正確的檔案權限', async () => { const indexPath = join(distPath, 'index.js'); const stats = await fs.stat(indexPath); // 檢查檔案是否可執行 const isExecutable = (stats.mode & parseInt('111', 8)) !== 0; expect(isExecutable).toBe(true); }); test('應該生成有效的 Source Maps', async () => { const sourceMapPath = join(distPath, 'index.js.map'); const sourceMapContent = await fs.readFile(sourceMapPath, 'utf-8'); const sourceMap = JSON.parse(sourceMapContent); expect(sourceMap.version).toBe(3); expect(sourceMap.sources).toBeDefined(); expect(sourceMap.mappings).toBeDefined(); }); }); describe('T5.2.2: NPX 執行測試', () => { test('應該正確處理 --version 參數', async () => { const result = await runCommand('node', [join(distPath, 'index.js'), '--version']); expect(result.exitCode).toBe(0); expect(result.stderr).toContain('Taiwan Holiday MCP Server v1.0.5'); expect(result.stderr).toContain('Node.js'); expect(result.stderr).toContain('Platform:'); }); test('應該正確處理 --help 參數', async () => { const result = await runCommand('node', [join(distPath, 'index.js'), '--help']); expect(result.exitCode).toBe(0); expect(result.stderr).toContain('Taiwan Holiday MCP Server'); expect(result.stderr).toContain('用法:'); expect(result.stderr).toContain('選項:'); expect(result.stderr).toContain('環境變數:'); }); test('應該正確處理無效參數', async () => { const result = await runCommand('node', [join(distPath, 'index.js'), '--invalid']); expect(result.exitCode).toBe(1); expect(result.stderr).toContain('未知選項'); expect(result.stderr).toContain('--help'); }); test('應該支援除錯模式', async () => { const result = await runCommand('node', [join(distPath, 'index.js'), '--debug'], { timeout: 2000, killSignal: 'SIGTERM' }); // 除錯模式應該啟動伺服器 expect(result.stderr).toContain('Taiwan Holiday MCP 伺服器已啟動'); expect(result.stderr).toContain('除錯模式'); }); test('效能測試:啟動時間應該合理', async () => { const startTime = Date.now(); const result = await runCommand('node', [join(distPath, 'index.js'), '--version']); const endTime = Date.now(); const startupTime = endTime - startTime; expect(result.exitCode).toBe(0); expect(startupTime).toBeLessThan(2000); // 啟動時間應該少於 2 秒 }); }); describe('T5.2.3: 端到端 MCP 流程測試', () => { test('應該正確處理 MCP 工具列表查詢', async () => { const mcpRequest = JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'tools/list' }); const result = await runCommandWithInput('node', [join(distPath, 'index.js')], mcpRequest); expect(result.exitCode).toBe(0); if (!result.stdout.trim()) { throw new Error(`Empty stdout. stderr: ${result.stderr}`); } const response = JSON.parse(result.stdout.trim()); expect(response.jsonrpc).toBe('2.0'); expect(response.id).toBe(1); expect(response.result.tools).toHaveLength(3); const toolNames = response.result.tools.map((tool: any) => tool.name); expect(toolNames).toContain('check_holiday'); expect(toolNames).toContain('get_holidays_in_range'); expect(toolNames).toContain('get_holiday_stats'); }); test('應該正確處理假期查詢', async () => { const mcpRequest = JSON.stringify({ jsonrpc: '2.0', id: 2, method: 'tools/call', params: { name: 'check_holiday', arguments: { date: '2024-01-01' } } }); const result = await runCommandWithInput('node', [join(distPath, 'index.js')], mcpRequest); expect(result.exitCode).toBe(0); if (!result.stdout.trim()) { throw new Error(`Empty stdout. stderr: ${result.stderr}`); } const response = JSON.parse(result.stdout.trim()); expect(response.jsonrpc).toBe('2.0'); expect(response.id).toBe(2); expect(response.result.content[0].text).toContain('開國紀念日'); }); test('應該正確處理錯誤情況', async () => { const mcpRequest = JSON.stringify({ jsonrpc: '2.0', id: 3, method: 'tools/call', params: { name: 'check_holiday', arguments: { date: 'invalid-date' } } }); const result = await runCommandWithInput('node', [join(distPath, 'index.js')], mcpRequest); expect(result.exitCode).toBe(0); if (!result.stdout.trim()) { throw new Error(`Empty stdout. stderr: ${result.stderr}`); } const response = JSON.parse(result.stdout.trim()); expect(response.jsonrpc).toBe('2.0'); expect(response.id).toBe(3); expect(response.result.isError).toBe(true); expect(response.result.content[0].text).toContain('日期格式'); }); test('記憶體洩漏測試:多次請求後記憶體應該穩定', async () => { const requests = Array.from({ length: 10 }, (_, i) => JSON.stringify({ jsonrpc: '2.0', id: i + 1, method: 'tools/call', params: { name: 'check_holiday', arguments: { date: '2024-01-01' } } }) ); const input = requests.join('\n'); const result = await runCommandWithInput('node', [join(distPath, 'index.js')], input, { timeout: 15000 }); expect(result.exitCode).toBe(0); // 檢查所有回應都正確 if (!result.stdout.trim()) { throw new Error(`Empty stdout. stderr: ${result.stderr}`); } const responses = result.stdout.trim().split('\n').filter(line => line.trim()).map(line => JSON.parse(line)); expect(responses.length).toBeGreaterThanOrEqual(9); // 允許部分丟失 responses.forEach((response) => { expect(response.jsonrpc).toBe('2.0'); expect(response.id).toBeGreaterThan(0); expect(response.id).toBeLessThanOrEqual(10); expect(response.result).toBeDefined(); }); }, 20000); }); describe('套件打包測試', () => { test('應該能夠成功打包', async () => { // npm pack --dry-run 需要先執行 build,需要更長的 timeout const result = await runCommand('npm', ['run', 'package:test'], { timeout: 30000 }); expect(result.exitCode).toBe(0); expect(result.stdout).toContain('taiwan-holiday-mcp-1.0.5.tgz'); expect(result.stdout).toContain('dist/'); }, 35000); // Jest 測試 timeout 略大於 runCommand timeout test('打包內容應該包含必要檔案', async () => { const result = await runCommand('npm', ['pack', '--dry-run'], { timeout: 15000 }); expect(result.exitCode).toBe(0); // 檢查是否有 tarball 檔案名稱 expect(result.stdout).toContain('taiwan-holiday-mcp-1.0.5.tgz'); // 如果有詳細內容列表,檢查必要檔案 if (result.stdout.includes('npm notice Tarball Contents')) { expect(result.stdout).toContain('dist/index.js'); expect(result.stdout).toContain('dist/index.d.ts'); expect(result.stdout).toContain('README.md'); expect(result.stdout).toContain('package.json'); } }, 20000); // Jest 測試 timeout 略大於 runCommand timeout }); }); // 輔助函數 interface CommandResult { exitCode: number; stdout: string; stderr: string; } interface CommandOptions { timeout?: number; killSignal?: NodeJS.Signals; } function runCommand( command: string, args: string[], options: CommandOptions = {} ): Promise<CommandResult> { const projectRoot = process.cwd(); return new Promise((resolve) => { const child = spawn(command, args, { stdio: 'pipe', cwd: projectRoot }); let stdout = ''; let stderr = ''; child.stdout?.on('data', (data) => { stdout += data.toString(); }); child.stderr?.on('data', (data) => { stderr += data.toString(); }); const timeout = options.timeout || 5000; const timer = setTimeout(() => { child.kill(options.killSignal || 'SIGTERM'); }, timeout); child.on('close', (code) => { clearTimeout(timer); resolve({ exitCode: code || 0, stdout, stderr }); }); }); } function runCommandWithInput( command: string, args: string[], input: string, options: CommandOptions = {} ): Promise<CommandResult> { const projectRoot = process.cwd(); return new Promise((resolve) => { const child = spawn(command, args, { stdio: 'pipe', cwd: projectRoot }); let stdout = ''; let stderr = ''; child.stdout?.on('data', (data) => { stdout += data.toString(); }); child.stderr?.on('data', (data) => { stderr += data.toString(); }); // 發送輸入並關閉 stdin if (child.stdin) { child.stdin.write(input + '\n'); child.stdin.end(); } const timeout = options.timeout || 5000; const timer = setTimeout(() => { child.kill('SIGTERM'); }, timeout); child.on('close', (code) => { clearTimeout(timer); resolve({ exitCode: code || 0, stdout, stderr }); }); child.on('error', (error) => { clearTimeout(timer); resolve({ exitCode: 1, stdout, stderr: stderr + error.message }); }); }); }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/lis186/taiwan-holiday-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server