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);
+++ /dev/null
-'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;
+++ /dev/null
-'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;
+++ /dev/null
-'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;
+++ /dev/null
-SELECT * FROM auth WHERE id = $(id)
\ No newline at end of file
+++ /dev/null
---
-INSERT INTO auth (id, secret, password) VALUES ($(id), $(secret), $(credential))
-ON CONFLICT (id) DO
-UPDATE SET password = $(credential), secret = $(secret)
+++ /dev/null
-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
+++ /dev/null
-UPDATE link SET expires = to_timestamp($(expires)) WHERE id = $(id)
\ No newline at end of file
+++ /dev/null
-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
+++ /dev/null
-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
+++ /dev/null
-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
+++ /dev/null
-UPDATE link SET url = $(url) WHERE id = $(id)
\ No newline at end of file
+++ /dev/null
-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
+++ /dev/null
-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;
+++ /dev/null
-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;
+++ /dev/null
-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;
+++ /dev/null
-BEGIN;
-
- ALTER TABLE link
- DROP COLUMN is_special
- ;
-
- DELETE FROM _meta_schema_version WHERE major = 1 AND minor = 0 AND patch = 1;
-
-COMMIT;
+++ /dev/null
-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;
+++ /dev/null
-'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;
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);
module.exports = {
...Errors,
- DatabaseError,
ServeStaticFile,
SlugGeneratorExhausted,
};
\ No newline at end of file
+++ /dev/null
-'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
+++ /dev/null
-'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
+++ /dev/null
-'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
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(),
ctx = {
params: {},
};
- manager = new Manager(logger, stubDb, options);
+ manager = new Manager(logger, db, options);
});
afterEach(function () {