@shaxpir/sharedb-storage-sqlite
Version:
Shared SQLite storage components for ShareDB adapters
307 lines (260 loc) • 10.4 kB
JavaScript
/**
* Tests for AttachedCollectionPerTableStrategy and SqliteStorage with attached databases
* Moved from sharedb-storage-node-sqlite/test/attached-adapter-test.js
*/
const assert = require('assert');
const fs = require('fs');
const path = require('path');
const { BetterSqliteAdapter, AttachedBetterSqliteAdapter } = require('@shaxpir/sharedb-storage-node-sqlite');
const { SqliteStorage, AttachedCollectionPerTableStrategy } = require('../..');
const { cleanupTestDatabases } = require('./test-cleanup');
describe('AttachedCollectionPerTableStrategy and SqliteStorage', function() {
const TEST_DIR = path.join(__dirname, 'test-databases');
const PRIMARY_DB = path.join(TEST_DIR, 'test-primary.db');
const ATTACHED_DB = path.join(TEST_DIR, 'test-attached.db');
// Ensure test directory exists
before(function() {
if (!fs.existsSync(TEST_DIR)) {
fs.mkdirSync(TEST_DIR, { recursive: true });
}
});
// Clean up test databases after each test
afterEach(function() {
try {
if (fs.existsSync(PRIMARY_DB)) fs.unlinkSync(PRIMARY_DB);
if (fs.existsSync(ATTACHED_DB)) fs.unlinkSync(ATTACHED_DB);
} catch (e) {
// Ignore errors during cleanup
}
});
after(function() {
cleanupTestDatabases();
});
describe('AttachedCollectionPerTableStrategy', function() {
it('should initialize schema in attached database', async function() {
// Create attached adapter
const adapter = new AttachedBetterSqliteAdapter(
PRIMARY_DB,
{
attachments: [
{ path: ATTACHED_DB, alias: 'sharedb' }
]
},
{ debug: false }
);
await adapter.connect();
// Create strategy with attachment alias
const strategy = new AttachedCollectionPerTableStrategy({
attachmentAlias: 'sharedb',
collectionConfig: {
'users': {
indexes: ['email'],
encryptedFields: []
}
}
});
// Initialize schema
await new Promise((resolve, reject) => {
strategy.initializeSchema(adapter, (err) => {
if (err) reject(err);
else resolve();
});
});
// Verify tables were created in attached database
const inventoryTable = await adapter.getFirstAsync(
"SELECT name FROM sharedb.sqlite_master WHERE type='table' AND name='sharedb_inventory'"
);
assert(inventoryTable);
const usersTable = await adapter.getFirstAsync(
"SELECT name FROM sharedb.sqlite_master WHERE type='table' AND name='users'"
);
assert(usersTable);
await adapter.disconnect();
});
it('should prefix table names correctly', function() {
const strategy = new AttachedCollectionPerTableStrategy({
attachmentAlias: 'sharedb'
});
assert.strictEqual(strategy.getTableName('users'), 'sharedb.users');
assert.strictEqual(strategy.getTableName('__inventory__'), 'sharedb.sharedb_inventory');
});
it('should work with SqliteStorage', async function() {
// Create attached adapter
const adapter = new AttachedBetterSqliteAdapter(
PRIMARY_DB,
{
attachments: [
{ path: ATTACHED_DB, alias: 'sharedb' }
]
},
{ debug: false }
);
// Create strategy with attachment alias
const strategy = new AttachedCollectionPerTableStrategy({
attachmentAlias: 'sharedb',
collectionConfig: {}
});
// Create storage with attached adapter and strategy
const storage = new SqliteStorage({
adapter: adapter,
schemaStrategy: strategy
});
await new Promise((resolve, reject) => {
storage.initialize((err) => {
if (err) reject(err);
else resolve();
});
});
// Verify that storage is initialized and working
assert(storage.isReady());
// Test that we can interact with the attached database
// Simply verify that the tables were created in the attached database
const tables = await adapter.getAllAsync(
"SELECT name FROM sharedb.sqlite_master WHERE type='table' ORDER BY name"
);
// Should have at least the inventory table
const tableNames = tables.map(t => t.name);
assert(tableNames.includes('sharedb_inventory'));
await new Promise((resolve, reject) => {
storage.close((err) => {
if (err) reject(err);
else resolve();
});
});
});
});
describe('Automatic Pre-initialization', function() {
it('should automatically initialize ShareDB database with indexes before attachment', async function() {
// Create a fresh ShareDB database path (doesn't exist yet)
const FRESH_SHAREDB_DB = path.join(TEST_DIR, 'fresh-sharedb.db');
try {
// Ensure it doesn't exist
if (fs.existsSync(FRESH_SHAREDB_DB)) {
fs.unlinkSync(FRESH_SHAREDB_DB);
}
// Create primary database
const primaryAdapter = new BetterSqliteAdapter(PRIMARY_DB);
await primaryAdapter.connect();
await primaryAdapter.runAsync('CREATE TABLE main_table (id INTEGER PRIMARY KEY)');
await primaryAdapter.disconnect();
// Create attached adapter with a strategy
const adapter = new AttachedBetterSqliteAdapter(
PRIMARY_DB,
{
attachments: [
{ path: FRESH_SHAREDB_DB, alias: 'sharedb' }
]
},
{ debug: false }
);
// Create strategy with collection config that includes indexes
// Using realistic field paths that match actual ShareDB document structure
const strategy = new AttachedCollectionPerTableStrategy({
attachmentAlias: 'sharedb',
collectionConfig: {
'test_collection': {
indexes: ['payload.data.field1', 'payload.data.field2', 'payload.data.field3'],
encryptedFields: []
}
}
});
// Create storage - this should trigger automatic initialization
const storage = new SqliteStorage({
adapter: adapter,
schemaStrategy: strategy
});
// Initialize storage
await new Promise((resolve, reject) => {
storage.initialize((err) => {
if (err) reject(err);
else resolve();
});
});
// Verify the ShareDB database was created with indexes
const indexes = await adapter.getAllAsync(
"SELECT name FROM sharedb.sqlite_master WHERE type='index' ORDER BY name"
);
const indexNames = indexes.map(i => i.name);
// Debug output to see what indexes were actually created
console.log('[Test] Indexes found in attached database:', indexNames);
// Should have inventory indexes
assert(indexNames.includes('idx_inventory_collection'));
assert(indexNames.includes('idx_inventory_updated'));
// Should have collection indexes with idx_ prefix and payload.data in the path
assert(indexNames.includes('idx_test_collection_payload_data_field1'));
assert(indexNames.includes('idx_test_collection_payload_data_field2'));
assert(indexNames.includes('idx_test_collection_payload_data_field3'));
// Verify tables were created
const tables = await adapter.getAllAsync(
"SELECT name FROM sharedb.sqlite_master WHERE type='table' ORDER BY name"
);
const tableNames = tables.map(t => t.name);
console.log('[Test] Tables found in attached database:', tableNames);
assert(tableNames.includes('sharedb_inventory'));
assert(tableNames.includes('test_collection'));
await new Promise((resolve, reject) => {
storage.close((err) => {
if (err) reject(err);
else resolve();
});
});
} finally {
// Cleanup
if (fs.existsSync(FRESH_SHAREDB_DB)) {
fs.unlinkSync(FRESH_SHAREDB_DB);
}
}
});
it('should handle pre-existing initialized databases correctly', async function() {
// First, create and initialize a ShareDB database
const sharedbAdapter = new BetterSqliteAdapter(ATTACHED_DB);
await sharedbAdapter.connect();
await sharedbAdapter.runAsync('CREATE TABLE sharedb_inventory (collection TEXT, doc_id TEXT, version_num REAL, version_str TEXT, has_pending INTEGER, updated_at INTEGER, PRIMARY KEY(collection, doc_id))');
await sharedbAdapter.runAsync('CREATE INDEX idx_inventory_collection ON sharedb_inventory (collection)');
await sharedbAdapter.runAsync('CREATE INDEX idx_inventory_updated ON sharedb_inventory (updated_at)');
await sharedbAdapter.disconnect();
// Create primary database
const primaryAdapter = new BetterSqliteAdapter(PRIMARY_DB);
await primaryAdapter.connect();
await primaryAdapter.runAsync('CREATE TABLE main_table (id INTEGER PRIMARY KEY)');
await primaryAdapter.disconnect();
// Now use it with attachment mode - it should detect existing indexes
const adapter = new AttachedBetterSqliteAdapter(
PRIMARY_DB,
{
attachments: [
{ path: ATTACHED_DB, alias: 'sharedb' }
]
},
{ debug: false }
);
const strategy = new AttachedCollectionPerTableStrategy({
attachmentAlias: 'sharedb',
collectionConfig: {}
});
const storage = new SqliteStorage({
adapter: adapter,
schemaStrategy: strategy
});
await new Promise((resolve, reject) => {
storage.initialize((err) => {
if (err) reject(err);
else resolve();
});
});
// Verify indexes still exist and weren't recreated
const indexes = await adapter.getAllAsync(
"SELECT name FROM sharedb.sqlite_master WHERE type='index'"
);
const indexNames = indexes.map(i => i.name);
assert(indexNames.includes('idx_inventory_collection'));
assert(indexNames.includes('idx_inventory_updated'));
await new Promise((resolve, reject) => {
storage.close((err) => {
if (err) reject(err);
else resolve();
});
});
});
});
});