@shaxpir/sharedb-storage-sqlite
Version:
Shared SQLite storage components for ShareDB adapters
351 lines (299 loc) • 10.2 kB
JavaScript
const expect = require('chai').expect;
const path = require('path');
const fs = require('fs');
const SqliteStorage = require('..');
const BetterSqliteAdapter = require('@shaxpir/sharedb-storage-node-sqlite');
const CollectionPerTableStrategy = require('..').CollectionPerTableStrategy;
const { cleanupTestDatabases } = require('./test-cleanup');
describe('CollectionPerTableStrategy Inventory Management', function() {
this.timeout(10000); // Increase timeout to 10 seconds
after(function() {
cleanupTestDatabases();
});
const testDbDir = path.join(__dirname, 'test-databases');
const testDbFile = 'test-inventory.db';
const testDbPath = path.join(testDbDir, testDbFile);
beforeEach(function(done) {
// Create test directory if it doesn't exist
if (!fs.existsSync(testDbDir)) {
fs.mkdirSync(testDbDir, { recursive: true });
}
// Clean up any existing test database
if (fs.existsSync(testDbPath)) {
fs.unlinkSync(testDbPath);
}
done();
});
afterEach(function(done) {
// Clean up test database
if (fs.existsSync(testDbPath)) {
fs.unlinkSync(testDbPath);
}
done();
});
it('should properly maintain inventory when writing documents', function(done) {
const adapter = new BetterSqliteAdapter(testDbPath, {debug: false});
const schemaStrategy = new CollectionPerTableStrategy({
collectionConfig: {
'term': {
indexes: ['text', 'starred_at'],
encryptedFields: []
},
'session': {
indexes: ['device_id', 'started_at'],
encryptedFields: []
}
},
debug: false
});
const storage = new SqliteStorage({
adapter: adapter,
schemaStrategy: schemaStrategy,
dbFileName: testDbFile,
dbFileDir: testDbDir,
debug: true
});
console.log('Initializing storage...');
storage.initialize(function(err, inventory) {
console.log('Initialize callback, err:', err, 'inventory:', inventory);
expect(err).to.be.null;
expect(inventory).to.exist;
// Create test documents
const termDoc = {
id: 'term/term1',
payload: {
collection: 'term',
id: 'term1',
text: 'hello',
v: 1
}
};
const sessionDoc = {
id: 'session/session1',
payload: {
collection: 'session',
id: 'session1',
device_id: 'device1',
v: 1
}
};
// Write documents
storage.writeRecords({docs: [termDoc, sessionDoc]}, function(writeErr) {
if (writeErr) {
console.error('Write error:', writeErr);
done(writeErr);
return;
}
expect(writeErr).to.not.exist;
// Check inventory was updated correctly
storage.readInventory(function(invErr, inv) {
expect(invErr).to.not.exist;
expect(inv).to.exist;
expect(inv.payload).to.exist;
expect(inv.payload.collections).to.exist;
// Verify term entry in inventory
expect(inv.payload.collections.term).to.exist;
expect(inv.payload.collections.term['term/term1']).to.deep.equal({v: 1, p: false});
// Verify session entry in inventory
expect(inv.payload.collections.session).to.exist;
expect(inv.payload.collections.session['session/session1']).to.deep.equal({v: 1, p: false});
storage.close(done);
});
});
});
});
it('should return null for non-existent documents', function(done) {
const adapter = new BetterSqliteAdapter(testDbPath, {debug: false});
const schemaStrategy = new CollectionPerTableStrategy({
collectionConfig: {
'term': {
indexes: [],
encryptedFields: []
}
},
debug: false
});
const storage = new SqliteStorage({
adapter: adapter,
schemaStrategy: schemaStrategy,
dbFileName: testDbFile,
dbFileDir: testDbDir,
debug: false
});
storage.initialize(function(err) {
expect(err).to.be.null;
// Try to read non-existent document
storage.readRecord('docs', 'term/nonexistent', function(payload) {
expect(payload).to.be.null;
storage.close(done);
});
});
});
it('should update inventory when documents are updated', function(done) {
const adapter = new BetterSqliteAdapter(testDbPath, {debug: false});
const schemaStrategy = new CollectionPerTableStrategy({
collectionConfig: {
'term': {
indexes: [],
encryptedFields: []
}
},
debug: false
});
const storage = new SqliteStorage({
adapter: adapter,
schemaStrategy: schemaStrategy,
dbFileName: testDbFile,
dbFileDir: testDbDir,
debug: false
});
storage.initialize(function(err) {
expect(err).to.be.null;
const termDoc = {
id: 'term/term1',
payload: {
collection: 'term',
id: 'term1',
text: 'v1',
v: 1
}
};
// Write initial version
storage.writeRecords({docs: [termDoc]}, function(writeErr1) {
expect(writeErr1).to.not.exist;
// Update document
termDoc.payload.text = 'v2';
termDoc.payload.v = 2;
storage.writeRecords({docs: [termDoc]}, function(writeErr2) {
expect(writeErr2).to.not.exist;
// Check inventory was updated with new version
storage.readInventory(function(invErr, inv) {
expect(invErr).to.not.exist;
expect(inv.payload.collections.term['term/term1']).to.deep.equal({v: 2, p: false});
storage.close(done);
});
});
});
});
});
it('should handle bulk reads with inventory lookups', function(done) {
const adapter = new BetterSqliteAdapter(testDbPath, {debug: false});
const schemaStrategy = new CollectionPerTableStrategy({
collectionConfig: {
'term': {
indexes: [],
encryptedFields: []
},
'session': {
indexes: [],
encryptedFields: []
}
},
debug: false
});
const storage = new SqliteStorage({
adapter: adapter,
schemaStrategy: schemaStrategy,
dbFileName: testDbFile,
dbFileDir: testDbDir,
debug: false
});
storage.initialize(function(err) {
expect(err).to.be.null;
const docs = [
{
id: 'term/term1',
payload: { collection: 'term', id: 'term1', text: 'hello', v: 1 }
},
{
id: 'term/term2',
payload: { collection: 'term', id: 'term2', text: 'world', v: 1 }
},
{
id: 'session/session1',
payload: { collection: 'session', id: 'session1', device: 'dev1', v: 1 }
}
];
// Write documents
storage.writeRecords({docs: docs}, function(writeErr) {
expect(writeErr).to.not.exist;
// With CollectionPerTableStrategy, we need to read from each collection separately
const termIdsToRead = ['term/term1', 'term/nonexistent', 'term/term2'];
const sessionIdsToRead = ['session/session1'];
// Read term documents
storage.readRecordsBulk('term', termIdsToRead, function(termErr, termRecords) {
expect(termErr).to.not.exist;
expect(termRecords).to.have.lengthOf(2); // Only 2 exist
// Verify term documents
const termRecordsById = {};
termRecords.forEach(r => termRecordsById[r.id] = r);
expect(termRecordsById['term/term1']).to.exist;
expect(termRecordsById['term/term1'].payload.text).to.equal('hello');
expect(termRecordsById['term/term2']).to.exist;
expect(termRecordsById['term/term2'].payload.text).to.equal('world');
expect(termRecordsById['term/nonexistent']).to.not.exist;
// Read session documents
storage.readRecordsBulk('session', sessionIdsToRead, function(sessionErr, sessionRecords) {
expect(sessionErr).to.not.exist;
expect(sessionRecords).to.have.lengthOf(1);
expect(sessionRecords[0].id).to.equal('session/session1');
expect(sessionRecords[0].payload.device).to.equal('dev1');
storage.close(done);
});
});
});
});
});
it('should properly clean up inventory when documents are deleted', function(done) {
const adapter = new BetterSqliteAdapter(testDbPath, {debug: false});
const schemaStrategy = new CollectionPerTableStrategy({
collectionConfig: {
'term': {
indexes: [],
encryptedFields: []
}
},
debug: false
});
const storage = new SqliteStorage({
adapter: adapter,
schemaStrategy: schemaStrategy,
dbFileName: testDbFile,
dbFileDir: testDbDir,
debug: false
});
storage.initialize(function(err) {
expect(err).to.be.null;
const termDoc = {
id: 'term/term1',
payload: {
collection: 'term',
id: 'term1',
text: 'test',
v: 1
}
};
// Write document
storage.writeRecords({docs: [termDoc]}, function(writeErr) {
expect(writeErr).to.not.exist;
// Delete document
storage.deleteRecord('term', 'term/term1', function(delErr) {
expect(delErr).to.not.exist;
// Check inventory was updated
storage.readInventory(function(invErr, inv) {
expect(invErr).to.not.exist;
// Term should either not exist in inventory or be empty
if (inv.payload.collections.term) {
expect(inv.payload.collections.term['term/term1']).to.not.exist;
}
// Try to read deleted document
storage.readRecord('docs', 'term/term1', function(payload) {
expect(payload).to.be.null;
storage.close(done);
});
});
});
});
});
});
});