diff --git a/classes/response.js b/classes/response.js index 70a354a..f754c8a 100644 --- a/classes/response.js +++ b/classes/response.js @@ -7,22 +7,32 @@ class Response { this.cacheFilePath = cacheFilePath; this.headers = new Headers(raw.headers); this.fromCache = fromCache; + this.consumed = false; if (this.bodyBuffer.type === 'Buffer') { this.bodyBuffer = Buffer.from(this.bodyBuffer); } } + consumeBody() { + if (this.consumed) { + throw new Error('Error: body used already'); + } + + this.consumed = true; + return this.bodyBuffer; + } + text() { - return this.bodyBuffer.toString(); + return this.consumeBody().toString(); } json() { - return JSON.parse(this.bodyBuffer.toString()); + return JSON.parse(this.consumeBody().toString()); } buffer() { - return this.bodyBuffer; + return this.consumeBody(); } async ejectFromCache() { diff --git a/index.js b/index.js index 44dd531..6927106 100644 --- a/index.js +++ b/index.js @@ -3,7 +3,6 @@ const fs = require('fs'); const { URLSearchParams } = require('url'); const crypto = require('crypto'); const path = require('path'); - const Response = require('./classes/response.js'); const CACHE_VERSION = 2; @@ -12,6 +11,9 @@ function md5(str) { return crypto.createHash('md5').update(str).digest('hex'); } +// Since the bounday in FormData is random, +// we ignore it for purposes of calculating +// the cache key. function getFormDataCacheKey(formData) { const cacheKey = { ...formData }; diff --git a/test/tests.js b/test/tests.js index dfd232b..8605e9b 100644 --- a/test/tests.js +++ b/test/tests.js @@ -7,11 +7,15 @@ const FetchCache = require('../index.js'); const { URLSearchParams } = require('url'); const CACHE_PATH = path.join(__dirname, '..', '.cache'); +const expectedPngBuffer = fs.readFileSync(path.join(__dirname, 'expected_png.png')); 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'; const TEXT_BODY_URL = 'https://httpbin.org/robots.txt'; +const JSON_BODY_URL = 'https://httpbin.org/json'; +const PNG_BODY_URL = 'https://httpbin.org/image/png'; + const TEXT_BODY_EXPECTED = 'User-agent: *\nDisallow: /deny\n'; let fetch; @@ -195,13 +199,51 @@ describe('Cache tests', function() { }).timeout(10000); describe('Data tests', function() { + it('Refuses to consume body twice', async function() { + res = await fetch(TEXT_BODY_URL); + await res.text(); + + try { + await res.text(); + throw new Error('The above line should have thrown.'); + } catch (err) { + // It threw + } + }); + it('Can get text body', async function() { res = await fetch(TEXT_BODY_URL); body = await res.text(); assert.strictEqual(body, TEXT_BODY_EXPECTED); + assert.strictEqual(res.fromCache, false); res = await fetch(TEXT_BODY_URL); body = await res.text(); assert.strictEqual(body, TEXT_BODY_EXPECTED); + assert.strictEqual(res.fromCache, true); + }); + + it('Can get JSON body', async function() { + res = await fetch(JSON_BODY_URL); + body = await res.json(); + assert(body.slideshow); + assert.strictEqual(res.fromCache, false); + + res = await fetch(JSON_BODY_URL); + body = await res.json(); + assert(body.slideshow); + assert.strictEqual(res.fromCache, true); + }); + + it('Can get PNG buffer body', async function() { + res = await fetch(PNG_BODY_URL); + body = await res.buffer(); + assert.strictEqual(expectedPngBuffer.equals(body), true); + assert.strictEqual(res.fromCache, false); + + res = await fetch(PNG_BODY_URL); + body = await res.buffer(); + assert.strictEqual(expectedPngBuffer.equals(body), true); + assert.strictEqual(res.fromCache, true); }); }).timeout(10000);