/**
* Agent Synch MCP Server - Storage Layer Tests
* TDD First: These tests define the expected behavior before implementation.
*/
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { AgentSynchStorage } from './storage';
import * as fs from 'fs/promises';
import * as path from 'path';
import * as os from 'os';
describe('AgentSynchStorage', () => {
let storage: AgentSynchStorage;
let testDir: string;
beforeEach(async () => {
// Use a temp directory for isolated tests
testDir = path.join(os.tmpdir(), `agent-synch-test-${Date.now()}`);
storage = new AgentSynchStorage(testDir);
await storage.initialize();
});
afterEach(async () => {
// Cleanup
try {
await fs.rm(testDir, { recursive: true, force: true });
} catch (e) {
// Ignore cleanup errors (SQLite file might be locked)
}
});
describe('Active Context', () => {
it('should get null for new project', async () => {
const context = await storage.getActiveContext('test-project');
expect(context).toBeNull();
});
it('should set and get active context', async () => {
const projectId = 'test-project';
const contextData = {
summary: 'Working on auth module',
lastUpdated: new Date().toISOString(),
focus: 'Implementing login flow',
taskGraph: { nodes: [] }
};
await storage.setActiveContext(projectId, contextData);
const retrieved = await storage.getActiveContext(projectId);
expect(retrieved).toEqual(contextData);
});
// Global context is just a project_id = 'global'
it('should get global context when no project specified', async () => {
const globalContext = { summary: 'Global state', lastUpdated: new Date().toISOString() };
await storage.setActiveContext('global', globalContext);
const retrieved = await storage.getActiveContext('global');
expect(retrieved).toEqual(globalContext);
});
});
describe('Filing Cabinet', () => {
it('should file a document to the cabinet', async () => {
const projectId = 'test-project';
const filePath = '/src/main.ts';
const content = {
originalPath: filePath,
summary: 'Main entry point',
keyExports: ['main', 'init'],
dependencies: ['./config', './utils'],
};
await storage.indexFile(projectId, content);
const retrieved = await storage.getFileFromCabinet(projectId, filePath);
expect(retrieved).toMatchObject(content);
expect(retrieved?.indexedAt).toBeDefined();
});
it('should list all files in cabinet', async () => {
const projectId = 'test-project';
await storage.indexFile(projectId, { originalPath: '/src/a.ts', summary: 'A' });
await storage.indexFile(projectId, { originalPath: '/src/b.ts', summary: 'B' });
const files = await storage.listCabinet(projectId);
expect(files).toHaveLength(2);
expect(files).toContain('/src/a.ts');
expect(files).toContain('/src/b.ts');
});
it('should return null for non-existent file', async () => {
const result = await storage.getFileFromCabinet('test-project', '/nonexistent.ts');
expect(result).toBeNull();
});
});
describe('Project Profile (Legacy JSON Support)', () => {
it('should set and get project profile', async () => {
const projectId = 'test-project';
const profile = {
name: 'My Project',
standards: { codeStyle: 'StandardJS' },
workflow: { start: 'npm run dev', build: 'npm run build' },
};
await storage.setProjectProfile(projectId, profile);
const retrieved = await storage.getProjectProfile(projectId);
expect(retrieved).toEqual(profile);
});
});
describe('Spatial Map', () => {
it('should add a room to the spatial map', async () => {
const projectId = 'test-project';
const room = {
path: 'src/components',
description: 'UI components',
depth: 2,
connectedRooms: [],
keyItems: ['Button.tsx'],
};
await storage.addRoom(projectId, room);
const map = await storage.getSpatialMap(projectId);
expect(map.rooms['src/components']).toEqual(room);
});
it('should link two rooms', async () => {
const projectId = 'test-project';
await storage.addRoom(projectId, { path: 'src/components', description: 'Components', depth: 2, connectedRooms: [], keyItems: [] });
await storage.addRoom(projectId, { path: 'src/hooks', description: 'Hooks', depth: 2, connectedRooms: [], keyItems: [] });
await storage.linkRooms(projectId, 'src/components', 'src/hooks');
const map = await storage.getSpatialMap(projectId);
expect(map.rooms['src/components'].connectedRooms).toContain('src/hooks'); // JSON stringify match might be order dependent if strict equal, but toContain checks array
expect(map.rooms['src/hooks'].connectedRooms).toContain('src/components');
});
});
describe('Search', () => {
it('should search across all indexed content', async () => {
const projectId = 'test-project';
await storage.indexFile(projectId, { originalPath: '/src/auth.ts', summary: 'Authentication module with login and logout' });
await storage.indexFile(projectId, { originalPath: '/src/db.ts', summary: 'Database connection handler' });
const results = await storage.searchMemory('login', projectId);
expect(results).toHaveLength(1);
expect(results[0].path).toBe('/src/auth.ts');
});
it('should search globally when no project specified', async () => {
await storage.indexFile('project-a', { originalPath: '/src/utils.ts', summary: 'Utility functions' });
await storage.indexFile('project-b', { originalPath: '/lib/helpers.ts', summary: 'Helper utilities' });
const results = await storage.searchMemory('util');
expect(results.length).toBeGreaterThanOrEqual(2);
});
});
describe('Project Management', () => {
it('should list all projects', async () => {
await storage.setActiveContext('project-a', { summary: 'A', lastUpdated: new Date().toISOString() });
await storage.setActiveContext('project-b', { summary: 'B', lastUpdated: new Date().toISOString() });
const projects = await storage.listProjects();
expect(projects).toContain('project-a');
expect(projects).toContain('project-b');
});
});
describe('Bug Logging', () => {
it('should log and resolve a bug', async () => {
const projectId = 'test-project';
const bugId = await storage.logBug({
projectId,
title: 'Test Bug',
description: 'Testing bug logging',
severity: 'high'
});
const bugs = await storage.getBugs(projectId, 'open');
expect(bugs).toHaveLength(1);
expect(bugs[0].id).toBe(bugId);
await storage.resolveBug(bugId, projectId, 'Fixed it');
const resolvedBugs = await storage.getBugs(projectId, 'resolved');
expect(resolvedBugs).toHaveLength(1);
expect(resolvedBugs[0].resolution).toBe('Fixed it');
});
});
});