body-parser.spec.js 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300
  1. import {expect} from 'chai';
  2. import HttpErrors from 'http-errors';
  3. import {HttpMethod} from '../route.js';
  4. import {format} from '@e22m4u/js-format';
  5. import {RouterOptions} from '../router-options.js';
  6. import {createRequestMock} from '../utils/index.js';
  7. import {
  8. BodyParser,
  9. METHODS_WITH_BODY,
  10. UNPARSABLE_MEDIA_TYPES,
  11. } from './body-parser.js';
  12. describe('BodyParser', function () {
  13. describe('defineParser', function () {
  14. it('requires the parameter "mediaType" to be a non-empty String', function () {
  15. const parser = new BodyParser();
  16. const throwable = v => () => parser.defineParser(v, () => undefined);
  17. const error = v =>
  18. format(
  19. 'The parameter "mediaType" of BodyParser.defineParser ' +
  20. 'should be a non-empty String, but %s was given.',
  21. v,
  22. );
  23. expect(throwable('')).to.throw(error('""'));
  24. expect(throwable(10)).to.throw(error('10'));
  25. expect(throwable(0)).to.throw(error('0'));
  26. expect(throwable(true)).to.throw(error('true'));
  27. expect(throwable(false)).to.throw(error('false'));
  28. expect(throwable(null)).to.throw(error('null'));
  29. expect(throwable({})).to.throw(error('Object'));
  30. expect(throwable([])).to.throw(error('Array'));
  31. expect(throwable(undefined)).to.throw(error('undefined'));
  32. expect(throwable(() => undefined)).to.throw(error('Function'));
  33. throwable('text/plain')();
  34. });
  35. it('requires the parameter "parser" to be a Function', function () {
  36. const parser = new BodyParser();
  37. const throwable = v => () => parser.defineParser('str', v);
  38. const error = v =>
  39. format(
  40. 'The parameter "parser" of BodyParser.defineParser ' +
  41. 'should be a Function, but %s was given.',
  42. v,
  43. );
  44. expect(throwable('str')).to.throw(error('"str"'));
  45. expect(throwable('')).to.throw(error('""'));
  46. expect(throwable(10)).to.throw(error('10'));
  47. expect(throwable(0)).to.throw(error('0'));
  48. expect(throwable(true)).to.throw(error('true'));
  49. expect(throwable(false)).to.throw(error('false'));
  50. expect(throwable(null)).to.throw(error('null'));
  51. expect(throwable({})).to.throw(error('Object'));
  52. expect(throwable([])).to.throw(error('Array'));
  53. expect(throwable(undefined)).to.throw(error('undefined'));
  54. throwable(() => undefined)();
  55. });
  56. it('overrides existing parser', function () {
  57. const parser = new BodyParser();
  58. const fn = v => v;
  59. parser.defineParser('text/plain', fn);
  60. expect(parser['_parsers']['text/plain']).to.be.eq(fn);
  61. });
  62. it('sets a new parser', function () {
  63. const parser = new BodyParser();
  64. const fn = v => v;
  65. parser.defineParser('my/type', fn);
  66. expect(parser['_parsers']['my/type']).to.be.eq(fn);
  67. });
  68. });
  69. describe('hasParser', function () {
  70. it('requires the parameter "mediaType" to be a non-empty String', function () {
  71. const parser = new BodyParser();
  72. const throwable = v => () => parser.hasParser(v);
  73. const error = v =>
  74. format(
  75. 'The parameter "mediaType" of BodyParser.hasParser ' +
  76. 'should be a non-empty String, but %s was given.',
  77. v,
  78. );
  79. expect(throwable('')).to.throw(error('""'));
  80. expect(throwable(10)).to.throw(error('10'));
  81. expect(throwable(0)).to.throw(error('0'));
  82. expect(throwable(true)).to.throw(error('true'));
  83. expect(throwable(false)).to.throw(error('false'));
  84. expect(throwable(null)).to.throw(error('null'));
  85. expect(throwable({})).to.throw(error('Object'));
  86. expect(throwable([])).to.throw(error('Array'));
  87. expect(throwable(undefined)).to.throw(error('undefined'));
  88. expect(throwable(() => undefined)).to.throw(error('Function'));
  89. throwable('text/plain')();
  90. });
  91. it('returns true if the parser is exist', function () {
  92. const parser = new BodyParser();
  93. parser.defineParser('type/media', v => v);
  94. expect(parser.hasParser('type/media')).to.be.true;
  95. });
  96. it('returns false if the parser is not exist', function () {
  97. const parser = new BodyParser();
  98. expect(parser.hasParser('text/unknown')).to.be.false;
  99. });
  100. });
  101. describe('deleteParser', function () {
  102. it('requires the parameter "mediaType" to be a non-empty String', function () {
  103. const parser = new BodyParser();
  104. const throwable = v => () => parser.deleteParser(v);
  105. const error = v =>
  106. format(
  107. 'The parameter "mediaType" of BodyParser.deleteParser ' +
  108. 'should be a non-empty String, but %s was given.',
  109. v,
  110. );
  111. expect(throwable('')).to.throw(error('""'));
  112. expect(throwable(10)).to.throw(error('10'));
  113. expect(throwable(0)).to.throw(error('0'));
  114. expect(throwable(true)).to.throw(error('true'));
  115. expect(throwable(false)).to.throw(error('false'));
  116. expect(throwable(null)).to.throw(error('null'));
  117. expect(throwable({})).to.throw(error('Object'));
  118. expect(throwable([])).to.throw(error('Array'));
  119. expect(throwable(undefined)).to.throw(error('undefined'));
  120. expect(throwable(() => undefined)).to.throw(error('Function'));
  121. throwable('text/plain')();
  122. });
  123. it('remove existing parser', function () {
  124. const parser = new BodyParser();
  125. const fn = v => v;
  126. parser.defineParser('my/type', fn);
  127. expect(parser['_parsers']['my/type']).to.be.eq(fn);
  128. parser.deleteParser('my/type');
  129. expect(parser['_parsers']['my/type']).to.be.undefined;
  130. });
  131. it('throws an error if the media type does not exist', function () {
  132. const parser = new BodyParser();
  133. const throwable = () => parser.deleteParser('unknown');
  134. expect(throwable).to.throw('The parser of "unknown" is not found.');
  135. });
  136. });
  137. describe('parse', function () {
  138. it('returns undefined if the request method is not supported', async function () {
  139. const parser = new BodyParser();
  140. const req = createRequestMock({
  141. method: 'unsupported',
  142. body: 'Lorem Ipsum is simply dummy text.',
  143. });
  144. const result = await parser.parse(req);
  145. expect(result).to.be.undefined;
  146. });
  147. it('returns undefined if the request method is not supported even the header "content-type" is specified', async function () {
  148. const parser = new BodyParser();
  149. const req = createRequestMock({
  150. method: 'unsupported',
  151. headers: {'content-type': 'text/plain'},
  152. body: 'Lorem Ipsum is simply dummy text.',
  153. });
  154. const result = await parser.parse(req);
  155. expect(result).to.be.undefined;
  156. });
  157. it('returns undefined if no "content-type" header', async function () {
  158. const parser = new BodyParser();
  159. const req = createRequestMock({method: HttpMethod.POST});
  160. const result = await parser.parse(req);
  161. expect(result).to.be.undefined;
  162. });
  163. it('returns undefined if the media type is excluded', async function () {
  164. const parser = new BodyParser();
  165. for await (const mediaType of UNPARSABLE_MEDIA_TYPES) {
  166. const req = createRequestMock({
  167. method: HttpMethod.POST,
  168. headers: {'content-type': mediaType},
  169. body: 'Lorem Ipsum is simply dummy text.',
  170. });
  171. const result = await parser.parse(req);
  172. expect(result).to.be.undefined;
  173. }
  174. });
  175. it('parses the request body for available methods', async function () {
  176. const parser = new BodyParser();
  177. const body = 'Lorem Ipsum is simply dummy text.';
  178. const headers = {'content-type': 'text/plain'};
  179. for await (const method of Object.values(METHODS_WITH_BODY)) {
  180. const req = createRequestMock({method, body, headers});
  181. const result = await parser.parse(req);
  182. expect(result).to.be.eq(body);
  183. }
  184. });
  185. it('throws an error for unsupported media type', function () {
  186. const parser = new BodyParser();
  187. const req = createRequestMock({
  188. method: HttpMethod.POST,
  189. headers: {'content-type': 'media/unknown'},
  190. });
  191. const throwable = () => parser.parse(req);
  192. expect(throwable).to.throw(
  193. 'Media type "media/unknown" is not supported.',
  194. );
  195. });
  196. it('uses the option "bodyBytesLimit" from the RouterOptions', async function () {
  197. const parser = new BodyParser();
  198. parser.getService(RouterOptions).setRequestBodyBytesLimit(1);
  199. const req = createRequestMock({
  200. method: HttpMethod.POST,
  201. headers: {
  202. 'content-type': 'text/plain',
  203. 'content-length': '2',
  204. },
  205. });
  206. const promise = parser.parse(req);
  207. await expect(promise).to.be.rejectedWith(HttpErrors.PayloadTooLarge);
  208. });
  209. describe('text/plain', function () {
  210. it('returns undefined if no request body', async function () {
  211. const parser = new BodyParser();
  212. const req = createRequestMock({
  213. method: HttpMethod.POST,
  214. headers: {'content-type': 'text/plain'},
  215. });
  216. const result = await parser.parse(req);
  217. expect(result).to.be.undefined;
  218. });
  219. it('returns a string from the string body', async function () {
  220. const body = 'Lorem Ipsum is simply dummy text.';
  221. const parser = new BodyParser();
  222. const req = createRequestMock({
  223. method: HttpMethod.POST,
  224. headers: {'content-type': 'text/plain'},
  225. body,
  226. });
  227. const result = await parser.parse(req);
  228. expect(result).to.be.eq(body);
  229. });
  230. it('returns a string from the Buffer body', async function () {
  231. const body = 'Lorem Ipsum is simply dummy text.';
  232. const parser = new BodyParser();
  233. const req = createRequestMock({
  234. method: HttpMethod.POST,
  235. headers: {'content-type': 'text/plain'},
  236. body: Buffer.from(body, 'utf-8'),
  237. });
  238. const result = await parser.parse(req);
  239. expect(result).to.be.eq(body);
  240. });
  241. });
  242. describe('application/json', function () {
  243. it('returns undefined if no request body', async function () {
  244. const parser = new BodyParser();
  245. const req = createRequestMock({
  246. method: HttpMethod.POST,
  247. headers: {'content-type': 'application/json'},
  248. });
  249. const result = await parser.parse(req);
  250. expect(result).to.be.undefined;
  251. });
  252. it('returns parsed JSON from the string body', async function () {
  253. const body = {foo: 'bar'};
  254. const parser = new BodyParser();
  255. const req = createRequestMock({
  256. method: HttpMethod.POST,
  257. headers: {'content-type': 'application/json'},
  258. body: JSON.stringify(body),
  259. });
  260. const result = await parser.parse(req);
  261. expect(result).to.be.eql(body);
  262. });
  263. it('returns parsed JSON from the Buffer body', async function () {
  264. const body = {foo: 'bar'};
  265. const parser = new BodyParser();
  266. const req = createRequestMock({
  267. method: HttpMethod.POST,
  268. headers: {'content-type': 'application/json'},
  269. body: Buffer.from(JSON.stringify(body)),
  270. });
  271. const result = await parser.parse(req);
  272. expect(result).to.be.eql(body);
  273. });
  274. });
  275. });
  276. });