mirror of
https://github.com/orangecoding/fredy.git
synced 2026-06-16 12:31:07 +00:00
142 lines
4.1 KiB
JavaScript
142 lines
4.1 KiB
JavaScript
/*
|
|
* Copyright (c) 2026 by Christian Kellner.
|
|
* Licensed under Apache-2.0 with Commons Clause and Attribution/Naming Clause
|
|
*/
|
|
|
|
import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
|
|
// 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;
|
|
};
|
|
};
|
|
|
|
vi.resetModules();
|
|
vi.doMock('fs', () => ({ default: fsMock, ...fsMock }));
|
|
vi.doMock('better-sqlite3', () => ({ default: BetterSqlite3Mock }));
|
|
SqliteConnection = (await import('../../lib/services/storage/SqliteConnection.js')).default;
|
|
});
|
|
|
|
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).toBe(db2);
|
|
// journal_mode, synchronous, cache_size, foreign_keys, optimize
|
|
expect(calls.db.pragma).toEqual([
|
|
'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).toHaveLength(0);
|
|
});
|
|
|
|
it('executes query and execute helpers', () => {
|
|
const rows = SqliteConnection.query('SELECT 1', {});
|
|
expect(rows).toBeInstanceOf(Array);
|
|
expect(rows[0]).toEqual({ x: 1 });
|
|
|
|
const info = SqliteConnection.execute('UPDATE x SET y=1 WHERE id=@id', { id: 5 });
|
|
expect(info).toHaveProperty('changes', 1);
|
|
});
|
|
|
|
it('tableExists uses sqlite_master get()', () => {
|
|
const exists = SqliteConnection.tableExists('users');
|
|
expect(exists).toBe(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).toBe(42);
|
|
expect(calls.db.prepare).toContain('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).toContain('optimize');
|
|
|
|
SqliteConnection.close();
|
|
// close increments close counter
|
|
expect(calls.db.close).toBe(1);
|
|
});
|
|
});
|