diff --git a/classes/headers.js b/classes/headers.js index c9967fc..6e3c8b2 100644 --- a/classes/headers.js +++ b/classes/headers.js @@ -4,19 +4,21 @@ class Headers { } entries() { - return Object.entries(this.rawHeaders); + return Object.entries(this.rawHeaders) + .sort((e1, e2) => e1[0].localeCompare(e2[0])) + .map(([key, val]) => [key, val[0]]); } keys() { - return Object.keys(this.rawHeaders); + return this.entries().map((e) => e[0]); } values() { - return Object.values(this.rawHeaders); + return this.entries().map((e) => e[1]); } get(name) { - return this.rawHeaders[name.toLowerCase()] || null; + return (this.rawHeaders[name.toLowerCase()] || [])[0]; } has(name) { diff --git a/index.js b/index.js index 1c6c66f..69dcde1 100644 --- a/index.js +++ b/index.js @@ -73,16 +73,13 @@ function getCacheKey(requestArguments) { async function createRawResponse(fetchRes) { const buffer = await fetchRes.buffer(); - const rawHeaders = Array.from(fetchRes.headers.entries()) - .reduce((aggregate, entry) => ({ ...aggregate, [entry[0]]: entry[1] }), {}); - return { status: fetchRes.status, statusText: fetchRes.statusText, type: fetchRes.type, url: fetchRes.url, ok: fetchRes.ok, - headers: rawHeaders, + headers: fetchRes.headers.raw(), redirected: fetchRes.redirected, bodyBuffer: buffer, }; diff --git a/package.json b/package.json index 8d58ea7..3f88087 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "index.js", "scripts": { "test": "mocha --timeout 10000 --exit", - "coverage": "nyc --reporter=lcov npm test", + "coverage": "nyc --reporter=lcov --reporter=text npm test", "lint": "./node_modules/.bin/eslint .", "lintfix": "./node_modules/.bin/eslint . --fix" }, diff --git a/test/tests.js b/test/tests.js index 5223218..094f740 100644 --- a/test/tests.js +++ b/test/tests.js @@ -3,8 +3,9 @@ const FormData = require('form-data'); const assert = require('assert'); const rimraf = require('rimraf'); const path = require('path'); -const FetchCache = require('../index.js'); const { URLSearchParams } = require('url'); +const standardFetch = require('node-fetch'); +const FetchCache = require('../index.js'); const MemoryCache = require('../classes/caching/memory_cache.js'); const CACHE_PATH = path.join(__dirname, '..', '.cache'); @@ -19,98 +20,170 @@ const PNG_BODY_URL = 'https://httpbin.org/image/png'; const TEXT_BODY_EXPECTED = 'User-agent: *\nDisallow: /deny\n'; -let fetch; -let res; +let cachedFetch; let body; function post(body) { return { method: 'POST', body }; } +function removeDates(arrOrObj) { + if (arrOrObj.date) { + const copy = { ...arrOrObj }; + delete copy.date; + return copy; + } + + if (Array.isArray(arrOrObj)) { + return [...arrOrObj].filter(e => !Date.parse(e)); + } + + return arrOrObj; +} + +async function dualFetch(...args) { + const [cachedFetchResponse, standardFetchResponse] = await Promise.all([ + cachedFetch(...args), + standardFetch(...args), + ]); + + return { cachedFetchResponse, standardFetchResponse }; +} + beforeEach(async function() { rimraf.sync(CACHE_PATH); - fetch = FetchCache.withCache(new MemoryCache()); + cachedFetch = FetchCache.withCache(new MemoryCache()); }); describe('Basic property tests', function() { it('Has a status property', async function() { - res = await fetch(TWO_HUNDRED_URL); - assert.strictEqual(res.status, 200); + let { cachedFetchResponse, standardFetchResponse } = await dualFetch(TWO_HUNDRED_URL); + assert.strictEqual(cachedFetchResponse.status, standardFetchResponse.status); - res = await fetch(TWO_HUNDRED_URL); - assert.strictEqual(res.status, 200); + cachedFetchResponse = await cachedFetch(TWO_HUNDRED_URL); + assert.strictEqual(cachedFetchResponse.status, standardFetchResponse.status); }); it('Has a statusText property', async function() { - res = await fetch(TWO_HUNDRED_URL); - assert.strictEqual(res.statusText, 'OK'); + let { cachedFetchResponse, standardFetchResponse } = await dualFetch(TWO_HUNDRED_URL); + assert.strictEqual(cachedFetchResponse.statusText, standardFetchResponse.statusText); - res = await fetch(TWO_HUNDRED_URL); - assert.strictEqual(res.statusText, 'OK'); + cachedFetchResponse = await cachedFetch(TWO_HUNDRED_URL); + assert.strictEqual(cachedFetchResponse.statusText, standardFetchResponse.statusText); }); it('Has a url property', async function() { - res = await fetch(TWO_HUNDRED_URL); - assert.strictEqual(res.url, TWO_HUNDRED_URL); + let { cachedFetchResponse, standardFetchResponse } = await dualFetch(TWO_HUNDRED_URL); + assert.strictEqual(cachedFetchResponse.url, standardFetchResponse.url); - res = await fetch(TWO_HUNDRED_URL); - assert.strictEqual(res.url, TWO_HUNDRED_URL); + cachedFetchResponse = await cachedFetch(TWO_HUNDRED_URL); + assert.strictEqual(cachedFetchResponse.url, standardFetchResponse.url); }); it('Has an ok property', async function() { - res = await fetch(FOUR_HUNDRED_URL); - assert.strictEqual(res.ok, false); - assert.strictEqual(res.status, 400); + let { cachedFetchResponse, standardFetchResponse } = await dualFetch(FOUR_HUNDRED_URL); + assert.strictEqual(cachedFetchResponse.ok, standardFetchResponse.ok); + assert.strictEqual(cachedFetchResponse.status, standardFetchResponse.status); - res = await fetch(FOUR_HUNDRED_URL); - assert.strictEqual(res.ok, false); - assert.strictEqual(res.status, 400); - }); - - it('Has a headers property', async function() { - res = await fetch(TWO_HUNDRED_URL); - assert.notStrictEqual(res.headers, undefined); - - res = await fetch(TWO_HUNDRED_URL); - assert.notStrictEqual(res.headers, undefined); + cachedFetchResponse = await cachedFetch(FOUR_HUNDRED_URL); + assert.strictEqual(cachedFetchResponse.ok, standardFetchResponse.ok); + assert.strictEqual(cachedFetchResponse.status, standardFetchResponse.status); }); it('Has a redirected property', async function() { - res = await fetch(THREE_HUNDRED_TWO_URL); - assert.strictEqual(res.redirected, true); + let { cachedFetchResponse, standardFetchResponse } = await dualFetch(THREE_HUNDRED_TWO_URL); + assert.strictEqual(cachedFetchResponse.redirected, standardFetchResponse.redirected); - res = await fetch(THREE_HUNDRED_TWO_URL); - assert.strictEqual(res.redirected, true); + cachedFetchResponse = await cachedFetch(THREE_HUNDRED_TWO_URL); + assert.strictEqual(cachedFetchResponse.redirected, standardFetchResponse.redirected); }); }).timeout(10000); +describe('Header tests', function() { + it('Gets correct raw headers', async function() { + let { cachedFetchResponse, standardFetchResponse } = await dualFetch(TWO_HUNDRED_URL); + assert.deepStrictEqual( + removeDates(cachedFetchResponse.headers.raw()), + removeDates(standardFetchResponse.headers.raw()), + ); + + cachedFetchResponse = await cachedFetch(TWO_HUNDRED_URL); + assert.deepStrictEqual( + removeDates(cachedFetchResponse.headers.raw()), + removeDates(standardFetchResponse.headers.raw()), + ); + }); + + it('Gets correct header keys', async function() { + let { cachedFetchResponse, standardFetchResponse } = await dualFetch(TWO_HUNDRED_URL); + assert.deepStrictEqual(cachedFetchResponse.headers.keys(), [...standardFetchResponse.headers.keys()]); + + cachedFetchResponse = await cachedFetch(TWO_HUNDRED_URL); + assert.deepStrictEqual(cachedFetchResponse.headers.keys(), [...standardFetchResponse.headers.keys()]); + }); + + it('Gets correct header values', async function() { + let { cachedFetchResponse, standardFetchResponse } = await dualFetch(TWO_HUNDRED_URL); + assert.deepStrictEqual(cachedFetchResponse.headers.values(), [...standardFetchResponse.headers.values()]); + + cachedFetchResponse = await cachedFetch(TWO_HUNDRED_URL); + assert.deepStrictEqual(cachedFetchResponse.headers.values(), [...standardFetchResponse.headers.values()]); + }); + + it('Gets correct header entries', async function() { + let { cachedFetchResponse, standardFetchResponse } = await dualFetch(TWO_HUNDRED_URL); + assert.deepStrictEqual(cachedFetchResponse.headers.entries(), [...standardFetchResponse.headers.entries()]); + + cachedFetchResponse = await cachedFetch(TWO_HUNDRED_URL); + assert.deepStrictEqual(cachedFetchResponse.headers.entries(), [...standardFetchResponse.headers.entries()]); + }); + + it('Can get a header by value', async function() { + let { cachedFetchResponse, standardFetchResponse } = await dualFetch(TWO_HUNDRED_URL); + assert(standardFetchResponse.headers.get('content-length')); + assert.deepStrictEqual(cachedFetchResponse.headers.get('content-length'), standardFetchResponse.headers.get('content-length')); + + cachedFetchResponse = await cachedFetch(TWO_HUNDRED_URL); + assert.deepStrictEqual(cachedFetchResponse.headers.get('content-length'), standardFetchResponse.headers.get('content-length')); + }); + + it('Can get whether a header is present', async function() { + let { cachedFetchResponse, standardFetchResponse } = await dualFetch(TWO_HUNDRED_URL); + assert(standardFetchResponse.headers.has('content-length')); + assert.deepStrictEqual(cachedFetchResponse.headers.has('content-length'), standardFetchResponse.headers.has('content-length')); + + cachedFetchResponse = await cachedFetch(TWO_HUNDRED_URL); + assert.deepStrictEqual(cachedFetchResponse.headers.has('content-length'), standardFetchResponse.headers.has('content-length')); + }); +}); + describe('Cache tests', function() { it('Uses cache', async function() { - res = await fetch(TWO_HUNDRED_URL); + res = await cachedFetch(TWO_HUNDRED_URL); assert.strictEqual(res.fromCache, false); - res = await fetch(TWO_HUNDRED_URL); + res = await cachedFetch(TWO_HUNDRED_URL); assert.strictEqual(res.fromCache, true); }); it('Can eject from cache', async function() { - res = await fetch(TWO_HUNDRED_URL); + res = await cachedFetch(TWO_HUNDRED_URL); assert.strictEqual(res.fromCache, false); - res = await fetch(TWO_HUNDRED_URL); + res = await cachedFetch(TWO_HUNDRED_URL); assert.strictEqual(res.fromCache, true); await res.ejectFromCache(); - res = await fetch(TWO_HUNDRED_URL); + res = await cachedFetch(TWO_HUNDRED_URL); assert.strictEqual(res.fromCache, false); - res = await fetch(TWO_HUNDRED_URL); + res = await cachedFetch(TWO_HUNDRED_URL); assert.strictEqual(res.fromCache, true); }); - it('Does not error if rejecting from cache twice', async function() { - res = await fetch(TWO_HUNDRED_URL); + it('Does not error if ejecting from cache twice', async function() { + res = await cachedFetch(TWO_HUNDRED_URL); assert.strictEqual(res.fromCache, false); await res.ejectFromCache(); @@ -118,34 +191,34 @@ describe('Cache tests', function() { }); it('Gives different string bodies different cache keys', async function() { - res = await fetch(TWO_HUNDRED_URL, post('a')); + res = await cachedFetch(TWO_HUNDRED_URL, post('a')); assert.strictEqual(res.fromCache, false); - res = await fetch(TWO_HUNDRED_URL, post('b')); + res = await cachedFetch(TWO_HUNDRED_URL, post('b')); assert.strictEqual(res.fromCache, false); }); it('Gives same string bodies same cache keys', async function() { - res = await fetch(TWO_HUNDRED_URL, post('a')); + res = await cachedFetch(TWO_HUNDRED_URL, post('a')); assert.strictEqual(res.fromCache, false); - res = await fetch(TWO_HUNDRED_URL, post('a')); + res = await cachedFetch(TWO_HUNDRED_URL, post('a')); assert.strictEqual(res.fromCache, true); }); it('Gives different URLSearchParams different cache keys', async function() { - res = await fetch(TWO_HUNDRED_URL, post(new URLSearchParams('a=a'))); + res = await cachedFetch(TWO_HUNDRED_URL, post(new URLSearchParams('a=a'))); assert.strictEqual(res.fromCache, false); - res = await fetch(TWO_HUNDRED_URL, post(new URLSearchParams('a=b'))); + res = await cachedFetch(TWO_HUNDRED_URL, post(new URLSearchParams('a=b'))); assert.strictEqual(res.fromCache, false); }); it('Gives same URLSearchParams same cache keys', async function() { - res = await fetch(TWO_HUNDRED_URL, post(new URLSearchParams('a=a'))); + res = await cachedFetch(TWO_HUNDRED_URL, post(new URLSearchParams('a=a'))); assert.strictEqual(res.fromCache, false); - res = await fetch(TWO_HUNDRED_URL, post(new URLSearchParams('a=a'))); + res = await cachedFetch(TWO_HUNDRED_URL, post(new URLSearchParams('a=a'))); assert.strictEqual(res.fromCache, true); }); @@ -153,20 +226,20 @@ describe('Cache tests', function() { const s1 = fs.createReadStream(__filename); const s2 = fs.createReadStream(path.join(__dirname, '..', 'index.js')); - res = await fetch(TWO_HUNDRED_URL, post(s1)); + res = await cachedFetch(TWO_HUNDRED_URL, post(s1)); assert.strictEqual(res.fromCache, false); - res = await fetch(TWO_HUNDRED_URL, post(s2)); + res = await cachedFetch(TWO_HUNDRED_URL, post(s2)); assert.strictEqual(res.fromCache, false); }); it('Gives the same read streams the same cache key', async function() { const s1 = fs.createReadStream(__filename); - res = await fetch(TWO_HUNDRED_URL, post(s1)); + res = await cachedFetch(TWO_HUNDRED_URL, post(s1)); assert.strictEqual(res.fromCache, false); - res = await fetch(TWO_HUNDRED_URL, post(s1)); + res = await cachedFetch(TWO_HUNDRED_URL, post(s1)); assert.strictEqual(res.fromCache, true); }); @@ -177,10 +250,10 @@ describe('Cache tests', function() { const data2 = new FormData(); data2.append('b', 'b'); - res = await fetch(TWO_HUNDRED_URL, post(data1)); + res = await cachedFetch(TWO_HUNDRED_URL, post(data1)); assert.strictEqual(res.fromCache, false); - res = await fetch(TWO_HUNDRED_URL, post(data2)); + res = await cachedFetch(TWO_HUNDRED_URL, post(data2)); assert.strictEqual(res.fromCache, false); }); @@ -191,17 +264,17 @@ describe('Cache tests', function() { const data2 = new FormData(); data2.append('a', 'a'); - res = await fetch(TWO_HUNDRED_URL, post(data1)); + res = await cachedFetch(TWO_HUNDRED_URL, post(data1)); assert.strictEqual(res.fromCache, false); - res = await fetch(TWO_HUNDRED_URL, post(data2)); + res = await cachedFetch(TWO_HUNDRED_URL, post(data2)); assert.strictEqual(res.fromCache, true); }); }).timeout(10000); describe('Data tests', function() { it('Refuses to consume body twice', async function() { - res = await fetch(TEXT_BODY_URL); + res = await cachedFetch(TEXT_BODY_URL); await res.text(); try { @@ -213,43 +286,43 @@ describe('Data tests', function() { }); it('Can get text body', async function() { - res = await fetch(TEXT_BODY_URL); + res = await cachedFetch(TEXT_BODY_URL); body = await res.text(); assert.strictEqual(body, TEXT_BODY_EXPECTED); assert.strictEqual(res.fromCache, false); - res = await fetch(TEXT_BODY_URL); + res = await cachedFetch(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); + res = await cachedFetch(JSON_BODY_URL); body = await res.json(); assert(body.slideshow); assert.strictEqual(res.fromCache, false); - res = await fetch(JSON_BODY_URL); + res = await cachedFetch(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); + res = await cachedFetch(PNG_BODY_URL); body = await res.buffer(); assert.strictEqual(expectedPngBuffer.equals(body), true); assert.strictEqual(res.fromCache, false); - res = await fetch(PNG_BODY_URL); + res = await cachedFetch(PNG_BODY_URL); body = await res.buffer(); assert.strictEqual(expectedPngBuffer.equals(body), true); assert.strictEqual(res.fromCache, true); }); it('Can stream a body', async function() { - res = await fetch(TEXT_BODY_URL); + res = await cachedFetch(TEXT_BODY_URL); body = ''; for await (const chunk of res.body) { @@ -259,7 +332,7 @@ describe('Data tests', function() { assert.strictEqual(TEXT_BODY_EXPECTED, body); assert.strictEqual(res.fromCache, false); - res = await fetch(TEXT_BODY_URL); + res = await cachedFetch(TEXT_BODY_URL); body = ''; for await (const chunk of res.body) {