From 462b1896dd1fc448b9c282047408f7228505365b Mon Sep 17 00:00:00 2001 From: Justin Wind Date: Tue, 15 Jul 2025 15:02:37 -0700 Subject: [PATCH] remove old db stuff --- server.js | 12 - src/db/base.js | 72 --- src/db/index-old.js | 43 -- src/db/postgres-old/index.js | 308 ----------- src/db/postgres-old/sql/auth-get-by-id.sql | 1 - src/db/postgres-old/sql/auth-upsert.sql | 4 - src/db/postgres-old/sql/link-access.sql | 5 - src/db/postgres-old/sql/link-expire.sql | 1 - src/db/postgres-old/sql/link-get-all.sql | 6 - src/db/postgres-old/sql/link-get-by-id.sql | 5 - src/db/postgres-old/sql/link-get-by-url.sql | 5 - src/db/postgres-old/sql/link-update.sql | 1 - src/db/postgres-old/sql/link-upsert.sql | 10 - .../postgres-old/sql/schema/1.0.0/apply.sql | 53 -- .../postgres-old/sql/schema/1.0.0/revert.sql | 7 - .../postgres-old/sql/schema/1.0.1/apply.sql | 14 - .../postgres-old/sql/schema/1.0.1/revert.sql | 9 - src/db/postgres-old/sql/schema/init.sql | 12 - src/db/sqlite-old/index.js | 343 ------------- src/errors.js | 8 - test/src/db/base.js | 66 --- test/src/db/postgres/index.js | 331 ------------ test/src/db/sqlite/index.js | 477 ------------------ test/src/manager.js | 16 +- 24 files changed, 4 insertions(+), 1805 deletions(-) delete mode 100644 src/db/base.js delete mode 100644 src/db/index-old.js delete mode 100644 src/db/postgres-old/index.js delete mode 100644 src/db/postgres-old/sql/auth-get-by-id.sql delete mode 100644 src/db/postgres-old/sql/auth-upsert.sql delete mode 100644 src/db/postgres-old/sql/link-access.sql delete mode 100644 src/db/postgres-old/sql/link-expire.sql delete mode 100644 src/db/postgres-old/sql/link-get-all.sql delete mode 100644 src/db/postgres-old/sql/link-get-by-id.sql delete mode 100644 src/db/postgres-old/sql/link-get-by-url.sql delete mode 100644 src/db/postgres-old/sql/link-update.sql delete mode 100644 src/db/postgres-old/sql/link-upsert.sql delete mode 100644 src/db/postgres-old/sql/schema/1.0.0/apply.sql delete mode 100644 src/db/postgres-old/sql/schema/1.0.0/revert.sql delete mode 100644 src/db/postgres-old/sql/schema/1.0.1/apply.sql delete mode 100644 src/db/postgres-old/sql/schema/1.0.1/revert.sql delete mode 100644 src/db/postgres-old/sql/schema/init.sql delete mode 100644 src/db/sqlite-old/index.js delete mode 100644 test/src/db/base.js delete mode 100644 test/src/db/postgres/index.js delete mode 100644 test/src/db/sqlite/index.js diff --git a/server.js b/server.js index 1770dd9..24a04b2 100644 --- a/server.js +++ b/server.js @@ -49,18 +49,6 @@ async function main() { server.on('error', (...args) => logger.error(_scope, 'server error', { args })); server.on('listening', (...args) => logger.info(_scope, 'server listening', { ...args, PORT, ADDR, version })); server.listen(PORT, ADDR); - - // http.createServer((req, res) => { - // asyncLocalStorage.run({}, async () => { - // await service.dispatch(req, res); - // }); - // }).listen(PORT, ADDR, (err) => { - // if (err) { - // logger.error(_scope, 'error starting server:', err); - // throw err; - // } - // logger.info(_scope, 'server started', { version, listenAddress: ADDR, listenPort: PORT }); - // }); } main().catch(console.error); diff --git a/src/db/base.js b/src/db/base.js deleted file mode 100644 index db20422..0000000 --- a/src/db/base.js +++ /dev/null @@ -1,72 +0,0 @@ -'use strict'; - -const common = require('../common'); -const DBErrors = require('./errors'); - -const _fileScope = common.fileScope(__filename); - -class BaseDatabase { - constructor(logger) { - this.logger = logger; - } - - static _camelfy(snakeCase, delim = '_') { - if (!snakeCase || typeof snakeCase.split !== 'function') { - return undefined; - } - const words = snakeCase.split(delim); - return [ - words.shift(), - ...words.map((word) => word.charAt(0).toUpperCase() + word.slice(1)), - ].join(''); - } - - _notImplemented(method, args) { - const _scope = _fileScope(method); - this.logger.error(_scope, 'abstract method called', Array.from(args)); - throw new DBErrors.NotImplemented(); - } - - async context(fn) { - this._notImplemented('context', { fn }); - } - - async transaction(dbCtx, fn) { - this._notImplemented('transaction', { dbCtx, fn }); - } - - async getAuthById(dbCtx, id) { - this._notImplemented('getAuthById', { dbCtx, id }); - } - - async upsertAuth(dbCtx, id, secert, credential) { - this._notImplemented('upsertAuthCredential', { dbCtx, id, credential }); - } - - async upsertLink(dbCtx, id, url, authToken) { - this._notImplemented('upsertLink', { dbCtx, id, url, authToken }); - } - - async getLinkById(dbCtx, id) { - this._notImplemented('getLinkById', { dbCtx, id }); - } - - async getLinkByUrl(dbCtx, url) { - this._notImplemented('getLinkByUrl', { dbCtx, url }); - } - - async accessLink(dbCtx, id) { - this._notImplemented('accessLink', { dbCtx, id }); - } - - async expireLink(dbCtx, id, expires) { - this._notImplemented('expireLink', { dbCtx, id, expires }); - } - - async getAllLinks(dbCtx) { - this._notImplemented('getAllLinks', { dbCtx }); - } - -} - -module.exports = BaseDatabase; diff --git a/src/db/index-old.js b/src/db/index-old.js deleted file mode 100644 index b7c9549..0000000 --- a/src/db/index-old.js +++ /dev/null @@ -1,43 +0,0 @@ -'use strict'; - -const common = require('../common'); -const DBErrors = require('./errors'); - -const _fileScope = common.fileScope(__filename); - -const defaultOptions = { - connectionString: '', -}; - -class DatabaseFactory { - constructor(logger, options, ...rest) { - const _scope = _fileScope + ':constructor'; - options = { ...defaultOptions, ...options }; - const protocol = options.connectionString.slice(0, options.connectionString.indexOf('://')).toLowerCase(); - switch (protocol) { - case DatabaseFactory.Engines.PostgreSQL: { - const Postgres = require('./postgres-old'); - return new Postgres(logger, options, ...rest); - } - - case DatabaseFactory.Engines.SQLite: { - const SQLite = require('./sqlite-old'); - return new SQLite(logger, options, ...rest); - } - - default: - logger.error(_scope, 'unsupported connectionString', { options }); - throw new DBErrors.UnsupportedEngine(protocol); - } - } - - static get Engines() { - return { - PostgreSQL: 'postgresql', - SQLite: 'sqlite', - }; - } - -} - -module.exports = DatabaseFactory; diff --git a/src/db/postgres-old/index.js b/src/db/postgres-old/index.js deleted file mode 100644 index 775a0a6..0000000 --- a/src/db/postgres-old/index.js +++ /dev/null @@ -1,308 +0,0 @@ -'use strict'; - -const pgpInitOptions = { - capSQL: true, -}; - -const path = require('node:path'); -const pgp = require('pg-promise')(pgpInitOptions); -const BaseDatabase = require('../base'); -const common = require('../../common'); - -const _fileScope = common.fileScope(__filename); - -const defaultOptions = { - connectionString: undefined, - queryLogLevel: undefined, - listenerPingDelayMs: 2000, - listenerRetryDelayMs: 5000, - listenerRetryTimes: 10, - listenerChannel: 'cache_invalidation', - listenerCallback: () => {}, - listenerLostCallback: () => {}, -}; - -class PostgresDatabase extends BaseDatabase { - constructor(logger, options, _pgp = pgp) { - const _scope = _fileScope('constructor'); - - super(logger); - common.setOptions(this, defaultOptions, options); - - this.logger.debug(_scope, 'connecting', { connectionString: this.connectionString }); - - this.db = _pgp(this.connectionString); - - if (this.queryLogLevel) { - pgpInitOptions.query = (e) => { - this.logger[this.queryLogLevel](_fileScope('pgp:query'), e.query, { params: e.params }); - }; - } - - pgpInitOptions.error = (err, event) => { - this.logger.error(_fileScope('pgp:error'), '', { err, event }); - }; - - pgpInitOptions.receive = ({ data, result, ctx: event }) => { - const exemplaryRow = data[0]; - for (const prop in exemplaryRow) { - const camel = BaseDatabase._camelfy(prop); - if (!(camel in exemplaryRow)) { - for (const d of data) { - // eslint-disable-next-line security/detect-object-injection - d[camel] = d[prop]; - // eslint-disable-next-line security/detect-object-injection - delete d[prop]; - } - } - } - - if (this.queryLogLevel) { - this.logger[this.queryLogLevel](_fileScope('pgp:result'), { query: event.query, ...PostgresDatabase._resultLog(result) }); - } - }; - this._initStatements(_pgp); - } - - static _resultLog(result) { - return { - command: result?.commaand, - rowCount: result?.rowCount, - duration: result?.duration, - }; - } - - _initStatements(_pgp) { - const _scope = _fileScope('_initStatements'); - - const qfOptions = { - minify: true, - }; - this.statement = _pgp.utils.enumSql(path.join(__dirname, 'sql'), {}, (file) => { - const qf = new _pgp.QueryFile(file, qfOptions); - this.logger.debug(_scope, file, { file: qf.file, options: qf.options, query: qf.query }); - if (qf.error) { - this.logger.error(_scope, file, { error: qf.error }); - throw qf.error; - } - return qf; - }); - - this.logger.debug(_scope, 'complete', { statementCount: Object.keys(this.statement).length }); - } - - // WIP not used yet - async _initListener() { - await this._reconnectListener(0, 1); - this._sendListenerNotifications(); - } - _sendListenerNotifications() { - const _scope = _fileScope('_sendListenerNotifications'); - setTimeout(async () => { - if (this.listenerConnection) { - try { - await this.listenerConnection.none('NOTIFY $1~, $2', [this.listenerChannel, 'ping']); - } catch (e) { - this.logger.error(_scope, 'failed', e); - } finally { - this._sendListenerNotifications(); - } - } - }, this.listenerPingDelayMs); - } - async _onListenerConnectionLost(err, ev) { - const _scope = _fileScope('_onConnectionLost'); - const eventName = 'notification'; - this.listenerConnection = null; - this.logger.error(_scope, 'listener connection lost', { err, ev }); - ev.client.removeListener(eventName, this._onListenerNotificationBound); - await this.listenerLostCallback(); - await this._reconnectListener(this.listenerRetryDelayMs, this.listenerRetryTimes); - this.logger.debug(_scope, 'listener reconnected'); - } - async _onListenerNotification(data) { - return await this.listenerCallback(data.payload); - } - async _reconnectListener(delay, retriesRemaining) { - const _scope = _fileScope('_reconnectListener'); - const eventName = 'notification'; - if (this.listenerConnection) { - this.listenerConnection.done(); - this.listenerConnection = null; - } - if (this.listenerReconnectPending) { - clearTimeout(this.listenerReconnectPending); - delete this.listenerReconnectPending; - } - return new Promise((resolve, reject) => { - this.listenerReconnectPending = setTimeout(async () => { - try { - this.listenerConnection = await this.db.connect({ - direct: true, - onLost: this._onListenerConnectionLost.bind(this), - }); - if (!this._onListenerNotificationBound) { - this._onListenerNotificationBound = this._onListenerNotification.bind(this); - } - this.listenerConnection.client.on(eventName, this._onListenerNotificationBound); - await this.listenerConnection.none('LISTEN $1~', this.listenerChannel); - delete this.listenerReconnectPending; - this.logger.debug(_scope, 'listener connection established'); - resolve(); - } catch (e) { - if (retriesRemaining > 0) { - try { - await this._reconnectListener(delay, retriesRemaining - 1); - resolve(); - } catch (e2) { - reject(e2); - } - } else { - reject(e); - } - } - }, delay); - }); - } - - - // eslint-disable-next-line class-methods-use-this - _postgresInfo(result) { - return { - changes: result.rowCount, - lastInsertRowid: result.rows.length ? result.rows[0].id : undefined, - duration: result.duration, - }; - } - - async context(fn) { - return await this.db.task(async (t) => await fn(t)); - } - - - async transaction(dbCtx, fn) { - dbCtx = dbCtx || this.db; - return await dbCtx.txIf(async (t) => await fn(t)); - } - - - async getAuthById(dbCtx, id) { - const _scope = _fileScope('getAuthById'); - this.logger.debug(_scope, 'called', { id }); - - dbCtx = dbCtx || this.db; - - const auth = await dbCtx.oneOrNone(this.statement.authGetById, { id }); - this.logger.debug(_scope, 'get', { auth }); - return auth; - } - - - async upsertAuth(dbCtx, id, secret, credential) { - const _scope = _fileScope('upsertAuth'); - this.logger.debug(_scope, 'called', { id }); - dbCtx = dbCtx || this.db; - - const result = await dbCtx.result(this.statement.authUpsert, { id, secret, credential }); - this.logger.debug(_scope, 'result', PostgresDatabase._resultLog(result) ); - } - - - static _epochFix(epoch) { - switch (epoch) { - case Infinity: - return Number.MAX_SAFE_INTEGER; - - case -Infinity: - return 0; - - default: - return epoch; - } - } - - - static _linkToNative(link) { - return link && { - ...link, - created: PostgresDatabase._epochFix(link.created), - lastAccess: PostgresDatabase._epochFix(link.lastAccess), - expires: PostgresDatabase._epochFix(link.expires), - }; - } - - async upsertLink(dbCtx, id, url, authToken) { - const _scope = _fileScope('upsertLink'); - this.logger.debug(_scope, 'called', { id, url, authToken }); - - dbCtx = dbCtx || this.db; - - const result = await dbCtx.result(this.statement.linkUpsert, { id, url, authToken }); - this.logger.debug(_scope, 'result', PostgresDatabase._resultLog(result) ); - return this._postgresInfo(result); - } - - - async getLinkById(dbCtx, id) { - const _scope = _fileScope('getLinkById'); - this.logger.debug(_scope, 'called', { id }); - - dbCtx = dbCtx || this.db; - - const link = await dbCtx.oneOrNone(this.statement.linkGetById, { id }); - this.logger.debug(_scope, 'get', { link }); - return PostgresDatabase._linkToNative(link); - } - - - async getLinkByUrl(dbCtx, url) { - const _scope = _fileScope('getLinkByUrl'); - this.logger.debug(_scope, 'called', { url }); - - dbCtx = dbCtx || this.db; - - const link = await dbCtx.oneOrNone(this.statement.linkGetByUrl, { url }); - this.logger.debug(_scope, 'get', { link }); - return PostgresDatabase._linkToNative(link); - } - - - async accessLink(dbCtx, id) { - const _scope = _fileScope('accessLink'); - this.logger.debug(_scope, 'called', { id }); - - dbCtx = dbCtx || this.db; - - const link = await dbCtx.oneOrNone(this.statement.linkAccess, { id }); - this.logger.debug(_scope, 'get', { link }); - return PostgresDatabase._linkToNative(link); - } - - - async expireLink(dbCtx, id, expires) { - const _scope = _fileScope('expireLink'); - this.logger.debug(_scope, 'called', { id, expires }); - - dbCtx = dbCtx || this.db; - - const result = await dbCtx.result(this.statement.linkExpire, { expires, id }); - this.logger.debug(_scope, 'result', PostgresDatabase._resultLog(result) ); - return this._postgresInfo(result); - } - - - async getAllLinks(dbCtx) { - const _scope = _fileScope('getAllLinks'); - this.logger.debug(_scope, 'called', { }); - - dbCtx = dbCtx || this.db; - - const links = await dbCtx.manyOrNone(this.statement.linkGetAll, { }); - this.logger.debug(_scope, 'get', { links }); - return links.map((l) => PostgresDatabase._linkToNative(l)); - } - - -} - -module.exports = PostgresDatabase; diff --git a/src/db/postgres-old/sql/auth-get-by-id.sql b/src/db/postgres-old/sql/auth-get-by-id.sql deleted file mode 100644 index 98f0434..0000000 --- a/src/db/postgres-old/sql/auth-get-by-id.sql +++ /dev/null @@ -1 +0,0 @@ -SELECT * FROM auth WHERE id = $(id) \ No newline at end of file diff --git a/src/db/postgres-old/sql/auth-upsert.sql b/src/db/postgres-old/sql/auth-upsert.sql deleted file mode 100644 index f90be73..0000000 --- a/src/db/postgres-old/sql/auth-upsert.sql +++ /dev/null @@ -1,4 +0,0 @@ --- -INSERT INTO auth (id, secret, password) VALUES ($(id), $(secret), $(credential)) -ON CONFLICT (id) DO -UPDATE SET password = $(credential), secret = $(secret) diff --git a/src/db/postgres-old/sql/link-access.sql b/src/db/postgres-old/sql/link-access.sql deleted file mode 100644 index 5f4c128..0000000 --- a/src/db/postgres-old/sql/link-access.sql +++ /dev/null @@ -1,5 +0,0 @@ -UPDATE link SET accesses = accesses + 1, last_access = now() WHERE id = $(id) -RETURNING *, - extract(epoch from created) AS created, - extract(epoch from last_access) AS last_access, - extract(epoch from expires) AS expires diff --git a/src/db/postgres-old/sql/link-expire.sql b/src/db/postgres-old/sql/link-expire.sql deleted file mode 100644 index a799b8a..0000000 --- a/src/db/postgres-old/sql/link-expire.sql +++ /dev/null @@ -1 +0,0 @@ -UPDATE link SET expires = to_timestamp($(expires)) WHERE id = $(id) \ No newline at end of file diff --git a/src/db/postgres-old/sql/link-get-all.sql b/src/db/postgres-old/sql/link-get-all.sql deleted file mode 100644 index 40a935f..0000000 --- a/src/db/postgres-old/sql/link-get-all.sql +++ /dev/null @@ -1,6 +0,0 @@ -SELECT *, - extract(epoch from created) AS created, - extract(epoch from last_access) AS last_access, - extract(epoch from expires) AS expires -FROM link -ORDER BY id \ No newline at end of file diff --git a/src/db/postgres-old/sql/link-get-by-id.sql b/src/db/postgres-old/sql/link-get-by-id.sql deleted file mode 100644 index 8eeb27d..0000000 --- a/src/db/postgres-old/sql/link-get-by-id.sql +++ /dev/null @@ -1,5 +0,0 @@ -SELECT *, - extract(epoch from created) AS created, - extract(epoch from last_access) AS last_access, - extract(epoch from expires) AS expires -FROM link WHERE id = $(id) \ No newline at end of file diff --git a/src/db/postgres-old/sql/link-get-by-url.sql b/src/db/postgres-old/sql/link-get-by-url.sql deleted file mode 100644 index 113837a..0000000 --- a/src/db/postgres-old/sql/link-get-by-url.sql +++ /dev/null @@ -1,5 +0,0 @@ -SELECT *, - extract(epoch from created) AS created, - extract(epoch from last_access) AS last_access, - extract(epoch from expires) AS expires -FROM link WHERE url = $(url) \ No newline at end of file diff --git a/src/db/postgres-old/sql/link-update.sql b/src/db/postgres-old/sql/link-update.sql deleted file mode 100644 index fb04ea4..0000000 --- a/src/db/postgres-old/sql/link-update.sql +++ /dev/null @@ -1 +0,0 @@ -UPDATE link SET url = $(url) WHERE id = $(id) \ No newline at end of file diff --git a/src/db/postgres-old/sql/link-upsert.sql b/src/db/postgres-old/sql/link-upsert.sql deleted file mode 100644 index 9a8e024..0000000 --- a/src/db/postgres-old/sql/link-upsert.sql +++ /dev/null @@ -1,10 +0,0 @@ -INSERT INTO link (id, url, auth_token) - VALUES - ($(id), $(url), $(authToken)) - ON CONFLICT (id) DO UPDATE - SET - url = $(url) -RETURNING *, - extract(epoch from created) AS created, - extract(epoch from last_access) AS last_access, - extract(epoch from expires) AS expires diff --git a/src/db/postgres-old/sql/schema/1.0.0/apply.sql b/src/db/postgres-old/sql/schema/1.0.0/apply.sql deleted file mode 100644 index c05abc9..0000000 --- a/src/db/postgres-old/sql/schema/1.0.0/apply.sql +++ /dev/null @@ -1,53 +0,0 @@ -BEGIN; - - -- core data - CREATE TABLE link ( - id TEXT NOT NULL PRIMARY KEY, - url TEXT NOT NULL UNIQUE, - created TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), - last_access TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT '-infinity'::timestamptz, - accesses INTEGER NOT NULL DEFAULT 0, - expires TIMESTAMP WITH TIME ZONE, - auth_token TEXT - ); - - -- send notices to invalidate cacheable link data - CREATE OR REPLACE FUNCTION cache_invalidation_link() - RETURNS TRIGGER - LANGUAGE plpgsql - AS $$ - DECLARE - payload varchar; - BEGIN - IF NEW.url != OLD.url OR NEW.expires != OLD.expires - THEN - payload = 'link|' || CAST(NEW.id AS text); - PERFORM pg_notify('cache_invalidation', payload); - END IF; - RETURN NEW; - END; - $$ - ; - CREATE TRIGGER cache_invalidation_link - AFTER UPDATE - ON link - FOR EACH ROW - EXECUTE PROCEDURE cache_invalidation(); - - -- better auth TBD - CREATE TABLE auth ( - id TEXT NOT NULL PRIMARY KEY, - secret TEXT NOT NULL, - password TEXT - ); - - -- system entries - INSERT INTO link (id, url) VALUES - ('static', '/static/index.html'), - ('favicon.ico', '/static/favicon.ico'), - ('robots.txt', '/static/robots.txt') - ; - - -- migration complete - INSERT INTO _meta_schema_version (major, minor, patch) VALUES (1, 0, 0); -COMMIT; diff --git a/src/db/postgres-old/sql/schema/1.0.0/revert.sql b/src/db/postgres-old/sql/schema/1.0.0/revert.sql deleted file mode 100644 index 38ed34d..0000000 --- a/src/db/postgres-old/sql/schema/1.0.0/revert.sql +++ /dev/null @@ -1,7 +0,0 @@ -BEGIN; - DROP TRIGGER cache_invalidation_link ON link; - DROP FUNCTION cache_invalidation_link(); - DROP TABLE auth; - DROP TABLE link; - DELETE FROM _meta_schema_version WHERE major = 1 AND minor = 0 AND patch = 0; -COMMIT; diff --git a/src/db/postgres-old/sql/schema/1.0.1/apply.sql b/src/db/postgres-old/sql/schema/1.0.1/apply.sql deleted file mode 100644 index 94374ff..0000000 --- a/src/db/postgres-old/sql/schema/1.0.1/apply.sql +++ /dev/null @@ -1,14 +0,0 @@ -BEGIN; - - ALTER TABLE link - ADD COLUMN is_special BOOLEAN NOT NULL DEFAULT false - ; - - UPDATE link - SET is_special = true - WHERE - url = ANY('/static/robots.txt', '/static/index.html', '/static/favicon.ico'); - - INSERT INTO _meta_schema_version (major, minor, patch) VALUES (1, 0, 1); - -COMMIT; diff --git a/src/db/postgres-old/sql/schema/1.0.1/revert.sql b/src/db/postgres-old/sql/schema/1.0.1/revert.sql deleted file mode 100644 index 08fa1c4..0000000 --- a/src/db/postgres-old/sql/schema/1.0.1/revert.sql +++ /dev/null @@ -1,9 +0,0 @@ -BEGIN; - - ALTER TABLE link - DROP COLUMN is_special - ; - - DELETE FROM _meta_schema_version WHERE major = 1 AND minor = 0 AND patch = 1; - -COMMIT; diff --git a/src/db/postgres-old/sql/schema/init.sql b/src/db/postgres-old/sql/schema/init.sql deleted file mode 100644 index 27fdae3..0000000 --- a/src/db/postgres-old/sql/schema/init.sql +++ /dev/null @@ -1,12 +0,0 @@ -BEGIN; - CREATE EXTENSION IF NOT EXISTS pg_stat_statements; - - CREATE TABLE IF NOT EXISTS _meta_schema_version ( - major BIGINT NOT NULL, - minor BIGINT NOT NULL, - patch BIGINT NOT NULL, - applied TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), - PRIMARY KEY (major, minor, patch) - ); - INSERT INTO _meta_schema_version (major, minor, patch) VALUES (0, 0, 0); -COMMIT; diff --git a/src/db/sqlite-old/index.js b/src/db/sqlite-old/index.js deleted file mode 100644 index 610a3d3..0000000 --- a/src/db/sqlite-old/index.js +++ /dev/null @@ -1,343 +0,0 @@ -'use strict'; - -const SQLite = require('better-sqlite3'); -const BaseDatabase = require('../base'); -const common = require('../../common'); -const DBErrors = require('../errors'); - -const _fileScope = common.fileScope(__filename); - -const defaultOptions = { - connectionString: '', - optimizeAfterChanges: 1000, - queryLogLevel: 'debug', -}; - -const EPOCH_NOW = '(strftime(\'%s\', \'now\'))'; - -class SQLiteDatabase extends BaseDatabase { - constructor(logger, options) { - const _scope = _fileScope('constructor'); - - super(logger); - common.setOptions(this, defaultOptions, options); - - this.dbFilename = this.connectionString.slice('sqlite://'.length) || ':memory:'; - - this.logger.debug(_scope, 'connecting', { dbFilename: this.dbFilename }); - - this.db = new SQLite(this.dbFilename, { - verbose: (query) => this.queryLogLevel && this.logger[this.queryLogLevel](_fileScope('statement'), 'executing', { query }), - }); - - this.changesSinceLastOptimize = BigInt(0); - - this.db.pragma('foreign_keys = on'); - this.db.pragma('journal_mode = WAL'); - this.db.defaultSafeIntegers(true); - - this._initTables(); - this._initStatements(); - } - - async initialize() { - this._x = true; - } - - healthCheck() { - const _scope = _fileScope('healthCheck'); - this.logger.debug(_scope, 'called', {}); - if (!this.db.open) { - throw new DBErrors.UnexpectedResult('database is not open'); - } - return { open: this.db.open }; - } - - _closeConnection() { - this.db.close(); - } - - _purgeTables(really) { - if (really) { - const extantTableNames = ['auth', 'link']; - extantTableNames.forEach((table) => { - const result = this.db.prepare(`DELETE FROM ${table}`).run(); - this.logger.debug(_fileScope('_purgeTables'), 'success', { table, result }); - }); - } - } - - _initTables() { - const _scope = _fileScope('_initTables'); - const tableExists = this.db.prepare('SELECT name FROM sqlite_master WHERE type=\'table\' AND name=:tableName').pluck(true); - - const tables = [ - { - name: 'auth', - statements: [ - `CREATE TABLE IF NOT EXISTS auth ( - id TEXT NOT NULL PRIMARY KEY CHECK (typeof(id) = 'text'), - secret TEXT NOT NULL CHECK (typeof(secret) = 'text'), - password TEXT CHECK (typeof(password) IN ('text', 'null')) - )`, - `INSERT INTO auth (id, secret, password) - VALUES - ('foo', 'secret', 'quux')`, - ], - }, - { - name: 'link', - statements: [ - `CREATE TABLE IF NOT EXISTS link ( - id TEXT NOT NULL PRIMARY KEY CHECK (typeof(id) = 'text'), - url TEXT NOT NULL UNIQUE CHECK (typeof(url) = 'text'), - created INTEGER NOT NULL DEFAULT ${EPOCH_NOW} CHECK (typeof(created) = 'integer'), - last_access INTEGER NOT NULL DEFAULT 0 CHECK (typeof(last_access) = 'integer'), - accesses INTEGER NOT NULL DEFAULT 0 CHECK (typeof(accesses) = 'integer'), - expires INTEGER CHECK (typeof(expires) IN ('integer', 'null')), - auth_token TEXT CHECK (typeof(auth_token) IN ('text', 'null')), - is_special INTEGER NOT NULL DEFAULT 0 CHECK (is_special IN (0, 1)) - )`, - 'CREATE INDEX IF NOT EXISTS link_url_idx ON link(url)', - ], - }, - ]; - - tables.forEach((t) => { - const table = tableExists.get({ tableName: t.name }); - if (table === undefined) { - t.statements.forEach((s) => { - this.logger.info(_scope, 'creating table', { tableName: t.name }); - this.db.prepare(s).run(); - }); - } - }); - } - - _initStatements() { - this.statement = { - _beginImmediate: this.db.prepare('BEGIN IMMEDIATE'), - _commit: this.db.prepare('COMMIT'), - _rollback: this.db.prepare('ROLLBACK'), - getAuthById: this.db.prepare('SELECT * FROM auth WHERE id = :id'), - insertAuth: this.db.prepare('INSERT INTO auth (id, secret, password) VALUES (:id, :secret, :credential)'), - updateAuth: this.db.prepare('UPDATE auth SET password = :credential, secret = :secret WHERE id = :id'), - getLinkById: this.db.prepare('SELECT * FROM link WHERE id = :id'), - getLinkByUrl: this.db.prepare('SELECT * FROM link WHERE url = :url'), - upsertLink: this.db.prepare('INSERT INTO link (id, url, auth_token) VALUES (:id, :url, :authToken)'), - updateLink: this.db.prepare('UPDATE link SET url = :url WHERE id = :id'), - incrementLinkAccess: this.db.prepare(`UPDATE link SET accesses = accesses + 1, last_access = ${EPOCH_NOW} WHERE id = :id`), - expireLink: this.db.prepare('UPDATE link SET expires = :expires WHERE id = :id'), - linkGetAll: this.db.prepare('SELECT * FROM link'), - }; - } - - _optimize() { - const _scope = _fileScope('_optimize'); - this.logger.debug(_scope, 'called', {}); - const optimizations = this.db.prepare('SELECT * FROM pragma_optimize(0x03)').all(); - this.logger.debug(_scope, 'pragma preview', { optimizations }); - this.db.pragma('optimize'); - this.changesSinceLastOptimize = BigInt(0); - } - - _maybeOptimize() { - if (this.changesSinceLastOptimize >= this.optimizeAfterChanges) { - this._optimize(); - } - } - - _sqliteInfo(info) { - if (info.changes) { - this.changesSinceLastOptimize += BigInt(info.changes); - this._maybeOptimize(); - } - return { - changes: Number(info.changes), - lastInsertRowid: Number(info.lastInsertRowid), - }; - } - - static _deOphidiate(row) { - return row && Object.keys(row).reduce((snakelessRow, k) => Object.assign(snakelessRow, { - // eslint-disable-next-line security/detect-object-injection - [BaseDatabase._camelfy(k)]: row[k], - }), {}); - } - - - async context(fn) { - const dbCtx = this.db; - return await fn(dbCtx); - } - - async transaction(dbCtx, fn, ...rest) { - dbCtx = dbCtx || this.db; - try { - this.statement._beginImmediate.run(); - const result = await fn(dbCtx, ...rest); - this.statement._commit.run(); - return result; - } finally { - if (this.db.inTransaction) { - this.statement._rollback.run(); - } - } - } - - async getAuthById(dbCtx, id) { - const _scope = _fileScope('getAuthById'); - this.logger.debug(_scope, 'called', { id }); - - let auth; - auth = this.statement.getAuthById.get({ id }); - auth = SQLiteDatabase._deOphidiate(auth); - - this.logger.debug(_scope, 'get', { auth }); - return auth; - } - - async upsertAuth(dbCtx, id, secret, credential) { - const _scope = _fileScope('upsertAuthCredential'); - this.logger.debug(_scope, 'called', { id }); - - let info; - try { - info = this.statement.insertAuth.run({ id, secret, credential }); - } catch (e) { - switch (e.code) { - case 'SQLITE_CONSTRAINT_UNIQUE': - case 'SQLITE_CONSTRAINT_PRIMARYKEY': { - this.logger.debug(_scope, 'updating existing auth', { id }); - info = this.statement.updateAuth.run({ id, secret, credential }); - break; - } - - default: { - this.logger.error(_scope, 'failed to upsert auth credential', { error: e, id }); - throw e; - } - } - } - this.logger.debug(_scope, 'run', { info }); - if (info.changes != 1) { - this.logger.error(_scope, 'failed to upsert auth credential', { id, info }); - throw new DBErrors.UnexpectedResult(); - } - - return this._sqliteInfo(info); - } - - async upsertLink(dbCtx, id, url, authToken) { - const _scope = _fileScope('upsertLink'); - this.logger.debug(_scope, 'called', { id, url }); - - let info; - try { - info = this.statement.upsertLink.run({ id, url, authToken }); - } catch (e) { - switch (e.code) { - case 'SQLITE_CONSTRAINT_UNIQUE': - case 'SQLITE_CONSTRAINT_PRIMARYKEY': { - this.logger.debug(_scope, 'updating existing id', { id, url }); - info = this.statement.updateLink.run({ id, url }); - break; - } - - default: { - this.logger.error(_scope, 'failed to insert link', { error: e, id, url }); - throw e; - } - } - } - this.logger.debug(_scope, 'run', { info }); - if (info.changes != 1) { - this.logger.error(_scope, 'failed to insert link', { id, url, info }); - throw new DBErrors.UnexpectedResult(); - } - - return this._sqliteInfo(info); - } - - static _linkToNative(link) { - return link && { - id: link.id, - url: link.url, - created: Number(link.created), - lastAccess: Number(link.lastAccess), - accesses: Number(link.accesses), - expires: ('expires' in link) ? Number(link.expires) : undefined, - authToken: link.authToken, - isSpecial: !! link.isSpecial, - }; - } - - async getLinkById(dbCtx, id) { - const _scope = _fileScope('getLinkById'); - this.logger.debug(_scope, 'called', { id }); - - let link = this.statement.getLinkById.get({ id }); - link = SQLiteDatabase._deOphidiate(link); - - this.logger.debug(_scope, 'get', { link }); - return SQLiteDatabase._linkToNative(link); - } - - async getLinkByUrl(dbCtx, url) { - const _scope = _fileScope('getLinkByUrl'); - this.logger.debug(_scope, 'called', { url }); - - let link = this.statement.getLinkByUrl.get({ url }); - link = SQLiteDatabase._deOphidiate(link); - - this.logger.debug(_scope, 'get', { link }); - return SQLiteDatabase._linkToNative(link); - } - - async accessLink(dbCtx, id) { - const _scope = _fileScope('accessLink'); - this.logger.debug(_scope, 'called', { id }); - - let link = this.statement.getLinkById.get({ id }); - link = SQLiteDatabase._deOphidiate(link); - - this.logger.debug(_scope, 'get', { id, link }); - - if (link) { - const info = this.statement.incrementLinkAccess.run({ id }); - this.logger.debug(_scope, 'increment', { id, info }); - if (info.changes != 1) { - this.logger.error(_scope, 'failed to increment link access', { id, link, info }); - throw new DBErrors.UnexpectedResult(); - } - } - return SQLiteDatabase._linkToNative(link); - } - - async expireLink(dbCtx, id, expires) { - const _scope = _fileScope('expireLink'); - this.logger.debug(_scope, 'called', { id }); - - const info = this.statement.expireLink.run({ id, expires }); - if (info.changes != 1) { - throw new DBErrors.UnexpectedResult(); - } - return this._sqliteInfo(info); - } - - // eslint-disable-next-line no-unused-vars - async getAllLinks(dbCtx) { - const _scope = _fileScope('getAllLinks'); - this.logger.debug(_scope, 'called', { }); - - let links; - links = this.statement.linkGetAll.all({}); - links = links.map((l) => SQLiteDatabase._deOphidiate(l)); - this.logger.debug(_scope, 'get', { links }); - links = links.map((l) => SQLiteDatabase._linkToNative(l)); - return links; - } - -} - -module.exports = SQLiteDatabase; diff --git a/src/errors.js b/src/errors.js index 1259fea..ca28841 100644 --- a/src/errors.js +++ b/src/errors.js @@ -2,13 +2,6 @@ const { Errors } = require('@squeep/api-dingus'); -class DatabaseError extends Errors.DingusError { - constructor(...args) { - super(...args); - Error.captureStackTrace(DatabaseError); - } -} - class ServeStaticFile extends Errors.DingusError { constructor(file, ...args) { super(...args); @@ -21,7 +14,6 @@ class SlugGeneratorExhausted extends Errors.DingusError {} module.exports = { ...Errors, - DatabaseError, ServeStaticFile, SlugGeneratorExhausted, }; \ No newline at end of file diff --git a/test/src/db/base.js b/test/src/db/base.js deleted file mode 100644 index fab28fb..0000000 --- a/test/src/db/base.js +++ /dev/null @@ -1,66 +0,0 @@ -'use strict'; - -const assert = require('node:assert'); -const sinon = require('sinon'); -const BaseDatabase = require('../../../src/db/base'); -const DBErrors = require('../../../src/db/errors'); - -const noExpectedException = 'did not get expected exception'; - -describe('BaseDatabase', function () { - let logger, db; - - beforeEach(function () { - logger = { error: () => {} }; - db = new BaseDatabase(logger); - }); - - afterEach(function () { - sinon.restore(); - }); - - describe('_camelfy', function () { - it('empty arg', function () { - const result = BaseDatabase._camelfy(); - assert.strictEqual(result, undefined); - }); - it('no change', function () { - const str = 'camelCase'; - const result = BaseDatabase._camelfy(str); - assert.strictEqual(result, str); - }); - it('does expected', function () { - const str = 'snake_case_thing'; - const result = BaseDatabase._camelfy(str); - assert.strictEqual(result, 'snakeCaseThing'); - }); - }); // _camelfy - - describe('interface', function () { - it('covers methods', async function () { - const methods = [ - 'context', - 'transaction', - 'getAuthById', - 'upsertAuth', - 'upsertLink', - 'getLinkById', - 'getLinkByUrl', - 'accessLink', - 'expireLink', - 'getAllLinks', - ]; - const invokedMethods = methods.map(async (m) => { - try { - // eslint-disable-next-line security/detect-object-injection - await db[m](); - assert.fail(noExpectedException); - } catch (e) { - assert(e instanceof DBErrors.NotImplemented); - } - }); - await Promise.all(invokedMethods); - }); - }); // interface - -}); \ No newline at end of file diff --git a/test/src/db/postgres/index.js b/test/src/db/postgres/index.js deleted file mode 100644 index 6f6cf04..0000000 --- a/test/src/db/postgres/index.js +++ /dev/null @@ -1,331 +0,0 @@ -'use strict'; - -const assert = require('node:assert'); -const sinon = require('sinon'); - -const noExpectedException = 'did not get expected exception'; - -describe('PostgresDatabase', function () { - const PostgresDatabase = require('../../../../src/db/postgres-old'); - let pgpStub, db, logger, options, dbCtx; - - beforeEach(function () { - - pgpStub = () => { - const stub = { - result: sinon.stub(), - all: sinon.stub(), - get: sinon.stub(), - run: sinon.stub(), - one: sinon.stub(), - manyOrNone: sinon.stub(), - oneOrNone: sinon.stub(), - query: sinon.stub(), - batch: sinon.stub(), - }; - stub.tx = async (fn) => await fn(stub); - stub.txIf = async (fn) => await fn(stub); - stub.task = async (fn) => await fn(stub); - return stub; - }; - pgpStub.utils = { - enumSql: () => ({}), - }; - pgpStub.QueryFile = class {}; - pgpStub.end = () => {}, - - logger = { - debug: sinon.stub(), - error: sinon.stub(), - }; - options = {}; - db = new PostgresDatabase(logger, options, pgpStub); - db.statement = {}; - dbCtx = undefined; - }); - - it('covers constructor options', function () { - options = { - queryLogLevel: 'debug', - }; - db = new PostgresDatabase(logger, options, pgpStub); - }); - - describe('context', function () { - it('covers', async function () { - const fn = sinon.stub(); - await db.context(fn); - assert(fn.called); - }); - }); // context - - describe('transaction', function () { - it('covers', async function () { - const fn = sinon.stub(); - await db.transaction(undefined, fn); - assert(fn.called); - }); - }); // transaction - - describe('getAuthById', function () { - let id; - it('stubbed success', async function () { - const expected = { - id: 'id', - secret: 'secret', - password: 'password', - }; - id = 'id'; - db.db.oneOrNone.resolves(expected); - const result = await db.getAuthById(dbCtx, id); - assert.deepStrictEqual(result, expected); - }); - it('stubbed failure', async function () { - const expectedExeption = new Error('blah'); - id = 'id'; - db.db.oneOrNone.rejects(expectedExeption); - try { - await db.getAuthById(dbCtx, id); - assert.fail(noExpectedException); - } catch (e) { - assert.deepStrictEqual(e, expectedExeption, noExpectedException); - } - }); - }); // getAuthById - - describe('upsertAuth', function () { - let id, secret, credential; - beforeEach(function () { - id = 'id'; - secret = 'secret'; - credential = 'credential'; - }); - it('stubbed success', async function () { - await db.upsertAuth(dbCtx, id, secret, credential); - }); - }); // upsertAuth - - describe('_epochFix', function () { - it('clamps infinity', function () { - const epoch = Infinity; - const expected = Number.MAX_SAFE_INTEGER; - const result = PostgresDatabase._epochFix(epoch); - assert.strictEqual(result, expected); - }); - it('clamps negative infinity', function () { - const epoch = -Infinity; - const expected = 0; - const result = PostgresDatabase._epochFix(epoch); - assert.strictEqual(result, expected); - }); - it('returns number', function () { - const epoch = 123; - const expected = 123; - const result = PostgresDatabase._epochFix(epoch); - assert.strictEqual(result, expected); - }); - }); // _epochFix - - describe('_linkToNative', function () { - it('handles missing link', function () { - const link = undefined; - const expected = undefined; - const result = PostgresDatabase._linkToNative(link); - assert.deepStrictEqual(result, expected); - }); - it('converts epochs', function () { - const link = { - id: 'id', - url: 'url', - expires: null, - lastAccess: -Infinity, - created: 123, - }; - const expected = { - id: 'id', - url: 'url', - expires: null, - lastAccess: 0, - created: 123, - }; - const result = PostgresDatabase._linkToNative(link); - assert.deepStrictEqual(result, expected); - }); - }); // _linkToNative - - describe('upsertLink', function () { - let id, url, authToken; - it('stubbed success', async function () { - const returns = { - rowCount: 0, - rows: [], - duration: 0, - }; - id = 'id'; - db.db.result.resolves(returns); - const expected = { - changes: 0, - duration: 0, - lastInsertRowid: undefined, - }; - const result = await db.upsertLink(dbCtx, id, url, authToken); - assert.deepStrictEqual(result, expected); - }); - it('stubbed failure', async function () { - const expectedExeption = new Error('blah'); - id = 'id'; - db.db.result.rejects(expectedExeption); - try { - await db.upsertLink(dbCtx, id, url, authToken); - assert.fail(noExpectedException); - } catch (e) { - assert.deepStrictEqual(e, expectedExeption, noExpectedException); - } - }); - }); // upsertLink - - describe('getLinkById', function () { - let id; - it('stubbed success', async function () { - const expected = { - id: 'id', - url: 'url', - created: 0, - expires: 0, - lastAccess: 0, - }; - id = 'id'; - db.db.oneOrNone.resolves(expected); - const result = await db.getLinkById(dbCtx, id); - assert.deepStrictEqual(result, expected); - }); - it('stubbed failure', async function () { - const expectedExeption = new Error('blah'); - id = 'id'; - db.db.oneOrNone.rejects(expectedExeption); - try { - await db.getLinkById(dbCtx, id); - assert.fail(noExpectedException); - } catch (e) { - assert.deepStrictEqual(e, expectedExeption, noExpectedException); - } - }); - }); // getLinkById - - describe('getLinkByUrl', function () { - let url; - it('stubbed success', async function () { - const expected = { - id: 'id', - url: 'url', - created: 0, - expires: 0, - lastAccess: 0, - }; - url = 'url'; - db.db.oneOrNone.resolves(expected); - const result = await db.getLinkByUrl(dbCtx, url); - assert.deepStrictEqual(result, expected); - }); - it('stubbed failure', async function () { - const expectedExeption = new Error('blah'); - url = 'url'; - db.db.oneOrNone.rejects(expectedExeption); - try { - await db.getLinkByUrl(dbCtx, url); - assert.fail(noExpectedException); - } catch (e) { - assert.deepStrictEqual(e, expectedExeption, noExpectedException); - } - }); - }); // getLinkByUrl - - describe('accessLink', function () { - let id; - it('stubbed success', async function () { - const expected = { - id: 'id', - url: 'url', - created: 0, - expires: 0, - lastAccess: 0, - }; - id = 'id'; - db.db.oneOrNone.resolves(expected); - const result = await db.accessLink(dbCtx, id); - assert.deepStrictEqual(result, expected); - }); - it('stubbed failure', async function () { - const expectedExeption = new Error('blah'); - id = 'id'; - db.db.oneOrNone.rejects(expectedExeption); - try { - await db.accessLink(dbCtx, id); - assert.fail(noExpectedException); - } catch (e) { - assert.deepStrictEqual(e, expectedExeption, noExpectedException); - } - }); - }); // accessLink - - describe('expireLink', function () { - let id, expires; - it('stubbed success', async function () { - const returns = { - rowCount: 1, - rows: [ { id: 1 } ], - duration: 10, - }; - const expected = { - changes: 1, - duration: 10, - lastInsertRowid: 1, - }; - id = 'id'; - expires = null; - db.db.result.resolves(returns); - const result = await db.expireLink(dbCtx, id, expires); - assert.deepStrictEqual(result, expected); - }); - it('stubbed failure', async function () { - const expectedExeption = new Error('blah'); - id = 'id'; - expires = null; - db.db.result.rejects(expectedExeption); - try { - await db.expireLink(dbCtx, id, expires); - assert.fail(noExpectedException); - } catch (e) { - assert.deepStrictEqual(e, expectedExeption, noExpectedException); - } - }); - }); // expireLink - - describe('getAllLinks', function () { - it('stubbed success', async function () { - const expected = [ - { - id: 'id', - url: 'url', - created: 0, - expires: 0, - lastAccess: 0, - }, - ]; - db.db.manyOrNone.resolves(expected); - const result = await db.getAllLinks(dbCtx); - assert.deepStrictEqual(result, expected); - }); - it('stubbed failure', async function () { - const expectedExeption = new Error('blah'); - db.db.manyOrNone.rejects(expectedExeption); - try { - await db.getAllLinks(dbCtx); - assert.fail(noExpectedException); - } catch (e) { - assert.deepStrictEqual(e, expectedExeption, noExpectedException); - } - }); - }); // getAllLinks - -}); \ No newline at end of file diff --git a/test/src/db/sqlite/index.js b/test/src/db/sqlite/index.js deleted file mode 100644 index 8bd7ccd..0000000 --- a/test/src/db/sqlite/index.js +++ /dev/null @@ -1,477 +0,0 @@ -'use strict'; - -const assert = require('node:assert'); -const sinon = require('sinon'); -const DBErrors = require('../../../../src/db/errors'); - -const noExpectedException = 'did not get expected exception'; - -describe('SQLiteDatabase', function () { - const SQLiteDatabase = require('../../../../src/db/sqlite-old'); - let db, logger, options, dbCtx; - - beforeEach(function () { - logger = { - debug: sinon.stub(), - info: sinon.stub(), - error: sinon.stub(), - }; - options = {}; - db = new SQLiteDatabase(logger, options); - dbCtx = undefined; - }); - - describe('context', function () { - it('covers', async function () { - const fn = sinon.stub(); - await db.context(fn); - assert(fn.called); - }); - }); // context - - describe('transaction', function () { - it('covers', async function () { - const fn = sinon.stub(); - await db.transaction(dbCtx, fn); - assert(fn.called); - }); - it('covers rollback', async function () { - const fn = sinon.stub(); - fn.throws(new Error('rollback')); - try { - await db.transaction(dbCtx, fn); - assert.fail(noExpectedException); - } catch (e) { - assert.strictEqual(e.message, 'rollback', noExpectedException); - } - }); - }); // transaction - - describe('getAuthById', function () { - let id; - beforeEach(function () { - sinon.stub(db.statement.getAuthById, 'get'); - }); - - it('stubbed success', async function () { - const expected = { - id: 'id', - secret: 'secret', - password: 'password', - }; - id = 'id'; - db.statement.getAuthById.get.returns(expected); - const result = await db.getAuthById(dbCtx, id); - assert.deepStrictEqual(result, expected); - }); - it('stubbed failure', async function () { - const expectedExeption = new Error('blah'); - id = 'id'; - db.statement.getAuthById.get.throws(expectedExeption); - try { - await db.getAuthById(dbCtx, id); - assert.fail(noExpectedException); - } catch (e) { - assert.deepStrictEqual(e, expectedExeption, noExpectedException); - } - }); - }); // getAuthById - - describe('upsertAuth', function () { - let id, secret, credential; - beforeEach(function () { - sinon.stub(db.statement.insertAuth, 'run').returns({ changes: 1n, lastInsertRowid: 123n }); - sinon.stub(db.statement.updateAuth, 'run').returns({ changes: 1n, lastInsertRowid: 123n }); - }); - it('stubbed insert success', async function () { - await db.upsertAuth(dbCtx, id, secret, credential); - }); - it('stubbed update success', async function () { - db.statement.insertAuth.run.throws({ code: 'SQLITE_CONSTRAINT_UNIQUE' }); - await db.upsertAuth(dbCtx, id, secret, credential); - }); - it('covers error', async function () { - const expectedException = new Error('blah'); - db.statement.insertAuth.run.throws(expectedException); - try { - await db.upsertAuth(dbCtx, id, secret, credential); - assert.fail(noExpectedException); - } catch (e) { - assert.deepStrictEqual(e, expectedException, noExpectedException); - } - }); - it('covers unexpected error', async function () { - const expectedException = DBErrors.UnexpectedResult; - const returns = { - changes: 0n, - lastInsertRowid: undefined, - }; - db.statement.insertAuth.run.returns(returns); - try { - await db.upsertAuth(dbCtx, id, secret, credential); - assert.fail(noExpectedException); - } catch (e) { - assert(e instanceof expectedException, noExpectedException); - } - }); - }); // upsertAuth - - describe('upsertLink', function () { - let id, url, authToken; - beforeEach(function () { - sinon.stub(db.statement.upsertLink, 'run'); - sinon.stub(db.statement.updateLink, 'run'); - }); - - it('stubbed insert success', async function () { - const info = { - changes: BigInt(1), - lastInsertRowid: BigInt(123), - }; - id = 'id'; - db.statement.upsertLink.run.returns(info); - const expected = { - changes: 1, - lastInsertRowid: 123, - }; - const result = await db.upsertLink(dbCtx, id, url, authToken); - assert.deepStrictEqual(result, expected); - }); - it('stubbed update success', async function () { - const info = { - changes: BigInt(1), - lastInsertRowid: BigInt(123), - }; - id = 'id'; - db.statement.upsertLink.run.throws({ code: 'SQLITE_CONSTRAINT_UNIQUE' }); - db.statement.updateLink.run.returns(info); - const expected = { - changes: 1, - lastInsertRowid: 123, - }; - const result = await db.upsertLink(dbCtx, id, url, authToken); - assert.deepStrictEqual(result, expected); - }); - it('stubbed failure', async function () { - const expectedExeption = new Error('blah'); - id = 'id'; - db.statement.upsertLink.run.throws(expectedExeption); - try { - await db.upsertLink(dbCtx, id, url, authToken); - assert.fail(noExpectedException); - } catch (e) { - assert.deepStrictEqual(e, expectedExeption, noExpectedException); - } - }); - it('stubbed unexpected failure', async function () { - const expectedException = DBErrors.UnexpectedResult; - const returns = { - changes: 0, - lastInsertRowid: undefined, - }; - id = 'id'; - db.statement.upsertLink.run.returns(returns); - try { - await db.upsertLink(dbCtx, id, url, authToken); - assert.fail(noExpectedException); - } catch (e) { - assert(e instanceof expectedException); - } - }); - }); // upsertLink - - describe('getLinkById', function () { - let id; - - beforeEach(function () { - sinon.stub(db.statement.getLinkById, 'get'); - }); - - it('stubbed success', async function () { - const returns = { - id: 'id', - isSpecial: false, - url: 'url', - created: 0, - expires: 0, - 'auth_token': 'abc', - 'last_access': 0, - accesses: 0, - }; - const expected = { - id: 'id', - isSpecial: false, - url: 'url', - created: 0, - expires: 0, - authToken: 'abc', - lastAccess: 0, - accesses: 0, - }; - id = 'id'; - db.statement.getLinkById.get.returns(returns); - const result = await db.getLinkById(dbCtx, id); - assert.deepStrictEqual(result, expected); - }); - it('stubbed failure', async function () { - const expectedExeption = new Error('blah'); - id = 'id'; - db.statement.getLinkById.get.throws(expectedExeption); - try { - await db.getLinkById(dbCtx, id); - assert.fail(noExpectedException); - } catch (e) { - assert.deepStrictEqual(e, expectedExeption, noExpectedException); - } - }); - }); // getLinkById - - describe('getLinkByUrl', function () { - let url; - - beforeEach(function () { - sinon.stub(db.statement.getLinkByUrl, 'get'); - }); - - it('stubbed success', async function () { - const returns = { - id: 'id', - isSpecial: false, - url: 'url', - created: 0, - expires: 123, - 'auth_token': 'abc', - 'last_access': 0, - accesses: 0, - }; - const expected = { - id: 'id', - isSpecial: false, - url: 'url', - created: 0, - expires: 123, - authToken: 'abc', - lastAccess: 0, - accesses: 0, - }; - url = 'url'; - db.statement.getLinkByUrl.get.returns(returns); - const result = await db.getLinkByUrl(dbCtx, url); - assert.deepStrictEqual(result, expected); - }); - it('stubbed failure', async function () { - const expectedExeption = new Error('blah'); - url = 'url'; - db.statement.getLinkByUrl.get.throws(expectedExeption); - try { - await db.getLinkByUrl(dbCtx, url); - assert.fail(noExpectedException); - } catch (e) { - assert.deepStrictEqual(e, expectedExeption, noExpectedException); - } - }); - }); // getLinkByUrl - - describe('accessLink', function () { - let id; - - beforeEach(function () { - sinon.stub(db.statement.getLinkById, 'get'); - sinon.stub(db.statement.incrementLinkAccess, 'run'); - }); - - it('stubbed exists success', async function () { - const returns = { - id: 'id', - isSpecial: false, - url: 'url', - created: 0, - expires: 0, - 'auth_token': 'abc', - 'last_access': 0, - accesses: 0, - }; - const expected = { - id: 'id', - isSpecial: false, - url: 'url', - created: 0, - expires: 0, - authToken: 'abc', - lastAccess: 0, - accesses: 0, - }; - id = 'id'; - db.statement.getLinkById.get.returns(returns); - db.statement.incrementLinkAccess.run.returns({ changes: 1 }); - const result = await db.accessLink(dbCtx, id); - assert.deepStrictEqual(result, expected); - }); - it('stubbed missing success', async function () { - const returns = undefined; - const expected = undefined; - id = 'id'; - db.statement.getLinkById.get.returns(returns); - db.statement.incrementLinkAccess.run.returns({ changes: 0 }); - const result = await db.accessLink(dbCtx, id); - assert.deepStrictEqual(result, expected); - }); - it('stubbed increment failure', async function () { - const expectedExeption = DBErrors.UnexpectedResult; - const returns = { - id: 'id', - url: 'url', - created: 0, - expires: 0, - 'auth_token': 'abc', - 'last_access': 0, - accesses: 0, - }; - id = 'id'; - db.statement.getLinkById.get.returns(returns); - db.statement.incrementLinkAccess.run.returns({ changes: 0 }); - try { - await db.accessLink(dbCtx, id); - assert.fail(noExpectedException); - } catch (e) { - assert(e instanceof expectedExeption, noExpectedException); - } - }); - it('stubbed failure', async function () { - const expectedExeption = new Error('blah'); - id = 'id'; - db.statement.getLinkById.get.throws(expectedExeption); - try { - await db.accessLink(dbCtx, id); - assert.fail(noExpectedException); - } catch (e) { - assert.deepStrictEqual(e, expectedExeption, noExpectedException); - } - }); - }); // accessLink - - describe('expireLink', function () { - let id, expires; - - beforeEach(function () { - sinon.stub(db.statement.expireLink, 'run'); - }); - - it('stubbed success', async function () { - const returns = { - changes: 1, - lastInsertRowid: 123, - }; - const expected = { - changes: 1, - lastInsertRowid: 123, - }; - id = 'id'; - expires = null; - db.statement.expireLink.run.returns(returns); - const result = await db.expireLink(dbCtx, id, expires); - assert.deepStrictEqual(result, expected); - }); - it('stubbed change failure', async function () { - const expectedExeption = DBErrors.UnexpectedResult; - const returns = { - changes: 0, - lastInsertRowid: undefined, - }; - id = 'id'; - db.statement.expireLink.run.returns(returns); - try { - await db.expireLink(dbCtx, id); - assert.fail(noExpectedException); - } catch (e) { - assert(e instanceof expectedExeption, noExpectedException); - } - }); - it('stubbed failure', async function () { - const expectedExeption = new Error('blah'); - id = 'id'; - expires = null; - db.statement.expireLink.run.throws(expectedExeption); - try { - await db.expireLink(dbCtx, id, expires); - assert.fail(noExpectedException); - } catch (e) { - assert.deepStrictEqual(e, expectedExeption, noExpectedException); - } - }); - }); // expireLink - - describe('getAllLinks', function () { - beforeEach(function () { - sinon.stub(db.statement.linkGetAll, 'all'); - }); - - it('stubbed success', async function () { - const returns = [ - { - id: 'id', - isSpecial: false, - url: 'url', - created: 0, - expires: 0, - 'auth_token': 'abc', - 'last_access': 0, - accesses: 0, - }, - ]; - const expected = [ - { - id: 'id', - isSpecial: false, - url: 'url', - created: 0, - expires: 0, - authToken: 'abc', - lastAccess: 0, - accesses: 0, - }, - ]; - db.statement.linkGetAll.all.returns(returns); - const result = await db.getAllLinks(dbCtx); - assert.deepStrictEqual(result, expected); - }); - it('stubbed failure', async function () { - const expectedExeption = new Error('blah'); - db.statement.linkGetAll.all.throws(expectedExeption); - try { - await db.getAllLinks(dbCtx); - assert.fail(noExpectedException); - } catch (e) { - assert.deepStrictEqual(e, expectedExeption, noExpectedException); - } - }); - }); // getAllLinks - - describe('_optimize', function () { - let cslo, oac; - beforeEach(function () { - cslo = db.changesSinceLastOptimize; - oac = db.optimizeAfterChanges; - sinon.stub(db.db, 'prepare').returns({ - all: sinon.stub(), - }); - sinon.stub(db.db, 'pragma'); - }); - afterEach(function () { - db.changesSinceLastOptimize = cslo; - db.optimizeAfterChanges = oac; - }); - it('covers', function () { - db._optimize(); - assert(db.db.pragma.called); - }); - it('_maybeOptimize', function () { - db.changesSinceLastOptimize = BigInt(1000); - db.optimizeAfterChanges = BigInt(10); - sinon.stub(db, '_optimize'); - db._maybeOptimize(); - assert(db._optimize.called); - }); - }); - -}); \ No newline at end of file diff --git a/test/src/manager.js b/test/src/manager.js index e22673b..ecb1f93 100644 --- a/test/src/manager.js +++ b/test/src/manager.js @@ -7,26 +7,18 @@ const sinon = require('sinon'); const Manager = require('../../src/manager'); const common = require('../../src/common'); const Enum = require('../../src/enum'); +const StubDB = require('../stub-db'); const { ServeStaticFile, SlugGeneratorExhausted } = require('../../src/errors'); const noExpectedException = 'did not get expected exception'; describe('Manager', function () { - let manager, logger, options; + let manager, logger, db, options; let res, ctx; beforeEach(function () { logger = new StubLogger(sinon); - const stubDb = { - context: async (fn) => await fn({}), - transaction: async (_dbCtx, fn) => await fn({}), - getLinkById: sinon.stub(), - accessLink: sinon.stub(), - getLinkByUrl: sinon.stub(), - expireLink: sinon.stub(), - upsertLink: sinon.stub(), - getAllLinks: sinon.stub(), - }; + db = new StubDB(logger); options = {}; res = { end: sinon.stub(), @@ -35,7 +27,7 @@ describe('Manager', function () { ctx = { params: {}, }; - manager = new Manager(logger, stubDb, options); + manager = new Manager(logger, db, options); }); afterEach(function () { -- 2.49.1