55 Commits
2.x ... 2.0

Author SHA1 Message Date
Randall Schmidt
185b47c8a6 update .npmignore 2021-06-12 19:25:21 -04:00
Randall Schmidt
b0b454de85 update README 2021-06-12 19:24:17 -04:00
Randall Schmidt
7b0835fcf1 make imports work properly 2021-06-12 15:47:06 -04:00
Randall Schmidt
34c8887e51 get rid of blob 2021-06-12 15:43:35 -04:00
Randall Schmidt
3d7b27ceb5 bump version 2021-06-11 17:52:29 -04:00
Randall Schmidt
49f1c0cdc1 add blob support 2021-06-11 16:40:35 -04:00
Randall Schmidt
2e7c35a34c add blob support 2021-06-11 16:38:23 -04:00
Randall Schmidt
3896ae2832 add blob support 2021-06-11 16:37:51 -04:00
Randall Schmidt
2f5409c25c update tests 2021-06-11 15:52:15 -04:00
Randall Schmidt
7f2dba84a3 add more header tests 2021-06-11 15:25:53 -04:00
Randall Schmidt
901e32fa09 add more files to npmignore 2021-06-11 14:17:11 -04:00
Randall Schmidt
ef5ddf69e4 npm ignore nyc_output 2021-06-11 14:12:34 -04:00
Randall Schmidt
6bc5de6581 npm ignore nyc_output 2021-06-11 14:12:34 -04:00
Randall Schmidt
f2a6b048bd add CI file for github actions 2021-06-11 14:12:34 -04:00
Randall Schmidt
2fb975d9a8 add coverage script 2021-06-11 14:12:34 -04:00
Randall Schmidt
41045996ae update readme 2021-06-11 14:12:33 -04:00
Randall Schmidt
ed25ae32ab update readme 2021-06-11 14:12:33 -04:00
Randall Schmidt
7779114055 update readme 2021-06-11 14:12:33 -04:00
Randall Schmidt
831440152a custom caching 2021-06-11 14:12:33 -04:00
Randall Schmidt
ad256800f8 npm audit fix 2021-06-11 14:12:32 -04:00
Randall Schmidt
421c68909e add fpersist as dependency 2021-06-11 14:12:32 -04:00
Randall Schmidt
1f9b533c66 update readme 2021-06-11 14:12:32 -04:00
Randall Schmidt
489da016fd update readme 2021-06-11 14:12:32 -04:00
Randall Schmidt
1e0030f5a3 update readme 2021-06-11 14:12:31 -04:00
Randall Schmidt
fe51a289e1 update readme 2021-06-11 14:12:31 -04:00
Randall Schmidt
1311282f5a update readme 2021-06-11 14:12:31 -04:00
Randall Schmidt
32a50f757a update readme 2021-06-11 14:12:30 -04:00
Randall Schmidt
664e2afc59 update readme 2021-06-11 14:12:30 -04:00
Randall Schmidt
ba9df959db update readme 2021-06-11 14:12:30 -04:00
Randall Schmidt
c46d08d76a update readme 2021-06-11 14:12:30 -04:00
Randall Schmidt
28348f511f update readme 2021-06-11 14:12:29 -04:00
Randall Schmidt
d676db7e88 update readme 2021-06-11 14:12:29 -04:00
Randall Schmidt
ac437a89ed update readme 2021-06-11 14:12:29 -04:00
Randall Schmidt
ff09dd8521 update readme 2021-06-11 14:12:29 -04:00
Randall Schmidt
9c9d2b88e1 support streaming response 2021-06-11 14:12:28 -04:00
Randall Schmidt
f92bd99968 only allow body to be consumed once, add some tests 2021-06-11 14:12:28 -04:00
Randall Schmidt
c3071e3059 give tests longer timeout 2021-06-11 14:12:28 -04:00
Randall Schmidt
bf4c295e48 add a CACHE_VERSION to invalid caches when versions become incompatible 2021-06-11 14:12:28 -04:00
Randall Schmidt
9241b74dde add support for FormData 2021-06-11 14:12:27 -04:00
Randall Schmidt
3b5adecd7f handle fs read streams 2021-06-11 14:12:27 -04:00
Randall Schmidt
8a338f5f5d refactor cache key calculation to have more space for dealing with different types of bodies 2021-06-11 14:12:27 -04:00
Randall Schmidt
a7fc94c3e9 add raw headers function 2021-06-11 14:12:26 -04:00
Randall Schmidt
facda80789 add tests for string body and URLSearchParams bodies 2021-06-11 14:12:26 -04:00
Randall Schmidt
8e1b57382a dont error if ejectFromCache is called when the response is already not cached 2021-06-11 14:12:26 -04:00
Randall Schmidt
558d63fd40 add husky to enforce lint rules and tests 2021-06-11 14:12:26 -04:00
Randall Schmidt
02653e04c4 fix lint warnings 2021-06-11 14:12:25 -04:00
Randall Schmidt
371d20e933 add test for cache ejection 2021-06-11 14:12:25 -04:00
Randall Schmidt
c452c8d860 add tests, fix flawed logic 2021-06-11 14:12:25 -04:00
Randall Schmidt
bc92aa6865 implement new expanded response caching logic 2021-06-11 14:12:24 -04:00
Randall Schmidt
01bc48594e add test boilerplate 2021-06-11 14:12:24 -04:00
Randall Schmidt
f854cccd23 add mocha for tests 2021-06-11 14:12:24 -04:00
Randall
dea019c42c Merge pull request #6 from mistval/dependabot/npm_and_yarn/hosted-git-info-2.8.9
Bump hosted-git-info from 2.8.8 to 2.8.9
2021-05-11 17:05:53 -06:00
Randall
eda9f98e3b Merge pull request #5 from mistval/dependabot/npm_and_yarn/lodash-4.17.21
Bump lodash from 4.17.19 to 4.17.21
2021-05-11 17:05:45 -06:00
dependabot[bot]
6d1369b50f Bump hosted-git-info from 2.8.8 to 2.8.9
Bumps [hosted-git-info](https://github.com/npm/hosted-git-info) from 2.8.8 to 2.8.9.
- [Release notes](https://github.com/npm/hosted-git-info/releases)
- [Changelog](https://github.com/npm/hosted-git-info/blob/v2.8.9/CHANGELOG.md)
- [Commits](https://github.com/npm/hosted-git-info/compare/v2.8.8...v2.8.9)

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-11 16:28:25 +00:00
dependabot[bot]
d97cb85f97 Bump lodash from 4.17.19 to 4.17.21
Bumps [lodash](https://github.com/lodash/lodash) from 4.17.19 to 4.17.21.
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.19...4.17.21)

Signed-off-by: dependabot[bot] <support@github.com>
2021-05-11 10:55:29 +00:00
9 changed files with 288 additions and 117 deletions

View File

@ -2,3 +2,7 @@
test test
.cache .cache
.nyc_output .nyc_output
.github
.eslintignore
.vscode
coverage

View File

@ -28,21 +28,27 @@ This module aims to expose the same API as `node-fetch` does for the most common
Load the module. Load the module.
### async fetch(resource [, init]) ### await fetch(resource [, init])
Same arguments as [node-fetch](https://www.npmjs.com/package/node-fetch). Same arguments as [node-fetch](https://www.npmjs.com/package/node-fetch).
Returns a **CachedResponse**. Returns a **CachedResponse**.
### async CachedResponse.text() ### await CachedResponse.ejectFromCache()
Eject the response from the cache, so that the next request will perform a true HTTP request rather than returning a cached response.
Keep in mind that this module caches **all** responses, even if they return errors. You might want to use this function in certain cases like receiving a 5xx response status, so that you can retry requests.
### await CachedResponse.text()
Returns the body as a string, same as [node-fetch](https://www.npmjs.com/package/node-fetch). Returns the body as a string, same as [node-fetch](https://www.npmjs.com/package/node-fetch).
### async CachedResponse.json() ### await CachedResponse.json()
Returns the body as a JavaScript object, parsed from JSON, same as [node-fetch](https://www.npmjs.com/package/node-fetch). Returns the body as a JavaScript object, parsed from JSON, same as [node-fetch](https://www.npmjs.com/package/node-fetch).
### async CachedResponse.buffer() ### await CachedResponse.buffer()
Returns the body as a Buffer, same as [node-fetch](https://www.npmjs.com/package/node-fetch). Returns the body as a Buffer, same as [node-fetch](https://www.npmjs.com/package/node-fetch).
@ -66,12 +72,6 @@ Returns true if the request was redirected, false otherwise, same as [node-fetch
Returns a **ResponseHeaders** object representing the headers of the response, same as [node-fetch](https://www.npmjs.com/package/node-fetch). Returns a **ResponseHeaders** object representing the headers of the response, same as [node-fetch](https://www.npmjs.com/package/node-fetch).
### async CachedResponse.ejectFromCache()
Eject the response from the cache, so that the next request will perform a true HTTP request rather than returning a cached response.
Keep in mind that this module caches **all** responses, even if they return error status codes. You might want to use this function when `!response.ok`, so that you can retry requests.
### ResponseHeaders.entries() ### ResponseHeaders.entries()
Returns the raw headers as an array of `[key, value]` pairs, same as [node-fetch](https://www.npmjs.com/package/node-fetch). Returns the raw headers as an array of `[key, value]` pairs, same as [node-fetch](https://www.npmjs.com/package/node-fetch).
@ -114,7 +114,7 @@ This is the default cache delegate. It caches responses in-process in a POJO.
Usage: Usage:
```js ```js
const fetchBuilder, { MemoryCache } = require('node-fetch-cache'); const { fetchBuilder, MemoryCache } = require('node-fetch-cache');
const fetch = fetchBuilder.withCache(new MemoryCache(options)); const fetch = fetchBuilder.withCache(new MemoryCache(options));
``` ```
@ -135,7 +135,7 @@ Cache to a directory on disk. This allows the cache to survive the process exiti
Usage: Usage:
```js ```js
const fetchBuilder, { FileSystemCache } = require('node-fetch-cache'); const { fetchBuilder, FileSystemCache } = require('node-fetch-cache');
const fetch = fetchBuilder.withCache(new FileSystemCache(options)); const fetch = fetchBuilder.withCache(new FileSystemCache(options));
``` ```

View File

@ -4,19 +4,21 @@ class Headers {
} }
entries() { 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() { keys() {
return Object.keys(this.rawHeaders); return this.entries().map((e) => e[0]);
} }
values() { values() {
return Object.values(this.rawHeaders); return this.entries().map((e) => e[1]);
} }
get(name) { get(name) {
return this.rawHeaders[name.toLowerCase()] || null; return (this.rawHeaders[name.toLowerCase()] || [])[0] || null;
} }
has(name) { has(name) {

View File

@ -27,15 +27,15 @@ class Response {
return this.bodyBuffer; return this.bodyBuffer;
} }
text() { async text() {
return this.consumeBody().toString(); return this.consumeBody().toString();
} }
json() { async json() {
return JSON.parse(this.consumeBody().toString()); return JSON.parse(this.consumeBody().toString());
} }
buffer() { async buffer() {
return this.consumeBody(); return this.consumeBody();
} }

View File

@ -4,6 +4,7 @@ const { URLSearchParams } = require('url');
const crypto = require('crypto'); const crypto = require('crypto');
const Response = require('./classes/response.js'); const Response = require('./classes/response.js');
const MemoryCache = require('./classes/caching/memory_cache.js'); const MemoryCache = require('./classes/caching/memory_cache.js');
const FileSystemCache = require('./classes/caching/file_system_cache.js');
const CACHE_VERSION = 2; const CACHE_VERSION = 2;
@ -16,27 +17,21 @@ function md5(str) {
// the cache key. // the cache key.
function getFormDataCacheKey(formData) { function getFormDataCacheKey(formData) {
const cacheKey = { ...formData }; const cacheKey = { ...formData };
const boundary = formData.getBoundary();
if (typeof formData.getBoundary === 'function') { // eslint-disable-next-line no-underscore-dangle
const boundary = formData.getBoundary(); delete cacheKey._boundary;
// eslint-disable-next-line no-underscore-dangle const boundaryReplaceRegex = new RegExp(boundary, 'g');
delete cacheKey._boundary;
// eslint-disable-next-line no-underscore-dangle // eslint-disable-next-line no-underscore-dangle
if (Array.isArray(cacheKey._streams)) { cacheKey._streams = cacheKey._streams.map((s) => {
const boundaryReplaceRegex = new RegExp(boundary, 'g'); if (typeof s === 'string') {
return s.replace(boundaryReplaceRegex, '');
// eslint-disable-next-line no-underscore-dangle
cacheKey._streams = cacheKey._streams.map((s) => {
if (typeof s === 'string') {
return s.replace(boundaryReplaceRegex, '');
}
return s;
});
} }
}
return s;
});
return cacheKey; return cacheKey;
} }
@ -54,14 +49,18 @@ function getBodyCacheKeyJson(body) {
return getFormDataCacheKey(body); return getFormDataCacheKey(body);
} }
throw new Error('Unsupported body type'); throw new Error('Unsupported body type. Supported body types are: string, number, undefined, null, url.URLSearchParams, fs.ReadStream, FormData');
} }
function getCacheKey(requestArguments) { function getCacheKey(requestArguments) {
const resource = requestArguments[0]; const resource = requestArguments[0];
const init = requestArguments[1] || {}; const init = requestArguments[1] || {};
const resourceCacheKeyJson = typeof resource === 'string' ? { url: resource } : { ...resource }; if (typeof resource !== 'string') {
throw new Error('The first argument must be a string (fetch.Request is not supported).');
}
const resourceCacheKeyJson = { url: resource };
const initCacheKeyJson = { ...init }; const initCacheKeyJson = { ...init };
resourceCacheKeyJson.body = getBodyCacheKeyJson(resourceCacheKeyJson.body); resourceCacheKeyJson.body = getBodyCacheKeyJson(resourceCacheKeyJson.body);
@ -73,16 +72,13 @@ function getCacheKey(requestArguments) {
async function createRawResponse(fetchRes) { async function createRawResponse(fetchRes) {
const buffer = await fetchRes.buffer(); const buffer = await fetchRes.buffer();
const rawHeaders = Array.from(fetchRes.headers.entries())
.reduce((aggregate, entry) => ({ ...aggregate, [entry[0]]: entry[1] }), {});
return { return {
status: fetchRes.status, status: fetchRes.status,
statusText: fetchRes.statusText, statusText: fetchRes.statusText,
type: fetchRes.type, type: fetchRes.type,
url: fetchRes.url, url: fetchRes.url,
ok: fetchRes.ok, ok: fetchRes.ok,
headers: rawHeaders, headers: fetchRes.headers.raw(),
redirected: fetchRes.redirected, redirected: fetchRes.redirected,
bodyBuffer: buffer, bodyBuffer: buffer,
}; };
@ -111,4 +107,9 @@ function createFetchWithCache(cache) {
return fetchCache; return fetchCache;
} }
module.exports = createFetchWithCache(new MemoryCache()); const defaultFetch = createFetchWithCache(new MemoryCache());
module.exports = defaultFetch;
module.exports.fetchBuilder = defaultFetch;
module.exports.MemoryCache = MemoryCache;
module.exports.FileSystemCache = FileSystemCache;

2
package-lock.json generated
View File

@ -1,6 +1,6 @@
{ {
"name": "node-fetch-cache", "name": "node-fetch-cache",
"version": "1.0.6", "version": "2.0.0",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {

View File

@ -1,11 +1,11 @@
{ {
"name": "node-fetch-cache", "name": "node-fetch-cache",
"version": "1.0.6", "version": "2.0.0",
"description": "node-fetch with a persistent cache.", "description": "node-fetch with a persistent cache.",
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"test": "mocha --timeout 10000 --exit", "test": "mocha --timeout 10000 --exit",
"coverage": "nyc --reporter=lcov npm test", "coverage": "nyc --reporter=lcov --reporter=text npm test",
"lint": "./node_modules/.bin/eslint .", "lint": "./node_modules/.bin/eslint .",
"lintfix": "./node_modules/.bin/eslint . --fix" "lintfix": "./node_modules/.bin/eslint . --fix"
}, },

View File

@ -3,9 +3,9 @@ const FormData = require('form-data');
const assert = require('assert'); const assert = require('assert');
const rimraf = require('rimraf'); const rimraf = require('rimraf');
const path = require('path'); const path = require('path');
const FetchCache = require('../index.js');
const { URLSearchParams } = require('url'); const { URLSearchParams } = require('url');
const MemoryCache = require('../classes/caching/memory_cache.js'); const standardFetch = require('node-fetch');
const FetchCache = require('../index.js');
const CACHE_PATH = path.join(__dirname, '..', '.cache'); const CACHE_PATH = path.join(__dirname, '..', '.cache');
const expectedPngBuffer = fs.readFileSync(path.join(__dirname, 'expected_png.png')); const expectedPngBuffer = fs.readFileSync(path.join(__dirname, 'expected_png.png'));
@ -19,98 +19,200 @@ const PNG_BODY_URL = 'https://httpbin.org/image/png';
const TEXT_BODY_EXPECTED = 'User-agent: *\nDisallow: /deny\n'; const TEXT_BODY_EXPECTED = 'User-agent: *\nDisallow: /deny\n';
let fetch; let cachedFetch;
let res;
let body; let body;
function post(body) { function post(body) {
return { method: 'POST', body }; return { method: 'POST', body };
} }
function removeDates(arrOrObj) {
if (arrOrObj.date) {
const copy = { ...arrOrObj };
delete copy.date;
return copy;
}
if (Array.isArray(arrOrObj)) {
if (Array.isArray(arrOrObj[0])) {
return arrOrObj.filter(e => e[0] !== 'date');
}
return arrOrObj.filter(e => !Date.parse(e));
}
return arrOrObj;
}
function wait(ms) {
return new Promise((fulfill) => setTimeout(fulfill, ms));
}
async function dualFetch(...args) {
const [cachedFetchResponse, standardFetchResponse] = await Promise.all([
cachedFetch(...args),
standardFetch(...args),
]);
return { cachedFetchResponse, standardFetchResponse };
}
beforeEach(async function() { beforeEach(async function() {
rimraf.sync(CACHE_PATH); rimraf.sync(CACHE_PATH);
fetch = FetchCache.withCache(new MemoryCache()); cachedFetch = FetchCache.withCache(new FetchCache.MemoryCache());
}); });
describe('Basic property tests', function() { describe('Basic property tests', function() {
it('Has a status property', async function() { it('Has a status property', async function() {
res = await fetch(TWO_HUNDRED_URL); let { cachedFetchResponse, standardFetchResponse } = await dualFetch(TWO_HUNDRED_URL);
assert.strictEqual(res.status, 200); assert.strictEqual(cachedFetchResponse.status, standardFetchResponse.status);
res = await fetch(TWO_HUNDRED_URL); cachedFetchResponse = await cachedFetch(TWO_HUNDRED_URL);
assert.strictEqual(res.status, 200); assert.strictEqual(cachedFetchResponse.status, standardFetchResponse.status);
}); });
it('Has a statusText property', async function() { it('Has a statusText property', async function() {
res = await fetch(TWO_HUNDRED_URL); let { cachedFetchResponse, standardFetchResponse } = await dualFetch(TWO_HUNDRED_URL);
assert.strictEqual(res.statusText, 'OK'); assert.strictEqual(cachedFetchResponse.statusText, standardFetchResponse.statusText);
res = await fetch(TWO_HUNDRED_URL); cachedFetchResponse = await cachedFetch(TWO_HUNDRED_URL);
assert.strictEqual(res.statusText, 'OK'); assert.strictEqual(cachedFetchResponse.statusText, standardFetchResponse.statusText);
}); });
it('Has a url property', async function() { it('Has a url property', async function() {
res = await fetch(TWO_HUNDRED_URL); let { cachedFetchResponse, standardFetchResponse } = await dualFetch(TWO_HUNDRED_URL);
assert.strictEqual(res.url, TWO_HUNDRED_URL); assert.strictEqual(cachedFetchResponse.url, standardFetchResponse.url);
res = await fetch(TWO_HUNDRED_URL); cachedFetchResponse = await cachedFetch(TWO_HUNDRED_URL);
assert.strictEqual(res.url, TWO_HUNDRED_URL); assert.strictEqual(cachedFetchResponse.url, standardFetchResponse.url);
}); });
it('Has an ok property', async function() { it('Has an ok property', async function() {
res = await fetch(FOUR_HUNDRED_URL); let { cachedFetchResponse, standardFetchResponse } = await dualFetch(FOUR_HUNDRED_URL);
assert.strictEqual(res.ok, false); assert.strictEqual(cachedFetchResponse.ok, standardFetchResponse.ok);
assert.strictEqual(res.status, 400); assert.strictEqual(cachedFetchResponse.status, standardFetchResponse.status);
res = await fetch(FOUR_HUNDRED_URL); cachedFetchResponse = await cachedFetch(FOUR_HUNDRED_URL);
assert.strictEqual(res.ok, false); assert.strictEqual(cachedFetchResponse.ok, standardFetchResponse.ok);
assert.strictEqual(res.status, 400); assert.strictEqual(cachedFetchResponse.status, standardFetchResponse.status);
});
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);
}); });
it('Has a redirected property', async function() { it('Has a redirected property', async function() {
res = await fetch(THREE_HUNDRED_TWO_URL); let { cachedFetchResponse, standardFetchResponse } = await dualFetch(THREE_HUNDRED_TWO_URL);
assert.strictEqual(res.redirected, true); assert.strictEqual(cachedFetchResponse.redirected, standardFetchResponse.redirected);
res = await fetch(THREE_HUNDRED_TWO_URL); cachedFetchResponse = await cachedFetch(THREE_HUNDRED_TWO_URL);
assert.strictEqual(res.redirected, true); 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(
removeDates(cachedFetchResponse.headers.values()),
removeDates([...standardFetchResponse.headers.values()]),
);
cachedFetchResponse = await cachedFetch(TWO_HUNDRED_URL);
assert.deepStrictEqual(
removeDates(cachedFetchResponse.headers.values()),
removeDates([...standardFetchResponse.headers.values()]),
);
});
it('Gets correct header entries', async function() {
let { cachedFetchResponse, standardFetchResponse } = await dualFetch(TWO_HUNDRED_URL);
assert.deepStrictEqual(
removeDates(cachedFetchResponse.headers.entries()),
removeDates([...standardFetchResponse.headers.entries()]),
);
cachedFetchResponse = await cachedFetch(TWO_HUNDRED_URL);
assert.deepStrictEqual(
removeDates(cachedFetchResponse.headers.entries()),
removeDates([...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('Returns undefined for non-existent header', async function() {
const headerName = 'zzzz';
let { cachedFetchResponse, standardFetchResponse } = await dualFetch(TWO_HUNDRED_URL);
assert(!standardFetchResponse.headers.get(headerName));
assert.deepStrictEqual(cachedFetchResponse.headers.get(headerName), standardFetchResponse.headers.get(headerName));
cachedFetchResponse = await cachedFetch(TWO_HUNDRED_URL);
assert.deepStrictEqual(cachedFetchResponse.headers.get(headerName), standardFetchResponse.headers.get(headerName));
});
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'));
}); });
}).timeout(10000); }).timeout(10000);
describe('Cache tests', function() { describe('Cache tests', function() {
it('Uses cache', async function() { it('Uses cache', async function() {
res = await fetch(TWO_HUNDRED_URL); res = await cachedFetch(TWO_HUNDRED_URL);
assert.strictEqual(res.fromCache, false); assert.strictEqual(res.fromCache, false);
res = await fetch(TWO_HUNDRED_URL); res = await cachedFetch(TWO_HUNDRED_URL);
assert.strictEqual(res.fromCache, true); assert.strictEqual(res.fromCache, true);
}); });
it('Can eject from cache', async function() { it('Can eject from cache', async function() {
res = await fetch(TWO_HUNDRED_URL); res = await cachedFetch(TWO_HUNDRED_URL);
assert.strictEqual(res.fromCache, false); assert.strictEqual(res.fromCache, false);
res = await fetch(TWO_HUNDRED_URL); res = await cachedFetch(TWO_HUNDRED_URL);
assert.strictEqual(res.fromCache, true); assert.strictEqual(res.fromCache, true);
await res.ejectFromCache(); await res.ejectFromCache();
res = await fetch(TWO_HUNDRED_URL); res = await cachedFetch(TWO_HUNDRED_URL);
assert.strictEqual(res.fromCache, false); assert.strictEqual(res.fromCache, false);
res = await fetch(TWO_HUNDRED_URL); res = await cachedFetch(TWO_HUNDRED_URL);
assert.strictEqual(res.fromCache, true); assert.strictEqual(res.fromCache, true);
}); });
it('Does not error if rejecting from cache twice', async function() { it('Does not error if ejecting from cache twice', async function() {
res = await fetch(TWO_HUNDRED_URL); res = await cachedFetch(TWO_HUNDRED_URL);
assert.strictEqual(res.fromCache, false); assert.strictEqual(res.fromCache, false);
await res.ejectFromCache(); await res.ejectFromCache();
@ -118,34 +220,34 @@ describe('Cache tests', function() {
}); });
it('Gives different string bodies different cache keys', async 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); 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); assert.strictEqual(res.fromCache, false);
}); });
it('Gives same string bodies same cache keys', async function() { 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); 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); assert.strictEqual(res.fromCache, true);
}); });
it('Gives different URLSearchParams different cache keys', async function() { 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); 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); assert.strictEqual(res.fromCache, false);
}); });
it('Gives same URLSearchParams same cache keys', async function() { 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); 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); assert.strictEqual(res.fromCache, true);
}); });
@ -153,20 +255,20 @@ describe('Cache tests', function() {
const s1 = fs.createReadStream(__filename); const s1 = fs.createReadStream(__filename);
const s2 = fs.createReadStream(path.join(__dirname, '..', 'index.js')); 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); 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); assert.strictEqual(res.fromCache, false);
}); });
it('Gives the same read streams the same cache key', async function() { it('Gives the same read streams the same cache key', async function() {
const s1 = fs.createReadStream(__filename); 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); 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); assert.strictEqual(res.fromCache, true);
}); });
@ -177,10 +279,10 @@ describe('Cache tests', function() {
const data2 = new FormData(); const data2 = new FormData();
data2.append('b', 'b'); 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); 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); assert.strictEqual(res.fromCache, false);
}); });
@ -191,65 +293,75 @@ describe('Cache tests', function() {
const data2 = new FormData(); const data2 = new FormData();
data2.append('a', 'a'); 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); 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); assert.strictEqual(res.fromCache, true);
}); });
}).timeout(10000); }).timeout(10000);
describe('Data tests', function() { describe('Data tests', function() {
it('Does not support Request objects', async function() {
try {
const request = new standardFetch.Request('https://google.com');
await cachedFetch(request);
throw new Error('The above line should have thrown.');
} catch (err) {
assert(err.message.includes('The first argument must be a string (fetch.Request is not supported).'));
}
});
it('Refuses to consume body twice', async function() { it('Refuses to consume body twice', async function() {
res = await fetch(TEXT_BODY_URL); res = await cachedFetch(TEXT_BODY_URL);
await res.text(); await res.text();
try { try {
await res.text(); await res.text();
throw new Error('The above line should have thrown.'); throw new Error('The above line should have thrown.');
} catch (err) { } catch (err) {
// It threw assert(err.message.includes('Error: body used already'));
} }
}); });
it('Can get text body', async function() { it('Can get text body', async function() {
res = await fetch(TEXT_BODY_URL); res = await cachedFetch(TEXT_BODY_URL);
body = await res.text(); body = await res.text();
assert.strictEqual(body, TEXT_BODY_EXPECTED); assert.strictEqual(body, TEXT_BODY_EXPECTED);
assert.strictEqual(res.fromCache, false); assert.strictEqual(res.fromCache, false);
res = await fetch(TEXT_BODY_URL); res = await cachedFetch(TEXT_BODY_URL);
body = await res.text(); body = await res.text();
assert.strictEqual(body, TEXT_BODY_EXPECTED); assert.strictEqual(body, TEXT_BODY_EXPECTED);
assert.strictEqual(res.fromCache, true); assert.strictEqual(res.fromCache, true);
}); });
it('Can get JSON body', async function() { it('Can get JSON body', async function() {
res = await fetch(JSON_BODY_URL); res = await cachedFetch(JSON_BODY_URL);
body = await res.json(); body = await res.json();
assert(body.slideshow); assert(body.slideshow);
assert.strictEqual(res.fromCache, false); assert.strictEqual(res.fromCache, false);
res = await fetch(JSON_BODY_URL); res = await cachedFetch(JSON_BODY_URL);
body = await res.json(); body = await res.json();
assert(body.slideshow); assert(body.slideshow);
assert.strictEqual(res.fromCache, true); assert.strictEqual(res.fromCache, true);
}); });
it('Can get PNG buffer body', async function() { it('Can get PNG buffer body', async function() {
res = await fetch(PNG_BODY_URL); res = await cachedFetch(PNG_BODY_URL);
body = await res.buffer(); body = await res.buffer();
assert.strictEqual(expectedPngBuffer.equals(body), true); assert.strictEqual(expectedPngBuffer.equals(body), true);
assert.strictEqual(res.fromCache, false); assert.strictEqual(res.fromCache, false);
res = await fetch(PNG_BODY_URL); res = await cachedFetch(PNG_BODY_URL);
body = await res.buffer(); body = await res.buffer();
assert.strictEqual(expectedPngBuffer.equals(body), true); assert.strictEqual(expectedPngBuffer.equals(body), true);
assert.strictEqual(res.fromCache, true); assert.strictEqual(res.fromCache, true);
}); });
it('Can stream a body', async function() { it('Can stream a body', async function() {
res = await fetch(TEXT_BODY_URL); res = await cachedFetch(TEXT_BODY_URL);
body = ''; body = '';
for await (const chunk of res.body) { for await (const chunk of res.body) {
@ -259,7 +371,7 @@ describe('Data tests', function() {
assert.strictEqual(TEXT_BODY_EXPECTED, body); assert.strictEqual(TEXT_BODY_EXPECTED, body);
assert.strictEqual(res.fromCache, false); assert.strictEqual(res.fromCache, false);
res = await fetch(TEXT_BODY_URL); res = await cachedFetch(TEXT_BODY_URL);
body = ''; body = '';
for await (const chunk of res.body) { for await (const chunk of res.body) {
@ -269,4 +381,56 @@ describe('Data tests', function() {
assert.strictEqual(TEXT_BODY_EXPECTED, body); assert.strictEqual(TEXT_BODY_EXPECTED, body);
assert.strictEqual(res.fromCache, true); assert.strictEqual(res.fromCache, true);
}); });
it('Errors if the body type is not supported', async function() {
try {
await cachedFetch(TEXT_BODY_URL, { body: {} });
throw new Error('It was supposed to throw');
} catch (err) {
assert(err.message.includes('Unsupported body type'));
}
});
}).timeout(10000); }).timeout(10000);
describe('Memory cache tests', function() {
it('Supports TTL', async function() {
cachedFetch = FetchCache.withCache(new FetchCache.MemoryCache({ ttl: 100 }));
let res = await cachedFetch(TWO_HUNDRED_URL);
assert.strictEqual(res.fromCache, false);
res = await cachedFetch(TWO_HUNDRED_URL);
assert.strictEqual(res.fromCache, true);
await wait(200);
res = await cachedFetch(TWO_HUNDRED_URL);
assert.strictEqual(res.fromCache, false);
});
}).timeout(10000);
describe('File system cache tests', function() {
it('Supports TTL', async function() {
cachedFetch = FetchCache.withCache(new FetchCache.FileSystemCache({ ttl: 100 }));
let res = await cachedFetch(TWO_HUNDRED_URL);
assert.strictEqual(res.fromCache, false);
res = await cachedFetch(TWO_HUNDRED_URL);
assert.strictEqual(res.fromCache, true);
await wait(200);
res = await cachedFetch(TWO_HUNDRED_URL);
assert.strictEqual(res.fromCache, false);
});
it('Can get PNG buffer body', async function() {
cachedFetch = FetchCache.withCache(new FetchCache.FileSystemCache());
res = await cachedFetch(PNG_BODY_URL);
body = await res.buffer();
assert.strictEqual(expectedPngBuffer.equals(body), true);
assert.strictEqual(res.fromCache, false);
res = await cachedFetch(PNG_BODY_URL);
body = await res.buffer();
assert.strictEqual(expectedPngBuffer.equals(body), true);
assert.strictEqual(res.fromCache, true);
});
});