Skip to main content
Glama

mcp-pinterest

/** * Pinterest Scraper 代码覆盖率测试 * * 此测试文件专注于提高 pinterest-scraper.js 的代码覆盖率 * 使用实际的 PinterestScraper 类而不是模拟对象 * 使用sinon代替jest.mock */ // 导入必要的模块 import { jest, describe, it, expect, beforeEach, afterEach, afterAll } from '@jest/globals'; import sinon from 'sinon'; import fs from 'fs'; import puppeteer from 'puppeteer-core'; // 导入实际的 PinterestScraper 类 import PinterestScraper from '../pinterest-scraper.js'; describe('PinterestScraper代码覆盖率测试', () => { let scraper; let sandbox; beforeEach(() => { // 创建一个sinon沙箱来管理所有模拟 sandbox = sinon.createSandbox(); // 模拟puppeteer.launch方法 const mockBrowser = { newPage: sinon.stub().resolves({ setViewport: sinon.stub().resolves(undefined), setUserAgent: sinon.stub().resolves(undefined), setDefaultNavigationTimeout: sinon.stub(), setDefaultTimeout: sinon.stub(), setRequestInterception: sinon.stub().resolves(undefined), on: sinon.stub(), goto: sinon.stub().resolves(undefined), waitForSelector: sinon.stub().resolves(undefined), evaluate: sinon.stub().resolves([ { title: '测试图片1', image_url: 'https://i.pinimg.com/236x/test1.jpg', link: 'https://pinterest.com/pin/1' }, { title: '测试图片2', image_url: 'https://i.pinimg.com/236x/test2.jpg', link: 'https://pinterest.com/pin/2' } ]), removeAllListeners: sinon.stub(), }), close: sinon.stub().resolves(undefined), // 添加Browser接口所需的其他方法 process: sinon.stub(), createBrowserContext: sinon.stub(), browserContexts: [], defaultBrowserContext: sinon.stub(), version: sinon.stub().returns('1.0.0'), userAgent: sinon.stub().returns('Mozilla/5.0'), wsEndpoint: sinon.stub().returns('ws://localhost:1234'), target: sinon.stub(), targets: sinon.stub().returns([]), waitForTarget: sinon.stub(), pages: sinon.stub().resolves([]) }; sandbox.stub(puppeteer, 'launch').resolves(mockBrowser); // 模拟fs模块的方法 sandbox.stub(fs, 'writeFileSync'); sandbox.stub(fs, 'existsSync').returns(true); sandbox.stub(fs, 'mkdirSync'); // 保存和模拟原始fetch global.originalFetch = global.fetch; global.fetch = sandbox.stub().resolves({ ok: true, status: 200, arrayBuffer: () => Promise.resolve(Buffer.from('测试图片数据').buffer) }); // 创建PinterestScraper实例 scraper = new PinterestScraper(); }); afterEach(() => { // 恢复所有模拟 sandbox.restore(); // 恢复原始fetch if (global.originalFetch) { global.fetch = global.originalFetch; } }); // 测试构造函数 describe('构造函数', () => { it('应该正确初始化属性', () => { expect(scraper.baseUrl).toBe('https://www.pinterest.com'); expect(scraper.searchUrl).toBe('https://www.pinterest.com/search/pins/?q='); expect(scraper.chromePaths).toBeDefined(); expect(Object.keys(scraper.chromePaths)).toContain('mac'); expect(Object.keys(scraper.chromePaths)).toContain('linux'); expect(Object.keys(scraper.chromePaths)).toContain('win'); }); }); // 测试 search 方法 describe('search方法', () => { it('应该返回搜索结果', async () => { const results = await scraper.search('测试关键词', 10, true, null); expect(Array.isArray(results)).toBe(true); expect(results.length).toBeGreaterThan(0); }); it('应该处理空关键词', async () => { const results = await scraper.search('', 10, true, null); expect(Array.isArray(results)).toBe(true); }); it('应该处理带有取消信号的搜索', async () => { const controller = new AbortController(); const signal = controller.signal; // 立即取消 controller.abort(); await expect(async () => { await scraper.search('测试', 10, true, signal); }).rejects.toThrow('操作被取消'); }); it('应该处理搜索过程中的错误', async () => { // 模拟puppeteer启动失败 sandbox.restore(); // 清除之前的stub sandbox = sinon.createSandbox(); sandbox.stub(puppeteer, 'launch').rejects(new Error('启动失败')); const results = await scraper.search('测试', 10, true, null); expect(Array.isArray(results)).toBe(true); expect(results.length).toBe(0); }); it('应该处理页面创建失败', async () => { // 模拟页面创建失败 sandbox.restore(); // 清除之前的stub sandbox = sinon.createSandbox(); const mockBrowser = { newPage: sinon.stub().rejects(new Error('页面创建失败')), close: sinon.stub().resolves(undefined) }; sandbox.stub(puppeteer, 'launch').resolves(mockBrowser); const results = await scraper.search('测试', 10, true, null); expect(Array.isArray(results)).toBe(true); expect(results.length).toBe(0); }); it('应该处理页面导航失败', async () => { // 模拟导航失败 sandbox.restore(); // 清除之前的stub sandbox = sinon.createSandbox(); const mockBrowser = { newPage: sinon.stub().resolves({ setViewport: sinon.stub().resolves(undefined), setUserAgent: sinon.stub().resolves(undefined), setDefaultNavigationTimeout: sinon.stub(), setDefaultTimeout: sinon.stub(), setRequestInterception: sinon.stub().resolves(undefined), on: sinon.stub(), goto: sinon.stub().rejects(new Error('导航失败')), removeAllListeners: sinon.stub() }), close: sinon.stub().resolves(undefined) }; sandbox.stub(puppeteer, 'launch').resolves(mockBrowser); const results = await scraper.search('测试', 10, true, null); expect(Array.isArray(results)).toBe(true); expect(results.length).toBe(0); }); it('应该处理图片提取失败', async () => { // 模拟图片提取失败 sandbox.restore(); // 清除之前的stub sandbox = sinon.createSandbox(); const mockBrowser = { newPage: sinon.stub().resolves({ setViewport: sinon.stub().resolves(undefined), setUserAgent: sinon.stub().resolves(undefined), setDefaultNavigationTimeout: sinon.stub(), setDefaultTimeout: sinon.stub(), setRequestInterception: sinon.stub().resolves(undefined), on: sinon.stub(), goto: sinon.stub().resolves(undefined), waitForSelector: sinon.stub().resolves(undefined), evaluate: sinon.stub().rejects(new Error('提取失败')), removeAllListeners: sinon.stub() }), close: sinon.stub().resolves(undefined) }; sandbox.stub(puppeteer, 'launch').resolves(mockBrowser); const results = await scraper.search('测试', 10, true, null); expect(Array.isArray(results)).toBe(true); expect(results.length).toBe(0); }); it('应该处理非数组结果', async () => { // 模拟非数组结果 sandbox.restore(); // 清除之前的stub sandbox = sinon.createSandbox(); const mockBrowser = { newPage: sinon.stub().resolves({ setViewport: sinon.stub().resolves(undefined), setUserAgent: sinon.stub().resolves(undefined), setDefaultNavigationTimeout: sinon.stub(), setDefaultTimeout: sinon.stub(), setRequestInterception: sinon.stub().resolves(undefined), on: sinon.stub(), goto: sinon.stub().resolves(undefined), waitForSelector: sinon.stub().resolves(undefined), evaluate: sinon.stub().resolves("非数组结果"), removeAllListeners: sinon.stub() }), close: sinon.stub().resolves(undefined) }; sandbox.stub(puppeteer, 'launch').resolves(mockBrowser); const results = await scraper.search('测试', 10, true, null); expect(Array.isArray(results)).toBe(true); expect(results.length).toBe(0); }); }); // 测试 autoScroll 方法 describe('autoScroll方法', () => { it('应该执行页面滚动', async () => { const mockPage = { evaluate: sinon.stub().resolves(undefined) }; await scraper.autoScroll(mockPage, 2000, null); expect(mockPage.evaluate.called).toBe(true); }); it('应该响应取消信号', async () => { const mockPage = { evaluate: sinon.stub().resolves(undefined) }; const controller = new AbortController(); const signal = controller.signal; // 立即取消 controller.abort(); await expect(async () => { await scraper.autoScroll(mockPage, 2000, signal); }).rejects.toThrow('操作被取消'); }); }); // 测试 downloadImage 方法 describe('downloadImage方法', () => { it('应该成功下载图片', async () => { const success = await scraper.downloadImage('https://i.pinimg.com/originals/test.jpg', '/tmp/test.jpg', null); expect(success).toBe(true); expect(global.fetch.called).toBe(true); }); it('应该处理响应错误', async () => { global.fetch = sandbox.stub().resolves({ ok: false, status: 404 }); const success = await scraper.downloadImage('https://example.com/error.jpg', '/tmp/error.jpg', null); expect(success).toBe(false); }); it('应该处理网络错误', async () => { global.fetch = sandbox.stub().rejects(new Error('网络错误')); const success = await scraper.downloadImage('https://example.com/network-error.jpg', '/tmp/network-error.jpg', null); expect(success).toBe(false); }); it('应该处理取消信号', async () => { const controller = new AbortController(); const signal = controller.signal; // 立即取消 controller.abort(); const success = await scraper.downloadImage('https://example.com/cancelled.jpg', '/tmp/cancelled.jpg', signal); expect(success).toBe(false); }); it('应该处理写入错误', async () => { // 先恢复之前的桩函数,然后重新设置 sandbox.restore(); sandbox = sinon.createSandbox(); // 模拟其他必要的依赖 sandbox.stub(puppeteer, 'launch').resolves({ newPage: sinon.stub().resolves({ setViewport: sinon.stub().resolves(undefined), evaluate: sinon.stub().resolves(undefined) }), close: sinon.stub().resolves(undefined) }); // 重新设置fs模拟 sandbox.stub(fs, 'existsSync').returns(true); sandbox.stub(fs, 'mkdirSync'); sandbox.stub(fs, 'writeFileSync').throws(new Error('写入错误')); // 设置fetch模拟 global.fetch = sandbox.stub().resolves({ ok: true, status: 200, arrayBuffer: () => Promise.resolve(Buffer.from('测试图片数据').buffer) }); const success = await scraper.downloadImage('https://example.com/write-error.jpg', '/tmp/write-error.jpg', null); expect(success).toBe(false); }); }); // 测试 transformImageUrl 方法 describe('transformImageUrl方法', () => { it('应该转换缩略图URL为原图URL', () => { expect(scraper.transformImageUrl('https://i.pinimg.com/236x/ab/cd/ef.jpg')) .toBe('https://i.pinimg.com/originals/ab/cd/ef.jpg'); expect(scraper.transformImageUrl('https://i.pinimg.com/474x/ab/cd/ef.jpg')) .toBe('https://i.pinimg.com/originals/ab/cd/ef.jpg'); expect(scraper.transformImageUrl('https://i.pinimg.com/60x60/ab/cd/ef.jpg')) .toBe('https://i.pinimg.com/originals/ab/cd/ef.jpg'); expect(scraper.transformImageUrl('https://i.pinimg.com/736x/ab/cd/ef.jpg')) .toBe('https://i.pinimg.com/originals/ab/cd/ef.jpg'); }); it('应该处理自定义尺寸格式', () => { expect(scraper.transformImageUrl('https://i.pinimg.com/123x456/ab/cd/ef.jpg')) .toBe('https://i.pinimg.com/originals/ab/cd/ef.jpg'); }); it('应该保持非缩略图URL不变', () => { const origUrl = 'https://example.com/image.jpg'; expect(scraper.transformImageUrl(origUrl)).toBe(origUrl); }); it('应该处理空URL', () => { expect(scraper.transformImageUrl(null)).toBeNull(); expect(scraper.transformImageUrl(undefined)).toBeUndefined(); expect(scraper.transformImageUrl('')).toBe(''); }); }); });

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/mcp-pinterest'

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