From da823db8a58ca05c9a3738512ff90372bbc87f89 Mon Sep 17 00:00:00 2001 From: Dimitri DO BAIRRO Date: Fri, 24 Jan 2020 15:39:52 +0100 Subject: [PATCH 01/13] feat(classes/types.ts): EAxiosCacheHeaders created --- src/classes/types.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/classes/types.ts b/src/classes/types.ts index 05507cd..a35a934 100644 --- a/src/classes/types.ts +++ b/src/classes/types.ts @@ -1,3 +1,7 @@ +enum EAxiosCacheHeaders { + CacheDuration = 'Axios-Redis-Cache-Duration', +} + enum ERedisFlag { EXPIRATION = 'EX', } @@ -14,4 +18,4 @@ enum EHttpMethod { UNLINK = 'unlink', } -export { ERedisFlag, EHttpMethod }; +export { ERedisFlag, EHttpMethod, EAxiosCacheHeaders }; From 8b9abdec077a17cd082917f981a83bde49bb8051 Mon Sep 17 00:00:00 2001 From: Dimitri DO BAIRRO Date: Fri, 24 Jan 2020 15:41:19 +0100 Subject: [PATCH 02/13] tests(__tests__/index.ts): some tests added --- __tests__/index.ts | 114 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) diff --git a/__tests__/index.ts b/__tests__/index.ts index dc024e8..d83443e 100644 --- a/__tests__/index.ts +++ b/__tests__/index.ts @@ -83,6 +83,56 @@ describe('index.ts', () => { expect(responseFromCache.data).toStrictEqual({ success: true }); }); + it('should cache the response on the first call (with custom cache expiration value)', async () => { + const axiosResponseSetCache = + '[{"status":200,"statusText":null,"headers":"1","config":"2","request":"3","data":"4"},{"content-type":"5"},{"url":"6","method":"7","headers":"8","baseURL":"9","transformRequest":"10","transformResponse":"11","timeout":0,"xsrfCookieName":"12","xsrfHeaderName":"13","maxContentLength":-1,"httpsAgent":"14"},{"_events":"15","_eventsCount":5,"outputData":"16","outputSize":0,"writable":true,"_last":false,"chunkedEncoding":false,"shouldKeepAlive":true,"useChunkedEncodingByDefault":true,"sendDate":false,"_removedConnection":false,"_removedContLen":false,"_removedTE":false,"_contentLength":null,"_hasBody":true,"_trailer":"17","finished":false,"_headerSent":false,"socket":"18","connection":"18","_header":null,"path":"6","method":"19","req":"3","options":"20","interceptors":"21","response":"22","playbackStarted":false,"requestBodyBuffers":"23","_redirectable":"24","headers":"25"},{"success":true},"application/json","/example2?param1=true¶m2=123","get",{"Accept":"26","User-Agent":"27","Api-Key":"28"},"https://api.example.com",[null],[null],"XSRF-TOKEN","X-XSRF-TOKEN",{"_events":"29","_eventsCount":1,"defaultPort":443,"protocol":"30","options":"31","requests":"32","sockets":"33","freeSockets":"34","keepAliveMsecs":1000,"keepAlive":false,"maxSockets":null,"maxFreeSockets":256,"maxCachedSessions":100,"_sessionCache":"35"},{},[],"",{"_events":"36","_eventsCount":2,"authorized":true,"bufferSize":0,"writable":true,"readable":true,"pending":false,"destroyed":false,"connecting":false,"totalDelayMs":0,"timeoutMs":null,"remoteFamily":"37","remoteAddress":"38","localAddress":"38","remotePort":443,"localPort":443},"GET",{"protocol":"30","maxRedirects":21,"maxBodyLength":10485760,"path":"6","method":"19","headers":"39","agent":"14","agents":"40","hostname":"41","port":443,"nativeProtocols":"42","pathname":"43","search":"44","proto":"45","host":"46"},[],{"_readableState":"47","readable":false,"_events":"48","_eventsCount":3,"socket":"18","connection":"18","httpVersionMajor":null,"httpVersionMinor":null,"httpVersion":null,"complete":true,"headers":"1","rawHeaders":"49","trailers":"50","rawTrailers":"51","aborted":false,"upgrade":null,"url":"17","method":null,"statusCode":200,"statusMessage":null,"client":"18","_consuming":true,"_dumped":false,"req":"3","responseUrl":"52","redirects":"53"},[],{"_writableState":"54","writable":true,"_events":"55","_eventsCount":2,"_options":"56","_redirectCount":0,"_redirects":"53","_requestBodyLength":0,"_requestBodyBuffers":"57","_currentRequest":"3","_currentUrl":"52"},{"accept":"26","user-agent":"27","api-key":"28","host":"41"},"application/json, text/plain, */*","@scope/package","3b48b9fd18ecca20ed5b0accbfeb6b70",{},"https:",{"rejectUnauthorized":false,"path":null},{},{},{},{"map":"58","list":"59"},{},"IPv4","127.0.0.1",{"accept":"26","user-agent":"27","api-key":"28"},{"https":"14"},"api.example.com",{"http:":"60","https:":"61"},"/example2","?param1=true¶m2=123","https","api.example.com:443",{"objectMode":false,"highWaterMark":16384,"buffer":"62","length":0,"pipes":null,"pipesCount":0,"flowing":true,"ended":true,"endEmitted":true,"reading":false,"sync":false,"needReadable":false,"emittedReadable":false,"readableListening":false,"resumeScheduled":false,"paused":false,"emitClose":true,"autoDestroy":false,"destroyed":false,"defaultEncoding":"63","awaitDrain":0,"readingMore":false,"decoder":null,"encoding":null},{},["64","5"],{},[],"https://api.example.com/example2?param1=true¶m2=123",[],{"objectMode":false,"highWaterMark":16384,"finalCalled":false,"needDrain":false,"ending":false,"ended":false,"finished":false,"destroyed":false,"decodeStrings":true,"defaultEncoding":"63","length":0,"writing":false,"corked":0,"sync":true,"bufferProcessing":false,"writecb":null,"writelen":0,"afterWriteTickInfo":null,"bufferedRequest":null,"lastBufferedRequest":null,"pendingcb":0,"prefinished":false,"errorEmitted":false,"emitClose":true,"autoDestroy":false,"bufferedRequestCount":0,"corkedRequestsFree":"65"},{},{"protocol":"30","maxRedirects":21,"maxBodyLength":10485760,"path":"6","method":"19","headers":"8","agent":"14","agents":"40","hostname":"41","port":null,"nativeProtocols":"42","pathname":"43","search":"44"},[],{},[],{"METHODS":"66","STATUS_CODES":"67","maxHeaderSize":8192,"globalAgent":"68"},{"globalAgent":"69"},{"head":null,"tail":null,"length":0},"utf8","Content-Type",{"next":null,"entry":null},["70","71","72","73","74","75","19","76","77","78","79","80","81","82","83","84","85","86","87","88","89","90","91","92","93","94","95","96","97","98","99","100","101","102"],{"100":"103","101":"104","102":"105","103":"106","200":"107","201":"108","202":"109","203":"110","204":"111","205":"112","206":"113","207":"114","208":"115","226":"116","300":"117","301":"118","302":"119","303":"120","304":"121","305":"122","307":"123","308":"124","400":"125","401":"126","402":"127","403":"128","404":"129","405":"130","406":"131","407":"132","408":"133","409":"134","410":"135","411":"136","412":"137","413":"138","414":"139","415":"140","416":"141","417":"142","418":"143","421":"144","422":"145","423":"146","424":"147","425":"148","426":"149","428":"150","429":"151","431":"152","451":"153","500":"154","501":"155","502":"156","503":"157","504":"158","505":"159","506":"160","507":"161","508":"162","509":"163","510":"164","511":"165"},{"_events":"166","_eventsCount":1,"defaultPort":80,"protocol":"167","options":"168","requests":"169","sockets":"170","freeSockets":"171","keepAliveMsecs":1000,"keepAlive":false,"maxSockets":null,"maxFreeSockets":256},{"_events":"172","_eventsCount":1,"defaultPort":443,"protocol":"30","options":"173","requests":"174","sockets":"175","freeSockets":"176","keepAliveMsecs":1000,"keepAlive":false,"maxSockets":null,"maxFreeSockets":256,"maxCachedSessions":100,"_sessionCache":"177"},"ACL","BIND","CHECKOUT","CONNECT","COPY","DELETE","HEAD","LINK","LOCK","M-SEARCH","MERGE","MKACTIVITY","MKCALENDAR","MKCOL","MOVE","NOTIFY","OPTIONS","PATCH","POST","PROPFIND","PROPPATCH","PURGE","PUT","REBIND","REPORT","SEARCH","SOURCE","SUBSCRIBE","TRACE","UNBIND","UNLINK","UNLOCK","UNSUBSCRIBE","Continue","Switching Protocols","Processing","Early Hints","OK","Created","Accepted","Non-Authoritative Information","No Content","Reset Content","Partial Content","Multi-Status","Already Reported","IM Used","Multiple Choices","Moved Permanently","Found","See Other","Not Modified","Use Proxy","Temporary Redirect","Permanent Redirect","Bad Request","Unauthorized","Payment Required","Forbidden","Not Found","Method Not Allowed","Not Acceptable","Proxy Authentication Required","Request Timeout","Conflict","Gone","Length Required","Precondition Failed","Payload Too Large","URI Too Long","Unsupported Media Type","Range Not Satisfiable","Expectation Failed","I\'m a Teapot","Misdirected Request","Unprocessable Entity","Locked","Failed Dependency","Unordered Collection","Upgrade Required","Precondition Required","Too Many Requests","Request Header Fields Too Large","Unavailable For Legal Reasons","Internal Server Error","Not Implemented","Bad Gateway","Service Unavailable","Gateway Timeout","HTTP Version Not Supported","Variant Also Negotiates","Insufficient Storage","Loop Detected","Bandwidth Limit Exceeded","Not Extended","Network Authentication Required",{},"http:",{"path":null},{},{},{},{},{"path":null},{},{},{},{"map":"178","list":"179"},{},[]]'; + + // tslint:disable-next-line:no-backbone-get-set-outside-model + const apiNock = nock('https://api.example.com') + .get('/example2') + .query({ param1: true, param2: 123 }) + .matchHeader('User-Agent', '@scope/package') + .matchHeader('Api-Key', '3b48b9fd18ecca20ed5b0accbfeb6b70') + // .matchHeader('Axios-Redis-Cache-Duration', '90000') + .reply(200, { + success: true, + }); + + const redisSetAsyncSpy = jest.spyOn(axiosRedis, 'redisSetAsync'); + const redisGetAsyncSpy = jest.spyOn(axiosRedis, 'redisGetAsync'); + + // tslint:disable-next-line:no-backbone-get-set-outside-model + const response = await axiosInstance.get('/example2?param1=true¶m2=123', { + headers: { 'Axios-Redis-Cache-Duration': 90000 }, + }); + + // tslint:disable-next-line:no-backbone-get-set-outside-model + const responseFromCache = await axiosInstance.get('/example2?param1=true¶m2=123'); + + apiNock.done(); + expect(redisSetAsyncSpy).toBeCalledTimes(1); + expect(redisSetAsyncSpy).nthCalledWith( + 1, + '@scope/package@1.0.1___["get"]___WyIvZXhhbXBsZTI/cGFyYW0xPXRydWUmcGFyYW0yPTEyMyJd___W10=___W10=', + axiosResponseSetCache, + 'EX', + 90000, + ); + expect(redisGetAsyncSpy).toBeCalledTimes(2); + expect(redisGetAsyncSpy).nthCalledWith( + 1, + '@scope/package@1.0.1___["get"]___WyIvZXhhbXBsZTI/cGFyYW0xPXRydWUmcGFyYW0yPTEyMyJd___W10=___W10=', + ); + expect(redisGetAsyncSpy).nthCalledWith( + 2, + '@scope/package@1.0.1___["get"]___WyIvZXhhbXBsZTI/cGFyYW0xPXRydWUmcGFyYW0yPTEyMyJd___W10=___W10=', + ); + expect(response.status).toEqual(200); + expect(response.data).toStrictEqual({ success: true }); + expect(responseFromCache.status).toEqual(200); + expect(responseFromCache.data).toStrictEqual({ success: true }); + }); + it('should cache the response on the first call (with default configuration)', async () => { const axiosRedisRaw = new AxiosRedis(redis); @@ -253,6 +303,70 @@ describe('index.ts', () => { }); describe('Not cache', () => { + describe('POST', () => { + it('should not cache (if "Axios-Redis-Cache-Duration" header is disabled)', async () => { + const apiNock = nock('https://api.example.com') + .post('/example/1', { hello: 'world' }) + .query({ sort: 'desc' }) + .matchHeader('User-Agent', '@scope/package') + .matchHeader('Api-Key', '3b48b9fd18ecca20ed5b0accbfeb6b70') + .reply(200, { + success: true, + }); + + const redisSetAsyncSpy = jest.spyOn(axiosRedis, 'redisSetAsync'); + const redisGetAsyncSpy = jest.spyOn(axiosRedis, 'redisGetAsync'); + + const response = await axiosInstance.post( + '/example/1?sort=desc', + { + hello: 'world', + }, + { headers: { 'Axios-Redis-Cache-Duration': 0 } }, + ); + + apiNock.done(); + expect(redisSetAsyncSpy).toBeCalledTimes(0); + expect(redisGetAsyncSpy).toBeCalledTimes(1); + expect(redisGetAsyncSpy).nthCalledWith( + 1, + '@scope/package@1.0.1___["post"]___WyIvZXhhbXBsZS8xP3NvcnQ9ZGVzYyJd___W10=___WyJ7XCJoZWxsb1wiOlwid29ybGRcIn0iXQ==', + ); + expect(response.status).toEqual(200); + expect(response.data).toStrictEqual({ success: true }); + }); + }); + + describe('GET', () => { + it('should not cache (if "Axios-Redis-Cache-Duration" header is disabled)', async () => { + const apiNock = nock('https://api.example.com') + .get('/example/1') + .query({ sort: 'desc' }) + .matchHeader('User-Agent', '@scope/package') + .matchHeader('Api-Key', '3b48b9fd18ecca20ed5b0accbfeb6b70') + .reply(200, { + success: true, + }); + + const redisSetAsyncSpy = jest.spyOn(axiosRedis, 'redisSetAsync'); + const redisGetAsyncSpy = jest.spyOn(axiosRedis, 'redisGetAsync'); + + const response = await axiosInstance.get('/example/1?sort=desc', { + headers: { 'Axios-Redis-Cache-Duration': 0 }, + }); + + apiNock.done(); + expect(redisSetAsyncSpy).toBeCalledTimes(0); + expect(redisGetAsyncSpy).toBeCalledTimes(1); + expect(redisGetAsyncSpy).nthCalledWith( + 1, + '@scope/package@1.0.1___["get"]___WyIvZXhhbXBsZS8xP3NvcnQ9ZGVzYyJd___W10=___W10=', + ); + expect(response.status).toEqual(200); + expect(response.data).toStrictEqual({ success: true }); + }); + }); + describe('PUT', () => { it('should not cache', async () => { const apiNock = nock('https://api.example.com') From 7496943b1c3d9f3b775636af0184a7d047bec4bd Mon Sep 17 00:00:00 2001 From: Dimitri DO BAIRRO Date: Fri, 24 Jan 2020 15:41:50 +0100 Subject: [PATCH 03/13] tests(__tests__/index.ts): comment removed --- __tests__/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/__tests__/index.ts b/__tests__/index.ts index d83443e..862d9dd 100644 --- a/__tests__/index.ts +++ b/__tests__/index.ts @@ -93,7 +93,6 @@ describe('index.ts', () => { .query({ param1: true, param2: 123 }) .matchHeader('User-Agent', '@scope/package') .matchHeader('Api-Key', '3b48b9fd18ecca20ed5b0accbfeb6b70') - // .matchHeader('Axios-Redis-Cache-Duration', '90000') .reply(200, { success: true, }); From f2fbec5e9255a32e273d7eab125f9c7138c428ea Mon Sep 17 00:00:00 2001 From: Dimitri DO BAIRRO Date: Fri, 24 Jan 2020 15:42:15 +0100 Subject: [PATCH 04/13] feat(classes/index.ts): custom header implemented --- src/classes/index.ts | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/src/classes/index.ts b/src/classes/index.ts index f2295d8..001436b 100644 --- a/src/classes/index.ts +++ b/src/classes/index.ts @@ -3,7 +3,7 @@ import axios, { AxiosRequestConfig, AxiosResponse, AxiosPromise } from 'axios'; import * as _ from 'lodash'; import * as flatted from 'flatted'; import { promisify } from 'util'; -import { EHttpMethod, ERedisFlag } from './types'; +import { EHttpMethod, ERedisFlag, EAxiosCacheHeaders } from './types'; import { ICacheConfiguration, defaultConfiguration } from './config'; /** @@ -12,7 +12,7 @@ import { ICacheConfiguration, defaultConfiguration } from './config'; export class AxiosRedis { private readonly redis: RedisClient; private config: ICacheConfiguration; - public redisSetAsync: (key: string, value: string, flag: ERedisFlag, expirationInMS: number) => Promise; + public redisSetAsync: (key: string, value: string, flag?: ERedisFlag, expirationInMS?: number) => Promise; public redisGetAsync: (key: string) => Promise; public keysToNotEncode: string[] = ['method']; public methodsToCache: EHttpMethod[] = [EHttpMethod.GET, EHttpMethod.POST]; @@ -42,10 +42,21 @@ export class AxiosRedis { * @description Caches data. * @param {string} key * @param {AxiosResponse} data + * @param {undefined | number} durationInMS * @returns Promise */ - setCache(key: string, data: AxiosResponse): Promise { - return this.redisSetAsync(key, flatted.stringify(data), ERedisFlag.EXPIRATION, this.config.expirationInMS); + async setCache(key: string, data: AxiosResponse, durationInMS: undefined | number): Promise { + if (durationInMS === undefined) { + return this.redisSetAsync(key, flatted.stringify(data), ERedisFlag.EXPIRATION, this.config.expirationInMS); + } + + if (durationInMS) { + return this.redisSetAsync(key, flatted.stringify(data), ERedisFlag.EXPIRATION, durationInMS); + } + + if (durationInMS === 0) { + return ''; + } } /** @@ -68,8 +79,9 @@ export class AxiosRedis { } // Send the request and store the result in case of success + const cacheDuration = config.headers[EAxiosCacheHeaders.CacheDuration]; response = await axiosRedis.fetch(config); - await axiosRedis.setCache(key, response); + await axiosRedis.setCache(key, response, cacheDuration); return response; } @@ -109,6 +121,8 @@ export class AxiosRedis { * @returns {AxiosPromise} */ private fetch(config: AxiosRequestConfig): AxiosPromise { + delete config.headers[EAxiosCacheHeaders.CacheDuration]; + const axiosDefaultAdapter = axios.create(Object.assign(config, { adapter: axios.defaults.adapter })); return axiosDefaultAdapter.request(config); From 490e01d7228ddc220fb924fa20417168a6ad01ce Mon Sep 17 00:00:00 2001 From: Dimitri DO BAIRRO Date: Fri, 24 Jan 2020 15:49:42 +0100 Subject: [PATCH 05/13] doc(README.md): documentation updated --- README.md | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index c012d86..8496eac 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ This repository provides an Axios Redis cache adapter. yarn add @tictactrip/axios-redis ``` -## How to use it? +## Example ```ts import * as redis from 'redis'; @@ -47,7 +47,7 @@ await axiosInstance.get('/user?ID=12345'); await axiosInstance.get('/user?ID=12345'); ``` -### Which http methods are cached? +### HTTP methods cached As default, all **GET** and **POST** responses are cached. If you want to customize them, you can also do: @@ -56,7 +56,7 @@ If you want to customize them, you can also do: axiosRedis.methodsToCache = [EHttpMethod.GET]; ``` -### What's the key structure? +### Key structure As default, keys have bellow pattern: @@ -72,7 +72,29 @@ Example: If you want to customize the keys, you just need to customize your `AxiosRedis` instance. -## Tests +### Disable cache for one request + +```ts +// This request won't be cached +await axiosInstance.get('/user?ID=12345', { + headers: { + 'Axios-Redis-Cache-Duration': 0, + }, +}); +``` + +### Customize cache duration for one request + +```ts +// This request will be cached during 90000ms +await axiosInstance.get('/user?ID=12345', { + headers: { + 'Axios-Redis-Cache-Duration': 90000, + }, +}); +``` + +### Tests How can I mock Redis connection with Jest in my unit tests? @@ -81,7 +103,6 @@ import * as redis from 'redis'; import { AxiosRedis } from '@tictactrip/axios-redis'; describe('Example', () => { - it('should send the request without a redis connection', () => { const redisClient = redis.createClient({ retry_strategy: jest.fn() }); @@ -90,6 +111,7 @@ describe('Example', () => { // ... }); +}); ``` ## Scripts From 3238d3042c9bd761d62221df224d65011c2274c4 Mon Sep 17 00:00:00 2001 From: Dimitri DO BAIRRO Date: Fri, 24 Jan 2020 15:49:54 +0100 Subject: [PATCH 06/13] feat(index.ts): export EAxiosCacheHeaders --- src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/index.ts b/src/index.ts index ffe2358..f13fae1 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1 +1,2 @@ export { AxiosRedis } from './classes'; +export { EAxiosCacheHeaders } from './classes/types'; From d255fa9d3fa0b82d2a40d8b56bb379310ca81e0c Mon Sep 17 00:00:00 2001 From: Dimitri DO BAIRRO Date: Fri, 24 Jan 2020 16:07:22 +0100 Subject: [PATCH 07/13] feat(package.json): packages updated --- package.json | 4 ++-- yarn.lock | 16 ++++++++-------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/package.json b/package.json index 5b5f7dc..a87d896 100644 --- a/package.json +++ b/package.json @@ -12,13 +12,13 @@ "redis": "2.8.0" }, "devDependencies": { - "@types/jest": "24.9.0", + "@types/jest": "24.9.1", "@types/node": "12.12.6", "@types/redis": "2.8.14", "jest": "25.1.0", "nock": "11.7.2", "prettier": "1.19.1", - "ts-jest": "24.3.0", + "ts-jest": "25.0.0", "tslint": "6.0.0", "tslint-config-prettier": "1.18.0", "tslint-microsoft-contrib": "6.2.0", diff --git a/yarn.lock b/yarn.lock index 859fc2e..6f1476a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -399,10 +399,10 @@ "@types/istanbul-lib-coverage" "*" "@types/istanbul-lib-report" "*" -"@types/jest@24.9.0": - version "24.9.0" - resolved "https://registry.yarnpkg.com/@types/jest/-/jest-24.9.0.tgz#78c6991cd1734cf0d390be24875e310bb0a9fb74" - integrity sha512-dXvuABY9nM1xgsXlOtLQXJKdacxZJd7AtvLsKZ/0b57ruMXDKCOXAC/M75GbllQX6o1pcZ5hAG4JzYy7Z/wM2w== +"@types/jest@24.9.1": + version "24.9.1" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-24.9.1.tgz#02baf9573c78f1b9974a5f36778b366aa77bd534" + integrity sha512-Fb38HkXSVA4L8fGKEZ6le5bB8r6MRWlOCZbVuWZcmOMSCd2wCYOwN1ibj8daIoV9naq7aaOZjrLCoCMptKU/4Q== dependencies: jest-diff "^24.3.0" @@ -3321,10 +3321,10 @@ tr46@^1.0.1: dependencies: punycode "^2.1.0" -ts-jest@24.3.0: - version "24.3.0" - resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-24.3.0.tgz#b97814e3eab359ea840a1ac112deae68aa440869" - integrity sha512-Hb94C/+QRIgjVZlJyiWwouYUF+siNJHJHknyspaOcZ+OQAIdFG/UrdQVXw/0B8Z3No34xkUXZJpOTy9alOWdVQ== +ts-jest@25.0.0: + version "25.0.0" + resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-25.0.0.tgz#d83b266e6ffda0c458a129951b3fe3567f8ce8df" + integrity sha512-F+hZg3j7XYOFpXJteXb4lnqy7vQzTmpTmX7AJT6pvSGeZejyXj1Lk0ArpnrEPOpv6Zu/NugHc5W7FINngC9WZQ== dependencies: bs-logger "0.x" buffer-from "1.x" From 7c3220da199ae3c8bd2187220bba9bea3ab21893 Mon Sep 17 00:00:00 2001 From: Dimitri DO BAIRRO Date: Fri, 24 Jan 2020 16:08:39 +0100 Subject: [PATCH 08/13] docs(README.md): typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8496eac..9908855 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ axiosRedis.methodsToCache = [EHttpMethod.GET]; ### Key structure -As default, keys have bellow pattern: +By default, redis keys follow this pattern ```ts {prefix}___{http_method}___{axios_config_url}___base64{axios_config_params}___base64{axios_config_data} From d27d48a7313334a8acba0b5fb1005e9652d708fa Mon Sep 17 00:00:00 2001 From: Dimitri DO BAIRRO Date: Fri, 24 Jan 2020 16:20:25 +0100 Subject: [PATCH 09/13] refact(classes/index.ts): setCache refactored after review --- src/classes/index.ts | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/classes/index.ts b/src/classes/index.ts index 001436b..df24301 100644 --- a/src/classes/index.ts +++ b/src/classes/index.ts @@ -43,20 +43,16 @@ export class AxiosRedis { * @param {string} key * @param {AxiosResponse} data * @param {undefined | number} durationInMS - * @returns Promise + * @returns Promise */ - async setCache(key: string, data: AxiosResponse, durationInMS: undefined | number): Promise { - if (durationInMS === undefined) { - return this.redisSetAsync(key, flatted.stringify(data), ERedisFlag.EXPIRATION, this.config.expirationInMS); + async setCache(key: string, data: AxiosResponse, durationInMS: undefined | number): Promise { + if (durationInMS === 0) { + return; } - if (durationInMS) { - return this.redisSetAsync(key, flatted.stringify(data), ERedisFlag.EXPIRATION, durationInMS); - } + const duration = durationInMS || this.config.expirationInMS; - if (durationInMS === 0) { - return ''; - } + return this.redisSetAsync(key, flatted.stringify(data), ERedisFlag.EXPIRATION, duration); } /** From 0ecf987bb4abfa1aa87268115d9decea4708e108 Mon Sep 17 00:00:00 2001 From: Dimitri DO BAIRRO Date: Fri, 24 Jan 2020 16:34:54 +0100 Subject: [PATCH 10/13] doc(README.md): null or 0 to disable cache --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9908855..32f7945 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,7 @@ If you want to customize the keys, you just need to customize your `AxiosRedis` // This request won't be cached await axiosInstance.get('/user?ID=12345', { headers: { - 'Axios-Redis-Cache-Duration': 0, + 'Axios-Redis-Cache-Duration': null, }, }); ``` From d9a8e29af593b3f03e1ed45f3335d732096bc358 Mon Sep 17 00:00:00 2001 From: Dimitri DO BAIRRO Date: Fri, 24 Jan 2020 16:36:03 +0100 Subject: [PATCH 11/13] tests(__tests__/index.ts): test updated --- __tests__/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/__tests__/index.ts b/__tests__/index.ts index 862d9dd..5d20f73 100644 --- a/__tests__/index.ts +++ b/__tests__/index.ts @@ -321,7 +321,7 @@ describe('index.ts', () => { { hello: 'world', }, - { headers: { 'Axios-Redis-Cache-Duration': 0 } }, + { headers: { 'Axios-Redis-Cache-Duration': null } }, ); apiNock.done(); From ddc040999db403c4f4bb16e486b890ed43826206 Mon Sep 17 00:00:00 2001 From: Dimitri DO BAIRRO Date: Fri, 24 Jan 2020 16:36:38 +0100 Subject: [PATCH 12/13] feat(classes/index.ts): handle null value --- src/classes/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/classes/index.ts b/src/classes/index.ts index df24301..73420b6 100644 --- a/src/classes/index.ts +++ b/src/classes/index.ts @@ -42,11 +42,11 @@ export class AxiosRedis { * @description Caches data. * @param {string} key * @param {AxiosResponse} data - * @param {undefined | number} durationInMS + * @param {undefined | number | null} durationInMS * @returns Promise */ - async setCache(key: string, data: AxiosResponse, durationInMS: undefined | number): Promise { - if (durationInMS === 0) { + async setCache(key: string, data: AxiosResponse, durationInMS: undefined | number | null): Promise { + if (durationInMS === 0 || typeof durationInMS === 'object') { return; } From 94adbbf165061d751df5357c136f2bd80e9acdaa Mon Sep 17 00:00:00 2001 From: Dimitri DO BAIRRO Date: Fri, 24 Jan 2020 16:40:25 +0100 Subject: [PATCH 13/13] tests(__tests__/index.ts): null test added --- __tests__/index.ts | 64 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 62 insertions(+), 2 deletions(-) diff --git a/__tests__/index.ts b/__tests__/index.ts index 5d20f73..023c403 100644 --- a/__tests__/index.ts +++ b/__tests__/index.ts @@ -303,7 +303,7 @@ describe('index.ts', () => { describe('Not cache', () => { describe('POST', () => { - it('should not cache (if "Axios-Redis-Cache-Duration" header is disabled)', async () => { + it('should not cache (if "Axios-Redis-Cache-Duration" header = null)', async () => { const apiNock = nock('https://api.example.com') .post('/example/1', { hello: 'world' }) .query({ sort: 'desc' }) @@ -334,10 +334,70 @@ describe('index.ts', () => { expect(response.status).toEqual(200); expect(response.data).toStrictEqual({ success: true }); }); + + it('should not cache (if "Axios-Redis-Cache-Duration" header = 0)', async () => { + const apiNock = nock('https://api.example.com') + .post('/example/1', { hello: 'world' }) + .query({ sort: 'desc' }) + .matchHeader('User-Agent', '@scope/package') + .matchHeader('Api-Key', '3b48b9fd18ecca20ed5b0accbfeb6b70') + .reply(200, { + success: true, + }); + + const redisSetAsyncSpy = jest.spyOn(axiosRedis, 'redisSetAsync'); + const redisGetAsyncSpy = jest.spyOn(axiosRedis, 'redisGetAsync'); + + const response = await axiosInstance.post( + '/example/1?sort=desc', + { + hello: 'world', + }, + { headers: { 'Axios-Redis-Cache-Duration': 0 } }, + ); + + apiNock.done(); + expect(redisSetAsyncSpy).toBeCalledTimes(0); + expect(redisGetAsyncSpy).toBeCalledTimes(1); + expect(redisGetAsyncSpy).nthCalledWith( + 1, + '@scope/package@1.0.1___["post"]___WyIvZXhhbXBsZS8xP3NvcnQ9ZGVzYyJd___W10=___WyJ7XCJoZWxsb1wiOlwid29ybGRcIn0iXQ==', + ); + expect(response.status).toEqual(200); + expect(response.data).toStrictEqual({ success: true }); + }); }); describe('GET', () => { - it('should not cache (if "Axios-Redis-Cache-Duration" header is disabled)', async () => { + it('should not cache (if "Axios-Redis-Cache-Duration" header = null)', async () => { + const apiNock = nock('https://api.example.com') + .get('/example/1') + .query({ sort: 'desc' }) + .matchHeader('User-Agent', '@scope/package') + .matchHeader('Api-Key', '3b48b9fd18ecca20ed5b0accbfeb6b70') + .reply(200, { + success: true, + }); + + const redisSetAsyncSpy = jest.spyOn(axiosRedis, 'redisSetAsync'); + const redisGetAsyncSpy = jest.spyOn(axiosRedis, 'redisGetAsync'); + + const response = await axiosInstance.get('/example/1?sort=desc', { + headers: { 'Axios-Redis-Cache-Duration': null }, + }); + + apiNock.done(); + expect(redisSetAsyncSpy).toBeCalledTimes(0); + expect(redisGetAsyncSpy).toBeCalledTimes(1); + expect(redisGetAsyncSpy).nthCalledWith( + 1, + '@scope/package@1.0.1___["get"]___WyIvZXhhbXBsZS8xP3NvcnQ9ZGVzYyJd___W10=___W10=', + ); + expect(response.status).toEqual(200); + expect(response.data).toStrictEqual({ success: true }); + }); + + it('should not cache (if "Axios-Redis-Cache-Duration" header = 0)', async () => { const apiNock = nock('https://api.example.com') .get('/example/1') .query({ sort: 'desc' })