UNPKG

node-persist

Version:

Super-easy (and fast) persistent data structures in Node.js, modeled after HTML5 localStorage

401 lines (336 loc) 12.3 kB
const path = require('path'); const fs = require('fs'); const assert = require('chai').assert; const rmdir = require('rimraf'); const pkg = require('../package.json'); const LocalStorage = require('../src/local-storage'); const nodePersist = require('../src/node-persist'); const TEST_BASE_DIR = path.join(__dirname, '/storage-dirs'); const rand = function (prefix) { return (prefix ? prefix + '-' : '') + (+ new Date()) + '-' + Math.floor(Math.random() * 1000); }; const randDir = function () { return path.join(TEST_BASE_DIR, '/' + rand()); }; process.on('unhandledRejection', (reason, p) => { console.error('Unhandled Rejection at: Promise', p); }); describe('node-persist ' + pkg.version + ' tests:', async function() { before(function(done) { fs.mkdir(TEST_BASE_DIR, {recursive: true}, done); }); after(function(done) { rmdir(TEST_BASE_DIR, done); }); describe('instances', function() { let dir1, dir2, storage1, storage2, storage11, storage22, storageSync; beforeEach(async function () { dir1 = randDir(); storage1 = nodePersist.create({ dir: dir1 }); dir2 = randDir(); storage2 = nodePersist.create({ dir: dir2 }); await storage1.init(); await storage2.init(); await storage1.setItem('s1', 1111); await storage2.setItem('s2', {a: 1}); storage11 = nodePersist.create({ dir: dir1 }); storage22 = nodePersist.create({ dir: dir2 }); dirSync = randDir(); storageSync = nodePersist.create({ dir: dirSync }); }); it('should create 2 new different instances of LocalStorage', async function() { assert.ok(storage1 instanceof LocalStorage); assert.ok(storage2 instanceof LocalStorage); assert.ok(storageSync instanceof LocalStorage); assert.ok(storage1 != storage2); }); it('should use the 2 previous dirs and init correctly', async function() { await storage11.init(); assert.equal(await storage11.getItem('s1'), 1111, `write/read didn't work`); await storage22.init(); let value = await storage2.getItem('s2'); assert.deepEqual(value, {a: 1}, `write/read didn't work`); }); it('should initSync properly', function() { storageSync.initSync(); }); it('should storageSync set and get async properly', async function() { storageSync.initSync(); await storageSync.setItem('item9977', 'hello'); assert.equal(await storageSync.getItem('item9977'), 'hello', `write/read didn't work`); }); it('should create the default instance of LocalStorage async and use it', async function() { await nodePersist.init({dir: randDir()}); assert.ok(nodePersist.defaultInstance instanceof LocalStorage); await nodePersist.setItem('item8877', 'hello'); assert.equal(await nodePersist.getItem('item8877'), 'hello', `write/read didn't work`); }); it('should create the default instance of LocalStorage sync and use it', async function() { nodePersist.initSync({dir: randDir()}); assert.ok(nodePersist.defaultInstance instanceof LocalStorage); await nodePersist.setItem('item8887', 'hello'); assert.equal(await nodePersist.getItem('item8887'), 'hello', `write/read didn't work`); }); it('should create a default instance', async function() { let dir = randDir(); let options = await nodePersist.init({dir: dir}); assert.equal(options.dir, dir, `Options don't match`); }); it('should create a default instance but initSync', async function() { let dir = randDir(); let options = nodePersist.initSync({dir: dir}); assert.equal(options.dir, dir, `Options don't match`); }); }); describe('operations', function() { let options = { dir: randDir(), // logging: true, writeQueue: true, writeQueueWriteOnlyLast: true }; let storage = nodePersist.create(); let items = { 'item1': 1, 'item2': 2, 'item3a': `3a`, 'item3b': `3b`, }; let itemKeys = Object.keys(items); const generatedItemsLength = 100; const generatedItemsParallel = 10; let generatedItems = {}; for (let i = 0; i < generatedItemsLength; i++) { generatedItems['generated' + i] = i } let generatedItemsKeys = Object.keys(generatedItems); describe('general items operations', function() { it('should init()', async function() { await storage.init(options); assert.equal(storage.options.dir, options.dir); assert.ok(fs.existsSync(options.dir)); }); it('should initSync()', function() { storage.initSync(options); assert.equal(storage.options.dir, options.dir); assert.ok(fs.existsSync(options.dir)); }); it('should setItem()', async function() { await storage.setItem('item1', items.item1); assert.equal(await storage.getItem('item1'), items.item1); }); it(`should write ${generatedItemsLength * generatedItemsParallel} times, with writeQueueWriteOnlyLast=true, in parallel setItem() then read them back`, async function() { let writePromises = []; for (let i = 0; i < generatedItemsParallel; i++) { writePromises = writePromises.concat(generatedItemsKeys.map(k => storage.setItem(k, i < generatedItemsParallel - 1 ? generatedItems[k] * i : generatedItems[k]))) } await Promise.all(writePromises); let readPromises = generatedItemsKeys.map(async (k) => { return assert.equal(await storage.getItem(k), generatedItems[k]) }); await Promise.all(readPromises); }); it(`should write ${generatedItemsLength * generatedItemsParallel} times, with writeQueueWriteOnlyLast=false, in parallel setItem() then read them back`, async function() { this.timeout(30000) storage.setOptions({ ...options, writeQueueWriteOnlyLast: false }); let writePromises = []; for (let i = 0; i < generatedItemsParallel; i++) { writePromises = writePromises.concat(generatedItemsKeys.map(k => storage.setItem(k, i < generatedItemsParallel - 1 ? generatedItems[k] * i : generatedItems[k]))) } await Promise.all(writePromises); let readPromises = generatedItemsKeys.map(async (k) => { return assert.equal(await storage.getItem(k), generatedItems[k]) }); await Promise.all(readPromises); storage.setOptions({ ...options, writeQueueWriteOnlyLast: true }); }); it('should setItem() with ttl as a Date Object', async function() { let now = +new Date(); let ttl = 10000; let in10sDate = new Date(now + ttl); await storage.setItem('item3b', items.item3b, { ttl: in10sDate }); let datum = await storage.getDatum('item3b'); assert.approximately(datum.ttl, now + ttl, 350); }); it('should updateItem()', async function() { let ttl = 10000; let now = +new Date(); await storage.setItem('item3a', items.item3a, { ttl }); await storage.setItem('item3b', items.item3b, { ttl }); await storage.updateItem('item3a', items.item3b); let datum = await storage.getDatum('item3a'); assert.approximately(datum.ttl, now + ttl, 350); assert.equal(datum.value, items.item3b); }); it('should getItem()', async function() { let value = await storage.getItem('item1'); assert.equal(value, items.item1); }); it('should getRawDatum()', async function() { let value = await storage.getRawDatum('item1'); assert.equal(value, JSON.stringify({key: 'item1', value: items.item1})); }); it('should valuesWithKeyMatch(String)', async function() { await storage.setItem('item2', items.item2); let value = await storage.valuesWithKeyMatch('item'); assert.equal(value.length, itemKeys.length); }); it('should valuesWithKeyMatch(RegEx)', async function() { let value = await storage.valuesWithKeyMatch(/item/); assert.equal(value.length, itemKeys.length); }); it('should removeItem()', async function() { await storage.removeItem('item1'); assert.equal(await storage.getItem('item1'), undefined); }); }); describe('general global operations', function() { let options = { dir: randDir() }; let storage = nodePersist.create(); beforeEach(async function() { await storage.init(options); await storage.setItem('item1', items.item1); await storage.setItem('item2', items.item2); }); afterEach(async function() { await storage.clear(); }); it('should keys()', async function() { assert.equal((await storage.keys()).length, 2); }); it('should length()', async function() { assert.equal((await storage.length()), 2); }); it('should values()', async function() { assert.equal((await storage.values()).length, 2); }); it('should clear()', async function() { await storage.clear(); assert.equal(await storage.getItem('item1'), undefined); assert.equal(await storage.getItem('item2'), undefined); }); }); }); describe('interval and ttl ', function() { this.timeout(5000); // increase the default mocha test timeout. it('should respect expired ttl and delete the items', async function() { let storage = nodePersist.create(); await storage.init({ dir: randDir(), ttl: 1000 // 1 second }); await storage.setItem('item1', 1); // wait 2 seconds, then try to read the file, should be undefined. await new Promise((resolve, reject) => { setTimeout(async function() { try { let value = await storage.getItem('item1'); assert.equal(value, undefined); resolve(); } catch (e) { reject(e); } }, 2000); }); }); it('should respect an expired different ttl per setItem and delete the items', async function() { this.timeout(10000); let storage = nodePersist.create(); await storage.init({ dir: randDir(), ttl: 1000 // 1 second, }); await storage.setItem('item1', 1, {ttl: 5000}); // wait 2 seconds, then try to read the item1 file, should still be there because we asked this one to live for 5 seconds, despite the default 1 second ttl await new Promise((resolve, reject) => { setTimeout(async function() { try { let value = await storage.getItem('item1'); assert.equal(value, 1); resolve(); } catch (e) { reject(e); } }, 2000); }); // wait 5.5 seconds, then try to read the item1 file, should be undefined await new Promise((resolve, reject) => { setTimeout(async function() { try { let value = await storage.getItem('item1'); assert.equal(value, undefined); resolve(); } catch (e) { reject(e); } }, 5500); }); }); it('should automatically delete expired items', async function() { this.timeout(10000); let storage = nodePersist.create(); await storage.init({dir: randDir(), expiredInterval: 3000}); storage.setItem('item1', 1, {ttl: 5000}); // wait 8 seconds, then check the keys, should be empty, item1 should've been deleted automatically based on the expiredInterval await new Promise((resolve, reject) => { setTimeout(async function() { try { let length = await storage.length(); assert.equal(length, 0); resolve(); } catch (e) { reject(e); } }, 7000); }); }); }); describe('Parsing errors', function() { it('should throw an error because of an invalid file in the storage dir', async function () { this.timeout(5000); let dir = randDir(); let storage = nodePersist.create(); // make sure the dir is there, and write a random file in there fs.mkdirSync(dir, {recursive: true}); fs.writeFileSync(dir + '/foo.bar', 'nothing that makes sense'); try { await storage.init({ dir: dir }); } catch (e) { assert.equal(true, /^\[node-persist]\[readFile]*does not look like a valid storage file/.test(e.message)); } }); it('should NOT throw an error because of an invalid file in the storage dir, because forgiveParseErrors=true', async function () { this.timeout(5000); let dir = randDir(); let storage = nodePersist.create(); // make sure the dir is there, and write a random file in there fs.mkdirSync(dir, {recursive: true}); fs.writeFileSync(dir + '/foo.bar', 'nothing that makes sense'); await storage.init({ dir: dir, forgiveParseErrors: true }); assert.equal(storage.options.dir, dir, `options.dir don't match`); }); }); });