Files
Momento/mcp-server/test/test.js
Antigravity 0784c94242
Some checks failed
CI / Lint, Test & Build (push) Failing after 57s
CI / Deploy production (on server) (push) Has been skipped
feat(notes): vues structurées tableau/kanban, flashcards et MCP robuste
Ajoute la base organisable par carnet (schéma, champs partagés, valeurs par note)
avec activation guidée, tableau éditable, kanban et suppression de colonnes.
Corrige le multiselect en vue tableau et enrichit sidebar, grille et i18n FR/EN.
Inclut aussi les améliorations flashcards SM-2, l'audit consentement IA et la
robustesse du serveur MCP (config, validation, rate-limit, métriques).

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-24 23:03:16 +00:00

224 lines
6.4 KiB
JavaScript

#!/usr/bin/env node
/**
* Memento MCP Server - Test Suite
*
* Run with: npm test
*/
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { fileURLToPath } from 'url';
import { dirname, join } from 'path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const projectRoot = join(__dirname, '..');
// Import modules to test
import { mcpError, validationError, notFoundError, authError, McpErrors } from '../errors.js';
import { validateConfig } from '../config.js';
import { validateToolInput, validateAndSanitize, checkXSS } from '../validation.js';
import { checkRateLimit, resetRateLimit, getRateLimitStats } from '../rate-limit.js';
import { getMetrics, getPrometheusMetrics, recordRequest, resetMetrics } from '../metrics.js';
describe('MCP Server - Error Handling', () => {
it('should create a structured error', () => {
const error = mcpError(McpErrors.INVALID_PARAMS.code, {
detail: 'Test error',
field: 'testField',
});
expect(error).toHaveProperty('_error', true);
expect(error).toHaveProperty('code', McpErrors.INVALID_PARAMS.code);
expect(error).toHaveProperty('message', 'Invalid params');
expect(error).toHaveProperty('detail', 'Test error');
expect(error).toHaveProperty('field', 'testField');
});
it('should create a validation error', () => {
const error = validationError('title', 'Title is required');
expect(error).toHaveProperty('code', McpErrors.INVALID_PARAMS.code);
expect(error).toHaveProperty('field', 'title');
expect(error).toHaveProperty('detail', 'Title is required');
});
it('should create a not found error', () => {
const error = notFoundError('Note', '123');
expect(error).toHaveProperty('code', McpErrors.NOT_FOUND.code);
expect(error.detail).toContain('Note not found');
});
it('should create an auth error', () => {
const error = authError('Invalid API key');
expect(error).toHaveProperty('code', McpErrors.AUTH_FAILED.code);
expect(error).toHaveProperty('detail', 'Invalid API key');
});
});
describe('MCP Server - Configuration', () => {
it('should validate missing DATABASE_URL', () => {
const originalDbUrl = process.env.DATABASE_URL;
delete process.env.DATABASE_URL;
const errors = validateConfig();
const dbError = errors.find((e) => e.key === 'DATABASE_URL');
expect(dbError).toBeDefined();
expect(dbError.critical).toBe(true);
process.env.DATABASE_URL = originalDbUrl;
});
it('should validate port range', () => {
const originalPort = process.env.PORT;
process.env.PORT = '99999';
const errors = validateConfig();
const portError = errors.find((e) => e.key === 'PORT');
expect(portError).toBeDefined();
process.env.PORT = originalPort;
});
});
describe('MCP Server - Input Validation', () => {
it('should validate create_note input', () => {
const result = validateToolInput('create_note', {
title: 'Test Note',
content: 'Test content',
color: 'blue',
});
expect(result.success).toBe(true);
expect(result.data.title).toBe('Test Note');
expect(result.data.content).toBe('Test content');
});
it('should reject invalid create_note input', () => {
const result = validateToolInput('create_note', {
// Missing required 'content' field
});
expect(result.success).toBe(false);
expect(result.errors).toBeDefined();
expect(result.errors.length).toBeGreaterThan(0);
});
it('should reject invalid color', () => {
const result = validateToolInput('create_note', {
content: 'Test',
color: 'invalid-color',
});
expect(result.success).toBe(false);
});
it('should detect XSS attempts', () => {
const xss = checkXSS({ content: '<script>alert("xss")</script>' });
expect(xss).toBe(true);
});
it('should allow safe HTML', () => {
const xss = checkXSS({ content: 'Hello <em>world</em>' });
// This will be true because we check for any HTML tags
// In production, you might want more sophisticated checking
expect(xss).toBe(true);
});
it('should sanitize input', () => {
const result = validateAndSanitize('create_note', {
content: 'Test content',
});
expect(result.success).toBe(true);
});
});
describe('MCP Server - Metrics', () => {
beforeEach(() => {
resetMetrics();
});
it('should record requests', () => {
recordRequest('create_note', 200, 'POST', 100);
recordRequest('get_notes', 200, 'GET', 50);
const metrics = getMetrics();
expect(metrics.requests.total).toBe(2);
expect(metrics.requests.byTool.create_note).toBe(1);
expect(metrics.requests.byTool.get_notes).toBe(1);
});
it('should calculate latency percentiles', () => {
for (let i = 0; i < 100; i++) {
recordRequest('test', 200, 'GET', i);
}
const metrics = getMetrics();
expect(metrics.latency.p50).toBeGreaterThan(0);
expect(metrics.latency.p95).toBeGreaterThan(metrics.latency.p50);
});
it('should export Prometheus metrics', () => {
recordRequest('create_note', 200, 'POST', 100);
const promMetrics = getPrometheusMetrics();
expect(promMetrics).toContain('mcp_requests_total');
expect(promMetrics).toContain('mcp_latency_ms');
});
});
describe('MCP Server - Rate Limiting', () => {
it('should rate limit requests', () => {
// This is a basic test - actual rate limiting requires more setup
const stats = getRateLimitStats();
expect(stats).toHaveProperty('store');
expect(stats).toHaveProperty('config');
});
});
describe('MCP Server - Tool Definitions', () => {
const toolNames = [
'create_note',
'get_notes',
'get_note',
'update_note',
'delete_note',
'search_notes',
'move_note',
'toggle_pin',
'toggle_archive',
'batch_move_notes',
'batch_delete_notes',
'create_notebook',
'get_notebooks',
'get_notebook',
'update_notebook',
'delete_notebook',
'reorder_notebooks',
'get_notebook_hierarchy',
'create_label',
'get_labels',
'update_label',
'delete_label',
'get_due_reminders',
'export_notes',
'import_notes',
];
it('should have all expected tools with schemas', () => {
const { toolSchemas } = await import('../validation.js');
for (const toolName of toolNames) {
expect(toolSchemas[toolName]).toBeDefined();
}
});
});
// Run tests
console.log('Running MCP Server tests...');