Files
webshare-api/__tests__/mcp-http.test.js
Michal Pemcak b04e70e261 Raise Jest coverage above global thresholds.
Add real MCP HTTP/tool/entrypoint tests and extend torrent/qbt coverage
so statements, branches, functions, and lines all meet the configured floors.
2026-10-19 19:14:15 +02:00

99 lines
3.2 KiB
JavaScript

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 });
});
});