const express = require('express'); const request = require('supertest'); jest.mock('@modelcontextprotocol/sdk/server/streamableHttp.js', () => ({ StreamableHTTPServerTransport: jest.fn().mockImplementation(() => ({ handleRequest: jest.fn(async (req, res) => { res.status(200).json({ ok: true }); }), close: jest.fn(), })), })); jest.mock('../src/mcp-server', () => ({ createWebshareMcpServer: jest.fn(() => ({ connect: jest.fn(async () => {}), close: jest.fn(), })), })); const { StreamableHTTPServerTransport } = require('@modelcontextprotocol/sdk/server/streamableHttp.js'); const { createWebshareMcpServer } = require('../src/mcp-server'); const { registerMcpHttp } = require('../src/mcp-http'); describe('registerMcpHttp', () => { let app; beforeEach(() => { jest.clearAllMocks(); createWebshareMcpServer.mockImplementation(() => ({ connect: jest.fn(async () => {}), close: jest.fn(), })); StreamableHTTPServerTransport.mockImplementation(() => ({ handleRequest: jest.fn(async (req, res) => { res.status(200).json({ ok: true }); }), close: jest.fn(), })); app = express(); app.use(express.json()); registerMcpHttp(app, { username: 'u', password: 'p', path: '/mcp' }); }); it('POST /mcp connects transport and handles request', async () => { const res = await request(app) .post('/mcp') .send({ jsonrpc: '2.0', method: 'initialize', id: 1 }); expect(res.status).toBe(200); expect(createWebshareMcpServer).toHaveBeenCalledWith({ username: 'u', password: 'p' }); expect(StreamableHTTPServerTransport).toHaveBeenCalled(); }); it('GET /mcp returns 405 with method-not-allowed body', async () => { const res = await request(app).get('/mcp'); expect(res.status).toBe(405); expect(res.body.error.message).toMatch(/Method not allowed/i); }); it('DELETE /mcp returns 405', async () => { const res = await request(app).delete('/mcp'); expect(res.status).toBe(405); }); it('POST /mcp returns 500 JSON-RPC when connect throws and headers not sent', async () => { createWebshareMcpServer.mockImplementation(() => ({ connect: jest.fn(async () => { throw new Error('boom'); }), close: jest.fn(), })); const res = await request(app).post('/mcp').send({}); expect(res.status).toBe(500); expect(res.body.error.code).toBe(-32603); }); it('uses default path /mcp when path option omitted', () => { const app2 = express(); registerMcpHttp(app2, { username: 'a', password: 'b' }); const layer = app2._router.stack.find( (l) => l.route && l.route.path === '/mcp' && l.route.methods.post ); expect(layer).toBeTruthy(); }); it('skips 500 body when headers already sent after handler error', async () => { StreamableHTTPServerTransport.mockImplementation(() => ({ handleRequest: jest.fn(async (req, res) => { res.status(202).json({ partial: true }); throw new Error('late failure'); }), close: jest.fn(), })); const res = await request(app).post('/mcp').send({}); // first write wins — not overwritten with 500 expect(res.status).toBe(202); expect(res.body).toEqual({ partial: true }); }); });