Skip to main content
Glama

tv-recommender-mcp-server

discoverShowsTool.test.ts26.9 kB
import { discoverShows, findShowsByPersonName, getRecommendationsByActor, findPersonId, getPersonTvCredits, convertCastToIds, findKeywordId } from '../tools/discoverShowsTool'; import { tmdbClient } from '../services/tmdbClient'; import { ApiError } from '../utils/errorHandler'; // 模拟 tmdbClient 和其他依赖 jest.mock('../services/tmdbClient', () => { return { tmdbClient: { discoverTvShows: jest.fn(), searchPerson: jest.fn(), searchKeyword: jest.fn(), getPersonTvCredits: jest.fn(), } }; }); // 模拟 console.log 以便测试日志输出 const originalConsoleLog = console.log; beforeAll(() => { console.log = jest.fn(); }); afterAll(() => { console.log = originalConsoleLog; }); jest.mock('../utils/genreMap', () => ({ mapGenreToId: jest.fn(), getGenreNameById: jest.fn() })); jest.mock('../utils/errorHandler', () => { const original = jest.requireActual('../utils/errorHandler'); return { ...original, formatErrorMessage: jest.fn(error => error instanceof Error ? error.message : String(error)), ApiError: original.ApiError }; }); describe('discoverShows Tool', () => { beforeEach(() => { jest.clearAllMocks(); // 设置默认的 mapGenreToId 实现 const { mapGenreToId } = require('../utils/genreMap'); (mapGenreToId as jest.Mock).mockImplementation((genre) => { if (genre === '喜剧') return 35; if (genre === '科幻') return 10765; return null; }); }); it('应该使用默认参数正确调用API', async () => { // 模拟API响应 const mockResponse = { page: 1, results: [ { id: 1, name: '测试剧集1', overview: '测试简介1', poster_path: '/path1.jpg', vote_average: 8.5, first_air_date: '2022-01-01' }, { id: 2, name: '测试剧集2', overview: '测试简介2', poster_path: '/path2.jpg', vote_average: 7.5, first_air_date: '2021-02-01' } ], total_pages: 10, total_results: 200 }; (tmdbClient.discoverTvShows as jest.Mock).mockResolvedValue(mockResponse); // 执行测试 const result = await discoverShows({}); // 验证调用 expect(tmdbClient.discoverTvShows).toHaveBeenCalledWith( expect.objectContaining({ sort_by: 'popularity.desc', page: 1 }) ); // 验证结果格式 expect(result).toEqual({ page: 1, results: [ { id: 1, name: '测试剧集1', overview: '测试简介1', poster_path: '/path1.jpg', vote_average: 8.5, first_air_date: '2022-01-01' }, { id: 2, name: '测试剧集2', overview: '测试简介2', poster_path: '/path2.jpg', vote_average: 7.5, first_air_date: '2021-02-01' } ], total_pages: 10, total_results: 2 }); }); it('应该正确处理类型参数', async () => { // 模拟API响应 const mockResponse = { page: 1, results: [], total_pages: 1, total_results: 0 }; (tmdbClient.discoverTvShows as jest.Mock).mockResolvedValue(mockResponse); // 执行测试 await discoverShows({ with_genres: ['喜剧', '科幻'] }); // 验证调用 expect(tmdbClient.discoverTvShows).toHaveBeenCalledWith( expect.objectContaining({ with_genres: '35,10765' }) ); }); it('应该抛出错误当无法识别所有类型', async () => { const { mapGenreToId } = require('../utils/genreMap'); (mapGenreToId as jest.Mock).mockReturnValue(null); // 执行测试并验证错误 await expect(discoverShows({ with_genres: ['未知类型'] })).rejects.toThrow('无法识别提供的类型'); }); it('应该使用findShowsByPersonName查找演员参与的作品', async () => { // 模拟API响应 const mockPersonResponse = { results: [{ id: 123 }] }; const mockCreditsResponse = { cast: [ { id: 1, name: '测试剧集1', overview: '测试简介1', poster_path: '/path1.jpg', vote_average: 8.5, first_air_date: '2022-01-01' } ] }; (tmdbClient.searchPerson as jest.Mock).mockResolvedValue(mockPersonResponse); (tmdbClient.getPersonTvCredits as jest.Mock).mockResolvedValue(mockCreditsResponse); // 执行测试 const result = await findShowsByPersonName('布莱恩·科兰斯顿'); // 验证调用 expect(tmdbClient.searchPerson).toHaveBeenCalledWith('布莱恩·科兰斯顿'); expect(tmdbClient.getPersonTvCredits).toHaveBeenCalledWith(123); // 验证结果 expect(result.results).toHaveLength(1); expect(result.results[0].name).toBe('测试剧集1'); }); it('应该使用getRecommendationsByActor获取演员推荐', async () => { // 模拟API响应 const mockPersonResponse = { results: [{ id: 123 }] }; const mockCreditsResponse = { cast: [ { id: 1, name: '测试剧集1', overview: '测试简介1', poster_path: '/path1.jpg', vote_average: 8.5, first_air_date: '2022-01-01' }, { id: 2, name: '测试剧集2', overview: '测试简介2', poster_path: '/path2.jpg', vote_average: 7.5, first_air_date: '2021-02-01' } ] }; (tmdbClient.searchPerson as jest.Mock).mockResolvedValue(mockPersonResponse); (tmdbClient.getPersonTvCredits as jest.Mock).mockResolvedValue(mockCreditsResponse); // 执行测试 const result = await getRecommendationsByActor('布莱恩·科兰斯顿', 5); // 验证调用 expect(tmdbClient.searchPerson).toHaveBeenCalledWith('布莱恩·科兰斯顿'); expect(tmdbClient.getPersonTvCredits).toHaveBeenCalledWith(123); // 验证结果 expect(result.results).toHaveLength(2); // 验证按评分排序 expect(result.results[0].vote_average).toBe(8.5); expect(result.results[1].vote_average).toBe(7.5); }); it('findShowsByPersonName应该在找不到人物时抛出错误', async () => { (tmdbClient.searchPerson as jest.Mock).mockResolvedValue({ results: [] }); // 执行测试并验证错误 await expect(findShowsByPersonName('未知演员')).rejects.toThrow('未找到人物'); }); it('getRecommendationsByActor应该在找不到演员时抛出错误', async () => { (tmdbClient.searchPerson as jest.Mock).mockResolvedValue({ results: [] }); // 执行测试并验证错误 await expect(getRecommendationsByActor('未知演员')).rejects.toThrow('未找到演员'); }); it('应该正确处理年份参数并过滤结果', async () => { // 模拟API响应,包含不同年份的剧集 const mockResponse = { page: 1, results: [ { id: 1, name: '2022剧集', overview: '测试简介', poster_path: '/path.jpg', vote_average: 8.5, first_air_date: '2022-05-15' }, { id: 2, name: '2023剧集', overview: '测试简介', poster_path: '/path.jpg', vote_average: 7.5, first_air_date: '2023-01-10' }, { id: 3, name: '无日期剧集', overview: '测试简介', poster_path: '/path.jpg', vote_average: 6.5, first_air_date: null } ], total_pages: 1, total_results: 3 }; (tmdbClient.discoverTvShows as jest.Mock).mockResolvedValue(mockResponse); // 执行测试 const result = await discoverShows({ first_air_date_year: 2022 }); // 验证API调用参数 expect(tmdbClient.discoverTvShows).toHaveBeenCalledWith( expect.objectContaining({ 'first_air_date.gte': '2022-01-01', 'first_air_date.lte': '2022-12-31' }) ); // 验证结果过滤 expect(result.results.length).toBe(1); expect(result.results[0].name).toBe('2022剧集'); expect(result.total_results).toBe(1); // 验证日志输出 expect(console.log).toHaveBeenCalledWith('过滤前剧集数量: 3'); expect(console.log).toHaveBeenCalledWith( expect.stringContaining('无日期剧集') && expect.stringContaining('缺少首播日期') ); expect(console.log).toHaveBeenCalledWith( expect.stringContaining('2023剧集') && expect.stringContaining('不匹配') ); expect(console.log).toHaveBeenCalledWith('过滤后剧集数量: 1'); }); it('应该正确处理评分参数', async () => { // 模拟API响应 const mockResponse = { page: 1, results: [], total_pages: 1, total_results: 0 }; (tmdbClient.discoverTvShows as jest.Mock).mockResolvedValue(mockResponse); // 执行测试 await discoverShows({ vote_average_gte: 8.0 }); // 验证调用 expect(tmdbClient.discoverTvShows).toHaveBeenCalledWith( expect.objectContaining({ 'vote_average.gte': 8.0 }) ); }); it('应该正确处理网络参数', async () => { // 模拟API响应 const mockResponse = { page: 1, results: [], total_pages: 1, total_results: 0 }; (tmdbClient.discoverTvShows as jest.Mock).mockResolvedValue(mockResponse); // 执行测试 await discoverShows({ with_networks: [213, 19] }); // 验证调用 expect(tmdbClient.discoverTvShows).toHaveBeenCalledWith( expect.objectContaining({ with_networks: '213,19' }) ); }); it('应该正确处理关键词参数', async () => { // 模拟关键词搜索响应 const mockKeywordResponse = { results: [{ id: 789 }] }; (tmdbClient.searchKeyword as jest.Mock).mockResolvedValue(mockKeywordResponse); // 模拟API响应 const mockDiscoverResponse = { page: 1, results: [], total_pages: 1, total_results: 0 }; (tmdbClient.discoverTvShows as jest.Mock).mockResolvedValue(mockDiscoverResponse); // 执行测试 await discoverShows({ with_keywords: ['太空', '机器人'] }); // 验证关键词搜索被正确调用 expect(tmdbClient.searchKeyword).toHaveBeenCalledWith('太空'); expect(tmdbClient.searchKeyword).toHaveBeenCalledWith('机器人'); // 验证discover调用使用了转换后的关键词ID expect(tmdbClient.discoverTvShows).toHaveBeenCalledWith( expect.objectContaining({ with_keywords: '789,789' }) ); }); it('应该抛出错误当无法识别所有关键词', async () => { // 模拟关键词搜索返回空结果 (tmdbClient.searchKeyword as jest.Mock).mockResolvedValue({ results: [] }); // 执行测试并验证错误 await expect(discoverShows({ with_keywords: ['未知关键词'] })).rejects.toThrow('无法找到关键词'); }); it('应该正确处理网络参数', async () => { const mockResponse = { page: 1, results: [], total_pages: 1, total_results: 0 }; (tmdbClient.discoverTvShows as jest.Mock).mockResolvedValue(mockResponse); // 执行测试 await discoverShows({ with_networks: [213, 1024] // Netflix, XXX网络 }); // 验证调用 expect(tmdbClient.discoverTvShows).toHaveBeenCalledWith( expect.objectContaining({ with_networks: '213,1024' }) ); }); it('应该正确处理排序参数', async () => { const mockResponse = { page: 1, results: [], total_pages: 1, total_results: 0 }; (tmdbClient.discoverTvShows as jest.Mock).mockResolvedValue(mockResponse); // 执行测试 await discoverShows({ sort_by: 'first_air_date.desc' }); // 验证调用 expect(tmdbClient.discoverTvShows).toHaveBeenCalledWith( expect.objectContaining({ sort_by: 'first_air_date.desc' }) ); }); it('应该正确处理评分参数', async () => { const mockResponse = { page: 1, results: [], total_pages: 1, total_results: 0 }; (tmdbClient.discoverTvShows as jest.Mock).mockResolvedValue(mockResponse); // 执行测试 await discoverShows({ vote_average_gte: 8.5 }); // 验证调用 expect(tmdbClient.discoverTvShows).toHaveBeenCalledWith( expect.objectContaining({ 'vote_average.gte': 8.5 }) ); }); it('应该正确处理语言参数', async () => { const mockResponse = { page: 1, results: [], total_pages: 1, total_results: 0 }; (tmdbClient.discoverTvShows as jest.Mock).mockResolvedValue(mockResponse); // 执行测试 await discoverShows({ with_original_language: 'en' }); // 验证调用 expect(tmdbClient.discoverTvShows).toHaveBeenCalledWith( expect.objectContaining({ with_original_language: 'en' }) ); }); it('应该正确处理成人内容参数', async () => { const mockResponse = { page: 1, results: [], total_pages: 1, total_results: 0 }; (tmdbClient.discoverTvShows as jest.Mock).mockResolvedValue(mockResponse); // 执行测试 await discoverShows({ include_adult: false }); // 验证调用 expect(tmdbClient.discoverTvShows).toHaveBeenCalledWith( expect.objectContaining({ include_adult: false }) ); }); it('应该正确处理无首播日期参数', async () => { const mockResponse = { page: 1, results: [], total_pages: 1, total_results: 0 }; (tmdbClient.discoverTvShows as jest.Mock).mockResolvedValue(mockResponse); // 执行测试 await discoverShows({ include_null_first_air_dates: true }); // 验证调用 expect(tmdbClient.discoverTvShows).toHaveBeenCalledWith( expect.objectContaining({ include_null_first_air_dates: true }) ); }); it('应该正确处理原产国参数', async () => { const mockResponse = { page: 1, results: [], total_pages: 1, total_results: 0 }; (tmdbClient.discoverTvShows as jest.Mock).mockResolvedValue(mockResponse); // 执行测试 await discoverShows({ with_origin_country: 'US' }); // 验证调用 expect(tmdbClient.discoverTvShows).toHaveBeenCalledWith( expect.objectContaining({ with_origin_country: 'US' }) ); }); it('应该正确处理状态参数', async () => { const mockResponse = { page: 1, results: [], total_pages: 1, total_results: 0 }; (tmdbClient.discoverTvShows as jest.Mock).mockResolvedValue(mockResponse); // 执行测试 await discoverShows({ with_status: '0' }); // 验证调用 expect(tmdbClient.discoverTvShows).toHaveBeenCalledWith( expect.objectContaining({ with_status: '0' }) ); }); it('应该正确处理院线放映参数', async () => { const mockResponse = { page: 1, results: [], total_pages: 1, total_results: 0 }; (tmdbClient.discoverTvShows as jest.Mock).mockResolvedValue(mockResponse); // 执行测试 await discoverShows({ screened_theatrically: true }); // 验证调用 expect(tmdbClient.discoverTvShows).toHaveBeenCalledWith( expect.objectContaining({ screened_theatrically: true }) ); }); it('应该正确处理时区参数', async () => { const mockResponse = { page: 1, results: [], total_pages: 1, total_results: 0 }; (tmdbClient.discoverTvShows as jest.Mock).mockResolvedValue(mockResponse); // 执行测试 await discoverShows({ timezone: 'America/New_York' }); // 验证调用 expect(tmdbClient.discoverTvShows).toHaveBeenCalledWith( expect.objectContaining({ timezone: 'America/New_York' }) ); }); it('应该正确处理观看区域参数', async () => { const mockResponse = { page: 1, results: [], total_pages: 1, total_results: 0 }; (tmdbClient.discoverTvShows as jest.Mock).mockResolvedValue(mockResponse); // 执行测试 await discoverShows({ watch_region: 'US' }); // 验证调用 expect(tmdbClient.discoverTvShows).toHaveBeenCalledWith( expect.objectContaining({ watch_region: 'US' }) ); }); it('应该正确处理观看提供商参数', async () => { const mockResponse = { page: 1, results: [], total_pages: 1, total_results: 0 }; (tmdbClient.discoverTvShows as jest.Mock).mockResolvedValue(mockResponse); // 执行测试 await discoverShows({ with_watch_providers: '8|9' }); // 验证调用 expect(tmdbClient.discoverTvShows).toHaveBeenCalledWith( expect.objectContaining({ with_watch_providers: '8|9' }) ); }); it('应该正确处理观看货币化类型参数', async () => { const mockResponse = { page: 1, results: [], total_pages: 1, total_results: 0 }; (tmdbClient.discoverTvShows as jest.Mock).mockResolvedValue(mockResponse); // 执行测试 await discoverShows({ with_watch_monetization_types: 'flatrate' }); // 验证调用 expect(tmdbClient.discoverTvShows).toHaveBeenCalledWith( expect.objectContaining({ with_watch_monetization_types: 'flatrate' }) ); }); it('应该正确处理制作公司参数', async () => { const mockResponse = { page: 1, results: [], total_pages: 1, total_results: 0 }; (tmdbClient.discoverTvShows as jest.Mock).mockResolvedValue(mockResponse); // 执行测试 await discoverShows({ with_companies: '420|421' }); // 验证调用 expect(tmdbClient.discoverTvShows).toHaveBeenCalledWith( expect.objectContaining({ with_companies: '420|421' }) ); }); it('应该处理API调用错误', async () => { // 模拟API调用错误 (tmdbClient.discoverTvShows as jest.Mock).mockRejectedValue(new Error('API错误')); // 执行测试并验证错误被正确传递 await expect(discoverShows({})).rejects.toThrow('API错误'); }); it('应该处理空结果集', async () => { const mockResponse = { page: 1, results: [], total_pages: 0, total_results: 0 }; (tmdbClient.discoverTvShows as jest.Mock).mockResolvedValue(mockResponse); // 执行测试 const result = await discoverShows({}); // 验证结果 expect(result.results).toEqual([]); expect(result.total_results).toBe(0); }); it('应该处理缺少数据的结果项', async () => { const mockResponse = { page: 1, results: [ { id: 1, name: '测试剧集' // 缺少其他字段 } ], total_pages: 1, total_results: 1 }; (tmdbClient.discoverTvShows as jest.Mock).mockResolvedValue(mockResponse); // 执行测试 const result = await discoverShows({}); // 验证结果中填充了默认值 expect(result.results[0]).toEqual({ id: 1, name: '测试剧集', overview: '暂无简介', poster_path: null, vote_average: 0 }); }); it('findPersonId应该返回null当未找到人物', async () => { (tmdbClient.searchPerson as jest.Mock).mockResolvedValue({ results: [] }); const result = await findPersonId('未知人物'); expect(result).toBeNull(); }); it('findPersonId应该返回null当API调用失败', async () => { (tmdbClient.searchPerson as jest.Mock).mockRejectedValue(new Error('API错误')); const result = await findPersonId('人物名'); expect(result).toBeNull(); }); it('应该测试演员转换为ID的逻辑', async () => { // 模拟相关API调用以测试演员ID转换逻辑 const mockPersonResponse = { results: [{ id: 123 }] }; const mockCreditsResponse = { cast: [ { id: 1, name: '测试剧集1', overview: '测试简介1', poster_path: '/path1.jpg', vote_average: 8.5, first_air_date: '2022-01-01' } ] }; (tmdbClient.searchPerson as jest.Mock).mockResolvedValue(mockPersonResponse); (tmdbClient.getPersonTvCredits as jest.Mock).mockResolvedValue(mockCreditsResponse); // 通过调用公开API来间接测试内部函数 await getRecommendationsByActor('布莱恩·科兰斯顿', 10); // 测试找不到演员的情况 (tmdbClient.searchPerson as jest.Mock).mockResolvedValue({ results: [] }); await expect(getRecommendationsByActor('未知演员')).rejects.toThrow('未找到演员'); }); it('应该处理API错误情况', async () => { // 测试API调用错误处理 // 模拟searchKeyword抛出错误,这会导致关键词搜索返回null (tmdbClient.searchKeyword as jest.Mock).mockRejectedValue(new Error('API错误')); // 使用空关键词列表,这样不会调用关键词搜索 await discoverShows({ with_keywords: [] }); // 使用有效关键词列表但模拟搜索总是返回空结果 (tmdbClient.searchKeyword as jest.Mock).mockResolvedValue({ results: [] }); await expect(discoverShows({ with_keywords: ['关键词'] })).rejects.toThrow('无法找到关键词'); }); it('应该测试getPersonTvCredits函数处理API错误的情况', async () => { // 模拟API调用错误 (tmdbClient.getPersonTvCredits as jest.Mock).mockRejectedValue(new Error('API错误')); // 执行测试并验证错误被正确传递 await expect(getPersonTvCredits(123)).rejects.toThrow('获取人物电视作品失败'); }); it('应该处理包含年份参数的查询并进行过滤', async () => { // 模拟console.log来验证过滤日志 const mockConsoleLog = jest.spyOn(console, 'log').mockImplementation(); // 模拟API响应,包含不同年份的剧集 const mockResponse = { page: 1, results: [ { id: 1, name: '测试剧集1', overview: '简介1', poster_path: '/path1.jpg', vote_average: 8.5, first_air_date: '2022-01-01' }, { id: 2, name: '测试剧集2', overview: '简介2', poster_path: '/path2.jpg', vote_average: 7.5, first_air_date: '2023-05-15' }, { id: 3, name: '测试剧集3', overview: '简介3', poster_path: '/path3.jpg', vote_average: 9.0, first_air_date: null // 缺少首播日期 } ], total_pages: 1, total_results: 3 }; (tmdbClient.discoverTvShows as jest.Mock).mockResolvedValue(mockResponse); // 执行测试,过滤2022年的剧集 const result = await discoverShows({ first_air_date_year: 2022 }); // 验证过滤后的结果 expect(result.results.length).toBe(1); expect(result.results[0].name).toBe('测试剧集1'); expect(result.total_results).toBe(1); // 验证日志输出 expect(mockConsoleLog).toHaveBeenCalledWith('过滤前剧集数量: 3'); expect(mockConsoleLog).toHaveBeenCalledWith('过滤后剧集数量: 1'); expect(mockConsoleLog).toHaveBeenCalledWith(expect.stringContaining('缺少首播日期')); expect(mockConsoleLog).toHaveBeenCalledWith(expect.stringContaining('与请求年份 2022 不匹配')); // 恢复原始实现 mockConsoleLog.mockRestore(); }); it('应该正确测试 API 错误处理(演员相关)', async () => { // 模拟演员搜索 API 抛出错误 (tmdbClient.searchPerson as jest.Mock).mockRejectedValue(new Error('API 错误')); // 应该返回 null 而不是抛出错误 const result = await findPersonId('演员名'); expect(result).toBeNull(); }); it('应该处理空演员数组', async () => { // 测试 findShowsByPersonName 使用空字符串参数,这会间接测试内部的演员转换函数 (tmdbClient.searchPerson as jest.Mock).mockResolvedValue({ results: [] }); await expect(findShowsByPersonName('')).rejects.toThrow('未找到人物'); }); it('应该测试关键词处理的更多场景', async () => { // 1. 空关键词数组 const mockResponse = { page: 1, results: [], total_pages: 1, total_results: 0 }; (tmdbClient.discoverTvShows as jest.Mock).mockResolvedValue(mockResponse); await discoverShows({ with_keywords: [] }); // 验证 API 调用不包含 with_keywords 参数 expect(tmdbClient.discoverTvShows).toHaveBeenCalledWith( expect.not.objectContaining({ with_keywords: expect.anything() }) ); // 2. 关键词 ID 转换(一个关键词成功,一个失败) (tmdbClient.searchKeyword as jest.Mock) .mockImplementation((query: string) => { if (query === '太空') { return Promise.resolve({ results: [{ id: 789 }] }); } return Promise.resolve({ results: [] }); }); // 这应该不会抛出错误,因为至少有一个关键词被成功转换 await discoverShows({ with_keywords: ['太空', '未知关键词'] }); // 验证 API 调用包含成功的关键词 ID expect(tmdbClient.discoverTvShows).toHaveBeenCalledWith( expect.objectContaining({ with_keywords: '789' }) ); }); it('应该实现 110 行的覆盖', async () => { // 首先模拟演员搜索成功 (tmdbClient.searchPerson as jest.Mock).mockResolvedValue({ results: [{ id: 123 }] }); // 模拟 getPersonTvCredits 以便完成测试 (tmdbClient.getPersonTvCredits as jest.Mock).mockResolvedValue({ cast: [ { id: 1, name: '测试剧集', overview: '测试简介', poster_path: '/test.jpg', vote_average: 8.0 } ] }); // 这将执行 convertCastToIds 函数的返回语句 await getRecommendationsByActor('测试演员', 1); // 验证相关的API调用 expect(tmdbClient.searchPerson).toHaveBeenCalledWith('测试演员'); expect(tmdbClient.getPersonTvCredits).toHaveBeenCalledWith(123); }); });

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/terryso/tv-recommender-mcp-server'

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