Skip to main content
Glama
readonly-connection.test.ts6.72 kB
import assert from 'assert'; import { describe, test, beforeEach, afterEach } from 'node:test'; import path from 'path'; import fs from 'fs/promises'; import { HybridSearchManager } from '../../src/database/search-manager.js'; import { DatabaseManager } from '../../src/database/schema.js'; describe('Read-Only Connection Tests', () => { const testWorkspacePath = path.join( process.cwd(), 'test', 'fixtures', 'readonly-test-workspace' ); let searchManager: HybridSearchManager; let dbManager: DatabaseManager; beforeEach(async () => { // Clean up any existing test workspace try { await fs.rm(testWorkspacePath, { recursive: true, force: true }); } catch { // Ignore if doesn't exist } // Create test workspace await fs.mkdir(testWorkspacePath, { recursive: true }); // Initialize managers searchManager = new HybridSearchManager(testWorkspacePath); dbManager = new DatabaseManager(testWorkspacePath); // Create a test note first using regular connection const testFilePath = path.join(testWorkspacePath, 'test-note.md'); await fs.writeFile(testFilePath, 'This is test content for search', 'utf8'); await searchManager.upsertNote( 'test-note-1', 'Test Note', 'This is test content for search', 'general', 'test-note.md', testFilePath, { title: 'Test Note', type: 'general', created: new Date().toISOString(), updated: new Date().toISOString(), filename: 'test-note.md', tags: ['test'] } ); }); afterEach(async () => { await searchManager.close(); await dbManager.close(); // Clean up test workspace try { await fs.rm(testWorkspacePath, { recursive: true, force: true }); } catch { // Ignore cleanup errors } }); test('search methods should use read-only connection', async () => { // Test basic search const searchResults = await searchManager.searchNotes('test'); assert.ok(searchResults.length > 0, 'Should find test notes'); assert.strictEqual(searchResults[0].title, 'Test Note', 'Should return correct note'); // Test advanced search const advancedResults = await searchManager.searchNotesAdvanced({ content_contains: 'test', limit: 10 }); assert.ok(advancedResults.results.length > 0, 'Advanced search should find results'); assert.strictEqual( advancedResults.results[0].title, 'Test Note', 'Should return correct note' ); // Test SQL search const sqlResults = await searchManager.searchNotesSQL({ query: 'SELECT * FROM notes WHERE title LIKE ?', params: ['%Test%'] }); assert.ok(sqlResults.results.length > 0, 'SQL search should find results'); assert.strictEqual( sqlResults.results[0].title, 'Test Note', 'Should return correct note' ); }); test('read-only connection should prevent write operations', async () => { // Get a read-only connection directly const readOnlyConnection = await dbManager.connectReadOnly(); // Attempt to insert data should fail await assert.rejects( async () => { await readOnlyConnection.run( `INSERT INTO notes (id, title, content, type, path, filename, created, updated) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [ 'test-write', 'Write Test', 'Content', 'general', '/test', 'test.md', new Date().toISOString(), new Date().toISOString() ] ); }, /readonly/i, 'Read-only connection should prevent INSERT operations' ); // Attempt to update data should fail await assert.rejects( async () => { await readOnlyConnection.run('UPDATE notes SET title = ? WHERE id = ?', [ 'Updated Title', 'test-note-1' ]); }, /readonly/i, 'Read-only connection should prevent UPDATE operations' ); // Attempt to delete data should fail await assert.rejects( async () => { await readOnlyConnection.run('DELETE FROM notes WHERE id = ?', ['test-note-1']); }, /readonly/i, 'Read-only connection should prevent DELETE operations' ); // But SELECT operations should work fine const selectResult = await readOnlyConnection.all( 'SELECT * FROM notes WHERE id = ?', ['test-note-1'] ); assert.ok( selectResult.length > 0, 'Read-only connection should allow SELECT operations' ); await readOnlyConnection.close(); }); test('read-only and read-write connections should coexist', async () => { // Get both types of connections const readWriteConnection = await dbManager.connect(); const readOnlyConnection = await dbManager.connectReadOnly(); // Read-write should be able to insert await readWriteConnection.run( `INSERT INTO notes (id, title, content, type, path, filename, created, updated) VALUES (?, ?, ?, ?, ?, ?, ?, ?)`, [ 'test-coexist', 'Coexist Test', 'Content', 'general', '/test', 'coexist.md', new Date().toISOString(), new Date().toISOString() ] ); // Read-only should be able to see the new data const readOnlyResult = await readOnlyConnection.all( 'SELECT * FROM notes WHERE id = ?', ['test-coexist'] ); assert.strictEqual( readOnlyResult.length, 1, 'Read-only connection should see data inserted by read-write connection' ); // But read-only should still not be able to write await assert.rejects( async () => { await readOnlyConnection.run('DELETE FROM notes WHERE id = ?', ['test-coexist']); }, /readonly/i, 'Read-only connection should still prevent write operations' ); await readWriteConnection.close(); await readOnlyConnection.close(); }); test('connection lifecycle management', async () => { // Test that connections are properly managed const initialSearchResults = await searchManager.searchNotes('test'); assert.ok(initialSearchResults.length > 0, 'Initial search should work'); // Close and ensure cleanup await searchManager.close(); // Create new instance and verify it works const newSearchManager = new HybridSearchManager(testWorkspacePath); const newSearchResults = await newSearchManager.searchNotes('test'); assert.ok( newSearchResults.length > 0, 'New search manager should work after previous one was closed' ); await newSearchManager.close(); }); });

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/disnet/flint-note'

If you have feedback or need assistance with the MCP directory API, please join our Discord server