mirror of
https://github.com/orangecoding/fredy.git
synced 2026-06-16 12:31:07 +00:00
Migrate to SQLite (#174)
* Migrating Fredy from LowDb to SqLite 🎉
* adding new sql migration system for future sql migrations
* adding setting to change sqlite path for db files
* create migration plan for graceful migration lowdb -> sqlite
* Improving Documentation
* adding test for sqliteconnection
* upgrading dependencies
* making nodejs 22 as min version
* improve scraper
* adding overwrite ability for db migra
This commit is contained in:
committed by
GitHub
parent
18fdbd761a
commit
8d95f052c6
142
test/storage/SqliteConnection.test.js
Normal file
142
test/storage/SqliteConnection.test.js
Normal file
@@ -0,0 +1,142 @@
|
||||
import { expect } from 'chai';
|
||||
import esmock from 'esmock';
|
||||
|
||||
// We explicitly avoid touching the real filesystem or creating a real DB file.
|
||||
// better-sqlite3 is fully mocked and operates in-memory via our stubs.
|
||||
|
||||
describe('SqliteConnection', () => {
|
||||
let SqliteConnection;
|
||||
let calls;
|
||||
|
||||
beforeEach(async () => {
|
||||
calls = {
|
||||
fs: { existsSync: [], mkdirSync: [] },
|
||||
db: { pragma: [], prepare: [], transactionWraps: 0, close: 0 },
|
||||
prepareAll: [],
|
||||
prepareRun: [],
|
||||
prepareGet: [],
|
||||
processOnce: [],
|
||||
logs: { warn: [], debug: [] },
|
||||
};
|
||||
|
||||
// stub for fs
|
||||
const fsMock = {
|
||||
existsSync: (dir) => {
|
||||
calls.fs.existsSync.push(dir);
|
||||
// Pretend directory always exists to avoid mkdir
|
||||
return true;
|
||||
},
|
||||
mkdirSync: (dir, opts) => {
|
||||
calls.fs.mkdirSync.push({ dir, opts });
|
||||
},
|
||||
};
|
||||
|
||||
// Prepare object returned from db.prepare()
|
||||
const prepareObj = {
|
||||
all: (params) => {
|
||||
calls.prepareAll.push(params);
|
||||
return [{ x: 1 }];
|
||||
},
|
||||
run: (params) => {
|
||||
calls.prepareRun.push(params);
|
||||
return { changes: 1 };
|
||||
},
|
||||
get: (param) => {
|
||||
calls.prepareGet.push(param);
|
||||
// return truthy by default
|
||||
return { one: 1 };
|
||||
},
|
||||
};
|
||||
|
||||
// Database mock constructor
|
||||
const BetterSqlite3Mock = function (filepath, options) {
|
||||
// expose on instance
|
||||
this.filepath = filepath;
|
||||
this.options = options;
|
||||
this.pragma = (p) => {
|
||||
calls.db.pragma.push(p);
|
||||
return undefined;
|
||||
};
|
||||
this.prepare = (sql) => {
|
||||
calls.db.prepare.push(sql);
|
||||
return prepareObj;
|
||||
};
|
||||
this.transaction = (fn) => {
|
||||
// better-sqlite3 returns a function that executes inside a transaction
|
||||
return (cb) => {
|
||||
calls.db.transactionWraps += 1;
|
||||
return fn(cb);
|
||||
};
|
||||
};
|
||||
this.close = () => {
|
||||
calls.db.close += 1;
|
||||
};
|
||||
};
|
||||
|
||||
// esmock the module with our stubs
|
||||
SqliteConnection = await esmock(
|
||||
'../../lib/services/storage/SqliteConnection.js',
|
||||
{},
|
||||
{
|
||||
fs: fsMock,
|
||||
'better-sqlite3': { default: BetterSqlite3Mock },
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
// ensure we can close between tests
|
||||
SqliteConnection.close();
|
||||
});
|
||||
|
||||
it('creates singleton connection and applies PRAGMAs without touching disk', () => {
|
||||
const db1 = SqliteConnection.getConnection();
|
||||
const db2 = SqliteConnection.getConnection();
|
||||
|
||||
expect(db1).to.equal(db2);
|
||||
// journal_mode, synchronous, cache_size, foreign_keys, optimize
|
||||
expect(calls.db.pragma).to.deep.equal([
|
||||
'journal_mode = WAL',
|
||||
'synchronous = NORMAL',
|
||||
'cache_size = -64000',
|
||||
'foreign_keys = ON',
|
||||
'optimize',
|
||||
]);
|
||||
// mkdirSync should not be called because existsSync returned true
|
||||
expect(calls.fs.mkdirSync).to.have.length(0);
|
||||
});
|
||||
|
||||
it('executes query and execute helpers', () => {
|
||||
const rows = SqliteConnection.query('SELECT 1', {});
|
||||
expect(rows).to.be.an('array');
|
||||
expect(rows[0]).to.deep.equal({ x: 1 });
|
||||
|
||||
const info = SqliteConnection.execute('UPDATE x SET y=1 WHERE id=@id', { id: 5 });
|
||||
expect(info).to.have.property('changes', 1);
|
||||
});
|
||||
|
||||
it('tableExists uses sqlite_master get()', () => {
|
||||
const exists = SqliteConnection.tableExists('users');
|
||||
expect(exists).to.equal(true);
|
||||
});
|
||||
|
||||
it('withTransaction wraps callback', () => {
|
||||
const result = SqliteConnection.withTransaction((db) => {
|
||||
// ensure we can use the db to prepare
|
||||
db.prepare('SELECT inside').all({});
|
||||
return 42;
|
||||
});
|
||||
expect(result).to.equal(42);
|
||||
expect(calls.db.prepare).to.include('SELECT inside');
|
||||
});
|
||||
|
||||
it('optimize() delegates to PRAGMA optimize and close() calls it again then closes', () => {
|
||||
SqliteConnection.optimize();
|
||||
// It will use the existing connection and call pragma('optimize')
|
||||
expect(calls.db.pragma).to.include('optimize');
|
||||
|
||||
SqliteConnection.close();
|
||||
// close increments close counter
|
||||
expect(calls.db.close).to.equal(1);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user