diff --git a/package.json b/package.json index e1b5e06..3e12ea1 100644 --- a/package.json +++ b/package.json @@ -47,6 +47,7 @@ "devDependencies": { "@release-it/conventional-changelog": "^1.1.0", "@types/axios": "^0.14.0", + "@types/content-type": "^1.1.3", "@types/cosmiconfig": "^5.0.3", "@types/fs-extra": "^8.0.0", "@types/jest": "^24.0.17", @@ -81,6 +82,7 @@ "awilix-koa": "4.0.0", "axios": "0.19.0", "chalk": "2.4.2", + "content-type": "^1.0.4", "cosmiconfig": "5.2.1", "fs-extra": "8.1.0", "koa": "2.8.1", diff --git a/src/infrastructure/repository/RequestRepositoryFile.test.ts b/src/infrastructure/repository/RequestRepositoryFile.test.ts index 961bf8c..32243ce 100644 --- a/src/infrastructure/repository/RequestRepositoryFile.test.ts +++ b/src/infrastructure/repository/RequestRepositoryFile.test.ts @@ -29,7 +29,7 @@ describe('persistResponseForRequest', () => { const CASES = [ 'APPLICATION/JSON', 'application/json', - 'application/json; chartset=UTF-8;', + 'application/json; charset=utf-8', ]; CASES.forEach(contentType => { @@ -92,6 +92,7 @@ describe('persistResponseForRequest', () => { const CASES = [ 'APPLICATION/XML', 'application/xml', + 'application/xml; charset=utf-8', 'TEXT/XML', 'text/xml', ]; @@ -203,7 +204,7 @@ describe('persistResponseForRequest', () => { `${OUTPUT_DIRECTORY}/get__text-${inputRequest.id}/metadata.json` ); const bodyContent = await fs.readFile( - `${OUTPUT_DIRECTORY}/get__text-${inputRequest.id}/body.txt`, + `${OUTPUT_DIRECTORY}/get__text-${inputRequest.id}/body`, 'utf-8' ); @@ -241,7 +242,7 @@ describe('persistResponseForRequest', () => { `${OUTPUT_DIRECTORY}/get__really_long_url-${inputRequest.id}/metadata.json` ); const bodyContent = await fs.readFile( - `${OUTPUT_DIRECTORY}/get__really_long_url-${inputRequest.id}/body.txt`, + `${OUTPUT_DIRECTORY}/get__really_long_url-${inputRequest.id}/body`, 'utf-8' ); diff --git a/src/infrastructure/repository/RequestRepositoryFile.ts b/src/infrastructure/repository/RequestRepositoryFile.ts index 89cf35b..684eb83 100644 --- a/src/infrastructure/repository/RequestRepositoryFile.ts +++ b/src/infrastructure/repository/RequestRepositoryFile.ts @@ -1,7 +1,11 @@ import path from 'path'; import fs from 'fs-extra'; -import { getProjectDirectory, getRequestDirectory } from '../../utils/path'; +import { + getProjectDirectory, + getRequestDirectory, + getFileExtension, +} from '../../utils/path'; import { RequestRepository } from '../../domain/repository'; import { Request, Response } from '../../domain/entity'; @@ -10,25 +14,6 @@ interface Dependencies { cacheDirectory: string; } -function isJson(contentType: string | undefined) { - return ( - contentType && contentType.toLowerCase().indexOf('application/json') >= 0 - ); -} - -function isXml(contentType: string | undefined) { - if (!contentType) { - return false; - } - - const lowerCaseContentType = contentType.toLowerCase(); - - return ( - lowerCaseContentType.indexOf('application/xml') >= 0 || - lowerCaseContentType.indexOf('text/xml') >= 0 - ); -} - export class RequestRepositoryFile implements RequestRepository { private targetUrl: string; private cacheDirectory: string; @@ -141,19 +126,11 @@ export class RequestRepositoryFile implements RequestRepository { private async getResponseBodyFilePath(request: Request) { const metadata = await this.getRequestMetaData(request); const contentType = metadata.responseHeaders['content-type']; - let fileExtension: string; - - if (isJson(contentType)) { - fileExtension = 'json'; - } else if (isXml(contentType)) { - fileExtension = 'xml'; - } else { - fileExtension = 'txt'; - } + const fileExtension = getFileExtension(contentType); return path.join( this.getRequestDirectoryPath(request), - `body.${fileExtension}` + `body${fileExtension}` ); } diff --git a/src/utils/path.test.ts b/src/utils/path.test.ts index 22f6c4d..69182e8 100644 --- a/src/utils/path.test.ts +++ b/src/utils/path.test.ts @@ -1,5 +1,9 @@ import { Request } from '../domain/entity'; -import { getProjectDirectory, getRequestDirectory } from './path'; +import { + getProjectDirectory, + getRequestDirectory, + getFileExtension, +} from './path'; describe('getProjectDirectory', () => { it('should return the project directory', () => { @@ -42,3 +46,144 @@ describe('getRequestDirectory', () => { ); }); }); + +describe('getFileExtension', () => { + it.each([ + ['application/json', '.json'], + ['application/json; charset=utf-8', '.json'], + ['application/xml', '.xml'], + ['application/xml; charset=utf-8', '.xml'], + ['application/envoy', '.evy'], + ['application/fractals', '.fif'], + ['application/futuresplash', '.spl'], + ['application/hta', '.hta'], + ['application/internet-property-stream', '.acx'], + ['application/mac-binhex40', '.hqx'], + ['application/msword', '.doc'], + ['application/octet-stream', ''], + ['application/oda', '.oda'], + ['application/olescript', '.axs'], + ['application/pdf', '.pdf'], + ['application/pics-rules', '.prf'], + ['application/pkcs10', '.p10'], + ['application/pkix-crl', '.crl'], + ['application/postscript', '.eps'], + ['application/rtf', '.rtf'], + ['application/xml', '.xml'], + ['application/set-payment-initiation', '.setpay'], + ['application/set-registration-initiation', '.setreg'], + ['application/vnd.ms-excel', '.xls'], + ['application/vnd.ms-outlook', '.msg'], + ['application/vnd.ms-pkicertstore', '.sst'], + ['application/vnd.ms-pkiseccat', '.cat'], + ['application/vnd.ms-pkistl', '.stl'], + ['application/vnd.ms-powerpoint', '.ppt'], + ['application/vnd.ms-project', '.mpp'], + ['application/vnd.ms-works', '.wks'], + ['application/winhlp', '.hlp'], + ['application/x-bcpio', '.bcpio'], + ['application/x-cdf', '.cdf'], + ['application/x-compress', '.z'], + ['application/x-compressed', '.tgz'], + ['application/x-cpio', '.cpio'], + ['application/x-csh', '.csh'], + ['application/x-director', '.dcr'], + ['application/x-dvi', '.dvi'], + ['application/x-gtar', '.gtar'], + ['application/x-gzip', '.gz'], + ['application/x-hdf', '.hdf'], + ['application/x-internet-signup', '.ins'], + ['application/x-iphone', '.iii'], + ['application/x-javascript', '.js'], + ['application/x-latex', '.latex'], + ['application/x-msaccess', '.mdb'], + ['application/x-mscardfile', '.crd'], + ['application/x-msclip', '.clp'], + ['application/x-msdownload', '.dll'], + ['application/x-msmediaview', '.m13'], + ['application/x-msmetafile', '.wmf'], + ['application/x-msmoney', '.mny'], + ['application/x-mspublisher', '.pub'], + ['application/x-msschedule', '.scd'], + ['application/x-msterminal', '.trm'], + ['application/x-mswrite', '.wri'], + ['application/x-netcdf', '.cdf'], + ['application/x-perfmon', '.pma'], + ['application/x-pkcs12', '.p12'], + ['application/x-pkcs7-certificates', '.p7b'], + ['application/x-pkcs7-certreqresp', '.p7r'], + ['application/x-pkcs7-mime', '.p7c'], + ['application/x-pkcs7-signature', '.p7s'], + ['application/x-sh', '.sh'], + ['application/x-shar', '.shar'], + ['application/x-shockwave-flash', '.swf'], + ['application/x-stuffit', '.sit'], + ['application/x-sv4cpio', '.sv4cpio'], + ['application/x-sv4crc', '.sv4crc'], + ['application/x-tar', '.tar'], + ['application/x-tcl', '.tcl'], + ['application/x-tex', '.tex'], + ['application/x-texinfo', '.texi'], + ['application/x-troff', '.roff'], + ['application/x-troff-man', '.man'], + ['application/x-troff-me', '.me'], + ['application/x-troff-ms', '.ms'], + ['application/x-ustar', '.ustar'], + ['application/x-wais-source', '.src'], + ['application/x-x509-ca-cert', '.cer'], + ['application/ynd.ms-pkipko', '.pko'], + ['application/zip', '.zip'], + ['audio/basic', '.au'], + ['audio/mid', '.mid'], + ['audio/mpeg', '.mp3'], + ['audio/x-aiff', '.aif'], + ['audio/x-mpegurl', '.m3u'], + ['audio/x-pn-realaudio', '.ra'], + ['audio/x-wav', '.wav'], + ['image/bmp', '.bmp'], + ['image/cis-cod', '.cod'], + ['image/gif', '.gif'], + ['image/ief', '.ief'], + ['image/jpeg', '.jpg'], + ['image/pipeg', '.jfif'], + ['image/svg+xml', '.svg'], + ['image/tiff', '.tiff'], + ['image/x-cmu-raster', '.ras'], + ['image/x-cmx', '.cmx'], + ['image/x-icon', '.ico'], + ['image/x-portable-anymap', '.pnm'], + ['image/x-portable-bitmap', '.pbm'], + ['image/x-portable-graymap', '.pgm'], + ['image/x-portable-pixmap', '.ppm'], + ['image/x-rgb', '.rgb'], + ['image/x-xbitmap', '.xbm'], + ['image/x-xpixmap', '.xpm'], + ['image/x-xwindowdump', '.xwd'], + ['message/rfc822', '.mhtml'], + ['text/css', '.css'], + ['text/h323', '.323'], + ['text/html', '.html'], + ['text/iuls', '.uls'], + ['text/plain', '.txt'], + ['text/xml', '.xml'], + ['text/richtext', '.rtx'], + ['text/scriptlet', '.sct'], + ['text/tab-separated-values', '.tsv'], + ['text/webviewhtml', '.htt'], + ['text/x-component', '.htc'], + ['text/x-setext', '.etx'], + ['text/x-vcard', '.vcf'], + ['video/mpeg', '.mpeg'], + ['video/mp4', '.mp4'], + ['video/quicktime', '.mov'], + ['video/x-la-asf', '.lsf'], + ['video/x-ms-asf', '.asf'], + ['video/x-msvideo', '.avi'], + ['video/x-sgi-movie', '.movie'], + ['x-world/x-vrml', '.vrml'], + ['invalid;type;;', ''], + ['', ''], + ])('%s returns %s', (contentType: string, expectedExtension: string) => { + expect(getFileExtension(contentType)).toEqual(expectedExtension); + }); +}); diff --git a/src/utils/path.ts b/src/utils/path.ts index d21c414..ae417a4 100644 --- a/src/utils/path.ts +++ b/src/utils/path.ts @@ -1,4 +1,5 @@ import path from 'path'; +import { parse as parseContentType } from 'content-type'; import { Request } from '../domain/entity'; @@ -22,3 +23,152 @@ export function getRequestDirectory( return path.join(projectDirectoryPath, requestDirectoryPath); } + +const extensionsMap = new Map(); + +extensionsMap.set('application/envoy', 'evy'); +extensionsMap.set('application/fractals', 'fif'); +extensionsMap.set('application/futuresplash', 'spl'); +extensionsMap.set('application/hta', 'hta'); +extensionsMap.set('application/internet-property-stream', 'acx'); +extensionsMap.set('application/mac-binhex40', 'hqx'); +extensionsMap.set('application/msword', 'doc'); +extensionsMap.set('application/oda', 'oda'); +extensionsMap.set('application/olescript', 'axs'); +extensionsMap.set('application/pdf', 'pdf'); +extensionsMap.set('application/pics-rules', 'prf'); +extensionsMap.set('application/pkcs10', 'p10'); +extensionsMap.set('application/pkix-crl', 'crl'); +extensionsMap.set('application/postscript', 'eps'); +extensionsMap.set('application/json', 'json'); +extensionsMap.set('application/xml', 'xml'); +extensionsMap.set('application/rtf', 'rtf'); +extensionsMap.set('application/set-payment-initiation', 'setpay'); +extensionsMap.set('application/set-registration-initiation', 'setreg'); +extensionsMap.set('application/vnd.ms-excel', 'xls'); +extensionsMap.set('application/vnd.ms-outlook', 'msg'); +extensionsMap.set('application/vnd.ms-pkicertstore', 'sst'); +extensionsMap.set('application/vnd.ms-pkiseccat', 'cat'); +extensionsMap.set('application/vnd.ms-pkistl', 'stl'); +extensionsMap.set('application/vnd.ms-powerpoint', 'ppt'); +extensionsMap.set('application/vnd.ms-project', 'mpp'); +extensionsMap.set('application/vnd.ms-works', 'wks'); +extensionsMap.set('application/winhlp', 'hlp'); +extensionsMap.set('application/x-bcpio', 'bcpio'); +extensionsMap.set('application/x-cdf', 'cdf'); +extensionsMap.set('application/x-compress', 'z'); +extensionsMap.set('application/x-compressed', 'tgz'); +extensionsMap.set('application/x-cpio', 'cpio'); +extensionsMap.set('application/x-csh', 'csh'); +extensionsMap.set('application/x-director', 'dcr'); +extensionsMap.set('application/x-dvi', 'dvi'); +extensionsMap.set('application/x-gtar', 'gtar'); +extensionsMap.set('application/x-gzip', 'gz'); +extensionsMap.set('application/x-hdf', 'hdf'); +extensionsMap.set('application/x-internet-signup', 'ins'); +extensionsMap.set('application/x-iphone', 'iii'); +extensionsMap.set('application/x-javascript', 'js'); +extensionsMap.set('application/x-latex', 'latex'); +extensionsMap.set('application/x-msaccess', 'mdb'); +extensionsMap.set('application/x-mscardfile', 'crd'); +extensionsMap.set('application/x-msclip', 'clp'); +extensionsMap.set('application/x-msdownload', 'dll'); +extensionsMap.set('application/x-msmediaview', 'm13'); +extensionsMap.set('application/x-msmetafile', 'wmf'); +extensionsMap.set('application/x-msmoney', 'mny'); +extensionsMap.set('application/x-mspublisher', 'pub'); +extensionsMap.set('application/x-msschedule', 'scd'); +extensionsMap.set('application/x-msterminal', 'trm'); +extensionsMap.set('application/x-mswrite', 'wri'); +extensionsMap.set('application/x-netcdf', 'cdf'); +extensionsMap.set('application/x-perfmon', 'pma'); +extensionsMap.set('application/x-pkcs12', 'p12'); +extensionsMap.set('application/x-pkcs7-certificates', 'p7b'); +extensionsMap.set('application/x-pkcs7-certreqresp', 'p7r'); +extensionsMap.set('application/x-pkcs7-mime', 'p7c'); +extensionsMap.set('application/x-pkcs7-signature', 'p7s'); +extensionsMap.set('application/x-sh', 'sh'); +extensionsMap.set('application/x-shar', 'shar'); +extensionsMap.set('application/x-shockwave-flash', 'swf'); +extensionsMap.set('application/x-stuffit', 'sit'); +extensionsMap.set('application/x-sv4cpio', 'sv4cpio'); +extensionsMap.set('application/x-sv4crc', 'sv4crc'); +extensionsMap.set('application/x-tar', 'tar'); +extensionsMap.set('application/x-tcl', 'tcl'); +extensionsMap.set('application/x-tex', 'tex'); +extensionsMap.set('application/x-texinfo', 'texi'); +extensionsMap.set('application/x-troff', 'roff'); +extensionsMap.set('application/x-troff-man', 'man'); +extensionsMap.set('application/x-troff-me', 'me'); +extensionsMap.set('application/x-troff-ms', 'ms'); +extensionsMap.set('application/x-ustar', 'ustar'); +extensionsMap.set('application/x-wais-source', 'src'); +extensionsMap.set('application/x-x509-ca-cert', 'cer'); +extensionsMap.set('application/ynd.ms-pkipko', 'pko'); +extensionsMap.set('application/zip', 'zip'); +extensionsMap.set('audio/basic', 'au'); +extensionsMap.set('audio/mid', 'mid'); +extensionsMap.set('audio/mpeg', 'mp3'); +extensionsMap.set('audio/x-aiff', 'aif'); +extensionsMap.set('audio/x-mpegurl', 'm3u'); +extensionsMap.set('audio/x-pn-realaudio', 'ra'); +extensionsMap.set('audio/x-wav', 'wav'); +extensionsMap.set('image/bmp', 'bmp'); +extensionsMap.set('image/cis-cod', 'cod'); +extensionsMap.set('image/gif', 'gif'); +extensionsMap.set('image/ief', 'ief'); +extensionsMap.set('image/jpeg', 'jpg'); +extensionsMap.set('image/pipeg', 'jfif'); +extensionsMap.set('image/svg+xml', 'svg'); +extensionsMap.set('image/tiff', 'tiff'); +extensionsMap.set('image/x-cmu-raster', 'ras'); +extensionsMap.set('image/x-cmx', 'cmx'); +extensionsMap.set('image/x-icon', 'ico'); +extensionsMap.set('image/x-portable-anymap', 'pnm'); +extensionsMap.set('image/x-portable-bitmap', 'pbm'); +extensionsMap.set('image/x-portable-graymap', 'pgm'); +extensionsMap.set('image/x-portable-pixmap', 'ppm'); +extensionsMap.set('image/x-rgb', 'rgb'); +extensionsMap.set('image/x-xbitmap', 'xbm'); +extensionsMap.set('image/x-xpixmap', 'xpm'); +extensionsMap.set('image/x-xwindowdump', 'xwd'); +extensionsMap.set('message/rfc822', 'mhtml'); +extensionsMap.set('text/css', 'css'); +extensionsMap.set('text/h323', '323'); +extensionsMap.set('text/html', 'html'); +extensionsMap.set('text/xml', 'xml'); +extensionsMap.set('text/iuls', 'uls'); +extensionsMap.set('text/plain', 'txt'); +extensionsMap.set('text/richtext', 'rtx'); +extensionsMap.set('text/scriptlet', 'sct'); +extensionsMap.set('text/tab-separated-values', 'tsv'); +extensionsMap.set('text/webviewhtml', 'htt'); +extensionsMap.set('text/x-component', 'htc'); +extensionsMap.set('text/x-setext', 'etx'); +extensionsMap.set('text/x-vcard', 'vcf'); +extensionsMap.set('video/mpeg', 'mpeg'); +extensionsMap.set('video/mp4', 'mp4'); +extensionsMap.set('video/quicktime', 'mov'); +extensionsMap.set('video/x-la-asf', 'lsf'); +extensionsMap.set('video/x-ms-asf', 'asf'); +extensionsMap.set('video/x-msvideo', 'avi'); +extensionsMap.set('video/x-sgi-movie', 'movie'); +extensionsMap.set('x-world/x-vrml', 'vrml'); + +const DEFAULT_EXTENSION = ''; + +export function getFileExtension(rawContentType: string = '') { + try { + const parsedContentType = parseContentType(rawContentType); + + for (const [contentType, extension] of extensionsMap.entries()) { + if (parsedContentType.type === contentType) { + return `.${extension}`; + } + } + + return DEFAULT_EXTENSION; + } catch (err) { + return DEFAULT_EXTENSION; + } +} diff --git a/tests/integration/get.test.ts b/tests/integration/get.test.ts index a9bf036..44a61e3 100644 --- a/tests/integration/get.test.ts +++ b/tests/integration/get.test.ts @@ -179,17 +179,13 @@ describe('2xx success / Headers check', () => { .set('Authorization', 'Bearer access-token'); // Then - expect(responseFromNetwork.status).toBe(200); expect(responseFromNetwork.header['x-custom-header']).toEqual( 'Response header example' ); - expect(responseFromNetwork.text).toEqual('Hello world'); - expect(responseFromCache.status).toBe(200); expect(responseFromCache.header['x-custom-header']).toEqual( 'Response header example' ); - expect(responseFromCache.text).toEqual('Hello world'); expect(networkSpy).toHaveBeenCalledTimes(1); }); @@ -199,9 +195,7 @@ describe('4xx errors / Plain text', () => { beforeEach(() => { nock(`${targetUrl}`) .get('/get-test-403') - .reply(403, 'Not authorized', { - 'content-type': 'text/plain', - }); + .reply(403, 'Not authorized', { 'content-type': 'text/plain' }); }); it('should forward the response', async () => { diff --git a/yarn.lock b/yarn.lock index f76209b..9305991 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1007,6 +1007,11 @@ dependencies: "@types/node" "*" +"@types/content-type@^1.1.3": + version "1.1.3" + resolved "https://registry.yarnpkg.com/@types/content-type/-/content-type-1.1.3.tgz#3688bd77fc12f935548eef102a4e34c512b03a07" + integrity sha512-pv8VcFrZ3fN93L4rTNIbbUzdkzjEyVMp5mPVjsFfOYTDOZMZiZ8P1dhu+kEv3faYyKzZgLlSvnyQNFg+p/v5ug== + "@types/cookiejar@*": version "2.1.1" resolved "https://registry.yarnpkg.com/@types/cookiejar/-/cookiejar-2.1.1.tgz#90b68446364baf9efd8e8349bb36bd3852b75b80"