From c0f6db5a9c3f231d85ae3c3d7a556aad1af369b9 Mon Sep 17 00:00:00 2001 From: Lawrence <34475808+acodeninja@users.noreply.github.com> Date: Tue, 24 Sep 2024 16:29:53 +0100 Subject: [PATCH] refactor: code quality changes (#14) * refactor: bad usage of String#match * refactor: default cases in switch * refactor: empty async arrow function * refactor: empty static method 'checkConfiguration' * refactor: async function without any await expressions * refactor: shorthand type coercions * refactor: async function should have await expression * docs: add jsdoc blocks to the transaction class * refactor: variable name shadows variable in outer scope --- src/Persist.test.js | 2 +- src/Query.js | 5 +-- src/SchemaCompiler.test.js | 2 +- src/Transactions.js | 82 ++++++++++++++++++++++++++++++++++++- src/engine/Engine.js | 42 +++++++++++-------- src/engine/HTTPEngine.js | 16 ++++---- src/engine/S3Engine.js | 2 +- src/engine/S3Engine.test.js | 20 +++++---- src/type/Model.js | 4 +- src/type/Model.test.js | 4 +- test/assertions.js | 2 +- test/mocks/engine.js | 36 +++++++++------- test/mocks/fetch.js | 49 ++++++++++++---------- test/mocks/fs.js | 21 ++++------ test/mocks/s3.js | 24 ++++++----- 15 files changed, 205 insertions(+), 106 deletions(-) diff --git a/src/Persist.test.js b/src/Persist.test.js index e5ae8eb..0cf5240 100644 --- a/src/Persist.test.js +++ b/src/Persist.test.js @@ -12,7 +12,7 @@ class TestEngine { } } -test('includes Type', async t => { +test('includes Type', t => { t.is(Persist.Type, Type); }); diff --git a/src/Query.js b/src/Query.js index 4cfb673..806b9cd 100644 --- a/src/Query.js +++ b/src/Query.js @@ -52,13 +52,12 @@ class Query { execute(model, index) { const matchIs = (query) => query?.$is !== undefined; const matchPrimitive = (query) => ['string', 'number', 'boolean'].includes(typeof query); - const matchContains = (query) => !!query?.$contains; + const matchContains = (query) => query?.$contains !== undefined; const matchesQuery = (subject, inputQuery = this.query) => { if (matchPrimitive(inputQuery)) return subject === inputQuery; - if (matchIs(inputQuery)) - if (subject === inputQuery.$is) return true; + if (matchIs(inputQuery) && subject === inputQuery.$is) return true; if (matchContains(inputQuery)) { if (subject.includes?.(inputQuery.$contains)) return true; diff --git a/src/SchemaCompiler.test.js b/src/SchemaCompiler.test.js index c6d6d19..60a5e85 100644 --- a/src/SchemaCompiler.test.js +++ b/src/SchemaCompiler.test.js @@ -424,7 +424,7 @@ test('.compile(MainModel).validate(invalidModel) throws a ValidationError', t => t.is(error.message, 'Validation failed'); - t.true(!!error.data.id.match(/MainModel\/[A-Z0-9]+/)); + t.true(new RegExp(/MainModel\/[A-Z0-9]+/).test(error.data.id)); for (const [name, value] of Object.entries(invalidModel.toData())) { t.deepEqual(error.data[name], value); diff --git a/src/Transactions.js b/src/Transactions.js index c2de9db..dec5e83 100644 --- a/src/Transactions.js +++ b/src/Transactions.js @@ -1,32 +1,110 @@ +/** + * Class representing a transaction-related error. + * + * @class TransactionError + * @extends Error + */ class TransactionError extends Error { } +/** + * Error thrown when a transaction is already committed. + * + * @class TransactionCommittedError + * @extends TransactionError + */ export class TransactionCommittedError extends TransactionError { + /** + * Creates an instance of TransactionCommittedError. + * This error is thrown when attempting to commit an already committed transaction. + * @property {string} message - The error message. + */ message = 'Transaction was already committed.'; } +/** + * Enables transaction support for the provided engine. + * + * This function enhances an engine class with transaction capabilities, allowing multiple + * changes to be grouped into a single transaction that can be committed or rolled back. + * + * @param {Engine.constructor} engine - The base engine class to be enhanced with transaction support. + * @returns {TransactionalEngine.constructor} TransactionalEngine - The enhanced engine class with transaction functionality. + */ export default function enableTransactions(engine) { + /** + * A class representing an engine with transaction capabilities. + * @class TransactionalEngine + * @extends {engine} + */ class TransactionalEngine extends engine { } + /** + * Starts a transaction on the engine. Returns a Transaction class that can handle + * put, commit, and rollback actions for the transaction. + * + * @returns {Transaction.constructor} Transaction - A class that manages the transaction's operations. + */ TransactionalEngine.start = () => { + /** + * A class representing an active transaction on the engine. + * Contains methods to put changes, commit the transaction, or roll back in case of failure. + * + * @class Transaction + */ class Transaction extends TransactionalEngine { + /** + * @property {Array} transactions - An array storing all the operations within the transaction. + * @static + */ static transactions = []; + + /** + * @property {boolean} committed - Indicates if the transaction has been committed. + * @static + */ static committed = false; + + /** + * @property {boolean} failed - Indicates if the transaction has failed. + * @static + */ static failed = false; - static async put(model) { + /** + * Adds a model to the transaction queue. + * + * @param {Object} model - The model to be added to the transaction. + * @returns {Promise} A promise that resolves once the model is added. + */ + static put(model) { this.transactions.push({ hasRun: false, hasRolledBack: false, model, }); + + return Promise.resolve(); } + /** + * Checks if the transaction has already been committed. If true, throws a TransactionCommittedError. + * + * @throws {TransactionCommittedError} If the transaction has already been committed. + * @private + */ static _checkCommitted() { if (this.committed) throw new TransactionCommittedError(); } + /** + * Commits the transaction, applying all the changes to the engine. + * Rolls back if any part of the transaction fails. + * + * @returns {Promise} A promise that resolves once the transaction is committed, or rejects if an error occurs. + * @throws {Error} If any operation in the transaction fails. + */ static async commit() { this._checkCommitted(); @@ -34,7 +112,7 @@ export default function enableTransactions(engine) { for (const [index, {model}] of this.transactions.entries()) { try { this.transactions[index].original = await engine.get(model.constructor, model.id); - } catch (_) { + } catch (_error) { this.transactions[index].original = null; } diff --git a/src/engine/Engine.js b/src/engine/Engine.js index e6dd92e..9ccda18 100644 --- a/src/engine/Engine.js +++ b/src/engine/Engine.js @@ -16,10 +16,11 @@ class Engine { * * @param {string} _id - The ID of the model to retrieve. * @throws {NotImplementedError} Throws if the method is not implemented. + * @returns {Promise} - Returns a promise resolving to the raw data of the requested model. * @abstract */ - static async getById(_id) { - throw new NotImplementedError(`${this.name} must implement .getById()`); + static getById(_id) { + return Promise.reject(new NotImplementedError(`${this.name} must implement .getById()`)); } /** @@ -27,10 +28,11 @@ class Engine { * * @param {Model} _data - The model data to save. * @throws {NotImplementedError} Throws if the method is not implemented. + * @returns {Promise} * @abstract */ - static async putModel(_data) { - throw new NotImplementedError(`${this.name} must implement .putModel()`); + static putModel(_data) { + return Promise.reject(new NotImplementedError(`${this.name} must implement .putModel()`)); } /** @@ -38,10 +40,11 @@ class Engine { * * @param {Model.constructor} _model - The model to retrieve the index for. * @throws {NotImplementedError} Throws if the method is not implemented. + * @returns {Promise} - Returns a promise resolving to the model index. * @abstract */ - static async getIndex(_model) { - throw new NotImplementedError(`${this.name} does not implement .getIndex()`); + static getIndex(_model) { + return Promise.reject(new NotImplementedError(`${this.name} does not implement .getIndex()`)); } /** @@ -49,10 +52,11 @@ class Engine { * * @param {Object} _index - The index data to save. * @throws {NotImplementedError} Throws if the method is not implemented. + * @returns {Promise} * @abstract */ - static async putIndex(_index) { - throw new NotImplementedError(`${this.name} does not implement .putIndex()`); + static putIndex(_index) { + return Promise.reject(new NotImplementedError(`${this.name} does not implement .putIndex()`)); } /** @@ -60,10 +64,11 @@ class Engine { * * @param {Model.constructor} _model - The model to retrieve the compiled search index for. * @throws {NotImplementedError} Throws if the method is not implemented. + * @returns {Promise} - Returns a promise resolving to the compiled search index. * @abstract */ - static async getSearchIndexCompiled(_model) { - throw new NotImplementedError(`${this.name} does not implement .getSearchIndexCompiled()`); + static getSearchIndexCompiled(_model) { + return Promise.reject(new NotImplementedError(`${this.name} does not implement .getSearchIndexCompiled()`)); } /** @@ -71,10 +76,11 @@ class Engine { * * @param {Model.constructor} _model - The model to retrieve the raw search index for. * @throws {NotImplementedError} Throws if the method is not implemented. + * @returns {Promise} - Returns a promise resolving to the raw search index. * @abstract */ - static async getSearchIndexRaw(_model) { - throw new NotImplementedError(`${this.name} does not implement .getSearchIndexRaw()`); + static getSearchIndexRaw(_model) { + return Promise.reject(new NotImplementedError(`${this.name} does not implement .getSearchIndexRaw()`)); } /** @@ -83,10 +89,11 @@ class Engine { * @param {Model.constructor} _model - The model for which the compiled search index is saved. * @param {Object} _compiledIndex - The compiled search index data. * @throws {NotImplementedError} Throws if the method is not implemented. + * @returns {Promise} * @abstract */ - static async putSearchIndexCompiled(_model, _compiledIndex) { - throw new NotImplementedError(`${this.name} does not implement .putSearchIndexCompiled()`); + static putSearchIndexCompiled(_model, _compiledIndex) { + return Promise.reject(new NotImplementedError(`${this.name} does not implement .putSearchIndexCompiled()`)); } /** @@ -95,10 +102,11 @@ class Engine { * @param {Model.constructor} _model - The model for which the raw search index is saved. * @param {Object} _rawIndex - The raw search index data. * @throws {NotImplementedError} Throws if the method is not implemented. + * @returns {Promise} * @abstract */ - static async putSearchIndexRaw(_model, _rawIndex) { - throw new NotImplementedError(`${this.name} does not implement .putSearchIndexRaw()`); + static putSearchIndexRaw(_model, _rawIndex) { + return Promise.reject(new NotImplementedError(`${this.name} does not implement .putSearchIndexRaw()`)); } /** @@ -321,7 +329,7 @@ class Engine { * @abstract */ static checkConfiguration() { - + // Implemented in extending Engine class } /** diff --git a/src/engine/HTTPEngine.js b/src/engine/HTTPEngine.js index 76e4a9a..8e63615 100644 --- a/src/engine/HTTPEngine.js +++ b/src/engine/HTTPEngine.js @@ -102,7 +102,7 @@ class HTTPEngine extends Engine { * * @throws {HTTPRequestFailedError} Thrown if the fetch request fails. */ - static async _processFetch(url, options, defaultValue = undefined) { + static _processFetch(url, options, defaultValue = undefined) { return this.configuration.fetch(url, options) .then(response => { if (!response.ok) { @@ -133,7 +133,7 @@ class HTTPEngine extends Engine { this.configuration.host, this.configuration.prefix, `${id}.json`, - ].filter(e => !!e).join('/')); + ].filter(e => Boolean(e)).join('/')); return await this._processFetch(url, this._getReadOptions()); } @@ -146,14 +146,14 @@ class HTTPEngine extends Engine { * * @throws {HTTPRequestFailedError} Thrown if the PUT request fails. */ - static async putModel(model) { + static putModel(model) { const url = new URL([ this.configuration.host, this.configuration.prefix, `${model.id}.json`, - ].filter(e => !!e).join('/')); + ].filter(e => Boolean(e)).join('/')); - return await this._processFetch(url, { + return this._processFetch(url, { ...this._getWriteOptions(), body: JSON.stringify(model.toData()), }); @@ -175,7 +175,7 @@ class HTTPEngine extends Engine { this.configuration.prefix, location, '_index.json', - ].filter(e => !!e).join('/')); + ].filter(e => Boolean(e)).join('/')); return await this._processFetch(url, { ...this._getWriteOptions(), @@ -259,7 +259,7 @@ class HTTPEngine extends Engine { this.configuration.prefix, model.toString(), '_search_index.json', - ].filter(e => !!e).join('/')); + ].filter(e => Boolean(e)).join('/')); return this._processFetch(url, { ...this._getWriteOptions(), @@ -282,7 +282,7 @@ class HTTPEngine extends Engine { this.configuration.prefix, model.toString(), '_search_index_raw.json', - ].filter(e => !!e).join('/')); + ].filter(e => Boolean(e)).join('/')); return await this._processFetch(url, { ...this._getWriteOptions(), diff --git a/src/engine/S3Engine.js b/src/engine/S3Engine.js index 81bd515..8dba80c 100644 --- a/src/engine/S3Engine.js +++ b/src/engine/S3Engine.js @@ -105,7 +105,7 @@ class S3Engine extends Engine { })); return JSON.parse(await data.Body.transformToString()); - } catch (_) { + } catch (_error) { return {}; } } diff --git a/src/engine/S3Engine.test.js b/src/engine/S3Engine.test.js index d2e5e19..f9f04ce 100644 --- a/src/engine/S3Engine.test.js +++ b/src/engine/S3Engine.test.js @@ -399,10 +399,11 @@ test('S3Engine.put(model) when the engine fails to put a compiled search index', }, }); - client.send.callsFake(async command => { + client.send.callsFake(command => { if (command.input.Key.endsWith('_search_index.json')) { - throw new Error(); + return Promise.reject(new Error()); } + return Promise.resolve(); }); const models = new Models(); @@ -458,10 +459,11 @@ test('S3Engine.put(model) when the engine fails to put a raw search index', asyn }, }); - client.send.callsFake(async command => { + client.send.callsFake(command => { if (command.input.Key.endsWith('_search_index_raw.json')) { - throw new Error(); + return Promise.reject(new Error()); } + return Promise.resolve(); }); const models = new Models(); @@ -510,10 +512,11 @@ test('S3Engine.put(model) when putting an index fails', async t => { }, }); - client.send.callsFake(async command => { + client.send.callsFake(command => { if (command.input.Key.endsWith('/_index.json')) { - throw new Error(); + return Promise.reject(new Error()); } + return Promise.resolve(); }); const models = new Models(); @@ -618,10 +621,11 @@ test('S3Engine.put(model) when the initial model put fails', async t => { const models = new Models(); const model = models.createFullTestModel(); - client.send.callsFake(async command => { + client.send.callsFake(command => { if (command.input.Key.endsWith('MainModel/000000000000.json')) { - throw new Error(); + return Promise.reject(new Error()); } + return Promise.resolve(); }); await t.throwsAsync(() => S3Engine.configure({ diff --git a/src/type/Model.js b/src/type/Model.js index 1e6337f..da3daec 100644 --- a/src/type/Model.js +++ b/src/type/Model.js @@ -253,9 +253,9 @@ class Model { return ( !this.isModel(possibleDryModel) && Object.keys(possibleDryModel).includes('id') && - !!possibleDryModel.id.match(/[A-Za-z]+\/[A-Z0-9]+/) + new RegExp(/[A-Za-z]+\/[A-Z0-9]+/).test(possibleDryModel.id) ); - } catch (_) { + } catch (_error) { return false; } } diff --git a/src/type/Model.test.js b/src/type/Model.test.js index be0d426..545ae8e 100644 --- a/src/type/Model.test.js +++ b/src/type/Model.test.js @@ -7,13 +7,13 @@ import test from 'ava'; test('constructor() creates a model instance with an id', t => { const model = new MainModel(); - t.true(!!model.id.match(/MainModel\/[A-Z0-9]+/)); + t.true(new RegExp(/MainModel\/[A-Z0-9]+/).test(model.id)); }); test('constructor(valid) creates a model using the input valid', t => { const model = new MainModel({string: 'String'}); - t.true(!!model.id.match(/MainModel\/[A-Z0-9]+/)); + t.true(new RegExp(/MainModel\/[A-Z0-9]+/).test(model.id)); t.like(model.toData(), {string: 'String'}); }); diff --git a/test/assertions.js b/test/assertions.js index d532e04..cb818c4 100644 --- a/test/assertions.js +++ b/test/assertions.js @@ -7,7 +7,7 @@ function parseArgument(arg) { return JSON.parse(JSON.stringify(arg.toData())); } return JSON.parse(JSON.stringify(arg)); - } catch (_) { + } catch (_error) { return arg; } } diff --git a/test/mocks/engine.js b/test/mocks/engine.js index 6c25366..9ac4bdc 100644 --- a/test/mocks/engine.js +++ b/test/mocks/engine.js @@ -15,43 +15,49 @@ export function getTestEngine(models = []) { class TestEngine extends Engine { } - TestEngine.getById = sinon.stub().callsFake(async (id) => { - if (_models[id]) return _.cloneDeep(_models[id]); + TestEngine.getById = sinon.stub().callsFake((id) => { + if (_models[id]) return Promise.resolve(_.cloneDeep(_models[id])); - throw new Error(`Model ${id} not found.`); + return Promise.reject(new Error(`Model ${id} not found.`)); }); - TestEngine.putModel = sinon.stub().callsFake(async (model) => { + TestEngine.putModel = sinon.stub().callsFake((model) => { _models[model.id] = _.cloneDeep(model.toData()); + return Promise.resolve(); }); - TestEngine.putIndex = sinon.stub().callsFake(async (index) => { + TestEngine.putIndex = sinon.stub().callsFake((index) => { for (const [key, value] of Object.entries(index)) { _index[key] = _.cloneDeep(value); } + return Promise.resolve(); }); - TestEngine.getSearchIndexCompiled = sinon.stub().callsFake(async (model) => { - if (_searchIndexCompiled[model.toString()]) return _.cloneDeep(_searchIndexCompiled[model.toString()]); + TestEngine.getSearchIndexCompiled = sinon.stub().callsFake((model) => { + if (_searchIndexCompiled[model.toString()]) + return Promise.resolve(_.cloneDeep(_searchIndexCompiled[model.toString()])); - throw new Error(`Search index does not exist for ${model.name}`); + return Promise.reject(new Error(`Search index does not exist for ${model.name}`)); }); - TestEngine.getSearchIndexRaw = sinon.stub().callsFake(async (model) => { - if (_searchIndexRaw[model.toString()]) return _.cloneDeep(_searchIndexRaw[model.toString()]); + TestEngine.getSearchIndexRaw = sinon.stub().callsFake((model) => { + if (_searchIndexRaw[model.toString()]) + return Promise.resolve(_.cloneDeep(_searchIndexRaw[model.toString()])); - return {}; + return Promise.resolve({}); }); - TestEngine.putSearchIndexCompiled = sinon.stub().callsFake(async (model, compiledIndex) => { + TestEngine.putSearchIndexCompiled = sinon.stub().callsFake((model, compiledIndex) => { _searchIndexCompiled[model.toString()] = _.cloneDeep(compiledIndex); + return Promise.resolve(); }); - TestEngine.putSearchIndexRaw = sinon.stub().callsFake(async (model, rawIndex) => { + TestEngine.putSearchIndexRaw = sinon.stub().callsFake((model, rawIndex) => { _searchIndexCompiled[model.toString()] = _.cloneDeep(rawIndex); + return Promise.resolve(); }); - TestEngine.findByValue = sinon.stub().callsFake(async (model, parameters) => { + TestEngine.findByValue = sinon.stub().callsFake((model, parameters) => { const found = []; for (const [id, data] of Object.entries(_models)) { if (id.startsWith(model.toString())) { @@ -63,7 +69,7 @@ export function getTestEngine(models = []) { } } } - return found; + return Promise.resolve(found); }); return TestEngine; diff --git a/test/mocks/fetch.js b/test/mocks/fetch.js index 504158d..dfad5f8 100644 --- a/test/mocks/fetch.js +++ b/test/mocks/fetch.js @@ -5,13 +5,13 @@ import sinon from 'sinon'; function stubFetch(filesystem = {}, models = [], errors = {}, prefix = '') { const modelsAddedToFilesystem = []; - function fileSystemFromModels(initialFilesystem = {}, ...models) { - for (const model of models) { - const modelIndexPath = [prefix, model.id.replace(/[A-Z0-9]+$/, '_index.json')].filter(i => !!i).join('/'); - const searchIndexRawPath = [prefix, model.id.replace(/[A-Z0-9]+$/, '_search_index_raw.json')].filter(i => !!i).join('/'); + function fileSystemFromModels(initialFilesystem = {}, ...initialModels) { + for (const model of initialModels) { + const modelIndexPath = [prefix, model.id.replace(/[A-Z0-9]+$/, '_index.json')].filter(i => Boolean(i)).join('/'); + const searchIndexRawPath = [prefix, model.id.replace(/[A-Z0-9]+$/, '_search_index_raw.json')].filter(i => Boolean(i)).join('/'); const modelIndex = initialFilesystem[modelIndexPath] || {}; - initialFilesystem[[prefix, model.id + '.json'].filter(i => !!i).join('/')] = model.toData(); + initialFilesystem[[prefix, model.id + '.json'].filter(i => Boolean(i)).join('/')] = model.toData(); initialFilesystem[modelIndexPath] = { ...modelIndex, [model.id]: model.toIndexData(), @@ -27,13 +27,13 @@ function stubFetch(filesystem = {}, models = [], errors = {}, prefix = '') { modelsAddedToFilesystem.push(model.id); - for (const [_, value] of Object.entries(model)) { + for (const [_property, value] of Object.entries(model)) { if (Model.isModel(value) && !modelsAddedToFilesystem.includes(value.id)) { initialFilesystem = fileSystemFromModels(initialFilesystem, value); } if (Array.isArray(value)) { - for (const [_, subModel] of Object.entries(value)) { + for (const [_subProperty, subModel] of Object.entries(value)) { if (Model.isModel(subModel) && !modelsAddedToFilesystem.includes(subModel.id)) { initialFilesystem = fileSystemFromModels(initialFilesystem, subModel); } @@ -41,6 +41,7 @@ function stubFetch(filesystem = {}, models = [], errors = {}, prefix = '') { } } } + return initialFilesystem; } @@ -51,7 +52,7 @@ function stubFetch(filesystem = {}, models = [], errors = {}, prefix = '') { if (searchIndexes.length > 0) { for (const [name, index] of searchIndexes) { - const fields = [...new Set(Object.values(index).map(i => Object.keys(i).filter(i => i !== 'id')).flat(Infinity))]; + const fields = [...new Set(Object.values(index).map(i => Object.keys(i).filter(p => p !== 'id')).flat(Infinity))]; const compiledIndex = lunr(function () { this.ref('id'); @@ -68,36 +69,42 @@ function stubFetch(filesystem = {}, models = [], errors = {}, prefix = '') { } } - return sinon.stub().callsFake(async (url, opts) => { + return sinon.stub().callsFake((url, opts) => { if (opts.method === 'PUT') { resolvedFiles[url.pathname ?? url] = JSON.parse(opts.body); - return {ok: true, status: 200, json: async () => ({})}; + return Promise.resolve({ + ok: true, + status: 200, + json: () => Promise.resolve({}), + }); } for (const [path, value] of Object.entries(errors)) { if ((url.pathname ?? url).endsWith(path)) { if (value) return value; - return { + return Promise.resolve({ ok: false, status: 404, - json: async () => { - throw new Error(); - }, - }; + json: () => Promise.reject(new Error()), + }); } } for (const [filename, value] of Object.entries(resolvedFiles)) { if ((url.pathname ?? url).endsWith(filename)) { - return {ok: true, status: 200, json: async () => value}; + return Promise.resolve({ + ok: true, + status: 200, + json:() => Promise.resolve(value), + }); } } - return { - ok: false, status: 404, json: async () => { - throw new Error(); - }, - }; + return Promise.resolve({ + ok: false, + status: 404, + json: () => Promise.reject(new Error()), + }); }); } diff --git a/test/mocks/fs.js b/test/mocks/fs.js index d1c5aeb..9ac3390 100644 --- a/test/mocks/fs.js +++ b/test/mocks/fs.js @@ -27,13 +27,13 @@ function stubFs(filesystem = {}, models = []) { modelsAddedToFilesystem.push(model.id); - for (const [_, value] of Object.entries(model)) { + for (const [_proptery, value] of Object.entries(model)) { if (Model.isModel(value) && !modelsAddedToFilesystem.includes(value.id)) { initialFilesystem = fileSystemFromModels(initialFilesystem, value); } if (Array.isArray(value)) { - for (const [_, subModel] of Object.entries(value)) { + for (const [_subProperty, subModel] of Object.entries(value)) { if (Model.isModel(subModel) && !modelsAddedToFilesystem.includes(subModel.id)) { initialFilesystem = fileSystemFromModels(initialFilesystem, subModel); } @@ -66,29 +66,24 @@ function stubFs(filesystem = {}, models = []) { } } - const readFile = sinon.stub().callsFake(async (filePath) => { + const readFile = sinon.stub().callsFake((filePath) => { for (const [filename, value] of Object.entries(resolvedFiles)) { if (filePath.endsWith(filename)) { if (typeof value === 'string') { - return Buffer.from(value); + return Promise.resolve(Buffer.from(value)); } - return Buffer.from(JSON.stringify(value)); + return Promise.resolve(Buffer.from(JSON.stringify(value))); } } const err = new Error(`ENOENT: no such file or directory, open '${filePath}'`); err.code = 'EPIPE'; err.errno = -3; - throw err; + return Promise.reject(err); }); - const writeFile = sinon.stub().callsFake(async () => { - - }); - - const mkdir = sinon.stub().callsFake(async () => { - - }); + const writeFile = sinon.stub(); + const mkdir = sinon.stub(); return {readFile, writeFile, mkdir}; } diff --git a/test/mocks/s3.js b/test/mocks/s3.js index b365f3c..bee0330 100644 --- a/test/mocks/s3.js +++ b/test/mocks/s3.js @@ -16,8 +16,8 @@ function S3ObjectWrapper(data) { function stubS3Client(filesystem = {}, models = {}) { const modelsAddedToFilesystem = []; - function bucketFilesFromModels(initialFilesystem = {}, ...models) { - for (const model of models) { + function bucketFilesFromModels(initialFilesystem = {}, ...initialModels) { + for (const model of initialModels) { const modelIndexPath = model.id.replace(/[A-Z0-9]+$/, '_index.json'); const modelIndex = initialFilesystem[modelIndexPath]; initialFilesystem[model.id + '.json'] = model.toData(); @@ -37,13 +37,13 @@ function stubS3Client(filesystem = {}, models = {}) { }; } - for (const [_, value] of Object.entries(model)) { + for (const [_property, value] of Object.entries(model)) { if (Model.isModel(value) && !modelsAddedToFilesystem.includes(value.id)) { initialFilesystem = bucketFilesFromModels(initialFilesystem, value); } if (Array.isArray(value)) { - for (const [_, subModel] of Object.entries(value)) { + for (const [_subProperty, subModel] of Object.entries(value)) { if (Model.isModel(subModel) && !modelsAddedToFilesystem.includes(subModel.id)) { initialFilesystem = bucketFilesFromModels(initialFilesystem, subModel); } @@ -64,7 +64,7 @@ function stubS3Client(filesystem = {}, models = {}) { if (searchIndexes.length > 0) { for (const [name, index] of searchIndexes) { - const fields = [...new Set(Object.values(index).map(i => Object.keys(i).filter(i => i !== 'id')).flat(Infinity))]; + const fields = [...new Set(Object.values(index).map(i => Object.keys(i).filter(p => p !== 'id')).flat(Infinity))]; resolvedFiles[name.replace('_raw', '')] = lunr(function () { this.ref('id'); @@ -85,22 +85,24 @@ function stubS3Client(filesystem = {}, models = {}) { }; } - const send = sinon.stub().callsFake(async (command) => { - switch (command.constructor.name) { + const send = sinon.stub().callsFake((command) => { + switch (command?.constructor?.name) { case 'GetObjectCommand': if (resolvedBuckets[command.input.Bucket]) { for (const [filename, value] of Object.entries(resolvedBuckets[command.input.Bucket])) { if (command.input.Key.endsWith(filename)) { if (typeof value === 'string') { - return S3ObjectWrapper(Buffer.from(value)); + return Promise.resolve(S3ObjectWrapper(Buffer.from(value))); } - return S3ObjectWrapper(Buffer.from(JSON.stringify(value))); + return Promise.resolve(S3ObjectWrapper(Buffer.from(JSON.stringify(value)))); } } } - throw new NoSuchKey({}); + return Promise.reject(new NoSuchKey({})); case 'PutObjectCommand': - break; + return Promise.resolve(null); + default: + return Promise.reject(new Error('Unsupported command')); } });