# Module MCP - Hướng Dẫn Tham Khảo Nhanh & Kiểm Thử ## 📋 Tổng Quan Module | Khía cạnh | Chi tiết | |--------|---------| | **Vị trí** | `apps/api/src/modules/mcp/` | | **Tổng số tệp** | 4 tệp nguồn (2 TypeScript + 1 cấu hình module + 1 xuất) | | **Tệp kiểm thử** | 1 tệp kiểm thử (174 dòng) | | **Kiến trúc** | Chỉ có tầng trình bày (đơn giản hóa) | | **Framework kiểm thử** | Vitest với Globals được bật | | **Độ phủ kiểm thử** | Controller được kiểm thử kỹ, Module/Infrastructure chưa được kiểm thử | --- ## 📁 Danh Sách Tệp (Đầy Đủ) ``` ✅ TESTED ✅ SOURCE ❌ NOT TESTED ───────────────────────────────────────────────────────────── MCP module/ mcp/ ├── mcp.module.ts [Module config - 22 lines] ├── index.ts [Re-export - 1 line] └── presentation/ ├── mcp-transport.controller.ts [Controller - 102 lines] └── __tests__/ └── mcp-transport.controller.spec.ts ✅ [174 lines] ``` ### Chi Tiết Tệp | Tệp | Loại | Dòng | Trạng thái | Mục đích | |------|------|-------|--------|---------| | `index.ts` | Xuất | 1 | ✅ | Điểm vào module | | `mcp.module.ts` | Cấu hình | 22 | ⚠️ | Thiết lập module NestJS | | `mcp-transport.controller.ts` | Controller | 102 | ✅ | Vận chuyển HTTP/SSE | | `mcp-transport.controller.spec.ts` | Kiểm thử | 174 | ✅ | Kiểm thử controller | --- ## 🏗️ Sơ Đồ Kiến Trúc ### Cấu Trúc DDD Hiện Tại (Đơn Giản Hóa) ``` ┌─────────────────────────────────────────────────┐ │ Presentation Layer (Only) ✅ │ ├─────────────────────────────────────────────────┤ │ • McpTransportController │ │ - GET /mcp/servers (list) │ │ - GET /mcp/:serverName/sse (connect) │ │ - POST /mcp/:serverName/messages (send) │ ├─────────────────────────────────────────────────┤ │ Dependencies: │ │ • McpRegistryService (external) │ │ • JwtAuthGuard (auth layer) │ │ • SSEServerTransport (external) │ └─────────────────────────────────────────────────┘ ``` ### Các Tầng Còn Thiếu (Chưa Được Triển Khai) ``` ❌ Domain Layer - No entities, value objects, events - No business logic abstractions ❌ Application Layer - No CQRS handlers - No command/query objects - No use case orchestration ❌ Infrastructure Layer - No repositories - No external service adapters - No persistence logic ``` --- ## 🧪 Tổng Quan Kiểm Thử ### Thống Kê Tệp Kiểm Thử ``` File: mcp-transport.controller.spec.ts ├── Total Tests: 11 ├── Test Suites: 4 describe blocks ├── Total Lines: 174 └── Coverage Areas: ├── Security Decorators: 4 tests ├── listServers(): 2 tests ├── handleSse(): 3 tests └── handleMessage(): 2 tests ``` ### Phân Tích Kiểm Thử Theo Bộ ``` 1. Security Decorators (4 tests) ├── JwtAuthGuard applied ├── listServers throttle (30 req/60s) ├── handleSse throttle (5 req/60s) ⚡ stricter └── handleMessage throttle (30 req/60s) 2. listServers (2 tests) ├── Returns server list └── Handles empty list 3. handleSse (3 tests) ├── Throws NOT_FOUND for missing server ├── Creates transport & connects └── Cleans up on connection close 4. handleMessage (2 tests) ├── Throws BAD_REQUEST for missing sessionId └── Throws NOT_FOUND for expired session ``` --- ## 🎯 Các Lớp & Phương Thức Chính ### McpIntegrationModule ```typescript class McpIntegrationModule implements OnModuleInit { constructor( typesenseClient: TypesenseClientService, mcpRegistry: McpRegistryService, logger: LoggerService, ) {} async onModuleInit(): Promise { // 1. Set typesense client on registry // 2. Re-initialize servers // 3. Log initialized servers } } ``` ### McpTransportController ```typescript @Controller('mcp') @UseGuards(JwtAuthGuard) class McpTransportController { private transports: Map constructor(registry: McpRegistryService) {} @Get('servers') @Throttle(30/60s) listServers(): { servers: string[] } @Get(':serverName/sse') @Throttle(5/60s) // Stricter limit for SSE async handleSse(serverName, user, req, res): Promise @Post(':serverName/messages') @Throttle(30/60s) async handleMessage(serverName, user, req, res): Promise } ``` --- ## 🔧 Các Mẫu Kiểm Thử Được Sử Dụng ### 1. Mẫu Mock (Vitest) ```typescript // Mocking external module classes vi.mock('@goodgo/mcp-servers', () => ({ SSEServerTransport: class MockSSEServerTransport { sessionId = 'mock-session-id'; handlePostMessage = vi.fn().mockResolvedValue(undefined); constructor(path: string, res: unknown) {} }, })); ``` ### 2. Mẫu Mock Dịch Vụ ```typescript const mockRegistry = { getServerNames: vi.fn(), getServer: vi.fn(), }; ``` ### 3. Mẫu Xác Minh Decorator ```typescript // Using Reflect API for NestJS decorators const guards = Reflect.getMetadata('__guards__', McpTransportController); const throttleLimit = Reflect.getMetadata( 'THROTTLER:LIMITdefault', McpTransportController.prototype.listServers, ); ``` ### 4. Mẫu Kiểm Thử Lỗi ```typescript // Testing HTTP errors await expect( controller.handleSse('nonexistent', user, req, res) ).rejects.toThrow(HttpException); // Checking status code try { await controller.handleSse(...); } catch (error) { expect((error as HttpException).getStatus()).toBe(HttpStatus.NOT_FOUND); } ``` --- ## 📚 Các Mẫu Kiểm Thử Từ Các Module Khác ### Module Auth - Mẫu Handler Đơn Giản ```typescript // Minimal dependencies, focused testing describe('LoginUserHandler', () => { let handler: LoginUserHandler; let mockTokenService = { generateTokenPair: vi.fn() }; beforeEach(() => { handler = new LoginUserHandler(mockTokenService as any); }); it('generates token pair', async () => { mockTokenService.generateTokenPair.mockResolvedValue(tokenPair); const result = await handler.execute(command); expect(result).toEqual(tokenPair); }); }); ``` ### Module Payments - Mẫu Handler Phức Tạp ```typescript // Multiple dependencies, rich testing scenarios describe('CreatePaymentHandler', () => { let handler: CreatePaymentHandler; let mockPaymentRepo: { [K in keyof IPaymentRepository]: ReturnType }; let mockGatewayFactory: { getGateway: ReturnType }; let mockEventBus: { publish: ReturnType }; beforeEach(() => { mockPaymentRepo = { findById: vi.fn(), save: vi.fn().mockResolvedValue(undefined), // ... more methods }; handler = new CreatePaymentHandler(mockPaymentRepo as any, ...); }); it('creates payment successfully', async () => { // Rich test scenario with multiple assertions expect(result.paymentId).toBeDefined(); expect(mockPaymentRepo.save).toHaveBeenCalledTimes(1); expect(mockEventBus.publish).toHaveBeenCalled(); }); }); ``` ### Domain Payments - Mẫu DDD ```typescript // Explicit imports, entity behavior testing import { describe, it, expect } from 'vitest'; describe('PaymentEntity', () => { it('should create payment with events', () => { const payment = PaymentEntity.createNew(...); expect(payment.status).toBe('PENDING'); expect(payment.domainEvents).toHaveLength(1); expect(payment.domainEvents[0]).toBeInstanceOf(PaymentCreatedEvent); }); it('should not refund non-completed payment', () => { const payment = createPayment(); // helper const result = payment.markRefunded(); expect(result.isErr).toBe(true); }); }); ``` ### Dịch Vụ Infrastructure - Mẫu Crypto ```typescript // Complex external service testing describe('ZalopayService', () => { let service: ZalopayService; beforeEach(() => { const mockConfig = { get: vi.fn((key) => env[key]), getOrThrow: vi.fn((key) => env[key] || throw), }; service = new ZalopayService(mockConfig as any); }); it('should verify valid callback', () => { const mac = crypto.createHmac('sha256', key2).update(dataStr).digest('hex'); const result = service.verifyCallback({ data: dataStr, mac }); expect(result.isValid).toBe(true); expect(result.orderId).toBe('expected-order-id'); }); }); ``` --- ## 🚀 Chạy Kiểm Thử ### Lệnh Kiểm Thử ```bash # Run all MCP tests pnpm test -- src/modules/mcp # Run with watch mode pnpm test -- --watch src/modules/mcp # Run with coverage pnpm test -- --coverage src/modules/mcp # Run specific test file pnpm test -- src/modules/mcp/presentation/__tests__/mcp-transport.controller.spec.ts ``` ### Cấu Hình Vitest ```typescript // apps/api/vitest.config.ts { test: { globals: true, // vi, describe, it, expect available globally environment: 'node', include: ['src/**/*.spec.ts'], // Matches *.spec.ts pattern exclude: ['**/*.integration.spec.ts', 'node_modules'], } } ``` --- ## 💡 Khuyến Nghị ### Ngay Lập Tức (Ưu Tiên Cao) - ✅ Controller được kiểm thử kỹ - duy trì điều này - 📝 Thêm kiểm thử cho `McpIntegrationModule.onModuleInit()` - 📝 Kiểm thử dependency injection của module ### Tương Lai (Ưu Tiên Thấp Hơn) - 🏗️ Nếu logic domain được thêm vào, tạo kiểm thử domain - 🏗️ Nếu application handler được thêm vào, theo mẫu module payments - 🏗️ Thêm kiểm thử tích hợp cho toàn bộ vòng đời SSE ### Các Phương Pháp Tốt Nhất Cần Tuân Theo 1. **Sử dụng mẫu globals** cho các kiểm thử đơn giản (như kiểm thử controller hiện có) 2. **Sử dụng import tường minh** cho các kiểm thử domain phức tạp 3. **Sử dụng helper factory** cho thiết lập entity phức tạp 4. **Sử dụng Reflect API** để xác minh decorator 5. **Kiểm thử cả happy path VÀ các trường hợp lỗi** 6. **Sử dụng khớp regex** cho các xác nhận thông báo lỗi 7. **Xác minh lời gọi dịch vụ** với `toHaveBeenCalledWith()` --- ## 📖 Tham Chiếu Vị Trí Tệp ``` GoodGo Platform Root ├── apps/api/src/modules/mcp/ ← MCP Module │ ├── index.ts │ ├── mcp.module.ts │ └── presentation/ │ ├── mcp-transport.controller.ts │ └── __tests__/ │ └── mcp-transport.controller.spec.ts │ ├── apps/api/vitest.config.ts ← Test config ├── apps/api/package.json ← Test scripts │ └── MCP_MODULE_EXPLORATION.md ← Full documentation ``` --- Được tạo: ngày 11 tháng 4 năm 2026