diff --git a/jest.integration.config.js b/jest.integration.config.js index e70f39d..3525dbe 100644 --- a/jest.integration.config.js +++ b/jest.integration.config.js @@ -22,7 +22,7 @@ module.exports = { testEnvironment: 'node', // The glob patterns Jest uses to detect test files - testMatch: ['**/src/**/?(*.)+(test.integration).(js|ts)'], + testMatch: ['**/*.integration.test.ts'], // An array of regexp pattern strings that are matched against all test paths, matched tests are skipped testPathIgnorePatterns: ['/node_modules/', '/dist/'], diff --git a/jest.unit.config.js b/jest.unit.config.js index 1ce02db..a200b5b 100644 --- a/jest.unit.config.js +++ b/jest.unit.config.js @@ -8,6 +8,10 @@ module.exports = { transform: { '^.+\\.(ts|tsx)$': 'ts-jest', }, - testMatch: ['**/src/**/?(*.)+(spec|test).(js|ts)'], + testMatch: [ + '**/*.test.ts', + '!**/*.integration.test.ts', + '!**/*.acceptance.test.ts', + ], testEnvironment: 'node', }; diff --git a/src/cache.integration.test.ts b/src/cache.integration.test.ts index c52fe8c..faa566f 100644 --- a/src/cache.integration.test.ts +++ b/src/cache.integration.test.ts @@ -1,3 +1,4 @@ +import { promises as fs } from 'fs'; import { sleep } from './utils/sleep'; import { createCache } from './cache'; @@ -65,6 +66,33 @@ describe('cache', () => { const value = await get('ghostie'); expect(value).toEqual(undefined); }); + it('should save to disk the value json parsed, if parseable, to make it easier to observe when debugging', async () => { + const { get, set } = createCache({ directoryToPersistTo }); + + // set + const key = 'city'; + const value = JSON.stringify({ + name: 'atlantis', + galaxy: 'pegasus', + code: 821, + }); + await set(key, value); + + // check that in the file it was json parsed before stringified + const contents = await fs.readFile( + [directoryToPersistTo.mounted.path, key].join('/'), + { + encoding: 'utf-8', + }, + ); + const parsedContents = JSON.parse(contents); + expect(parsedContents.deserializedForObservability).toEqual(true); + expect(typeof parsedContents.value).not.toEqual('string'); + + // check that we can read the value + const foundValue = await get(key); + expect(foundValue).toEqual(value); + }); }); describe.skip('s3', () => { const directoryToPersistTo = { diff --git a/src/cache.ts b/src/cache.ts index 9c75be2..9de357c 100644 --- a/src/cache.ts +++ b/src/cache.ts @@ -117,13 +117,32 @@ export const createCache = ({ ): Promise => { assertIsValidOnDiskCacheKey({ key }); const expiresAtMse = getMseNow() + secondsUntilExpiration * 1000; + + // define the most observable format of the value; specifically, see if it is json.parseable; if so, parse it and use that, since its easier to look at in the cache file + const awaitedValue = await value; + const mostObservableValue = (() => { + try { + // if we can, then return the parsed value, so when we save it it is easy to read manually + return JSON.parse(awaitedValue); + } catch { + // otherwise, return the raw value, nothing more we can do + return awaitedValue; + } + })(); + + // save to disk await saveToDisk({ directory: directoryToPersistTo, key, - value: JSON.stringify({ - expiresAtMse, - value: await value, - }), + value: JSON.stringify( + { + expiresAtMse, + deserializedForObservability: typeof mostObservableValue !== 'string', // if its not a string, then it was deserialized by this method for observability + value: mostObservableValue, + }, + null, + 2, + ), }); }; @@ -137,6 +156,8 @@ export const createCache = ({ if (cacheContentSerialized === undefined) return undefined; // if not in cache, then undefined const cacheContent = JSON.parse(cacheContentSerialized); if (cacheContent.expiresAtMse < getMseNow()) return undefined; // if already expired, then undefined + if (cacheContent.deserializedForObservability) + return JSON.stringify(cacheContent.value); // if it had been deserialized for observability, reserialize it return cacheContent.value as string; // otherwise, its in the cache and not expired, so return the value };