diff --git a/data-cache.d.ts b/data-cache.d.ts index 9016e7c..f9859d5 100644 --- a/data-cache.d.ts +++ b/data-cache.d.ts @@ -1,42 +1,25 @@ // MOST Web Framework 2.0 Codename Blueshift BSD-3-Clause license Copyright (c) 2017-2022, THEMOST LP All rights reserved import {ConfigurationStrategy} from "@themost/common"; - export declare interface DataCacheStrategyBase { add(key: string, value: any, absoluteExpiration?: number): Promise; remove(key: string): Promise; - clear(): Promise; - get(key: string): Promise; - getOrDefault(key: string, getFunc: Promise, absoluteExpiration?: number): Promise; + clear(): Promise; + get(key: string): Promise; + getOrDefault(key: string, getFunc: () => Promise, absoluteExpiration?: number): Promise; } export declare interface DataCacheFinalize extends DataCacheStrategyBase { finalize(): Promise; } -export declare class DataCache { - init(callback: (err?: Error) => void): void; - - remove(key: string, callback: (err?: Error) => void): void; - - removeAll(callback: (err?: Error) => void): void; - - add(key: string, value: any, ttl?: number, callback?: (err?: Error) => void): void; - - ensure(key: string, getFunc: (err?: Error, res?: any) => void, callback?: (err?: Error) => void): void; - - get(key: string, callback?: (err?: Error, res?: any) => void): void; - - static getCurrent(): DataCache; -} - export declare abstract class DataCacheStrategy extends ConfigurationStrategy implements DataCacheStrategyBase { abstract add(key: string, value: any, absoluteExpiration?: number): Promise; abstract remove(key: string): Promise; - abstract clear(): Promise; - abstract get(key: string): Promise; - abstract getOrDefault(key: string, getFunc: Promise, absoluteExpiration?: number): Promise; + abstract clear(): Promise; + abstract get(key: string): Promise; + abstract getOrDefault(key: string, getFunc: () => Promise, absoluteExpiration?: number): Promise; } @@ -44,9 +27,9 @@ export declare class DefaultDataCacheStrategy extends DataCacheStrategy implemen add(key: string, value: any, absoluteExpiration?: number): Promise; remove(key: string): Promise; - clear(): Promise; - get(key: string): Promise; - getOrDefault(key: string, getFunc: Promise, absoluteExpiration?: number): Promise; + clear(): Promise; + get(key: string): Promise; + getOrDefault(key: string, getFunc: () => Promise, absoluteExpiration?: number): Promise; finalize(): Promise; } diff --git a/data-cache.js b/data-cache.js index 57d976a..4f4d1ee 100644 --- a/data-cache.js +++ b/data-cache.js @@ -1,443 +1,207 @@ // MOST Web Framework 2.0 Codename Blueshift BSD-3-Clause license Copyright (c) 2017-2022, THEMOST LP All rights reserved -var _ = require('lodash'); -var {LangUtils, Args, SequentialEventEmitter, AbstractMethodError, ConfigurationStrategy} = require('@themost/common'); -var {hasOwnProperty} = require('./has-own-property'); -var Symbol = require('symbol'); -var Q = require('q'); -var currentProperty = Symbol('current'); +var {LangUtils, Args, AbstractMethodError, ConfigurationStrategy} = require('@themost/common'); var CACHE_ABSOLUTE_EXPIRATION = 1200; -/** - * @class - * @classdesc Implements data cache mechanisms in MOST Data Applications. - * DataCache class is used as the internal data caching engine, if any other caching mechanism is not defined. - * @property {Number} ttl - An amount of time in seconds which is the default cached item lifetime. - * @constructor - * @augments SequentialEventEmitter - * @deprecated - */ -function DataCache() { - // noinspection JSUnusedGlobalSymbols - this.initialized = false; -} -LangUtils.inherits(DataCache, SequentialEventEmitter); -/** - * Initializes data caching. - * @param {Function} callback - A callback function where the first argument will contain the Error object if an error occurred, or null otherwise. - * - * @example - var d = require("most-data"); - //try to find article with id 100 in cache - d.cache.current.init(function(err) { - done(err); - }; - */ -DataCache.prototype.init = function(callback) { - try { - if (this.initialized) { - callback(); - return; - } - var NodeCache = require( 'node-cache' ); - this.rawCache = new NodeCache(); - this.initialized = true; - callback(); - } - catch (e) { - callback(e); - } -}; -/** - * Removes a cached value. - * @param {string} key - A string that represents the key of the cached value - * @param {function(Error=)=} callback - A callback function where the first argument will contain the Error object if an error occurred, or null otherwise. - * - * @example - var d = require("most-data"); - //try to find article with id 100 in cache - d.cache.current.remove('/Article/100', function(err) { - done(err); - }; - */ -DataCache.prototype.remove = function(key, callback) { - var self = this; - callback = callback || function() {}; - self.init(function(err) { - if (err) { - callback(err); - } - else { - self.rawCache.set(key, callback); - } - }); -}; -/** - * Flush all cached data. - * @param {function(Error=)=} callback - A callback function where the first argument will contain the Error object if an error occurred, or null otherwise. - * - * @example - var d = require("most-data"); - //try to find article with id 100 in cache - d.cache.current.removeAll(function(err) { - done(err); - }; - */ -DataCache.prototype.removeAll = function(callback) { - var self = this; - callback = callback || function() {}; - self.init(function(err) { - if (err) { - callback(err); - } - else { - self.rawCache.flushAll(); - callback(); - } - }); -}; +class DataCacheStrategy extends ConfigurationStrategy { + /** + * + * @param {import('@themost/common').ConfigurationBase} config + * @constructor + * + */ + constructor(config) { + super(config); + } -/** - * Sets a key value pair in cache. - * @param {string} key - A string that represents the key of the cached value - * @param {*} value - The value to be cached - * @param {number=} ttl - A TTL in seconds. This parameter is optional. - * @param {Function=} callback - A callback function where the first argument will contain the Error object if an error occurred and the second argument will return true on success. - * - * @example - var d = require("most-data"); - d.cache.current.add('/User/100', { "id":100,"name":"user1@example.com","description":"User #1" }, 1200, function(err) { - done(err); - }); - */ -DataCache.prototype.add = function(key, value, ttl, callback) { - var self = this; - callback = callback || function() {}; - self.init(function(err) { - if (err) { - callback(err); - } - else { - self.rawCache.set(key, value, ttl, callback); - } - }); -}; -/** - * Gets data from cache or executes the defined function and adds the result to the cache with the specified key - * @param {string|*} key - A string that represents the of the cached data - * @param {function(function(Error=,*=))} fn - A function to execute if data will not be found in cache - * @param {function(Error=,*=)} callback - A callback function where the first argument will contain the Error object if an error occurred and the second argument will contain the result. - * @example - var d = require("most-data"); - //try to find user with id 100 in cache - d.cache.current.ensure('/User/100', function(cb) { - //otherwise get user from database - context.model('User').where('id').equal(100).first().then(function(result) { - cb(null, result); - }).catch(function(err) { - cb(err); - } - }, function(err, result) { - //and finally return the result - done(err,result); - }; - */ -DataCache.prototype.ensure = function(key, fn, callback) { - var self = this; - callback = callback || function() {}; - if (typeof fn !== 'function') { - callback(new Error('Invalid argument. Expected function.')); - return; + /** + * Sets a key value pair in cache. + * @abstract + * @param {string} key - A string that represents the key of the cached value + * @param {*} value - The value to be cached + * @param {number=} absoluteExpiration - An absolute expiration time in seconds. This parameter is optional. + * @returns {Promise|*} + */ + // eslint-disable-next-line no-unused-vars + add(key, value, absoluteExpiration) { + throw new AbstractMethodError(); } - //try to get from cache - self.get(key, function(err, result) { - if (err) { callback(err); return; } - if (typeof result !== 'undefined') { - callback(null, result); - } - else { - //execute fn - fn(function(err, result) { - if (err) { callback(err); return; } - self.add(key, (typeof result === 'undefined') ? null: result, self.ttl, function() { - callback(null, result); - }); - }); - } - }); -}; -/** - * Gets a cached value defined by the given key. - * @param {string|*} key - A string that represents the key of the cached value - * @param {function(Error=,*=)} callback - A callback function where the first argument will contain the Error object if an error occurred and the second argument will contain the result. - * - * @example - var d = require("most-data"); - //try to find article with id 100 in cache - d.cache.current.get('/Article/100', function(err, result) { - done(err,result); - }; - */ -DataCache.prototype.get = function(key, callback) { - var self = this; - callback = callback || function() {}; - if (_.isNil(key)) { - return callback(); + + /** + * Removes a cached value. + * @abstract + * @param {string} key - A string that represents the key of the cached value to be removed + * @returns {Promise|*} + */ + // eslint-disable-next-line no-unused-vars + remove(key) { + throw new AbstractMethodError(); } - self.init(function(err) { - if (err) { - callback(err); - } - else { - self.rawCache.get(key, function(err, value) { - if (err) { - callback(err); - } - else { - if (typeof value[key] !== 'undefined') { - callback(null, value[key]); - } - else { - callback(); - } - } - }); - } - }); -}; -/** - * @returns DataCache - */ -DataCache.getCurrent = function() { - if (typeof global !== 'undefined' || global !== null) { - var app = global.application; - if (app) { - //and if this application has a cache object - if (app.cache) { - //use this cache - return app.cache; - } - } + + /** + * Flush all cached data. + * @abstract + * @returns {Promise|*} + */ + clear() { + throw new AbstractMethodError(); } - if (DataCache[currentProperty]) { - return DataCache[currentProperty]; + + // noinspection JSUnusedLocalSymbols + /** + * Gets a cached value defined by the given key. + * @param {string} key + * @returns {Promise|*} + */ + // eslint-disable-next-line no-unused-vars + get(key) { + throw new AbstractMethodError(); } - DataCache[currentProperty] = new DataCache(); - return DataCache[currentProperty]; -}; -/** - * - * @param {ConfigurationBase} config - * @constructor - * - */ -function DataCacheStrategy(config) { - DataCacheStrategy.super_.bind(this)(config); + // noinspection JSUnusedLocalSymbols + /** + * Gets data from cache or executes the defined function and adds the result to the cache with the specified key + * @param {string|*} key - A string which represents the key of the cached data + * @param {Function} getFunc - A function to execute if data will not be found in cache + * @param {number=} absoluteExpiration - An absolute expiration time in seconds. This parameter is optional. + * @returns {Promise|*} + */ + // eslint-disable-next-line no-unused-vars + getOrDefault(key, getFunc, absoluteExpiration) { + throw new AbstractMethodError(); + } } -LangUtils.inherits(DataCacheStrategy, ConfigurationStrategy); -/** - * Sets a key value pair in cache. - * @abstract - * @param {string} key - A string that represents the key of the cached value - * @param {*} value - The value to be cached - * @param {number=} absoluteExpiration - An absolute expiration time in seconds. This parameter is optional. - * @returns {Promise|*} - */ -// eslint-disable-next-line no-unused-vars -DataCacheStrategy.prototype.add = function(key, value, absoluteExpiration) { - throw new AbstractMethodError(); -}; - -/** - * Removes a cached value. - * @abstract - * @param {string} key - A string that represents the key of the cached value to be removed - * @returns {Promise|*} - */ -// eslint-disable-next-line no-unused-vars -DataCacheStrategy.prototype.remove = function(key) { - throw new AbstractMethodError(); -}; -/** - * Flush all cached data. - * @abstract - * @returns {Promise|*} - */ -DataCacheStrategy.prototype.clear = function() { - throw new AbstractMethodError(); -}; -/** - * Gets a cached value defined by the given key. - * @param {string} key - * @returns {Promise|*} - */ -// eslint-disable-next-line no-unused-vars -DataCacheStrategy.prototype.get = function(key) { - throw new AbstractMethodError(); -}; -/** - * Gets data from cache or executes the defined function and adds the result to the cache with the specified key - * @param {string|*} key - A string which represents the key of the cached data - * @param {Function} getFunc - A function to execute if data will not be found in cache - * @param {number=} absoluteExpiration - An absolute expiration time in seconds. This parameter is optional. - * @returns {Promise|*} - */ -// eslint-disable-next-line no-unused-vars -DataCacheStrategy.prototype.getOrDefault = function(key, getFunc, absoluteExpiration) { - throw new AbstractMethodError(); -}; - -/** - * - * @param {ConfigurationBase} config - * @constructor - * - */ -function DefaultDataCacheStrategy(config) { - DefaultDataCacheStrategy.super_.bind(this)(config); - var NodeCache = require( 'node-cache' ); - var expiration = CACHE_ABSOLUTE_EXPIRATION; - var absoluteExpiration = LangUtils.parseInt(config.getSourceAt('settings/cache/absoluteExpiration')); - if (absoluteExpiration>0) { - expiration = absoluteExpiration; +class DefaultDataCacheStrategy extends DataCacheStrategy { + /** + * + * @param {import('@themost/common').ConfigurationBase} config + * @constructor + * + */ + constructor(config) { + super(config); + var NodeCache = require( 'node-cache' ); + var expiration = CACHE_ABSOLUTE_EXPIRATION; + var absoluteExpiration = LangUtils.parseInt(config.getSourceAt('settings/cache/absoluteExpiration')); + if (absoluteExpiration>0) { + expiration = absoluteExpiration; + } + this.rawCache = new NodeCache({ + stdTTL:expiration + }); } - this.rawCache = new NodeCache({ - stdTTL:expiration - }); -} -LangUtils.inherits(DefaultDataCacheStrategy, DataCacheStrategy); -/** - * Sets a key value pair in cache. - * @param {string} key - A string that represents the key of the cached value - * @param {*} value - The value to be cached - * @param {number=} absoluteExpiration - An absolute expiration time in seconds. This parameter is optional. - * @returns {Promise|*} - */ -// eslint-disable-next-line no-unused-vars -DefaultDataCacheStrategy.prototype.add = function(key, value, absoluteExpiration) { - var self = this; - return Q.Promise(function(resolve, reject) { - self.rawCache.set(key, value, absoluteExpiration, function(err) { - if (err) { + /** + * Sets a key value pair in cache. + * @param {string} key - A string that represents the key of the cached value + * @param {*} value - The value to be cached + * @param {number=} absoluteExpiration - An absolute expiration time in seconds. This parameter is optional. + * @returns {Promise|*} + */ + add(key, value, absoluteExpiration) { + var self = this; + return new Promise(function(resolve, reject) { + try { + self.rawCache.set(key, value, absoluteExpiration); + return resolve(); + } catch (err) { return reject(err); } - return resolve(); }); - }); -}; + } -/** - * Gets a cached value defined by the given key. - * @param {string} key - * @returns {Promise|*} - */ -// eslint-disable-next-line no-unused-vars -DefaultDataCacheStrategy.prototype.get = function(key) { - var self = this; - return Q.Promise(function(resolve, reject) { - self.rawCache.get(key, function(err, res) { - if (err) { + /** + * Gets a cached value defined by the given key. + * @param {string} key + * @returns {Promise|*} + */ + get(key) { + var self = this; + return new Promise(function(resolve, reject) { + try { + var res = self.rawCache.get(key); + return resolve(res); + } catch (err) { return reject(err); } - return resolve(res[key]); }); - }); -}; + } -/** - * Removes a cached value. - * @param {string} key - A string that represents the key of the cached value to be removed - * @returns {Promise|*} - */ -// eslint-disable-next-line no-unused-vars -DefaultDataCacheStrategy.prototype.remove = function(key) { - var self = this; - return Q.Promise(function(resolve, reject) { - self.rawCache.del(key, function(err, count) { - if (err) { + /** + * Removes a cached value. + * @param {string} key - A string that represents the key of the cached value to be removed + * @returns {Promise|*} + */ + remove(key) { + var self = this; + return new Promise(function(resolve, reject) { + try { + self.rawCache.del(key); + return resolve(); + } catch (err) { return reject(err); } - return resolve(count); }); - }); -}; - -/** - * Flush all cached data. - * @returns {Promise|*} - */ -DefaultDataCacheStrategy.prototype.clear = function() { - var self = this; - return Q.Promise(function(resolve, reject) { - try { - self.rawCache.flushAll(); - } catch (err) { - return reject(err); - } - return resolve(); - }); -}; - -DefaultDataCacheStrategy.prototype.finalize = function() { - var self = this; - return self.clear().then(function() { - // destroy timer - if (self.rawCache && typeof self.rawCache._killCheckPeriod === 'function') { - self.rawCache._killCheckPeriod(); - } - }); -} + } -/** - * Gets data from cache or executes the defined function and adds the result to the cache with the specified key - * @param {string|*} key - A string which represents the key of the cached data - * @param {Function} getFunc - A function to execute if data will not be found in cache - * @param {number=} absoluteExpiration - An absolute expiration time in seconds. This parameter is optional. - * @returns {Promise|*} - */ -// eslint-disable-next-line no-unused-vars -DefaultDataCacheStrategy.prototype.getOrDefault = function(key, getFunc, absoluteExpiration) { - var self = this; - return Q.Promise(function(resolve, reject) { - //try to get from cache - self.rawCache.get(key, function(err, result) { - if (err) { + /** + * Flush all cached data. + * @returns {Promise|*} + */ + clear() { + var self = this; + return new Promise(function(resolve, reject) { + try { + self.rawCache.flushAll(); + } catch (err) { return reject(err); } - else if (typeof result !== 'undefined' && hasOwnProperty(result, key)) { - return resolve(result[key]); + return resolve(); + }); + } + + finalize() { + var self = this; + return self.clear().then(function() { + // destroy timer + if (self.rawCache) { + self.rawCache.close(); } - else { - try { - //execute function and validate promise - var source = getFunc(); - Args.check(typeof source !== 'undefined' && typeof source.then === 'function', 'Invalid argument. Expected a valid promise.'); - return source.then(function (res) { - if (_.isNil(res)) { - return resolve(); - } - return self.rawCache.set(key, res, absoluteExpiration, function (err) { - if (err) { - return reject(err); - } - return resolve(res); - }); - }) - .catch(function(err) { + }); + } + + /** + * Gets data from cache or executes the defined function and adds the result to the cache with the specified key + * @param {string|*} key - A string which represents the key of the cached data + * @param {Function} getFunc - A function to execute if data will not be found in cache + * @param {number=} absoluteExpiration - An absolute expiration time in seconds. This parameter is optional. + * @returns {Promise|*} + */ + getOrDefault(key, getFunc, absoluteExpiration) { + var self = this; + return new Promise(function(resolve, reject) { + //try to get from cache + try { + if (self.rawCache.has(key)) { + return resolve(self.rawCache.get(key)); + } + var source = getFunc(); + Args.check(typeof source !== 'undefined' && typeof source.then === 'function', 'Invalid argument. Expected a valid promise.'); + void source.then(function (res) { + self.rawCache.set(key, res, absoluteExpiration); + return resolve(res); + }).catch(function(err) { return reject(err); }); - } - catch(err) { - return reject(err); - } + + } catch (err) { + return reject(err); } }); - }); -}; + } +} module.exports = { DataCacheStrategy, diff --git a/data-permission.js b/data-permission.js index 0230215..71bab52 100644 --- a/data-permission.js +++ b/data-permission.js @@ -894,7 +894,7 @@ DataPermissionEventListener.prototype.beforeExecute = function (event, callback) var cancel = false, assigned = false, entity = new QueryEntity(model.viewAdapter), expand = null, perms1 = new QueryEntity(permissions.viewAdapter).as(permissions.viewAdapter + event.query.$lastIndex.toString()), expr = null; async.eachSeries(privileges, function (item, cb) { - if (cancel) { + if (assigned) { return cb(); } try { diff --git a/package-lock.json b/package-lock.json index ae5e272..228da0c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@themost/data", - "version": "2.16.0", + "version": "2.16.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@themost/data", - "version": "2.16.0", + "version": "2.16.1", "license": "BSD-3-Clause", "dependencies": { "@themost/events": "^1.0.5", @@ -14,7 +14,7 @@ "async": "^2.6.4", "lodash": "^4.17.21", "moment": "^2.29.4", - "node-cache": "^1.1.0", + "node-cache": "^5.1.2", "pluralize": "^7.0.0", "q": "^1.4.1", "sprintf-js": "^1.1.2", @@ -3862,6 +3862,15 @@ "wrap-ansi": "^7.0.0" } }, + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, "node_modules/co": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", @@ -7793,14 +7802,15 @@ "dev": true }, "node_modules/node-cache": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/node-cache/-/node-cache-1.1.0.tgz", - "integrity": "sha512-3q6GeGOZuI+yeZzM8IV9pjzEXg5v8/w6WfW2uIunDnacv9mDNBlVcUdbJGL2sr8aG7dP7Cw1KApnEDAk9poR8g==", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/node-cache/-/node-cache-5.1.2.tgz", + "integrity": "sha512-t1QzWwnk4sjLWaQAS8CHgOJ+RAfmHpxFWmc36IWTiWHQfs0w5JDMBS1b1ZxQteo0vVVuWJvIUKHDkkeK7vIGCg==", + "license": "MIT", "dependencies": { - "underscore": "*" + "clone": "2.x" }, "engines": { - "node": ">= 0.4.6" + "node": ">= 8.0.0" } }, "node_modules/node-fetch": { @@ -9257,11 +9267,6 @@ "node": ">=4.2.0" } }, - "node_modules/underscore": { - "version": "1.13.6", - "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.13.6.tgz", - "integrity": "sha512-+A5Sja4HP1M08MaXya7p5LvjuM7K6q/2EaC0+iovj/wOcMsTzMvDFbasi/oSapiwOlt252IqsKqPjCl7huKS0A==" - }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz", diff --git a/package.json b/package.json index 93c12ea..4b14267 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@themost/data", - "version": "2.16.0", + "version": "2.16.1", "description": "MOST Web Framework Codename Blueshift - Data module", "main": "index.js", "scripts": { @@ -31,7 +31,7 @@ "async": "^2.6.4", "lodash": "^4.17.21", "moment": "^2.29.4", - "node-cache": "^1.1.0", + "node-cache": "^5.1.2", "pluralize": "^7.0.0", "q": "^1.4.1", "sprintf-js": "^1.1.2", diff --git a/spec/DataCacheStrategy.spec.ts b/spec/DataCacheStrategy.spec.ts new file mode 100644 index 0000000..40d2db8 --- /dev/null +++ b/spec/DataCacheStrategy.spec.ts @@ -0,0 +1,118 @@ +import {resolve} from 'path'; +import {DataCacheStrategy, DataContext} from '../index'; +import {TestApplication} from './TestApplication'; + +describe('DataCacheStrategy', () => { + let app: TestApplication; + let context: DataContext; + beforeAll(async () => { + app = new TestApplication(resolve(__dirname, 'test2')); + context = app.createContext(); + }); + afterAll(async () => { + await context.finalizeAsync(); + await app.finalize(); + }); + + afterEach(async () => { + const cache = app.getConfiguration().getStrategy(DataCacheStrategy); + await cache.clear(); + }) + + it('should get cached item', async () => { + const cache = app.getConfiguration().getStrategy(DataCacheStrategy); + expect(cache).toBeTruthy(); + let value = await cache.get<{message: string}>('test-key'); + expect(value).toBeUndefined(); + await cache.add('test-key', { + message: 'Hello World' + }); + value = await cache.get<{message: string}>('test-key'); + expect(value).toBeTruthy(); + expect(value.message).toBe('Hello World'); + }); + + it('should remove cached item', async () => { + const cache = app.getConfiguration().getStrategy(DataCacheStrategy); + expect(cache).toBeTruthy(); + let value = await cache.get('test-key'); + expect(value).toBeUndefined(); + await cache.add('test-key', { + message: 'Hello World' + }); + await cache.remove('test-key'); + value = await cache.get('test-key'); + expect(value).toBeUndefined(); + }); + + it('should use absolute expiration', async () => { + const cache = app.getConfiguration().getStrategy(DataCacheStrategy); + expect(cache).toBeTruthy(); + await cache.add('test-key', { + message: 'Hello World' + }, 1); + let value = await cache.get('test-key'); + expect(value).toBeTruthy(); + // wait for 2 seconds + await new Promise((resolve) => setTimeout(() => { + return resolve(void 0); + }, 2000)); + value = await cache.get('test-key'); + expect(value).toBeUndefined(); + }); + + it('should clear cache', async () => { + const cache = app.getConfiguration().getStrategy(DataCacheStrategy); + expect(cache).toBeTruthy(); + await cache.add('test-key-1', { + message: 'Hello World' + }); + await cache.add('test-key-2', { + message: 'Hello World!' + }); + let value = await cache.get('test-key-1'); + expect(value).toBeTruthy(); + await cache.clear(); + value = await cache.get('test-key-1'); + expect(value).toBeUndefined(); + value = await cache.get('test-key-2'); + expect(value).toBeUndefined(); + }); + + it('should get default value', async () => { + const cache = app.getConfiguration().getStrategy(DataCacheStrategy); + await cache.getOrDefault<{ message: string }>('test-key-1', async () => { + return { + message: 'Hello World' + } + }); + const value = await cache.get<{message: string}>('test-key-1'); + expect(value).toBeTruthy(); + expect(value.message).toEqual('Hello World'); + }); + + it('should get default null value', async () => { + const cache = app.getConfiguration().getStrategy(DataCacheStrategy); + const getValue: () => Promise = async () => { + return null; + }; + await cache.getOrDefault('test-key-1', getValue); + let value = await cache.get('test-key-1'); + expect(value).toBeNull(); + }); + + it('should get default value once', async () => { + const cache = app.getConfiguration().getStrategy(DataCacheStrategy); + let counter = 0; + await cache.getOrDefault('test-key-1', async () => { + counter++; + return counter; + }); + let value = await cache.get('test-key-1'); + expect(value).toEqual(1); + counter++; + value = await cache.get('test-key-1'); + expect(value).toEqual(1); + }); + +}); diff --git a/spec/DataModel.filter.spec.ts b/spec/DataModel.filter.spec.ts index 18ec330..1658126 100644 --- a/spec/DataModel.filter.spec.ts +++ b/spec/DataModel.filter.spec.ts @@ -1,7 +1,5 @@ import { TestApplication2 } from './TestApplication'; import { DataContext, SchemaLoaderStrategy } from '../index'; -import { resolve } from 'path'; -const moment = require('moment'); describe('DataModel.filter', () => { let app: TestApplication2; @@ -12,7 +10,7 @@ describe('DataModel.filter', () => { return done(); }); afterAll(async () => { - await context.finalize(); + await context.finalizeAsync(); await app.finalize(); }); it('should use $filter param', async () => { diff --git a/spec/DataPrivileges.spec.ts b/spec/DataPrivileges.spec.ts index f35f5c7..49e1c5d 100644 --- a/spec/DataPrivileges.spec.ts +++ b/spec/DataPrivileges.spec.ts @@ -1,6 +1,6 @@ -import { resolve } from 'path'; import { DataContext } from '../index'; import { TestApplication, TestApplication2 } from './TestApplication'; +import { TestUtils } from "./adapter/TestUtils"; describe('Permissions', () => { let app: TestApplication; @@ -14,7 +14,7 @@ describe('Permissions', () => { await app.finalize(); }); - it('should validate read access', async () => { + it('should read access', async () => { const Products = context.model('Product'); const items = await Products.getItems(); expect(Array.isArray(items)).toBeTruthy(); @@ -65,43 +65,89 @@ describe('Permissions', () => { }); it('should validate update access', async () => { - await context.model('ActionStatusType').getItems() - const Products = context.model('Product'); - const user = { - name: 'margaret.davis@example.com' - } - // add user to contributors - const group = await context.model('Group').where('name').equal('Contributors').getTypedItem(); - expect(group).toBeTruthy(); - const members = group.property('members').silent(); - await members.insert(user); - Object.assign(context, { - user + await TestUtils.executeInTransaction(context, async () => { + await context.model('ActionStatusType').getItems() + const Products = context.model('Product'); + const user = { + name: 'margaret.davis@example.com' + } + // add user to contributors + const group = await context.model('Group').where('name').equal('Contributors').getTypedItem(); + expect(group).toBeTruthy(); + const members = group.property('members').silent(); + await members.insert(user); + Object.assign(context, { + user + }); + const user1 = await context.model('User').find(user) + .expand('groups').silent().getItem(); + expect(user1).toBeTruthy(); + expect(user1.groups).toBeTruthy(); + const orderedItem = await Products.where('name').equal( + 'Lenovo Yoga 2 Pro' + ).getItem(); + expect(orderedItem).toBeTruthy(); + const customer = await context.model('People').where('user/name') + .equal('christina.ali@example.com') + .getItem(); + expect(customer).toBeTruthy(); + const agent = await context.model('People').where('user/name') + .equal(user.name) + .getItem(); + expect(agent).toBeTruthy(); + const OrderActions = context.model('OrderAction'); + let newAction = { + orderedItem, + customer, + agent + }; + await expect(OrderActions.save(newAction)).resolves.toBeTruthy(); }); - const user1 = await context.model('User').find(user) - .expand('groups').silent().getItem(); - expect(user1).toBeTruthy(); - expect(user1.groups).toBeTruthy(); - const orderedItem = await Products.where('name').equal( - 'Lenovo Yoga 2 Pro' - ).getItem(); - expect(orderedItem).toBeTruthy(); - const customer = await context.model('People').where('user/name') - .equal('christina.ali@example.com') - .getItem(); - expect(customer).toBeTruthy(); - const agent = await context.model('People').where('user/name') - .equal(user.name) - .getItem(); - expect(agent).toBeTruthy(); - const OrderActions = context.model('OrderAction'); - let newAction = { - orderedItem, - customer, - agent - }; - await expect(OrderActions.save(newAction)).resolves.toBeTruthy(); + }); + it('should validate read access based based on self permissions', async () => { + await TestUtils.executeInTransaction(context, async () => { + await context.model('ActionStatusTypes').getItems(); + const OrderActions = context.model('OrderActions'); + // set context user + const user = { + name: 'margaret.davis@example.com' + }; + Object.assign(context, { + user + }); + const group = await context.model('Group').where('name').equal('Contributors').getTypedItem(); + expect(group).toBeTruthy(); + const members = group.property('members').silent(); + await members.insert(user); + Object.assign(context, { + user + }); + let items = await OrderActions.getItems(); + expect(items.length).toEqual(0); + const Products = context.model('Products'); + const orderedItem = await Products.where('name').equal( + 'Lenovo Yoga 2 Pro' + ).getItem(); + expect(orderedItem).toBeTruthy(); + const customer = await context.model('People').where('user/name') + .equal('christina.ali@example.com') + .getItem(); + expect(customer).toBeTruthy(); + const agent = await context.model('People').where('user/name') + .equal(user.name) + .getItem(); + expect(agent).toBeTruthy(); + let newAction = { + orderedItem, + customer, + agent + }; + await expect(OrderActions.save(newAction)).resolves.toBeTruthy(); + items = await OrderActions.getItems(); + expect(items.length).toBeGreaterThan(0); + + }); }); }); diff --git a/spec/test2/config/models/Product.json b/spec/test2/config/models/Product.json index 9b3de9c..2cb3275 100644 --- a/spec/test2/config/models/Product.json +++ b/spec/test2/config/models/Product.json @@ -5,7 +5,7 @@ "hidden": false, "sealed": false, "abstract": false, - "version": "2.1", + "version": "2.2", "inherits": "Thing", "caching":"conditional", "fields": [