diff --git a/package.json b/package.json index ccf7d79..93ba999 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "contentful-sdk-core", - "version": "0.0.1-determined-by-semantic-release", + "version": "0.0.0-determined-by-semantic-release", "description": "Core modules for the Contentful JS SDKs", "homepage": "https://www.contentful.com/developers/docs/javascript/", "main": "dist/index.js", @@ -23,9 +23,9 @@ "build": "npm run clean && npm run build:types && npm run build:js", "lint": "eslint src test --ext '.ts,.js'", "pretest": "npm run lint", - "test": "tsdx test", - "test:watch": "tsdx test --watch", - "test:cover": "tsdx test --coverage", + "test": "jest", + "test:watch": "jest --watch", + "test:cover": "jest --coverage", "browser-coverage": "npm run test:cover && opener coverage/lcov-report/index.html", "prepublish": "in-publish && npm run build || not-in-publish", "semantic-release": "semantic-release", @@ -41,7 +41,7 @@ "bin" ], "engines": { - "node": ">=6" + "node": ">=12" }, "dependencies": { "fast-copy": "^2.1.0", @@ -64,13 +64,14 @@ "@semantic-release/npm": "^7.1.3", "@semantic-release/release-notes-generator": "^9.0.3", "@types/chai": "^4.2.21", + "@types/jest": "^27.4.0", "@types/lodash.isplainobject": "^4.0.6", "@types/lodash.isstring": "^4.0.6", "@types/qs": "^6.9.5", - "@typescript-eslint/eslint-plugin": "4.33.0", - "@typescript-eslint/parser": "4.31.2", - "axios": "^0.21.0", - "axios-mock-adapter": "^1.15.0", + "@typescript-eslint/eslint-plugin": "^5.11.0", + "@typescript-eslint/parser": "^5.11.0", + "axios": "^0.25.0", + "axios-mock-adapter": "^1.20.0", "babel-eslint": "^10.1.0", "bundlesize": "^0.18.1", "chai": "^4.3.4", @@ -86,6 +87,7 @@ "eslint-plugin-standard": "^5.0.0", "husky": "^7.0.0", "in-publish": "^2.0.0", + "jest": "^27.5.1", "lint-staged": "^12.0.2", "mkdirp": "^1.0.4", "moxios": "^0.4.0", @@ -97,9 +99,9 @@ "rollup": "^2.34.0", "rollup-plugin-babel": "^4.4.0", "semantic-release": "^17.0.7", - "tsdx": "^0.14.1", + "ts-jest": "^27.1.3", "tslib": "^2.0.3", - "typescript": "3.9.10" + "typescript": "^4.5.5" }, "bundlesize": [ { diff --git a/src/create-http-client.ts b/src/create-http-client.ts index 277ab79..5c3db7c 100644 --- a/src/create-http-client.ts +++ b/src/create-http-client.ts @@ -1,3 +1,4 @@ +import { AxiosRequestHeaders } from 'axios' import copy from 'fast-copy' import qs from 'qs' import type { AxiosStatic } from 'axios' @@ -38,7 +39,7 @@ export default function createHttpClient( console.log(`[${level}] ${data}`) }, // Passed to axios - headers: {} as Record, + headers: {} as AxiosRequestHeaders, httpAgent: false as const, httpsAgent: false as const, timeout: 30000, diff --git a/src/error-handler.ts b/src/error-handler.ts index ba01932..f587554 100644 --- a/src/error-handler.ts +++ b/src/error-handler.ts @@ -15,7 +15,7 @@ export default function errorHandler(errorResponse: AxiosError): never { // Obscure the Management token if (config && config.headers && config.headers['Authorization']) { - const token = `...${config.headers['Authorization'].substr(-5)}` + const token = `...${config.headers['Authorization'].toString().substr(-5)}` config.headers['Authorization'] = `Bearer ${token}` } diff --git a/src/to-plain-object.ts b/src/to-plain-object.ts index 734890e..46ac8a0 100644 --- a/src/to-plain-object.ts +++ b/src/to-plain-object.ts @@ -6,14 +6,7 @@ import copy from 'fast-copy' * @param data - Any plain JSON response returned from the API * @return Enhanced object with toPlainObject method */ -export default function toPlainObject, R = T>( - data: T -): T & { - /** - * Returns this entity as a plain JS object - */ - toPlainObject(): R -} { +export default function toPlainObject(data: unknown) { return Object.defineProperty(data, 'toPlainObject', { enumerable: false, configurable: false, diff --git a/src/types.ts b/src/types.ts index f16f74b..88826c2 100644 --- a/src/types.ts +++ b/src/types.ts @@ -1,5 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ +import { AxiosRequestHeaders } from 'axios' import type { AxiosInstance as OriginalAxiosInstance, AxiosRequestConfig, @@ -61,7 +62,7 @@ export type CreateHttpClientParams = { logHandler?: DefaultOptions['logHandler'] /** Optional additional headers */ - headers?: Record + headers?: AxiosRequestHeaders defaultHostname?: string diff --git a/test/unit/create-http-client-integration.spec.ts b/test/unit/create-http-client-integration.spec.ts index 538e945..af27b76 100644 --- a/test/unit/create-http-client-integration.spec.ts +++ b/test/unit/create-http-client-integration.spec.ts @@ -21,7 +21,7 @@ it('should retrieve token asynchronously', async () => { expect(instance.defaults.headers.Authorization).not.toBeDefined() await instance.get('/test-endpoint') - expect(mock.history.get[0].headers.Authorization).toEqual('Bearer async-token') + expect(mock.history.get[0].headers?.Authorization).toEqual('Bearer async-token') }) describe('custom interceptors', () => { @@ -45,10 +45,10 @@ describe('custom interceptors', () => { mock.onGet('/test-endpoint').replyOnce(200) await instance.get('/test-endpoint') - expect(mock.history.get[0].headers['custom-header']).toEqual('custom-header-value') + expect(mock.history.get[0].headers?.['custom-header']).toEqual('custom-header-value') }) - it('is able to intercept responce codes', async () => { + it('is able to intercept response codes', async () => { let accessToken = 'invalid-token' const refreshToken = () => { @@ -63,6 +63,8 @@ describe('custom interceptors', () => { if (error.response.status === 403 && !originalRequest._retry403) { originalRequest._retry403 = true const newToken = await refreshToken() + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore axios.defaults.headers.Authorization = 'Bearer ' + newToken return instance(originalRequest) } @@ -75,7 +77,7 @@ describe('custom interceptors', () => { await instance.get('/test-endpoint') - expect(mock.history.get[0].headers.Authorization).toEqual('Bearer invalid-token') - expect(mock.history.get[1].headers.Authorization).toEqual('Bearer valid-token') + expect(mock.history.get[0].headers?.Authorization).toEqual('Bearer invalid-token') + expect(mock.history.get[1].headers?.Authorization).toEqual('Bearer valid-token') }) }) diff --git a/test/unit/create-http-client-test.spec.ts b/test/unit/create-http-client-test.spec.ts index f2ce0e0..cb464a9 100644 --- a/test/unit/create-http-client-test.spec.ts +++ b/test/unit/create-http-client-test.spec.ts @@ -1,3 +1,5 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ + import createHttpClient from '../../src/create-http-client' import axios, { AxiosAdapter } from 'axios' @@ -84,25 +86,25 @@ it('Calls axios based on passed headers', () => { const [callConfig] = mockedAxios.create.mock.calls[0] expect(callConfig?.baseURL) - expect(callConfig?.headers['X-Custom-Header']).toEqual('example') - expect(callConfig?.headers.Authorization).toEqual('Basic customAuth') + expect(callConfig?.headers?.['X-Custom-Header']).toEqual('example') + expect(callConfig?.headers?.Authorization).toEqual('Basic customAuth') }) -it('Calls axios with reques/response logger', () => { - const requestloggerStub = jest.fn() - const responseloggerStub = jest.fn() +it('Calls axios with request/response logger', () => { + const requestLoggerStub = jest.fn() + const responseLoggerStub = jest.fn() createHttpClient(axios, { accessToken: 'clientAccessToken', host: 'contentful.com', insecure: true, - requestLogger: requestloggerStub, - responseLogger: responseloggerStub, + requestLogger: requestLoggerStub, + responseLogger: responseLoggerStub, }) const [callConfig] = mockedAxios.create.mock.calls[0] expect(callConfig?.baseURL).toEqual('http://contentful.com:80/spaces/') - expect(requestloggerStub).not.toHaveBeenCalled() - expect(responseloggerStub).not.toHaveBeenCalled() + expect(requestLoggerStub).not.toHaveBeenCalled() + expect(responseLoggerStub).not.toHaveBeenCalled() }) it('Fails with missing access token', () => { @@ -111,7 +113,7 @@ it('Fails with missing access token', () => { createHttpClient(axios, { logHandler: logHandlerStub, }) - } catch (err) { + } catch (err: any) { expect(err instanceof TypeError).toBeTruthy() expect(err.message).toEqual('Expected parameter accessToken') expect(logHandlerStub).toHaveBeenCalledTimes(1) diff --git a/test/unit/error-handler-test.spec.ts b/test/unit/error-handler-test.spec.ts index 9793ca2..588002b 100644 --- a/test/unit/error-handler-test.spec.ts +++ b/test/unit/error-handler-test.spec.ts @@ -1,3 +1,5 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ + import errorHandler from '../../src/error-handler' import { errorMock } from './mocks' import { expect } from 'chai' @@ -21,7 +23,7 @@ describe('A errorHandler', () => { try { errorHandler(error) - } catch (err) { + } catch (err: any) { const parsedMessage = JSON.parse(err.message) expect(err.name).equals('SpecificError', 'error name') expect(parsedMessage.request.url).equals('requesturl', 'request url') @@ -46,7 +48,7 @@ describe('A errorHandler', () => { try { errorHandler(error) - } catch (err) { + } catch (err: any) { const parsedMessage = JSON.parse(err.message) expect(err.name).equals('500 Internal', 'error name defaults to status code and text') expect(parsedMessage.request.url).equals('requesturl', 'request url') @@ -61,7 +63,7 @@ describe('A errorHandler', () => { try { errorHandler(error) - } catch (err) { + } catch (err: any) { const parsedMessage = JSON.parse(err.message) expect(err.name).equals( '500 Everything is on fire', @@ -79,7 +81,7 @@ describe('A errorHandler', () => { try { errorHandler(responseError) - } catch (err) { + } catch (err: any) { const parsedMessage = JSON.parse(err.message) expect(parsedMessage.request.headers.Authorization).equals( 'Bearer ...token', @@ -105,7 +107,7 @@ describe('A errorHandler', () => { try { errorHandler(requestError) - } catch (err) { + } catch (err: any) { expect(err.config.headers.Authorization).equals('Bearer ...token', 'Obscures token') } }) diff --git a/test/unit/rate-limit-test.spec.ts b/test/unit/rate-limit-test.spec.ts index 2b30a03..fd603f8 100644 --- a/test/unit/rate-limit-test.spec.ts +++ b/test/unit/rate-limit-test.spec.ts @@ -1,3 +1,5 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ + import axios from 'axios' import MockAdapter from 'axios-mock-adapter' @@ -89,7 +91,7 @@ it('Retry on network error', async () => { try { await client.get('/rate-limit-me') - } catch (error) { + } catch (error: any) { // logs two attempts, one initial and one retry expect(error.attempts).toEqual(2) } @@ -105,7 +107,7 @@ it('no retry when automatic handling flag is disabled', async () => { expect.assertions(6) try { await client.get('/rate-limit-me') - } catch (error) { + } catch (error: any) { expect(error.response.status).toEqual(500) expect(error.response.headers['x-contentful-request-id']).toEqual(3) expect(error.response.data).toEqual('Mocked 500 Error') @@ -124,7 +126,7 @@ it('Should Fail if it hits maxRetries', async () => { expect.assertions(5) try { await client.get('/error') - } catch (error) { + } catch (error: any) { // returned error since maxRetries was reached expect(error.response.data).toEqual('error attempt #2') expect(logHandlerStub).toHaveBeenCalledTimes(1) @@ -144,7 +146,7 @@ it('Retry on responses when X-Contentful-Request-Id header is missing', async () try { await client.get('/error') - } catch (error) { + } catch (error: any) { expect(error.response.data).toEqual('error attempt 2') expect(logHandlerStub).toHaveBeenCalledTimes(1) expect(logHandlerStub.mock.calls[0][0]).toEqual('warning') @@ -161,7 +163,7 @@ it('Rejects errors with strange status codes', async () => { expect.assertions(2) try { await client.get('/error') - } catch (error) { + } catch (error: any) { expect(error.response.data).toEqual('error attempt') // did not log anything expect(logHandlerStub).toBeCalledTimes(0)