-
Notifications
You must be signed in to change notification settings - Fork 223
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(fetch-requester): add
@algolia/requester-fetch
(#1411)
* feat(fetch-requester): add `@algolia/requester-fetch` * chore: unpin cross-fetch Co-authored-by: Haroen Viaene <[email protected]>
- Loading branch information
Showing
11 changed files
with
428 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"extends": "../../api-extractor.json", | ||
"mainEntryPointFilePath": "./dist/packages/<unscopedPackageName>/src/index.d.ts", | ||
"dtsRollup": { | ||
"untrimmedFilePath": "./dist/<unscopedPackageName>.d.ts" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
// eslint-disable-next-line functional/immutable-data, import/no-commonjs | ||
module.exports = require('./dist/requester-fetch.cjs.js'); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
{ | ||
"name": "@algolia/requester-fetch", | ||
"version": "4.13.1", | ||
"private": false, | ||
"description": "Promise-based request library for Fetch.", | ||
"repository": { | ||
"type": "git", | ||
"url": "git://github.com/algolia/algoliasearch-client-javascript.git" | ||
}, | ||
"license": "MIT", | ||
"sideEffects": false, | ||
"main": "index.js", | ||
"module": "dist/requester-fetch.esm.js", | ||
"types": "dist/requester-fetch.d.ts", | ||
"files": [ | ||
"index.js", | ||
"dist" | ||
], | ||
"dependencies": { | ||
"@algolia/requester-common": "4.13.1" | ||
} | ||
} |
270 changes: 270 additions & 0 deletions
270
packages/requester-fetch/src/__tests__/unit/fetch-requester.test.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,270 @@ | ||
import { MethodEnum, Request } from '@algolia/requester-common'; | ||
import crossFetch from 'cross-fetch'; | ||
import nock from 'nock'; | ||
// @ts-ignore | ||
import { Readable } from 'readable-stream'; | ||
|
||
import { createFetchRequester } from '../..'; | ||
|
||
const originalFetch = window.fetch; | ||
|
||
beforeEach(() => { | ||
window.fetch = crossFetch; | ||
}); | ||
|
||
afterEach(() => { | ||
window.fetch = originalFetch; | ||
}); | ||
|
||
const requester = createFetchRequester(); | ||
|
||
const headers = { | ||
'content-type': 'application/x-www-form-urlencoded', | ||
}; | ||
|
||
const timeoutRequest: Request = { | ||
url: 'missing-url-here', | ||
data: '', | ||
headers: {}, | ||
method: 'GET', | ||
responseTimeout: 5, | ||
connectTimeout: 2, | ||
}; | ||
|
||
const requestStub: Request = { | ||
url: 'https://algolia-dns.net/foo?x-algolia-header=foo', | ||
method: MethodEnum.Post, | ||
headers: { | ||
'Content-Type': 'application/x-www-form-urlencoded', | ||
}, | ||
data: JSON.stringify({ foo: 'bar' }), | ||
responseTimeout: 2, | ||
connectTimeout: 1, | ||
}; | ||
|
||
describe('status code handling', () => { | ||
it('sends requests', async () => { | ||
const body = JSON.stringify({ foo: 'bar' }); | ||
|
||
nock('https://algolia-dns.net', { reqheaders: headers }) | ||
.post('/foo') | ||
.query({ 'x-algolia-header': 'foo' }) | ||
.reply(200, body); | ||
|
||
const response = await requester.send(requestStub); | ||
|
||
expect(response.content).toEqual(JSON.stringify({ foo: 'bar' })); | ||
}); | ||
|
||
it('resolves status 200', async () => { | ||
const body = JSON.stringify({ foo: 'bar' }); | ||
|
||
nock('https://algolia-dns.net', { reqheaders: headers }) | ||
.post('/foo') | ||
.query({ 'x-algolia-header': 'foo' }) | ||
.reply(200, body); | ||
|
||
const response = await requester.send(requestStub); | ||
|
||
expect(response.status).toBe(200); | ||
expect(response.content).toBe(body); | ||
expect(response.isTimedOut).toBe(false); | ||
}); | ||
|
||
it('resolves status 300', async () => { | ||
const reason = 'Multiple Choices'; | ||
|
||
nock('https://algolia-dns.net', { reqheaders: headers }) | ||
.post('/foo') | ||
.query({ 'x-algolia-header': 'foo' }) | ||
.reply(300, reason); | ||
|
||
const response = await requester.send(requestStub); | ||
|
||
expect(response.status).toBe(300); | ||
expect(response.content).toBe(reason); | ||
expect(response.isTimedOut).toBe(false); | ||
}); | ||
|
||
it('resolves status 400', async () => { | ||
const body = { message: 'Invalid Application-Id or API-Key' }; | ||
|
||
nock('https://algolia-dns.net', { reqheaders: headers }) | ||
.post('/foo') | ||
.query({ 'x-algolia-header': 'foo' }) | ||
.reply(400, JSON.stringify(body)); | ||
|
||
const response = await requester.send(requestStub); | ||
|
||
expect(response.status).toBe(400); | ||
expect(response.content).toBe(JSON.stringify(body)); | ||
expect(response.isTimedOut).toBe(false); | ||
}); | ||
|
||
it('handles chunked responses inside unicode character boundaries', async () => { | ||
const testdata = Buffer.from('äöü'); | ||
|
||
// create a test response stream that is chunked inside a unicode character | ||
function* generate() { | ||
yield testdata.slice(0, 3); | ||
yield testdata.slice(3); | ||
} | ||
|
||
const testStream = Readable.from(generate()); | ||
|
||
nock('https://algolia-dns.net', { reqheaders: headers }) | ||
.post('/foo') | ||
.query({ 'x-algolia-header': 'foo' }) | ||
.reply(200, testStream); | ||
|
||
const response = await requester.send(requestStub); | ||
|
||
expect(response.content).toEqual(testdata.toString()); | ||
}); | ||
}); | ||
|
||
describe('timeout handling', () => { | ||
it('timeouts with the given 1 seconds connection timeout', async () => { | ||
const before = Date.now(); | ||
const response = await requester.send({ | ||
...timeoutRequest, | ||
...{ connectTimeout: 1, url: 'http://www.google.com:81' }, | ||
}); | ||
|
||
const now = Date.now(); | ||
|
||
expect(response.content).toBe('Connection timeout'); | ||
expect(now - before).toBeGreaterThan(999); | ||
expect(now - before).toBeLessThan(1200); | ||
}); | ||
|
||
it('connection timeouts with the given 2 seconds connection timeout', async () => { | ||
const before = Date.now(); | ||
const response = await requester.send({ | ||
...timeoutRequest, | ||
...{ connectTimeout: 2, url: 'http://www.google.com:81' }, | ||
}); | ||
|
||
const now = Date.now(); | ||
|
||
expect(response.content).toBe('Connection timeout'); | ||
expect(now - before).toBeGreaterThan(1999); | ||
expect(now - before).toBeLessThan(2200); | ||
}); | ||
|
||
it('socket timeouts if response dont appears before the timeout with 2 seconds timeout', async () => { | ||
const before = Date.now(); | ||
|
||
const response = await requester.send({ | ||
...timeoutRequest, | ||
...{ responseTimeout: 2, url: 'http://localhost:1111/' }, | ||
}); | ||
|
||
const now = Date.now(); | ||
|
||
expect(now - before).toBeGreaterThan(1999); | ||
expect(now - before).toBeLessThan(2200); | ||
expect(response.content).toBe('Socket timeout'); | ||
}); | ||
|
||
it('socket timeouts if response dont appears before the timeout with 3 seconds timeout', async () => { | ||
const before = Date.now(); | ||
const response = await requester.send({ | ||
...timeoutRequest, | ||
...{ | ||
responseTimeout: 3, | ||
url: 'http://localhost:1111', | ||
}, | ||
}); | ||
|
||
const now = Date.now(); | ||
|
||
expect(response.content).toBe('Socket timeout'); | ||
expect(now - before).toBeGreaterThan(2999); | ||
expect(now - before).toBeLessThan(3200); | ||
}); | ||
|
||
it('do not timeouts if response appears before the timeout', async () => { | ||
const request = Object.assign({}, requestStub); | ||
const before = Date.now(); | ||
const response = await requester.send({ | ||
...request, | ||
url: 'http://localhost:1111', | ||
responseTimeout: 6, // the fake server sleeps for 5 seconds... | ||
}); | ||
|
||
const now = Date.now(); | ||
|
||
expect(response.isTimedOut).toBe(false); | ||
expect(response.status).toBe(200); | ||
expect(response.content).toBe('{"foo": "bar"}'); | ||
expect(now - before).toBeGreaterThan(4999); | ||
expect(now - before).toBeLessThan(5200); | ||
}); | ||
}); | ||
|
||
describe('error handling', (): void => { | ||
it('resolves dns not found', async () => { | ||
const request = { | ||
url: 'https://this-dont-exist.algolia.com', | ||
method: MethodEnum.Post, | ||
headers: { | ||
'X-Algolia-Application-Id': 'ABCDE', | ||
'X-Algolia-API-Key': '12345', | ||
'Content-Type': 'application/x-www-form-urlencoded', | ||
}, | ||
data: JSON.stringify({ foo: 'bar' }), | ||
responseTimeout: 2, | ||
connectTimeout: 1, | ||
}; | ||
|
||
const response = await requester.send(request); | ||
|
||
expect(response.status).toBe(0); | ||
expect(response.content).toContain(''); | ||
expect(response.isTimedOut).toBe(false); | ||
}); | ||
|
||
it('resolves general network errors', async () => { | ||
nock('https://algolia-dns.net', { reqheaders: headers }) | ||
.post('/foo') | ||
.query({ 'x-algolia-header': 'foo' }) | ||
.replyWithError('This is a general error'); | ||
|
||
const response = await requester.send(requestStub); | ||
|
||
expect(response.status).toBe(0); | ||
expect(response.content).toBe( | ||
'request to https://algolia-dns.net/foo?x-algolia-header=foo failed, reason: This is a general error' | ||
); | ||
expect(response.isTimedOut).toBe(false); | ||
}); | ||
}); | ||
|
||
describe('requesterOptions', () => { | ||
it('allows to pass requesterOptions', async () => { | ||
const body = JSON.stringify({ foo: 'bar' }); | ||
const requesterTmp = createFetchRequester({ | ||
requesterOptions: { | ||
headers: { | ||
'x-algolia-foo': 'bar', | ||
}, | ||
}, | ||
}); | ||
|
||
nock('https://algolia-dns.net', { | ||
reqheaders: { | ||
...headers, | ||
'x-algolia-foo': 'bar', | ||
}, | ||
}) | ||
.post('/foo') | ||
.query({ 'x-algolia-header': 'foo' }) | ||
.reply(200, body); | ||
|
||
const response = await requesterTmp.send(requestStub); | ||
|
||
expect(response.status).toBe(200); | ||
expect(response.content).toBe(body); | ||
}); | ||
}); |
Oops, something went wrong.