operation-logger.test.tsā¢14.4 kB
import { OperationLogger } from '../operation-logger.js';
import { DatabaseManager } from '../database.js';
import { existsSync, rmSync } from 'fs';
import { join } from 'path';
import { tmpdir } from 'os';
describe('OperationLogger', () => {
let dbPath: string;
let dbManager: DatabaseManager;
let opLogger: OperationLogger;
let testSessionId: string;
beforeEach(() => {
const uniqueId = `${Date.now()}-${process.pid}-${Math.random().toString(36).slice(2, 9)}`;
dbPath = join(tmpdir(), `test-clipboard-${uniqueId}.db`);
dbManager = new DatabaseManager(dbPath);
opLogger = new OperationLogger(dbManager);
// Create a test session
const db = dbManager.getConnection();
testSessionId = 'test-session-1';
const timestamp = Date.now();
db.prepare('INSERT INTO sessions (session_id, created_at, last_activity) VALUES (?, ?, ?)').run(
testSessionId,
timestamp,
timestamp
);
});
afterEach(() => {
if (dbManager) {
dbManager.close();
const keyPath = dbManager.getEncryptionKeyPath();
if (keyPath && existsSync(keyPath)) {
rmSync(keyPath);
}
}
if (existsSync(dbPath)) {
rmSync(dbPath);
}
});
describe('Log Copy Operation - RED', () => {
it('should log copy with full metadata', () => {
const opId = opLogger.logCopy(testSessionId, {
sourceFile: '/path/to/file.ts',
startLine: 10,
endLine: 20,
content: 'copied content',
});
expect(opId).toBeGreaterThan(0);
const db = dbManager.getConnection();
const op = db.prepare('SELECT * FROM operations_log WHERE id = ?').get(opId) as any;
expect(op.operation_type).toBe('copy');
expect(op.source_file).toBe('/path/to/file.ts');
expect(op.source_start_line).toBe(10);
expect(op.source_end_line).toBe(20);
});
it('should include timestamp', () => {
const beforeLog = Date.now();
const opId = opLogger.logCopy(testSessionId, {
sourceFile: '/file.ts',
startLine: 1,
endLine: 5,
content: 'content',
});
const afterLog = Date.now();
const db = dbManager.getConnection();
const op = db.prepare('SELECT * FROM operations_log WHERE id = ?').get(opId) as any;
expect(op.timestamp).toBeGreaterThanOrEqual(beforeLog);
expect(op.timestamp).toBeLessThanOrEqual(afterLog);
});
it('should link to session', () => {
const opId = opLogger.logCopy(testSessionId, {
sourceFile: '/file.ts',
startLine: 1,
endLine: 1,
content: 'c',
});
const db = dbManager.getConnection();
const op = db.prepare('SELECT * FROM operations_log WHERE id = ?').get(opId) as any;
expect(op.session_id).toBe(testSessionId);
});
});
describe('Log Cut Operation - RED', () => {
it('should log cut with full metadata', () => {
const opId = opLogger.logCut(testSessionId, {
sourceFile: '/path/to/file.ts',
startLine: 5,
endLine: 15,
content: 'cut content',
});
const db = dbManager.getConnection();
const op = db.prepare('SELECT * FROM operations_log WHERE id = ?').get(opId) as any;
expect(op.operation_type).toBe('cut');
expect(op.source_file).toBe('/path/to/file.ts');
});
it('should mark as undoable', () => {
const opId = opLogger.logCut(testSessionId, {
sourceFile: '/file.ts',
startLine: 1,
endLine: 5,
content: 'content',
});
const db = dbManager.getConnection();
const op = db.prepare('SELECT * FROM operations_log WHERE id = ?').get(opId) as any;
expect(op.undoable).toBe(1);
});
});
describe('Log Paste Operation - RED', () => {
it('should log paste with all targets', () => {
const targets = [
{ filePath: '/file1.ts', targetLine: 10, originalContent: 'orig1' },
{ filePath: '/file2.ts', targetLine: 20, originalContent: 'orig2' },
];
const opId = opLogger.logPaste(testSessionId, {
targets,
content: 'pasted content',
});
const db = dbManager.getConnection();
const op = db.prepare('SELECT * FROM operations_log WHERE id = ?').get(opId) as any;
expect(op.operation_type).toBe('paste');
// Verify content_snapshot contains metadata, not actual content
const snapshot = JSON.parse(op.content_snapshot);
expect(snapshot.targets).toBe(2);
expect(snapshot.bytes).toBe(Buffer.byteLength('pasted content', 'utf-8'));
expect(snapshot.hasCutSource).toBe(false);
});
it('should store content snapshots as metadata', () => {
const targets = [{ filePath: '/file1.ts', targetLine: 5, originalContent: 'orig' }];
const opId = opLogger.logPaste(testSessionId, {
targets,
content: 'paste content',
});
const db = dbManager.getConnection();
const op = db.prepare('SELECT * FROM operations_log WHERE id = ?').get(opId) as any;
// Verify content_snapshot contains metadata summary, not plaintext
const snapshot = JSON.parse(op.content_snapshot);
expect(snapshot.targets).toBe(1);
expect(snapshot.bytes).toBeGreaterThan(0);
});
it('should create paste_history entries', () => {
const targets = [{ filePath: '/file1.ts', targetLine: 10, originalContent: 'original1' }];
const opId = opLogger.logPaste(testSessionId, {
targets,
content: 'new content',
});
const db = dbManager.getConnection();
const history = db
.prepare('SELECT * FROM paste_history WHERE operation_log_id = ?')
.all(opId) as any[];
expect(history.length).toBe(1);
expect(history[0].file_path).toBe('/file1.ts');
expect(history[0].paste_line).toBe(10);
// Verify content is encrypted in database
expect(history[0].original_content).toBe('[encrypted]');
expect(history[0].modified_content).toBe('[encrypted]');
// Verify encryption metadata exists
expect(history[0].encrypted_payload).toBeTruthy();
expect(history[0].encryption_iv).toBeTruthy();
expect(history[0].encryption_tag).toBeTruthy();
expect(history[0].encryption_version).toBe(1);
});
it('should handle multiple targets', () => {
const targets = [
{ filePath: '/file1.ts', targetLine: 5, originalContent: 'orig1' },
{ filePath: '/file2.ts', targetLine: 10, originalContent: 'orig2' },
{ filePath: '/file3.ts', targetLine: 15, originalContent: 'orig3' },
];
const opId = opLogger.logPaste(testSessionId, {
targets,
content: 'content',
});
const db = dbManager.getConnection();
const history = db
.prepare('SELECT * FROM paste_history WHERE operation_log_id = ?')
.all(opId) as any[];
expect(history.length).toBe(3);
expect(history.map((h) => h.file_path)).toEqual(['/file1.ts', '/file2.ts', '/file3.ts']);
});
it('should encrypt sensitive content in paste history', () => {
const sensitiveOriginal = 'API_KEY=sk-1234567890abcdef';
const sensitiveModified = 'PASSWORD=MySecretPass123!';
const sensitiveCut = 'TOKEN=bearer_xyz789';
const opId = opLogger.logPaste(testSessionId, {
targets: [
{
filePath: '/config.ts',
targetLine: 5,
originalContent: sensitiveOriginal,
},
],
content: sensitiveModified,
cutSourceFile: '/old-config.ts',
cutSourceContent: sensitiveCut,
});
const db = dbManager.getConnection();
const history = db
.prepare('SELECT * FROM paste_history WHERE operation_log_id = ?')
.get(opId) as any;
// Verify sensitive content is NOT in plaintext in database
expect(history.original_content).toBe('[encrypted]');
expect(history.modified_content).toBe('[encrypted]');
expect(history.cut_source_content).toBeNull();
// Verify encrypted payload exists
expect(history.encrypted_payload).toBeTruthy();
expect(history.encryption_iv).toBeTruthy();
expect(history.encryption_tag).toBeTruthy();
// Verify we can retrieve and decrypt the content correctly
const retrieved = opLogger.getLastPaste(testSessionId);
expect(retrieved).not.toBeNull();
expect(retrieved!.targets[0].originalContent).toBe(sensitiveOriginal);
expect(retrieved!.targets[0].modifiedContent).toBe(sensitiveModified);
expect(retrieved!.cutSourceContent).toBe(sensitiveCut);
});
});
describe('Log Undo Operation - RED', () => {
it('should log undo', () => {
// First create a paste operation
const pasteOpId = opLogger.logPaste(testSessionId, {
targets: [{ filePath: '/file.ts', targetLine: 5, originalContent: 'original' }],
content: 'new',
});
// Then undo it
const undoOpId = opLogger.logUndo(testSessionId, pasteOpId);
const db = dbManager.getConnection();
const op = db.prepare('SELECT * FROM operations_log WHERE id = ?').get(undoOpId) as any;
expect(op.operation_type).toBe('undo');
});
it('should reference original operation', () => {
const pasteOpId = opLogger.logPaste(testSessionId, {
targets: [{ filePath: '/file.ts', targetLine: 5, originalContent: 'original' }],
content: 'new',
});
const undoOpId = opLogger.logUndo(testSessionId, pasteOpId);
const db = dbManager.getConnection();
const op = db.prepare('SELECT * FROM operations_log WHERE id = ?').get(undoOpId) as any;
// The reference is stored in content_snapshot as JSON
expect(op.content_snapshot).toContain(String(pasteOpId));
});
it('should mark paste history as undone', () => {
const pasteOpId = opLogger.logPaste(testSessionId, {
targets: [{ filePath: '/file.ts', targetLine: 5, originalContent: 'original' }],
content: 'new',
});
opLogger.logUndo(testSessionId, pasteOpId);
const db = dbManager.getConnection();
const history = db
.prepare('SELECT * FROM paste_history WHERE operation_log_id = ?')
.get(pasteOpId) as any;
expect(history.undone).toBe(1);
});
});
describe('Get Operation History - RED', () => {
it('should retrieve operations for session', () => {
opLogger.logCopy(testSessionId, {
sourceFile: '/file1.ts',
startLine: 1,
endLine: 5,
content: 'c1',
});
opLogger.logCut(testSessionId, {
sourceFile: '/file2.ts',
startLine: 10,
endLine: 15,
content: 'c2',
});
const history = opLogger.getHistory(testSessionId);
expect(history.length).toBe(2);
});
it('should order by timestamp DESC', () => {
const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
return Promise.resolve()
.then(() =>
opLogger.logCopy(testSessionId, {
sourceFile: '/file1.ts',
startLine: 1,
endLine: 1,
content: 'first',
})
)
.then(() => wait(10))
.then(() =>
opLogger.logCopy(testSessionId, {
sourceFile: '/file2.ts',
startLine: 2,
endLine: 2,
content: 'second',
})
)
.then(() => wait(10))
.then(() =>
opLogger.logCopy(testSessionId, {
sourceFile: '/file3.ts',
startLine: 3,
endLine: 3,
content: 'third',
})
)
.then(() => {
const history = opLogger.getHistory(testSessionId);
expect(history[0].sourceFile).toBe('/file3.ts'); // Most recent first
expect(history[2].sourceFile).toBe('/file1.ts'); // Oldest last
});
});
it('should respect limit parameter', () => {
for (let i = 0; i < 10; i++) {
opLogger.logCopy(testSessionId, {
sourceFile: `/file${i}.ts`,
startLine: i,
endLine: i,
content: `c${i}`,
});
}
const history = opLogger.getHistory(testSessionId, 5);
expect(history.length).toBe(5);
});
it('should not return operations from other sessions', () => {
// Create another session
const db = dbManager.getConnection();
const timestamp = Date.now();
db.prepare(
'INSERT INTO sessions (session_id, created_at, last_activity) VALUES (?, ?, ?)'
).run('other-session', timestamp, timestamp);
opLogger.logCopy(testSessionId, {
sourceFile: '/file1.ts',
startLine: 1,
endLine: 1,
content: 'c1',
});
opLogger.logCopy('other-session', {
sourceFile: '/file2.ts',
startLine: 2,
endLine: 2,
content: 'c2',
});
const history = opLogger.getHistory(testSessionId);
expect(history.length).toBe(1);
expect(history[0].sourceFile).toBe('/file1.ts');
});
});
describe('Get Last Paste - RED', () => {
it('should retrieve last paste operation', () => {
opLogger.logCopy(testSessionId, {
sourceFile: '/file.ts',
startLine: 1,
endLine: 1,
content: 'c',
});
const pasteOpId = opLogger.logPaste(testSessionId, {
targets: [{ filePath: '/target.ts', targetLine: 5, originalContent: 'orig' }],
content: 'pasted',
});
const lastPaste = opLogger.getLastPaste(testSessionId);
expect(lastPaste).toBeDefined();
expect(lastPaste?.operationId).toBe(pasteOpId);
});
it('should return null if no paste operations', () => {
opLogger.logCopy(testSessionId, {
sourceFile: '/file.ts',
startLine: 1,
endLine: 1,
content: 'c',
});
const lastPaste = opLogger.getLastPaste(testSessionId);
expect(lastPaste).toBeNull();
});
it('should not return already undone pastes', () => {
const pasteOpId = opLogger.logPaste(testSessionId, {
targets: [{ filePath: '/file.ts', targetLine: 5, originalContent: 'orig' }],
content: 'pasted',
});
opLogger.logUndo(testSessionId, pasteOpId);
const lastPaste = opLogger.getLastPaste(testSessionId);
expect(lastPaste).toBeNull();
});
});
});