feat: improve error handling and add initialization checks

- Add custom error types and error codes
- Add configuration validation
- Add dependency checks
- Add safe cleanup handling
- Improve code organization
This commit is contained in:
kevinwatt 2025-02-21 00:34:18 +08:00
parent 480e2c62ad
commit ae532256e3
7 changed files with 4365 additions and 98 deletions

13
jest.config.mjs Normal file
View File

@ -0,0 +1,13 @@
export default {
preset: 'ts-jest/presets/default-esm',
testEnvironment: 'node',
extensionsToTreatAsEsm: ['.ts', '.mts'],
moduleNameMapper: {
'^(\\.{1,2}/.*)\\.m?js$': '$1',
},
transform: {
'^.+\\.m?[tj]s$': ['ts-jest', {
useESM: true,
}],
},
};

3867
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{
"name": "@kevinwatt/yt-dlp-mcp",
"version": "0.6.9",
"version": "0.6.10",
"description": "yt-dlp MCP Server - Download video content via Model Context Protocol",
"keywords": [
"mcp",
@ -26,7 +26,8 @@
],
"main": "./lib/index.mjs",
"scripts": {
"prepare": "tsc && shx chmod +x ./lib/index.mjs"
"prepare": "tsc && shx chmod +x ./lib/index.mjs",
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js --detectOpenHandles --forceExit"
},
"author": "Dewei Yen <k@funmula.com>",
"license": "MIT",
@ -42,7 +43,11 @@
"spawn-rx": "^4.0.0"
},
"devDependencies": {
"@jest/globals": "^29.7.0",
"@types/jest": "^29.5.14",
"jest": "^29.7.0",
"shx": "^0.3.4",
"ts-jest": "^29.2.5",
"ts-node": "^10.9.2",
"typescript": "^5.6.3"
}

View File

@ -0,0 +1,79 @@
// @ts-nocheck
// @jest-environment node
import { jest } from '@jest/globals';
import { describe, test, expect, beforeAll, afterAll, beforeEach } from '@jest/globals';
import * as path from 'path';
import * as os from 'os';
// 簡化 mock
jest.mock('spawn-rx', () => ({
spawnPromise: jest.fn().mockImplementation(async (cmd, args) => {
if (args.includes('--get-filename')) {
return 'mock_video.mp4';
}
return 'Download completed';
})
}));
jest.mock('rimraf', () => ({
rimraf: { sync: jest.fn() }
}));
import { downloadVideo } from '../index.mts';
describe('downloadVideo', () => {
const mockTimestamp = '2024-03-20_12-30-00';
let originalDateToISOString: () => string;
// 全局清理
afterAll(done => {
// 清理所有計時器
jest.useRealTimers();
// 確保所有異步操作完成
process.nextTick(done);
});
beforeAll(() => {
originalDateToISOString = Date.prototype.toISOString;
Date.prototype.toISOString = jest.fn(() => '2024-03-20T12:30:00.000Z');
});
afterAll(() => {
Date.prototype.toISOString = originalDateToISOString;
});
beforeEach(() => {
jest.clearAllMocks();
});
test('downloads video successfully with correct format', async () => {
const result = await downloadVideo('https://www.youtube.com/watch?v=dQw4w9WgXcQ');
// 驗證基本功能
expect(result).toMatch(/Video successfully downloaded as/);
expect(result).toContain(mockTimestamp);
expect(result).toContain(os.homedir());
expect(result).toContain('Downloads');
});
test('handles special characters in video URL', async () => {
// 使用有效的視頻 ID但包含需要編碼的字符
const result = await downloadVideo('https://www.youtube.com/watch?v=dQw4w9WgXcQ&title=特殊字符');
expect(result).toMatch(/Video successfully downloaded as/);
expect(result).toContain(mockTimestamp);
});
test('uses correct resolution format', async () => {
const resolutions = ['480p', '720p', '1080p', 'best'];
// 使用 Promise.all 並行執行測試
const results = await Promise.all(resolutions.map(resolution => downloadVideo(
'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
resolution
)));
results.forEach(result => {
expect(result).toMatch(/Video successfully downloaded as/);
});
});
});

File diff suppressed because it is too large Load Diff

8
tsconfig.jest.json Normal file
View File

@ -0,0 +1,8 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "./lib",
"module": "ES2020",
"target": "ES2020"
}
}

View File

@ -10,10 +10,10 @@
"noUnusedLocals": true,
"noImplicitThis": true,
"noUnusedParameters": true,
"module": "node16",
"moduleResolution": "node16",
"module": "ES2020",
"moduleResolution": "node",
"pretty": true,
"target": "es2015",
"target": "ES2020",
"outDir": "lib",
"lib": ["dom", "es2015"],
"esModuleInterop": true,