UNPKG

@lishenxydlgzs/simple-files-vectorstore

Version:

A Model Context Protocol (MCP) server that provides semantic search capabilities across files. This server watches specified directories and creates vector embeddings of file contents, enabling semantic search across your documents.

143 lines (142 loc) 6.21 kB
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'; import * as fs from 'fs/promises'; import { SimpleFilesVectorStore } from '../index.js'; // Mock the FileProcessor class vi.mock('../fileProcessor.js', () => ({ FileProcessor: vi.fn().mockImplementation(() => ({ processFile: vi.fn().mockResolvedValue([]), })), })); // Mock the FileWatcher class vi.mock('../fileWatcher.js', () => ({ FileWatcher: vi.fn().mockImplementation(() => ({ initializeIgnorePatterns: vi.fn().mockResolvedValue(undefined), setWatchedDirectories: vi.fn(), setupDirectoryWatch: vi.fn(), processDirectory: vi.fn().mockResolvedValue(undefined), close: vi.fn().mockResolvedValue(undefined), })), })); // Mock the VectorStore class vi.mock('../vectorStore.js', () => ({ VectorStore: vi.fn().mockImplementation(() => ({ setWatchedDirectories: vi.fn(), incrementProcessingCount: vi.fn(), decrementProcessingCount: vi.fn(), updateDocuments: vi.fn().mockResolvedValue(undefined), removeDocumentsBySource: vi.fn().mockResolvedValue(undefined), similaritySearch: vi.fn().mockResolvedValue([]), getStats: vi.fn().mockReturnValue({ totalDocuments: 0, documentsByType: {}, watchedDirectories: [], filesBeingProcessed: 0 }), })), })); // Mock the fs module vi.mock('fs/promises', () => ({ readFile: vi.fn(), access: vi.fn().mockResolvedValue(undefined), readdir: vi.fn().mockResolvedValue([]), stat: vi.fn().mockResolvedValue({ isFile: () => true, isDirectory: () => false }), })); // Mock the fs watch function vi.mock('fs', () => ({ watch: vi.fn().mockReturnValue({ close: vi.fn(), }), })); // Mock the server connection vi.mock('@modelcontextprotocol/sdk/server/index.js', () => ({ Server: vi.fn().mockImplementation(() => ({ connect: vi.fn().mockResolvedValue(undefined), close: vi.fn().mockResolvedValue(undefined), setRequestHandler: vi.fn(), onerror: null, })), })); vi.mock('@modelcontextprotocol/sdk/server/stdio.js', () => ({ StdioServerTransport: vi.fn().mockImplementation(() => ({ onmessage: null, send: vi.fn(), close: vi.fn().mockResolvedValue(undefined), start: vi.fn().mockResolvedValue(undefined), })), })); describe('Watch Config File', () => { const originalEnv = process.env; beforeEach(() => { // Reset process.env process.env = { ...originalEnv }; // Clear all mocks vi.clearAllMocks(); // Mock console.error to avoid cluttering test output vi.spyOn(console, 'error').mockImplementation(() => { }); }); afterEach(() => { // Restore process.env process.env = originalEnv; }); it('should load directories from WATCH_CONFIG_FILE', async () => { // Setup const mockConfigPath = '/path/to/config.json'; const mockWatchList = ['dir1', 'dir2', 'dir3']; // Mock the readFile function to return our test config fs.readFile.mockResolvedValue(JSON.stringify({ watchList: mockWatchList })); // Set environment variables process.env.WATCH_CONFIG_FILE = mockConfigPath; // Create an instance of SimpleFilesVectorStore const server = new SimpleFilesVectorStore(); // Run the server await server.run(); // Verify that readFile was called with the correct path expect(fs.readFile).toHaveBeenCalledWith(mockConfigPath, 'utf-8'); // Verify that the directories were loaded correctly // We can't directly access private properties, but we can check if the console.error was called // with the expected message expect(console.error).toHaveBeenCalledWith(`Loaded watch list from config file: ${mockWatchList.join(', ')}`); }); it('should throw an error if WATCH_CONFIG_FILE is invalid', async () => { // Setup const mockConfigPath = '/path/to/invalid-config.json'; // Mock the readFile function to return invalid JSON fs.readFile.mockResolvedValue('{ invalid json }'); // Set environment variables process.env.WATCH_CONFIG_FILE = mockConfigPath; // Create an instance of SimpleFilesVectorStore const server = new SimpleFilesVectorStore(); // Run the server should throw an error await expect(server.run()).rejects.toThrow(); }); it('should throw an error if watchList is missing or empty', async () => { // Setup const mockConfigPath = '/path/to/empty-config.json'; // Mock the readFile function to return a config without watchList fs.readFile.mockResolvedValue(JSON.stringify({})); // Set environment variables process.env.WATCH_CONFIG_FILE = mockConfigPath; // Create an instance of SimpleFilesVectorStore const server = new SimpleFilesVectorStore(); // Run the server should throw an error await expect(server.run()).rejects.toThrow(); }); it('should prefer WATCH_CONFIG_FILE over WATCH_DIRECTORIES when both are set', async () => { // Setup const mockConfigPath = '/path/to/config.json'; const mockWatchList = ['config-dir1', 'config-dir2']; const envDirs = 'env-dir1,env-dir2'; // Mock the readFile function to return our test config fs.readFile.mockResolvedValue(JSON.stringify({ watchList: mockWatchList })); // Set environment variables process.env.WATCH_CONFIG_FILE = mockConfigPath; process.env.WATCH_DIRECTORIES = envDirs; // Create an instance of SimpleFilesVectorStore const server = new SimpleFilesVectorStore(); // Run the server await server.run(); // Verify that readFile was called with the correct path expect(fs.readFile).toHaveBeenCalledWith(mockConfigPath, 'utf-8'); // Verify that the directories from the config file were used, not from WATCH_DIRECTORIES expect(console.error).toHaveBeenCalledWith(`Loaded watch list from config file: ${mockWatchList.join(', ')}`); }); });