From bc92aa68654faef67e8f6c0b0506c496cd48ec1f Mon Sep 17 00:00:00 2001 From: Randall Schmidt Date: Fri, 27 Nov 2020 21:46:49 -0500 Subject: [PATCH] implement new expanded response caching logic --- .gitignore | 3 ++ .npmignore | 1 + .vscode/launch.json | 22 +++++++++++ index.js | 89 ++++++++++++++++++++++++--------------------- package.json | 2 +- test/tests.js | 50 +++++++++++++++++++++++++ 6 files changed, 125 insertions(+), 42 deletions(-) create mode 100644 .vscode/launch.json diff --git a/.gitignore b/.gitignore index 6704566..057a81c 100644 --- a/.gitignore +++ b/.gitignore @@ -102,3 +102,6 @@ dist # TernJS port file .tern-port + +# Other +.cache diff --git a/.npmignore b/.npmignore index 4689b7d..b498b31 100644 --- a/.npmignore +++ b/.npmignore @@ -1,2 +1,3 @@ .eslintrc.js test +.cache diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..bf4e7a6 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,22 @@ +{ + // Use IntelliSense to learn about possible attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "args": [ + "--colors", + "${workspaceFolder}/test" + ], + "internalConsoleOptions": "openOnSessionStart", + "name": "Mocha Tests", + "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", + "request": "launch", + "skipFiles": [ + "/**" + ], + "type": "pwa-node" + } + ] +} \ No newline at end of file diff --git a/index.js b/index.js index 185dd2d..ecff4a1 100644 --- a/index.js +++ b/index.js @@ -7,9 +7,48 @@ function md5(str) { return crypto.createHash('md5').update(str).digest('hex'); } -async function getResponse(cacheDirPath, requestArguments, bodyFunctionName) { +class Response { + constructor(raw, cacheFilePath) { + Object.assign(this, raw); + this.cacheFilePath = cacheFilePath; + } + + text() { + return this.bodyBuffer.toString(); + } + + json() { + return JSON.parse(this.bodyBuffer.toString()); + } + + buffer() { + return this.bodyBuffer; + } + + ejectFromCache() { + return fs.promises.unlink(this.cacheFilePath); + } +} + +async function createRawResponse(fetchRes) { + const buffer = await fetchRes.buffer(); + + return { + status: fetchRes.status, + statusText: fetchRes.statusText, + type: fetchRes.type, + url: fetchRes.url, + useFinalURL: fetchRes.useFinalURL, + ok: fetchRes.ok, + headers: fetchRes.headers, + redirected: fetchRes.redirected, + bodyBuffer: buffer, + }; +} + +async function getResponse(cacheDirPath, requestArguments) { const [url, requestInit, ...rest] = requestArguments; - const requestParams = requestInit && requestInit.body + const requestParams = requestInit.body ? ({ ...requestInit, body: typeof requestInit.body === 'object' ? requestInit.body.toString() : requestInit.body }) : requestInit; @@ -17,40 +56,13 @@ async function getResponse(cacheDirPath, requestArguments, bodyFunctionName) { const cachedFilePath = path.join(cacheDirPath, `${cacheHash}.json`); try { - const body = JSON.parse(await fs.promises.readFile(cachedFilePath)); - if (bodyFunctionName === 'buffer') { - return Buffer.from(body); - } - - return body; + const rawResponse = JSON.parse(await fs.promises.readFile(cachedFilePath)); + return new Response(rawResponse); } catch (err) { const fetchResponse = await fetch(...requestArguments); - const bodyResponse = await fetchResponse[bodyFunctionName](); - await fs.promises.writeFile(cachedFilePath, JSON.stringify(bodyResponse)); - return bodyResponse; - } -} - -class ResponseWrapper { - constructor(cacheDirPath, requestArguments) { - this.cacheDirPath = cacheDirPath; - this.requestArguments = requestArguments; - } - - text() { - return getResponse(this.cacheDirPath, this.requestArguments, this.text.name); - } - - json() { - return getResponse(this.cacheDirPath, this.requestArguments, this.json.name); - } - - buffer() { - return getResponse(this.cacheDirPath, this.requestArguments, this.buffer.name); - } - - textConverted() { - return getResponse(this.cacheDirPath, this.requestArguments, this.textConverted.name); + const rawResponse = createRawResponse(fetchResponse); + await fs.promises.writeFile(cachedFilePath, JSON.stringify(rawResponse)); + return new Response(rawResponse); } } @@ -59,16 +71,11 @@ function createFetch(cacheDirPath) { return async (...args) => { if (!madeDir) { - try { - await fs.promises.mkdir(cacheDirPath, { recursive: true }); - } catch (err) { - // Ignore. - } - + await fs.promises.mkdir(cacheDirPath, { recursive: true }); madeDir = true; } - return new ResponseWrapper(cacheDirPath, args); + return getResponse(cacheDirPath, args); }; } diff --git a/package.json b/package.json index cd317f9..8bbefba 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "description": "node-fetch with a persistent cache.", "main": "index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1", + "test": "mocha", "lint": "./node_modules/.bin/eslint .", "lintfix": "./node_modules/.bin/eslint . --fix" }, diff --git a/test/tests.js b/test/tests.js index e69de29..074e816 100644 --- a/test/tests.js +++ b/test/tests.js @@ -0,0 +1,50 @@ +const assert = require('assert'); +const path = require('path'); +const fetch = require('../index.js')(path.join(__dirname, '..', '.cache')); + +const TWO_HUNDRED_URL = 'https://httpbin.org/status/200'; +const FOUR_HUNDRED_URL = 'https://httpbin.org/status/400'; +const THREE_HUNDRED_TWO_URL = 'https://httpbin.org/status/302'; + +describe('Basic property tests', function() { + it('Has a status property', async function() { + const res = await fetch(TWO_HUNDRED_URL); + assert.strictEqual(res.status, 200); + }); + + it('Has a statusText property', async function() { + const res = await fetch(TWO_HUNDRED_URL); + assert.strictEqual(res.statusText, 'OK'); + }); + + it('Has a type property', async function() { + const res = await fetch(TWO_HUNDRED_URL); + assert.strictEqual(res.type, 'basic'); + }); + + it('Has a url property', async function() { + const res = await fetch(TWO_HUNDRED_URL); + assert.strictEqual(res.url, TWO_HUNDRED_URL); + }); + + it('Has a useFinalURL property', async function() { + const res = await fetch(TWO_HUNDRED_URL); + assert.strictEqual(res.useFinalURL, true); + }); + + it('Has an ok property', async function() { + const res = await fetch(FOUR_HUNDRED_URL); + assert.strictEqual(res.ok, false); + assert.strictEqual(res.status, 400); + }); + + it('Has a headers property', async function() { + const res = await fetch(TWO_HUNDRED_URL); + assert.notStrictEqual(res.headers, undefined); + }); + + it('Has a redirected property', async function() { + const res = await fetch(THREE_HUNDRED_TWO_URL); + assert.strictEqual(res.redirected, true); + }); +}); \ No newline at end of file