import { Eltro as t, assert, spy } from 'eltro' import path from 'path' import crypto from 'crypto' import os from 'os' import fsSyncOriginal from 'fs' import fsPromisesOriginal from 'fs/promises' import Cache from '../index.mjs' import { fakeFs, fakeFsPromises, fakeFsSync } from './helper.mjs' let fsSync let fsPromises t.beforeEach(function() { fsSync = fakeFsSync() fsPromises = fakeFsPromises() }) function createCache(opts) { return new Cache(opts, fsSync, fsPromises) } t.describe('#constructor()', function() { t.test('uses default fs', function() { let cache = new Cache({}) assert.strictEqual(cache.fsSync, fsSyncOriginal) assert.strictEqual(cache.fsPromises, fsPromisesOriginal) }) t.test('should be able to override the fs', function() { let cache = createCache({}) assert.strictEqual(cache.fsSync, fsSync) assert.strictEqual(cache.fsPromises, fsPromises) }) t.test('comes with default options', function() { let cache = createCache({}) assert.ok(cache.id) assert.strictEqual(cache.parse_json, true) assert.strictEqual(cache.prefix, '') assert.strictEqual(cache.hash_alg, 'md5') assert.strictEqual(cache.cache_dir, path.join(os.tmpdir(), cache.id)) }) t.test('can overwrite options', function() { const assertHash = 'sha256' const assertDir = '/something/else' const assertPrefix = 'blabla' const assertParseJson = false let cache = createCache({ prefix: assertPrefix, hash_alg: assertHash, cache_dir: assertDir, parse_json: assertParseJson, }) assert.ok(cache.id) assert.strictEqual(cache.parse_json, assertParseJson) assert.strictEqual(cache.prefix, assertPrefix + '-') assert.strictEqual(cache.hash_alg, assertHash) assert.strictEqual(cache.cache_dir, assertDir) }) t.test('should create the directory by default', function() { assert.notOk(fsSync.mkdirSync.called) let cache = createCache({}) assert.ok(fsSync.mkdirSync.called) assert.strictEqual(fsSync.mkdirSync.firstCall[0], cache.cache_dir) assert.strictEqual(fsSync.mkdirSync.firstCall[1]?.recursive, true) }) t.test('should check if hash_alg is valid', function() { assert.throws(function() { createCache({ hash_alg: 'dafdsagasdgwa4e' }) }) }) }) t.describe('FSCache', function() { t.describe('#hash()', function() { t.test('should use cache hasher to hash string', function() { let cache = createCache({ hash_alg: 'sha256' }) assert.strictEqual(cache.hash('asdf'), crypto.hash('sha256', 'asdf')) cache = createCache({ hash_alg: 'md5' }) assert.strictEqual(cache.hash('asdf'), crypto.hash('md5', 'asdf')) }) }) t.describe('#_parseCacheData()', function() { t.test('should default parse as json', function() { let cache = createCache() let output = cache._parseCacheData('{"hello":"world"}') assert.strictEqual(typeof output, 'object') assert.strictEqual(output.hello, 'world') }) t.test('can be overwritten in options', function() { let cache = createCache({ parse_json: false }) let output = cache._parseCacheData('{"hello":"world"}') assert.strictEqual(typeof output, 'string') assert.strictEqual(output, '{"hello":"world"}') }) t.test('can be overwritten in parameter', function() { let cache = createCache() let output = cache._parseCacheData('{"hello":"world"}', { parse_json: false }) assert.strictEqual(typeof output, 'string') assert.strictEqual(output, '{"hello":"world"}') }) }) t.describe('#_parseSetData()', function() { t.test('should default stringify to json', function() { let cache = createCache() let output = cache._parseSetData({ hello: 'world' }) assert.strictEqual(typeof output, 'string') assert.strictEqual(output, '{"hello":"world"}') }) t.test('can be overwritten in options', function() { let cache = createCache({ parse_json: false }) let output = cache._parseSetData('Hello world') assert.strictEqual(typeof output, 'string') assert.strictEqual(output, 'Hello world') }) t.test('can be overwritten in parameter', function() { let cache = createCache() let output = cache._parseSetData('Hello world', { parse_json: false }) assert.strictEqual(typeof output, 'string') assert.strictEqual(output, 'Hello world') }) }) t.describe('#get()', function() { t.test('should call promise readFile and parse result', async function() { const assertKey = 'asdf1234' const assertContent = '{"hello":"world"}' const assertResult = { hello: 'world' } let cache = createCache() fsPromises.readFile.resolves(assertContent) cache._parseCacheData = spy().returns(assertResult) let output = await cache.get(assertKey) assert.strictEqual(output, assertResult) assert.ok(fsPromises.readFile.called) assert.strictEqual(fsPromises.readFile.firstCall[0], path.join(cache.cache_dir, cache.hash(assertKey))) assert.strictEqual(fsPromises.readFile.firstCall[1]?.encoding, 'utf8') assert.ok(cache._parseCacheData.called) assert.ok(cache._parseCacheData.firstCall[0], assertContent) }) t.test('should pass extra options to the parser', async function() { const assertOptions = { a: 1 } let cache = createCache() cache._parseCacheData = spy() await cache.get('asdf', null, assertOptions) assert.ok(cache._parseCacheData.called) assert.ok(cache._parseCacheData.firstCall[1], assertOptions) }) t.test('should support fallback value if file does not exist', async function() { const assertFallback = { a: 1 } let cache = createCache() fsPromises.readFile.rejects(new Error('asdf')) cache._parseCacheData = spy() let output = await cache.get('bla', assertFallback) assert.strictEqual(output, assertFallback) assert.notOk(cache._parseCacheData.called) }) t.test('parser error should propogate', async function() { const assertError = new Error('Hello') let cache = createCache() cache._parseCacheData = spy().throws(assertError) let err = await assert.isRejected(cache.get('asdf')) assert.strictEqual(err, assertError) }) }) t.describe('#getSync()', function() { t.test('should call sync readFile and parse result', function() { const assertKey = 'asdf1234' const assertContent = '{"hello":"world"}' const assertResult = { hello: 'world' } let cache = createCache() fsSync.readFileSync.returns(assertContent) cache._parseCacheData = spy().returns(assertResult) let output = cache.getSync(assertKey) assert.strictEqual(output, assertResult) assert.ok(fsSync.readFileSync.called) assert.strictEqual(fsSync.readFileSync.firstCall[0], path.join(cache.cache_dir, cache.hash(assertKey))) assert.strictEqual(fsSync.readFileSync.firstCall[1]?.encoding, 'utf8') assert.ok(cache._parseCacheData.called) assert.ok(cache._parseCacheData.firstCall[0], assertContent) }) t.test('should pass extra options to the parser', function() { const assertOptions = { a: 1 } let cache = createCache() cache._parseCacheData = spy() cache.getSync('asdf', null, assertOptions) assert.ok(cache._parseCacheData.called) assert.ok(cache._parseCacheData.firstCall[1], assertOptions) }) t.test('should support fallback value if file does not exist', function() { const assertFallback = { a: 1 } let cache = createCache() fsSync.readFileSync.throws(new Error('asdf')) cache._parseCacheData = spy() let output = cache.getSync('bla', assertFallback) assert.strictEqual(output, assertFallback) assert.notOk(cache._parseCacheData.called) }) t.test('parser error should propogate', async function() { const assertError = new Error('Hello') let cache = createCache() cache._parseCacheData = spy().throws(assertError) assert.throws(function() { cache.getSync('asdf') }, assertError) }) }) t.describe('#set()', function() { t.test('should call promise writeFile', async function() { const assertKey = 'asdf1234' const assertInput = { hello: 'world' } const assertContent = JSON.stringify(assertInput) let cache = createCache() cache._parseSetData = spy().returns(assertContent) await cache.set(assertKey, assertInput) assert.ok(fsPromises.writeFile.called) assert.strictEqual(fsPromises.writeFile.firstCall[0], path.join(cache.cache_dir, cache.hash(assertKey))) assert.strictEqual(fsPromises.writeFile.firstCall[1], assertContent) assert.strictEqual(fsPromises.writeFile.firstCall[2]?.encoding, 'utf8') assert.ok(cache._parseSetData.called) assert.ok(cache._parseSetData.firstCall[0], assertInput) }) t.test('should pass extra options to the parser', async function() { const assertOptions = { a: 1 } let cache = createCache() cache._parseSetData = spy() await cache.set('asdf', null, assertOptions) assert.ok(cache._parseSetData.called) assert.ok(cache._parseSetData.firstCall[1], assertOptions) }) t.test('should pass extra options to the parser', async function() { const assertEncoding = 'asdf' let cache = createCache() await cache.set('asdf', null, { encoding: assertEncoding }) assert.strictEqual(fsPromises.writeFile.firstCall[2]?.encoding, assertEncoding) }) t.test('parse error should reject properly', async function() { const assertError = new Error('what is up') let cache = createCache() cache._parseSetData = spy().throws(assertError) // make sure it's properly promise wrapped let inbetween = cache.set('asdf') let err = await assert.isRejected(inbetween) assert.strictEqual(err, assertError) }) }) t.describe('#setSync()', function() { t.test('should call sync writeFileSync', function() { const assertKey = 'asdf1234' const assertInput = { hello: 'world' } const assertContent = JSON.stringify(assertInput) let cache = createCache() cache._parseSetData = spy().returns(assertContent) cache.setSync(assertKey, assertInput) assert.ok(fsSync.writeFileSync.called) assert.strictEqual(fsSync.writeFileSync.firstCall[0], path.join(cache.cache_dir, cache.hash(assertKey))) assert.strictEqual(fsSync.writeFileSync.firstCall[1], assertContent) assert.strictEqual(fsSync.writeFileSync.firstCall[2]?.encoding, 'utf8') assert.ok(cache._parseSetData.called) assert.ok(cache._parseSetData.firstCall[0], assertInput) }) t.test('should pass extra options to the parser', function() { const assertOptions = { a: 1 } let cache = createCache() cache._parseSetData = spy() cache.setSync('asdf', null, assertOptions) assert.ok(cache._parseSetData.called) assert.ok(cache._parseSetData.firstCall[1], assertOptions) }) t.test('should pass extra options to the parser', function() { const assertEncoding = 'asdf' let cache = createCache() cache.setSync('asdf', null, { encoding: assertEncoding }) assert.strictEqual(fsSync.writeFileSync.firstCall[2]?.encoding, assertEncoding) }) t.test('parse error should throw', function() { const assertError = new Error('what is up') let cache = createCache() cache._parseSetData = spy().throws(assertError) assert.throws(function() { cache.setSync('asdf') }, assertError) }) }) })