|
1 | 1 | import { describe, it, expect, vi, beforeEach } from 'vitest'; |
2 | 2 | import { Client } from '@modelcontextprotocol/sdk/client/index.js'; |
3 | 3 | import { InMemoryTransport } from '@modelcontextprotocol/sdk/inMemory.js'; |
4 | | -import { createServer } from './index.js'; |
| 4 | +import { createServer, callApi } from './index.js'; |
5 | 5 |
|
6 | 6 | // Mock fetch globally |
7 | 7 | const mockFetch = vi.fn(); |
@@ -276,13 +276,38 @@ describe('ValidKit MCP Server', () => { |
276 | 276 | expect(parsed.results).toHaveLength(2); |
277 | 277 | expect(parsed.summary.total).toBe(2); |
278 | 278 |
|
279 | | - // Verify correct endpoint (not /v1/verify/batch) |
| 279 | + // Verify correct endpoint and body |
280 | 280 | expect(mockFetch).toHaveBeenCalledWith( |
281 | 281 | 'https://api.validkit.com/v1/verify/bulk', |
282 | | - expect.anything() |
| 282 | + expect.objectContaining({ |
| 283 | + body: JSON.stringify({ emails: ['a@gmail.com', 'b@fake.xyz'] }), |
| 284 | + }) |
283 | 285 | ); |
284 | 286 | }); |
285 | 287 |
|
| 288 | + it('rejects empty emails array via Zod schema', async () => { |
| 289 | + const { client } = await createTestClient(); |
| 290 | + |
| 291 | + const result = await client.callTool({ |
| 292 | + name: 'validate_emails_bulk', |
| 293 | + arguments: { emails: [] }, |
| 294 | + }); |
| 295 | + |
| 296 | + expect(result.isError).toBe(true); |
| 297 | + }); |
| 298 | + |
| 299 | + it('rejects >1000 emails via Zod schema', async () => { |
| 300 | + const { client } = await createTestClient(); |
| 301 | + const tooMany = Array.from({ length: 1001 }, (_, i) => `u${i}@test.com`); |
| 302 | + |
| 303 | + const result = await client.callTool({ |
| 304 | + name: 'validate_emails_bulk', |
| 305 | + arguments: { emails: tooMany }, |
| 306 | + }); |
| 307 | + |
| 308 | + expect(result.isError).toBe(true); |
| 309 | + }); |
| 310 | + |
286 | 311 | it('handles API error on bulk', async () => { |
287 | 312 | const { client } = await createTestClient(); |
288 | 313 | mockFetchResponse(401, { error: { message: 'Invalid API key' } }); |
@@ -507,6 +532,61 @@ describe('ValidKit MCP Server', () => { |
507 | 532 | expect(getText(result)).toContain('must use https://'); |
508 | 533 | }); |
509 | 534 |
|
| 535 | + it('rejects URL with path', async () => { |
| 536 | + process.env.VALIDKIT_API_URL = 'https://api.validkit.com/v1'; |
| 537 | + const { client } = await createTestClient(); |
| 538 | + |
| 539 | + const result = await client.callTool({ |
| 540 | + name: 'validate_email', |
| 541 | + arguments: { email: 'test@gmail.com' }, |
| 542 | + }); |
| 543 | + |
| 544 | + expect(result.isError).toBe(true); |
| 545 | + expect(getText(result)).toContain('origin URL without a path'); |
| 546 | + }); |
| 547 | + |
| 548 | + it('rejects invalid URL', async () => { |
| 549 | + process.env.VALIDKIT_API_URL = 'https://not a valid url'; |
| 550 | + const { client } = await createTestClient(); |
| 551 | + |
| 552 | + const result = await client.callTool({ |
| 553 | + name: 'validate_email', |
| 554 | + arguments: { email: 'test@gmail.com' }, |
| 555 | + }); |
| 556 | + |
| 557 | + expect(result.isError).toBe(true); |
| 558 | + expect(getText(result)).toContain('not a valid URL'); |
| 559 | + }); |
| 560 | + |
| 561 | + it('rejects URL with query params', async () => { |
| 562 | + process.env.VALIDKIT_API_URL = 'https://api.validkit.com?foo=bar'; |
| 563 | + const { client } = await createTestClient(); |
| 564 | + |
| 565 | + const result = await client.callTool({ |
| 566 | + name: 'validate_email', |
| 567 | + arguments: { email: 'test@gmail.com' }, |
| 568 | + }); |
| 569 | + |
| 570 | + expect(result.isError).toBe(true); |
| 571 | + expect(getText(result)).toContain('query parameters'); |
| 572 | + }); |
| 573 | + |
| 574 | + it('strips trailing slash from URL', async () => { |
| 575 | + process.env.VALIDKIT_API_URL = 'https://api.validkit.com/'; |
| 576 | + const { client } = await createTestClient(); |
| 577 | + mockFetchResponse(200, { email: 'a@b.com', valid: true }); |
| 578 | + |
| 579 | + await client.callTool({ |
| 580 | + name: 'validate_email', |
| 581 | + arguments: { email: 'a@b.com' }, |
| 582 | + }); |
| 583 | + |
| 584 | + expect(mockFetch).toHaveBeenCalledWith( |
| 585 | + 'https://api.validkit.com/v1/verify', |
| 586 | + expect.anything() |
| 587 | + ); |
| 588 | + }); |
| 589 | + |
510 | 590 | it('handles non-JSON API response gracefully', async () => { |
511 | 591 | const { client } = await createTestClient(); |
512 | 592 | mockFetch.mockResolvedValueOnce({ |
@@ -624,5 +704,14 @@ describe('ValidKit MCP Server', () => { |
624 | 704 | expect(getText(result)).toContain('30 seconds'); |
625 | 705 | expect(getText(result)).not.toContain('was aborted'); |
626 | 706 | }); |
| 707 | + |
| 708 | + it('throws clear error when body cannot be serialized', async () => { |
| 709 | + const circular: Record<string, unknown> = {}; |
| 710 | + circular.self = circular; |
| 711 | + |
| 712 | + await expect(callApi('/v1/verify', 'POST', circular)).rejects.toThrow( |
| 713 | + 'Failed to serialize request body' |
| 714 | + ); |
| 715 | + }); |
627 | 716 | }); |
628 | 717 | }); |
0 commit comments