feat(http): implement fetch-based http client
This commit is contained in:
parent
79ada57a3b
commit
c0659a8d5d
11 changed files with 6121 additions and 151 deletions
92
packages/http/README.md
Normal file
92
packages/http/README.md
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
# HTTP
|
||||
|
||||
[//]: # 'AUTO INSERT HEADER PREPUBLISH'
|
||||
|
||||
`http` is a small wrapper around `fetch` which:
|
||||
|
||||
- Allows globally registering request and response transformers
|
||||
- Throws on 4xx and 5xx status codes
|
||||
- Supports a JSON request which automatically encodes/decodes body request and response payload as JSON
|
||||
- Adds accept-language header to requests based on application language
|
||||
- Adds XSRF header to request if the cookie is present
|
||||
|
||||
## How to use
|
||||
|
||||
### Installation
|
||||
|
||||
```sh
|
||||
npm i --save @lion/http
|
||||
```
|
||||
|
||||
### Relation to fetch
|
||||
|
||||
`http` delegates all requests to fetch. `http.request` and `http.requestJson` have the same function signature as `window.fetch`, you can use any online resource to learn more about fetch. [MDN](https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch) is a great start.
|
||||
|
||||
### Example requests
|
||||
|
||||
#### GET request
|
||||
|
||||
```js
|
||||
import { http } from '@lion/http';
|
||||
|
||||
const response = await http.request('/api/users');
|
||||
const users = await response.json();
|
||||
```
|
||||
|
||||
#### POST request
|
||||
|
||||
```js
|
||||
import { http } from '@lion/http';
|
||||
|
||||
const response = await http.request('/api/users', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ username: 'steve' }),
|
||||
});
|
||||
const newUser = await response.json();
|
||||
```
|
||||
|
||||
### JSON requests
|
||||
|
||||
We usually deal with JSON requests and responses. With `requestJson` you don't need to specifically stringify the request body or parse the response body:
|
||||
|
||||
#### GET JSON request
|
||||
|
||||
```js
|
||||
import { http } from '@lion/http';
|
||||
|
||||
const { response, body } = await http.requestJson('/api/users');
|
||||
```
|
||||
|
||||
#### POST JSON request
|
||||
|
||||
```js
|
||||
import { http } from '@lion/http';
|
||||
|
||||
const { response, body } = await http.requestJson('/api/users', {
|
||||
method: 'POST',
|
||||
body: { username: 'steve' },
|
||||
});
|
||||
```
|
||||
|
||||
### Error handling
|
||||
|
||||
Different from fetch, `http` throws when the server returns a 4xx or 5xx, returning the request and response:
|
||||
|
||||
```js
|
||||
import { http } from '@lion/http';
|
||||
|
||||
try {
|
||||
const users = await http.requestJson('/api/users');
|
||||
} catch (error) {
|
||||
if (error.response) {
|
||||
if (error.response.status === 400) {
|
||||
// handle a specific status code, for example 400 bad request
|
||||
} else {
|
||||
console.error(error);
|
||||
}
|
||||
} else {
|
||||
// an error happened before receiving a response, ex. an incorrect request or network error
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
```
|
||||
7
packages/http/index.js
Normal file
7
packages/http/index.js
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
export { http, setHttp } from './src/http.js';
|
||||
export { HttpClient } from './src/HttpClient.js';
|
||||
|
||||
export {
|
||||
acceptLanguageRequestTransformer,
|
||||
createXSRFRequestTransformer,
|
||||
} from './src/transformers.js';
|
||||
44
packages/http/package.json
Normal file
44
packages/http/package.json
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
{
|
||||
"name": "@lion/http",
|
||||
"version": "0.0.0",
|
||||
"description": "Thin wrapper around fetch.",
|
||||
"author": "ing-bank",
|
||||
"homepage": "https://github.com/ing-bank/lion/",
|
||||
"license": "MIT",
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/ing-bank/lion.git",
|
||||
"directory": "packages/http"
|
||||
},
|
||||
"scripts": {
|
||||
"prepublishOnly": "../../scripts/npm-prepublish.js"
|
||||
},
|
||||
"keywords": [
|
||||
"lion",
|
||||
"web-components",
|
||||
"fetch",
|
||||
"ajax",
|
||||
"http"
|
||||
],
|
||||
"main": "index.js",
|
||||
"module": "index.js",
|
||||
"files": [
|
||||
"docs",
|
||||
"src",
|
||||
"stories",
|
||||
"test",
|
||||
"translations",
|
||||
"*.js"
|
||||
],
|
||||
"dependencies": {
|
||||
"@lion/localize": "^0.4.14"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@open-wc/demoing-storybook": "^0.2.0",
|
||||
"@open-wc/testing": "^2.3.4",
|
||||
"sinon": "^7.2.2"
|
||||
}
|
||||
}
|
||||
158
packages/http/src/HttpClient.js
Normal file
158
packages/http/src/HttpClient.js
Normal file
|
|
@ -0,0 +1,158 @@
|
|||
/* eslint-disable consistent-return */
|
||||
import { acceptLanguageRequestTransformer, createXSRFRequestTransformer } from './transformers.js';
|
||||
import { HttpClientFetchError } from './HttpClientFetchError.js';
|
||||
|
||||
/**
|
||||
* @typedef {Object} HttpClientConfig configuration for the HttpClient instance
|
||||
* @property {boolean} addAcceptLanguage the Accept-Language request HTTP header advertises
|
||||
* which languages the client is able to understand, and which locale variant is preferred.
|
||||
* @property {string} [xsrfCookieName] name of the XSRF cookie to read from
|
||||
* @property {string} [xsrfHeaderName] name of the XSRF header to set
|
||||
* @property {string} [jsonPrefix] the json prefix to use when fetching json (if any)
|
||||
*/
|
||||
|
||||
/**
|
||||
* Transforms a Request before fetching. Must return an instance of Request or Response.
|
||||
* If a Respone is returned, the network call is skipped and it is returned as is.
|
||||
* @typedef {(request: Request) => Request | Response} RequestTransformer
|
||||
*/
|
||||
|
||||
/**
|
||||
* Transforms a Response before returning. Must return an instance of Response.
|
||||
* @typedef {(response: Response) => Response} ResponseTransformer
|
||||
*/
|
||||
|
||||
/**
|
||||
* HTTP Client which acts as a small wrapper around `fetch`. Allows registering hooks which
|
||||
* transform request and responses, for example to add authorization headers or logging. A
|
||||
* request can also be prevented from reaching the network at all by returning the Response directly.
|
||||
*/
|
||||
export class HttpClient {
|
||||
/**
|
||||
* @param {HttpClientConfig} config
|
||||
*/
|
||||
constructor(config = {}) {
|
||||
const {
|
||||
addAcceptLanguage = true,
|
||||
xsrfCookieName = 'XSRF-TOKEN',
|
||||
xsrfHeaderName = 'X-XSRF-TOKEN',
|
||||
jsonPrefix,
|
||||
} = config;
|
||||
|
||||
/** @type {string | undefined} */
|
||||
this._jsonPrefix = jsonPrefix;
|
||||
/** @type {RequestTransformer[]} */
|
||||
this._requestTransformers = [];
|
||||
/** @type {ResponseTransformer[]} */
|
||||
this._responseTransformers = [];
|
||||
|
||||
if (addAcceptLanguage) {
|
||||
this.addRequestTransformer(acceptLanguageRequestTransformer);
|
||||
}
|
||||
|
||||
if (xsrfCookieName && xsrfHeaderName) {
|
||||
this.addRequestTransformer(createXSRFRequestTransformer(xsrfCookieName, xsrfHeaderName));
|
||||
}
|
||||
}
|
||||
|
||||
/** @param {RequestTransformer} requestTransformer */
|
||||
addRequestTransformer(requestTransformer) {
|
||||
this._requestTransformers.push(requestTransformer);
|
||||
}
|
||||
|
||||
/** @param {RequestTransformer} requestTransformer */
|
||||
removeRequestTransformer(requestTransformer) {
|
||||
const indexOf = this._requestTransformers.indexOf(requestTransformer);
|
||||
if (indexOf !== -1) {
|
||||
this._requestTransformers.splice(indexOf);
|
||||
}
|
||||
}
|
||||
|
||||
/** @param {ResponseTransformer} responseTransformer */
|
||||
addResponseTransformer(responseTransformer) {
|
||||
this._responseTransformers.push(responseTransformer);
|
||||
}
|
||||
|
||||
/** @param {ResponseTransformer} responseTransformer */
|
||||
removeResponseTransformer(responseTransformer) {
|
||||
const indexOf = this._responseTransformers.indexOf(responseTransformer);
|
||||
if (indexOf !== -1) {
|
||||
this._responseTransformers.splice(indexOf, 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a fetch request, calling the registered fetch request and response
|
||||
* transformers.
|
||||
*
|
||||
* @param {RequestInfo} info
|
||||
* @param {RequestInit} [init]
|
||||
* @returns {Promise<Response>}
|
||||
*/
|
||||
async request(info, init) {
|
||||
const request = new Request(info, init);
|
||||
/** @type {Request | Response} */
|
||||
let transformedRequestOrResponse = request;
|
||||
|
||||
// run request transformers, returning directly and skipping the network
|
||||
// if a transformer returns a Response
|
||||
this._requestTransformers.forEach(transform => {
|
||||
transformedRequestOrResponse = transform(transformedRequestOrResponse);
|
||||
if (transformedRequestOrResponse instanceof Response) {
|
||||
return transformedRequestOrResponse;
|
||||
}
|
||||
});
|
||||
|
||||
const response = await fetch(transformedRequestOrResponse);
|
||||
const transformedResponse = this._responseTransformers.reduce(
|
||||
(prev, transform) => transform(prev),
|
||||
response,
|
||||
);
|
||||
if (transformedResponse.status >= 400 && transformedResponse.status < 600) {
|
||||
throw new HttpClientFetchError(request, transformedResponse);
|
||||
}
|
||||
return transformedResponse;
|
||||
}
|
||||
|
||||
/**
|
||||
* Makes a fetch request, calling the registered fetch request and response
|
||||
* transformers. Encodes/decodes the request and response body as JSON.
|
||||
*
|
||||
* @param {RequestInfo} info
|
||||
* @param {RequestInit} [init]
|
||||
* @template T
|
||||
* @returns {Promise<{ response: Response, body: T }>}
|
||||
*/
|
||||
async requestJson(info, init) {
|
||||
const jsonInit = {
|
||||
...init,
|
||||
headers: {
|
||||
...(init && init.headers),
|
||||
accept: 'application/json',
|
||||
},
|
||||
};
|
||||
|
||||
if (init && init.body) {
|
||||
jsonInit.headers['content-type'] = 'application/json';
|
||||
jsonInit.body = JSON.stringify(init.body);
|
||||
}
|
||||
|
||||
const response = await this.request(info, jsonInit);
|
||||
let responseText = await response.text();
|
||||
|
||||
if (typeof this._jsonPrefix === 'string') {
|
||||
if (responseText.startsWith(this._jsonPrefix)) {
|
||||
responseText = responseText.substring(this._jsonPrefix.length);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return {
|
||||
response,
|
||||
body: JSON.parse(responseText),
|
||||
};
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to parse response from ${response.url} as JSON.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
8
packages/http/src/HttpClientFetchError.js
Normal file
8
packages/http/src/HttpClientFetchError.js
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
export class HttpClientFetchError extends Error {
|
||||
constructor(request, response) {
|
||||
super(`Fetch request to ${request.url} failed.`);
|
||||
|
||||
this.request = request;
|
||||
this.response = response;
|
||||
}
|
||||
}
|
||||
14
packages/http/src/http.js
Normal file
14
packages/http/src/http.js
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
import { HttpClient } from './HttpClient.js';
|
||||
|
||||
export let http = new HttpClient(); // eslint-disable-line import/no-mutable-exports
|
||||
|
||||
/**
|
||||
* setHttp allows the Application Developer to override the globally used instance of {@link:http}.
|
||||
* All interactions with {@link:http} after the call to setHttp will use this new instance
|
||||
* (so make sure to call this method before dependant code using {@link:http} is ran and this
|
||||
* method is not called by any of your (indirect) dependencies.)
|
||||
* @param {HttpClient} newHttp the globally used instance of {@link:http}.
|
||||
*/
|
||||
export function setHttp(newHttp) {
|
||||
http = newHttp;
|
||||
}
|
||||
50
packages/http/src/transformers.js
Normal file
50
packages/http/src/transformers.js
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
import { localize } from '@lion/localize';
|
||||
|
||||
/**
|
||||
* @typedef {import('./HttpClient').RequestTransformer} RequestTransformer
|
||||
*/
|
||||
|
||||
/**
|
||||
* @param {string} name the cookie name
|
||||
* @param {Document} _document overwriteable for testing
|
||||
* @returns {string | null}
|
||||
*/
|
||||
export function getCookie(name, _document = document) {
|
||||
const match = _document.cookie.match(new RegExp(`(^|;\\s*)(${name})=([^;]*)`));
|
||||
return match ? decodeURIComponent(match[3]) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms a request, adding an accept-language header with the current application's locale
|
||||
* if it has not already been set.
|
||||
* @type {RequestTransformer}
|
||||
*/
|
||||
export function acceptLanguageRequestTransformer(request) {
|
||||
if (!request.headers.has('accept-language')) {
|
||||
request.headers.set('accept-language', localize.locale);
|
||||
}
|
||||
return request;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a request transformer that adds a XSRF header for protecting
|
||||
* against cross-site request forgery.
|
||||
* @param {string} cookieName the cookie name
|
||||
* @param {string} headerName the header name
|
||||
* @param {Document} _document overwriteable for testing
|
||||
* @returns {RequestTransformer}
|
||||
*/
|
||||
export function createXSRFRequestTransformer(cookieName, headerName, _document = document) {
|
||||
/**
|
||||
* @type {RequestTransformer}
|
||||
*/
|
||||
function xsrfRequestTransformer(request) {
|
||||
const xsrfToken = getCookie(cookieName, _document);
|
||||
if (xsrfToken) {
|
||||
request.headers.set(headerName, xsrfToken);
|
||||
}
|
||||
return request;
|
||||
}
|
||||
|
||||
return xsrfRequestTransformer;
|
||||
}
|
||||
199
packages/http/test/HttpClient.test.js
Normal file
199
packages/http/test/HttpClient.test.js
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
import { expect } from '@open-wc/testing';
|
||||
import { stub } from 'sinon';
|
||||
import { localize } from '@lion/localize';
|
||||
import { HttpClient } from '../src/HttpClient.js';
|
||||
import { HttpClientFetchError } from '../src/HttpClientFetchError.js';
|
||||
|
||||
describe('HttpClient', () => {
|
||||
/** @type {import('sinon').SinonStub} */
|
||||
let fetchStub;
|
||||
/** @type {HttpClient} */
|
||||
let http;
|
||||
|
||||
beforeEach(() => {
|
||||
fetchStub = stub(window, 'fetch');
|
||||
fetchStub.returns(Promise.resolve('mock response'));
|
||||
http = new HttpClient();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
fetchStub.restore();
|
||||
});
|
||||
|
||||
describe('request()', () => {
|
||||
it('calls fetch with the given args, returning the result', async () => {
|
||||
const response = await http.request('/foo', { method: 'POST' });
|
||||
|
||||
expect(fetchStub).to.have.been.calledOnce;
|
||||
const request = fetchStub.getCall(0).args[0];
|
||||
expect(request.url).to.equal(`${window.location.origin}/foo`);
|
||||
expect(request.method).to.equal('POST');
|
||||
expect(response).to.equal('mock response');
|
||||
});
|
||||
|
||||
it('throws on 4xx responses', async () => {
|
||||
fetchStub.returns(Promise.resolve(new Response('', { status: 400 })));
|
||||
|
||||
let thrown = false;
|
||||
try {
|
||||
await http.request('/foo');
|
||||
} catch (e) {
|
||||
expect(e).to.be.an.instanceOf(HttpClientFetchError);
|
||||
expect(e.request).to.be.an.instanceOf(Request);
|
||||
expect(e.response).to.be.an.instanceOf(Response);
|
||||
thrown = true;
|
||||
}
|
||||
expect(thrown).to.be.true;
|
||||
});
|
||||
|
||||
it('throws on 5xx responses', async () => {
|
||||
fetchStub.returns(Promise.resolve(new Response('', { status: 599 })));
|
||||
|
||||
let thrown = false;
|
||||
try {
|
||||
await http.request('/foo');
|
||||
} catch (e) {
|
||||
expect(e).to.be.an.instanceOf(HttpClientFetchError);
|
||||
expect(e.request).to.be.an.instanceOf(Request);
|
||||
expect(e.response).to.be.an.instanceOf(Response);
|
||||
thrown = true;
|
||||
}
|
||||
expect(thrown).to.be.true;
|
||||
});
|
||||
});
|
||||
|
||||
describe('requestJson', () => {
|
||||
beforeEach(() => {
|
||||
fetchStub.returns(Promise.resolve(new Response('{}')));
|
||||
});
|
||||
|
||||
it('sets json accept header', async () => {
|
||||
await http.requestJson('/foo');
|
||||
const request = fetchStub.getCall(0).args[0];
|
||||
expect(request.headers.get('accept')).to.equal('application/json');
|
||||
});
|
||||
|
||||
it('decodes response from json', async () => {
|
||||
fetchStub.returns(Promise.resolve(new Response('{"a":1,"b":2}')));
|
||||
const response = await http.requestJson('/foo');
|
||||
expect(response.body).to.eql({ a: 1, b: 2 });
|
||||
});
|
||||
|
||||
describe('given a request body', () => {
|
||||
it('encodes the request body as json', async () => {
|
||||
await http.requestJson('/foo', { method: 'POST', body: { a: 1, b: 2 } });
|
||||
const request = fetchStub.getCall(0).args[0];
|
||||
expect(await request.text()).to.equal('{"a":1,"b":2}');
|
||||
});
|
||||
|
||||
it('sets json content-type header', async () => {
|
||||
await http.requestJson('/foo', { method: 'POST', body: { a: 1, b: 2 } });
|
||||
const request = fetchStub.getCall(0).args[0];
|
||||
expect(request.headers.get('content-type')).to.equal('application/json');
|
||||
});
|
||||
});
|
||||
|
||||
describe('given a json prefix', () => {
|
||||
it('strips json prefix from response before decoding', async () => {
|
||||
const localHttp = new HttpClient({ jsonPrefix: '//.,!' });
|
||||
fetchStub.returns(Promise.resolve(new Response('//.,!{"a":1,"b":2}')));
|
||||
const response = await localHttp.requestJson('/foo');
|
||||
expect(response.body).to.eql({ a: 1, b: 2 });
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('request and response transformers', () => {
|
||||
it('addRequestTransformer() adds a function which transforms the request', async () => {
|
||||
http.addRequestTransformer(r => new Request(`${r.url}/transformed-1`));
|
||||
http.addRequestTransformer(r => new Request(`${r.url}/transformed-2`));
|
||||
|
||||
await http.request('/foo', { method: 'POST' });
|
||||
|
||||
const request = fetchStub.getCall(0).args[0];
|
||||
expect(request.url).to.equal(`${window.location.origin}/foo/transformed-1/transformed-2`);
|
||||
});
|
||||
|
||||
it('addResponseTransformer() adds a function which transforms the response', async () => {
|
||||
http.addResponseTransformer(r => `${r} transformed-1`);
|
||||
http.addResponseTransformer(r => `${r} transformed-2`);
|
||||
|
||||
const response = await http.request('/foo', { method: 'POST' });
|
||||
expect(response).to.equal('mock response transformed-1 transformed-2');
|
||||
});
|
||||
|
||||
it('removeRequestTransformer() removes a request transformer', async () => {
|
||||
const transformer = r => new Request(`${r.url}/transformed-1`);
|
||||
http.addRequestTransformer(transformer);
|
||||
http.removeRequestTransformer(transformer);
|
||||
|
||||
await http.request('/foo', { method: 'POST' });
|
||||
|
||||
const request = fetchStub.getCall(0).args[0];
|
||||
expect(request.url).to.equal(`${window.location.origin}/foo`);
|
||||
});
|
||||
|
||||
it('removeResponseTransformer() removes a request transformer', async () => {
|
||||
const transformer = r => `${r} transformed-1`;
|
||||
http.addResponseTransformer(transformer);
|
||||
http.removeResponseTransformer(transformer);
|
||||
|
||||
const response = await http.request('/foo', { method: 'POST' });
|
||||
expect(response).to.equal('mock response');
|
||||
});
|
||||
});
|
||||
|
||||
describe('accept-language header', () => {
|
||||
it('is set by default based on localize.locale', async () => {
|
||||
await http.request('/foo');
|
||||
const request = fetchStub.getCall(0).args[0];
|
||||
expect(request.headers.get('accept-language')).to.equal(localize.locale);
|
||||
});
|
||||
|
||||
it('can be disabled', async () => {
|
||||
const customHttp = new HttpClient({ addAcceptLanguage: false });
|
||||
await customHttp.request('/foo');
|
||||
const request = fetchStub.getCall(0).args[0];
|
||||
expect(request.headers.has('accept-language')).to.be.false;
|
||||
});
|
||||
});
|
||||
|
||||
describe('XSRF token', () => {
|
||||
let cookieStub;
|
||||
beforeEach(() => {
|
||||
cookieStub = stub(document, 'cookie');
|
||||
cookieStub.get(() => 'foo=bar;XSRF-TOKEN=1234; CSRF-TOKEN=5678;lorem=ipsum;');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cookieStub.restore();
|
||||
});
|
||||
|
||||
it('XSRF token header is set based on cookie', async () => {
|
||||
await http.request('/foo');
|
||||
|
||||
const request = fetchStub.getCall(0).args[0];
|
||||
expect(request.headers.get('X-XSRF-TOKEN')).to.equal('1234');
|
||||
});
|
||||
|
||||
it('XSRF behavior can be disabled', async () => {
|
||||
const customHttp = new HttpClient({ xsrfCookieName: null, xsrfHeaderName: null });
|
||||
await customHttp.request('/foo');
|
||||
await http.request('/foo');
|
||||
|
||||
const request = fetchStub.getCall(0).args[0];
|
||||
expect(request.headers.has('X-XSRF-TOKEN')).to.be.false;
|
||||
});
|
||||
|
||||
it('XSRF token header and cookie can be customized', async () => {
|
||||
const customHttp = new HttpClient({
|
||||
xsrfCookieName: 'CSRF-TOKEN',
|
||||
xsrfHeaderName: 'X-CSRF-TOKEN',
|
||||
});
|
||||
await customHttp.request('/foo');
|
||||
|
||||
const request = fetchStub.getCall(0).args[0];
|
||||
expect(request.headers.get('X-CSRF-TOKEN')).to.equal('5678');
|
||||
});
|
||||
});
|
||||
});
|
||||
15
packages/http/test/http.test.js
Normal file
15
packages/http/test/http.test.js
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
import { expect } from '@open-wc/testing';
|
||||
import { http, setHttp } from '../src/http.js';
|
||||
import { HttpClient } from '../src/HttpClient.js';
|
||||
|
||||
describe('http', () => {
|
||||
it('exports an instance of HttpClient', () => {
|
||||
expect(http).to.be.an.instanceOf(HttpClient);
|
||||
});
|
||||
|
||||
it('can replace http with another instance', () => {
|
||||
const newHttp = new HttpClient();
|
||||
setHttp(newHttp);
|
||||
expect(http).to.equal(newHttp);
|
||||
});
|
||||
});
|
||||
63
packages/http/test/transformers.test.js
Normal file
63
packages/http/test/transformers.test.js
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
import { expect } from '@open-wc/testing';
|
||||
import { localize } from '@lion/localize';
|
||||
import {
|
||||
createXSRFRequestTransformer,
|
||||
getCookie,
|
||||
acceptLanguageRequestTransformer,
|
||||
} from '../src/transformers.js';
|
||||
|
||||
describe('transformers', () => {
|
||||
describe('getCookie()', () => {
|
||||
it('returns the cookie value', () => {
|
||||
expect(getCookie('foo', { cookie: 'foo=bar' })).to.equal('bar');
|
||||
});
|
||||
|
||||
it('returns the cookie value when there are multiple cookies', () => {
|
||||
expect(getCookie('foo', { cookie: 'foo=bar; bar=foo;lorem=ipsum' })).to.equal('bar');
|
||||
});
|
||||
|
||||
it('returns null when the cookie cannot be found', () => {
|
||||
expect(getCookie('foo', { cookie: 'bar=foo;lorem=ipsum' })).to.equal(null);
|
||||
});
|
||||
|
||||
it('decodes the cookie vaue', () => {
|
||||
expect(getCookie('foo', { cookie: `foo=${decodeURIComponent('/foo/ bar "')}` })).to.equal(
|
||||
'/foo/ bar "',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('acceptLanguageRequestTransformer()', () => {
|
||||
it('adds the locale as accept-language header', () => {
|
||||
const request = new Request('/foo/');
|
||||
acceptLanguageRequestTransformer(request);
|
||||
expect(request.headers.get('accept-language')).to.equal(localize.locale);
|
||||
});
|
||||
|
||||
it('does not change an existing accept-language header', () => {
|
||||
const request = new Request('/foo/', { headers: { 'accept-language': 'my-accept' } });
|
||||
acceptLanguageRequestTransformer(request);
|
||||
expect(request.headers.get('accept-language')).to.equal('my-accept');
|
||||
});
|
||||
});
|
||||
|
||||
describe('createXSRFRequestTransformer()', () => {
|
||||
it('adds the xsrf token header to the request', () => {
|
||||
const transformer = createXSRFRequestTransformer('XSRF-TOKEN', 'X-XSRF-TOKEN', {
|
||||
cookie: 'XSRF-TOKEN=foo',
|
||||
});
|
||||
const request = new Request('/foo/');
|
||||
transformer(request);
|
||||
expect(request.headers.get('X-XSRF-TOKEN')).to.equal('foo');
|
||||
});
|
||||
|
||||
it('doesnt set anything if the cookie is not there', () => {
|
||||
const transformer = createXSRFRequestTransformer('XSRF-TOKEN', 'X-XSRF-TOKEN', {
|
||||
cookie: 'XXSRF-TOKEN=foo',
|
||||
});
|
||||
const request = new Request('/foo/');
|
||||
transformer(request);
|
||||
expect(request.headers.get('X-XSRF-TOKEN')).to.equal(null);
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Reference in a new issue