database.test.tsā¢5.18 kB
import { DatabaseManager } from '../database.js';
import { existsSync, rmSync, statSync } from 'fs';
import { join, dirname } from 'path';
import { tmpdir } from 'os';
describe('DatabaseManager', () => {
let dbPath: string;
let dbManager: DatabaseManager;
beforeEach(() => {
// Create a unique temp database in a subdirectory for each test
// This ensures DatabaseManager creates the directory with secure permissions
const testDir = join(tmpdir(), `mcp-test-${Date.now()}`);
dbPath = join(testDir, 'clipboard.db');
});
afterEach(() => {
if (dbManager) {
dbManager.close();
}
// Clean up test directory and all contents (database + key file)
const testDir = dirname(dbPath);
if (existsSync(testDir)) {
rmSync(testDir, { recursive: true, force: true });
}
});
describe('initialization', () => {
it('should create database file at specified path', () => {
dbManager = new DatabaseManager(dbPath);
expect(existsSync(dbPath)).toBe(true);
const keyPath = dbManager.getEncryptionKeyPath();
expect(keyPath).toBeTruthy();
if (keyPath) {
expect(existsSync(keyPath)).toBe(true);
}
});
it('should create all required tables', () => {
dbManager = new DatabaseManager(dbPath);
const db = dbManager.getConnection();
const tables = db
.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%'`)
.all() as { name: string }[];
const tableNames = tables.map((t) => t.name);
expect(tableNames).toContain('sessions');
expect(tableNames).toContain('clipboard_buffer');
expect(tableNames).toContain('operations_log');
expect(tableNames).toContain('paste_history');
});
it('should enable foreign key constraints', () => {
dbManager = new DatabaseManager(dbPath);
const db = dbManager.getConnection();
const result = db.prepare('PRAGMA foreign_keys').get() as { foreign_keys: number };
expect(result.foreign_keys).toBe(1);
});
it('should create required indexes', () => {
dbManager = new DatabaseManager(dbPath);
const db = dbManager.getConnection();
const indexes = db
.prepare(`SELECT name FROM sqlite_master WHERE type='index' AND name LIKE 'idx_%'`)
.all() as { name: string }[];
const indexNames = indexes.map((i) => i.name);
expect(indexNames).toContain('idx_sessions_last_activity');
expect(indexNames).toContain('idx_operations_log_session');
expect(indexNames).toContain('idx_paste_history_session');
expect(indexNames).toContain('idx_paste_history_undone');
});
it('should enforce secure filesystem permissions', () => {
dbManager = new DatabaseManager(dbPath);
if (process.platform !== 'win32') {
const dirMode = statSync(dirname(dbPath)).mode & 0o777;
const dbMode = statSync(dbPath).mode & 0o777;
expect(dirMode).toBe(0o700);
expect(dbMode).toBe(0o600);
const keyPath = dbManager.getEncryptionKeyPath();
if (keyPath) {
const keyMode = statSync(keyPath).mode & 0o777;
expect(keyMode).toBe(0o600);
}
}
});
});
describe('transactions', () => {
beforeEach(() => {
dbManager = new DatabaseManager(dbPath);
});
it('should commit successful transactions', () => {
const db = dbManager.getConnection();
const timestamp = Date.now();
dbManager.transaction(() => {
db.prepare(
`INSERT INTO sessions (session_id, created_at, last_activity) VALUES (?, ?, ?)`
).run('test-session', timestamp, timestamp);
});
const session = db.prepare('SELECT * FROM sessions WHERE session_id = ?').get('test-session');
expect(session).toBeDefined();
});
it('should rollback failed transactions', () => {
const db = dbManager.getConnection();
const timestamp = Date.now();
try {
dbManager.transaction(() => {
db.prepare(
`INSERT INTO sessions (session_id, created_at, last_activity) VALUES (?, ?, ?)`
).run('test-session', timestamp, timestamp);
throw new Error('Test error');
});
} catch (error) {
// Expected error
}
const session = db.prepare('SELECT * FROM sessions WHERE session_id = ?').get('test-session');
expect(session).toBeUndefined();
});
});
describe('connection management', () => {
it('should provide access to underlying database connection', () => {
dbManager = new DatabaseManager(dbPath);
const db = dbManager.getConnection();
expect(db).toBeDefined();
expect(typeof db.prepare).toBe('function');
});
it('should close database connection', () => {
dbManager = new DatabaseManager(dbPath);
expect(() => dbManager.close()).not.toThrow();
});
it('should expose 32-byte encryption key', () => {
dbManager = new DatabaseManager(dbPath);
const key = dbManager.getEncryptionKey();
expect(key).toBeInstanceOf(Buffer);
expect(key.length).toBe(32);
});
});
});