Files
claude-mem/tests/cursor-registry.test.ts
T
Alex Newman 129c22c48d Add comprehensive tests for Cursor functionality
- Implement tests for cursor context updates in `cursor-context-update.test.ts`, validating context file creation, content structure, and edge cases.
- Create tests for cursor hook outputs in `cursor-hook-outputs.test.ts`, ensuring correct JSON output from hook scripts and handling of various input scenarios.
- Add tests for JSON utility functions in `cursor-hooks-json-utils.test.ts`, covering parsing, project name extraction, and URL encoding.
- Introduce tests for MCP configuration in `cursor-mcp-config.test.ts`, verifying configuration creation, updates, and format validation.
- Develop tests for the cursor project registry in `cursor-registry.test.ts`, ensuring correct registration, unregistration, and JSON format compliance.
2025-12-29 22:58:42 -05:00

172 lines
6.0 KiB
TypeScript

import { describe, it, expect, beforeEach, afterEach } from 'bun:test';
import { mkdirSync, writeFileSync, readFileSync, existsSync, rmSync } from 'fs';
import { join } from 'path';
import { tmpdir } from 'os';
import {
readCursorRegistry,
writeCursorRegistry,
registerCursorProject,
unregisterCursorProject
} from '../src/utils/cursor-utils';
/**
* Tests for Cursor Project Registry functionality
*
* These tests validate the file-based registry that tracks which projects
* have Cursor hooks installed for automatic context updates.
*
* The registry is stored at ~/.claude-mem/cursor-projects.json
*/
describe('Cursor Project Registry', () => {
let tempDir: string;
let registryFile: string;
beforeEach(() => {
// Create unique temp directory for each test
tempDir = join(tmpdir(), `cursor-registry-test-${Date.now()}-${Math.random().toString(36).slice(2)}`);
mkdirSync(tempDir, { recursive: true });
registryFile = join(tempDir, 'cursor-projects.json');
});
afterEach(() => {
// Clean up temp directory
try {
rmSync(tempDir, { recursive: true, force: true });
} catch {
// Ignore cleanup errors
}
});
describe('readCursorRegistry', () => {
it('returns empty object when registry file does not exist', () => {
const registry = readCursorRegistry(registryFile);
expect(registry).toEqual({});
});
it('returns empty object when registry file is corrupt JSON', () => {
writeFileSync(registryFile, 'not valid json {{{');
const registry = readCursorRegistry(registryFile);
expect(registry).toEqual({});
});
it('returns parsed registry when file exists', () => {
const expected = {
'my-project': {
workspacePath: '/home/user/projects/my-project',
installedAt: '2025-01-01T00:00:00.000Z'
}
};
writeFileSync(registryFile, JSON.stringify(expected));
const registry = readCursorRegistry(registryFile);
expect(registry).toEqual(expected);
});
});
describe('registerCursorProject', () => {
it('creates registry file if it does not exist', () => {
registerCursorProject(registryFile, 'new-project', '/path/to/project');
expect(existsSync(registryFile)).toBe(true);
});
it('stores project with workspacePath and installedAt', () => {
const before = Date.now();
registerCursorProject(registryFile, 'test-project', '/workspace/test');
const after = Date.now();
const registry = readCursorRegistry(registryFile);
expect(registry['test-project']).toBeDefined();
expect(registry['test-project'].workspacePath).toBe('/workspace/test');
// Verify installedAt is a valid ISO timestamp within the test window
const installedAt = new Date(registry['test-project'].installedAt).getTime();
expect(installedAt).toBeGreaterThanOrEqual(before);
expect(installedAt).toBeLessThanOrEqual(after);
});
it('preserves existing projects when registering new one', () => {
registerCursorProject(registryFile, 'project-a', '/path/a');
registerCursorProject(registryFile, 'project-b', '/path/b');
const registry = readCursorRegistry(registryFile);
expect(Object.keys(registry)).toHaveLength(2);
expect(registry['project-a'].workspacePath).toBe('/path/a');
expect(registry['project-b'].workspacePath).toBe('/path/b');
});
it('overwrites existing project with same name', () => {
registerCursorProject(registryFile, 'my-project', '/old/path');
registerCursorProject(registryFile, 'my-project', '/new/path');
const registry = readCursorRegistry(registryFile);
expect(Object.keys(registry)).toHaveLength(1);
expect(registry['my-project'].workspacePath).toBe('/new/path');
});
it('handles special characters in project name', () => {
const projectName = 'my-project_v2.0 (beta)';
registerCursorProject(registryFile, projectName, '/path/to/project');
const registry = readCursorRegistry(registryFile);
expect(registry[projectName]).toBeDefined();
expect(registry[projectName].workspacePath).toBe('/path/to/project');
});
});
describe('unregisterCursorProject', () => {
it('removes specified project from registry', () => {
registerCursorProject(registryFile, 'project-a', '/path/a');
registerCursorProject(registryFile, 'project-b', '/path/b');
unregisterCursorProject(registryFile, 'project-a');
const registry = readCursorRegistry(registryFile);
expect(registry['project-a']).toBeUndefined();
expect(registry['project-b']).toBeDefined();
});
it('does nothing when unregistering non-existent project', () => {
registerCursorProject(registryFile, 'existing', '/path');
// Should not throw
unregisterCursorProject(registryFile, 'non-existent');
const registry = readCursorRegistry(registryFile);
expect(registry['existing']).toBeDefined();
});
it('handles unregister when registry file does not exist', () => {
// Should not throw even when file doesn't exist
unregisterCursorProject(registryFile, 'any-project');
// File should not be created by unregister
expect(existsSync(registryFile)).toBe(false);
});
});
describe('registry format validation', () => {
it('stores registry as pretty-printed JSON', () => {
registerCursorProject(registryFile, 'test', '/path');
const content = readFileSync(registryFile, 'utf-8');
// Should be indented (pretty-printed)
expect(content).toContain('\n');
expect(content).toContain(' ');
});
it('registry file is valid JSON that can be read by other tools', () => {
registerCursorProject(registryFile, 'project-1', '/path/1');
registerCursorProject(registryFile, 'project-2', '/path/2');
// Read raw and parse with JSON.parse (not our helper)
const content = readFileSync(registryFile, 'utf-8');
const parsed = JSON.parse(content);
expect(parsed).toHaveProperty('project-1');
expect(parsed).toHaveProperty('project-2');
});
});
});