UNPKG

als-store

Version:

Library for streamlined file management and advanced data caching, featuring intelligent file searching, dynamic cache control, and flexible file operations

343 lines (310 loc) 13.9 kB
const assert = require('node:assert') const { describe, it, beforeEach, afterEach } = require('node:test') const Store = require('../lib/store') const { join } = require('node:path') const File = require('../lib/file') const { ensureDirSync, removeSync, existsSync, readFileSync } = require('fs-extra') const { readdir, readdirSync } = require('als-readdir') const delay = (ms, toResolve) => new Promise((resolve) => { setTimeout(() => { resolve(toResolve) }, ms); }); function generateRandomInteger(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; } const rootPath = join(__dirname, '..') describe('basic tests', () => { it('constructor', () => { assert.throws(() => new Store({ dirPath: 'aa' })) assert.throws(() => new Store({ dirPath: 'a'.repeat(256) })) const dirPath = rootPath assert.throws(() => new Store({ dirPath, maxAge: 'fgh' })) assert.throws(() => new Store({ dirPath, maxActiveAge: 500 })) // assert.throws(() => new Store({dirPath,schema:''})) }) it('async get()', async () => { const store = new Store({ dirPath: __dirname }) const { results, errors } = await store.get() const files = readdirSync(__dirname).filter(t => t !== 'test') assert(results.length === files.length) assert(errors.length === 0) const names = results.map(({ name }) => name) assert(names.includes('store.test.js')) assert.deepEqual(Object.keys(results[0].stats), File.stats, 'All files has stats') }) it('async first()', async () => { const store = new Store({ dirPath: __dirname }) const file = await store.first() assert(file.name.endsWith('test.js')) }) it('values()', async () => { const store = new Store({ dirPath: __dirname }) const { results: r1 } = await store.get() assert(r1[0].value === null) const { results: r2 } = await store.values().get() assert(typeof r2[0].value === 'string') }) it('dir(dir)', async () => { // TODO check param validation const store = new Store({ dirPath: rootPath }) const { results: r1 } = await store.dir('node_modules/als-schema').get() const names = r1.map(({ name }) => name) assert(names.includes('package.json')) assert.rejects(async () => await store.dir('kjdhfkjgh').get(), 'trows error if no such dir') }) it('filter(fn)', async () => { // TODO check param validation const store = new Store({ dirPath: rootPath }) let { results: all } = await store.get() all = [...all] const mdFilter = file => file.name.endsWith('.md') let { results: mdFiles } = await store.filter(mdFilter).get() assert(all.length > mdFiles.length) const index = generateRandomInteger(0, mdFiles.length - 1) assert(mdFiles[index].name.endsWith('.md')) mdFiles = [...mdFiles] const smallFilter = file => file.stats.size < 1024 let { results: smallMdFiles } = await store.filter(mdFilter, smallFilter).get() assert(smallMdFiles.length < mdFiles.length) const index1 = generateRandomInteger(0, smallMdFiles.length - 1) assert(mdFiles.includes(smallMdFiles[index1])) smallMdFiles = [...smallMdFiles] let { results: smallMdFiles1 } = await store.filter(mdFilter).filter(smallFilter).get() assert(smallMdFiles.length === smallMdFiles1.length) }) it('filter by params', async () => { const schema = ['name', 'ext', 'ext1', 'ext2', 'ext3'] const store = new Store({ dirPath: rootPath, schema }) const testFilter = ({ params }) => params.ext === 'test' && params.ext1 === 'js' const { results, errors } = await store.filter(testFilter).get() const names = results.map(({ name }) => name) assert(names[0].endsWith('test.js')) const { results: pljson } = await store.filter(({ params }) => params.ext === 'package-lock' && params.ext1 === 'json').get() assert(pljson.length === 1) }) it('limit and skip', async () => { const store = new Store({ dirPath: rootPath }) let { results: all } = await store.get() all = [...all] let { results: limited } = await store.limit(10).get() assert(limited.length === 10) let { results: skiped } = await store.skip(10).get() assert(all.length - 10 === skiped.length) assert(skiped[0] === all[10]) let { results: skipedAndLimited } = await store.skip(10).limit(10).get() assert(skipedAndLimited.length === 10) assert(skiped[0] === skipedAndLimited[0]) }) it('level', async () => { const store = new Store({ dirPath: rootPath }) let { results: all } = await store.get() all = [...all] let { results: level3 } = await store.level(3).get() assert(all.length > level3.length) level3 = [...level3] let { results: level2 } = await store.level(2).get() assert(level3.length > level2.length) let { results: level0 } = await store.level(0).get() let files = readdirSync(rootPath,{withFileTypes:true}).filter(item => item.isFile()) files = files.filter(file => file.isFile()) assert(level0.length === files.length) }) }) describe('sort and desc', () => { let dirPath = rootPath it('sort and desc', async () => { const store = new Store({ dirPath }) let { results: all } = await store.get() all = [...all] let { results: size } = await store.sort('-stats.size').get() assert(all[0].stats.size < size[0].stats.size) let { results: sizeDesc } = await store.sort('stats.size').desc().get() assert(all[0].stats.size < sizeDesc[0].stats.size) sizeDesc = [...sizeDesc] let { results: sizeDesc1 } = await store.sort('-stats.size').get() assert(sizeDesc[0].stats.size === sizeDesc1[0].stats.size) let { results: byName } = await store.sort('name').get() assert(all[0].name !== byName[0].name) assert(byName[0].name.match(/^\.|\_/) !== null) }) it('sort by params', async () => { const schema = ['name', 'ext', 'ext1', 'ext2', 'ext3'] const store = new Store({ dirPath, schema }) let { results: all } = await store.get() all = [...all] const { results: sortedExt } = await store.sort('params.ext').get() assert(all[0].name !== sortedExt[0].name) const { results: sortedExt1 } = await store.sort('params.ext1').get() assert(sortedExt1[0].params.ext1 === undefined) const { results: sortedExt1Desc } = await store.sort('-params.ext1').get() assert(sortedExt1Desc[0].params.ext1 !== undefined) }) it('sort with filter', async () => { const store = new Store({ dirPath }) let { results: sorted } = await store.sort('name').get() sorted = [...sorted] const filter = ({ name }) => !name.startsWith('.') && !name.startsWith('_') let { results: filteredAndSorted } = await store.filter(filter).sort('name').get() assert(sorted[0].name !== filteredAndSorted[0].name) // sort should apply after filter }) it('sort with skip and limit', async () => { const store = new Store({ dirPath }) let { results: sorted } = await store.sort('name').get() sorted = [...sorted] let { results: skiped } = await store.skip(10).sort('name').get() assert(sorted[10].name === skiped[0].name) // skip and limit should apply after sort }) }) describe('create(name)', () => { const store = new Store({ dirPath: __dirname }) it('validation', () => { assert.throws(() => store.create(5)) assert.throws(() => store.create('a'.repeat(256))) }) it('create simple file', () => { const file = store.create('test') assert(file instanceof File) assert(file.cacheDir['test'] === file) }) it('create file in directory', () => { const dir = 'some/test' const file = store.dir(dir).create('test') assert(file.path === join(__dirname, dir)) }) }) describe('remove and rename dir,maxAge', () => { const dirPath = join(__dirname, 'test') const store = new Store({ dirPath }) beforeEach(() => ensureDirSync(dirPath)) afterEach(() => removeSync(dirPath)) async function createStructure(maxD = 5, maxI = 5) { let path = [] for (let d = 0; d < maxD; d++) { path.push('level' + d) for (let i = 0; i < maxI; i++) { const file = store.dir(path.join('/')).create(`test${d}_${i}.txt`) file.value = `${d}_${i}` await file.save() } } } function inCache(results) { const index = generateRandomInteger(0, results.length - 1) const file = results[index] if (!File.map.has(file)) return false const { path, name } = file return File.getDir(path).cache[name] === file } it('remove files by filter', async () => { for (let i = 0; i < 10; i++) { const file = store.create(`test${i}.txt`) await file.save() } const { results: all } = await store.get() assert(all.length === 10) const filter = ({ name }) => Number(name.split('.')[0].slice(-1)) > 5 await store.filter(filter).remove() const { results: afterRemove } = await store.get() assert(afterRemove.length === 6) // 6,7,8,9 removed await store.remove() const { results: shouldRemoveAll } = await store.get() assert(shouldRemoveAll.length === 0) }) it('remove directory', async () => { const file = store.create(`test.txt`) for (let d = 0; d < 3; d++) { await file.save() for (let i = 0; i < 5; i++) { const file = store.dir(`dir${d}`).create(`test${i}.txt`) await file.save() } } const { results: all } = await store.get() // 16 const fileInDir = await store.dir('dir0').first() assert(fileInDir.cacheDir[fileInDir.name] === fileInDir) const allN = all.length assert(inCache(all)) assert(existsSync(join(dirPath, 'dir0'))) await store.dir('dir0').remove() assert(!existsSync(join(dirPath, 'dir0'))) const { results: dir0Removed } = await store.get() // 11 assert(dir0Removed.length === allN - 5) assert(fileInDir.cacheDir[fileInDir.name] !== fileInDir) }) it('remove nested directory and by level', async () => { await createStructure() const { results: all } = await store.get() assert(all.length === 25) await store.level(1).remove() const { results: level1Removed } = await store.get() assert(level1Removed.length === 20) await store.level(3).remove() const { results: level3Removed } = await store.get() assert(level3Removed.length === 10) }) it('rename dir', async () => { await createStructure() await store.get() // for adding files to cache const oldName = 'level0', newName = 'renamed' const oldPath = join(dirPath, oldName) const newPath = join(dirPath, newName) assert(existsSync(oldPath)) assert(File.getDir(oldPath).cache !== undefined) assert(File.getDir(newPath).cache === undefined) await store.renameDir(oldName, newName) assert(existsSync(newPath)) assert(File.getDir(oldPath).cache === undefined) assert(File.getDir(newPath).cache !== undefined) assert(File.getDir(newPath).cache['test0_1.txt'].path.endsWith(newName)) }) it('rename nested', async () => { await createStructure() const dir = 'level0/level1/level2' const oldPath = join(dirPath, dir, 'level3') const newPath = join(dirPath, dir, 'renamed') assert(existsSync(oldPath)) assert(!existsSync(newPath)) assert(File.getDir(oldPath).cache !== undefined) assert(File.getDir(newPath).cache === undefined) await store.dir(dir).renameDir('level3', 'renamed') assert(!existsSync(oldPath)) assert(existsSync(newPath)) assert(File.getDir(oldPath).cache === undefined) assert(File.getDir(newPath).cache !== undefined) assert(File.getDir(newPath).cache['test3_4.txt'].path.endsWith('renamed')) }) it('validate maxAge and maxActiveAge', () => { assert.throws(() => new Store({ dirPath, maxAge: -1 })) assert.throws(() => new Store({ dirPath, maxAge: '' })) }) it('maxAge', async () => { await createStructure(10,10) store.maxAge = 500 const { results: all } = await store.get() assert(all.length === 100) const file = all[generateRandomInteger(0,49)] const file1 = all[generateRandomInteger(50,99)] await delay(500) const { results: maxAged } = await store.get() assert(maxAged.length === 0) assert(store.toRemove.length === 50) await delay(200) // give time to delete outdated assert(existsSync(join(file.path,file.name)) === false) assert(existsSync(join(file1.path,file1.name))) await store.get() await delay(200) // give time to delete outdated assert(existsSync(join(file1.path,file1.name)) === false) store.maxAge = undefined }) it('maxActiveAge', async () => { await createStructure(1,10) store.maxActiveAge = 500 const { results: all } = await store.get() assert(all.length === 10) const file = all[generateRandomInteger(0,9)] await file.getValue() await file.save() await delay(300) const { results: maxAged } = await store.get() assert(maxAged.length === 1) }) })