route.spec.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426
  1. import {expect} from 'chai';
  2. import {format} from '@e22m4u/js-format';
  3. import {ROOT_PATH} from '../constants.js';
  4. import {Route, HttpMethod} from './route.js';
  5. import {ServiceContainer} from '@e22m4u/js-service';
  6. import {RequestContext} from '../request-context.js';
  7. import {HookRegistry, RouterHookType} from '../hooks/index.js';
  8. import {createRequestMock, createResponseMock} from '../utils/index.js';
  9. describe('Route', function () {
  10. describe('constructor', function () {
  11. it('should require the "routeDef" parameter to be an Object', function () {
  12. const throwable = v => () => new Route(v);
  13. const error = v =>
  14. format('Route definition must be an Object, but %s was given.', v);
  15. expect(throwable('str')).to.throw(error('"str"'));
  16. expect(throwable('')).to.throw(error('""'));
  17. expect(throwable(10)).to.throw(error('10'));
  18. expect(throwable(0)).to.throw(error('0'));
  19. expect(throwable(true)).to.throw(error('true'));
  20. expect(throwable(false)).to.throw(error('false'));
  21. expect(throwable(null)).to.throw(error('null'));
  22. expect(throwable([])).to.throw(error('Array'));
  23. expect(throwable(undefined)).to.throw(error('undefined'));
  24. expect(throwable(() => undefined)).to.throw(error('Function'));
  25. throwable({
  26. method: HttpMethod.GET,
  27. path: ROOT_PATH,
  28. handler: () => 'Ok',
  29. })();
  30. });
  31. it('should require the "method" option to be a non-empty String', function () {
  32. const throwable = v => () =>
  33. new Route({
  34. method: v,
  35. path: ROOT_PATH,
  36. handler: () => 'Ok',
  37. });
  38. const error = v =>
  39. format(
  40. 'Option "method" must be a non-empty String, but %s was given.',
  41. v,
  42. );
  43. expect(throwable('')).to.throw(error('""'));
  44. expect(throwable(10)).to.throw(error('10'));
  45. expect(throwable(0)).to.throw(error('0'));
  46. expect(throwable(true)).to.throw(error('true'));
  47. expect(throwable(false)).to.throw(error('false'));
  48. expect(throwable(null)).to.throw(error('null'));
  49. expect(throwable({})).to.throw(error('Object'));
  50. expect(throwable([])).to.throw(error('Array'));
  51. expect(throwable(undefined)).to.throw(error('undefined'));
  52. expect(throwable(() => undefined)).to.throw(error('Function'));
  53. throwable(HttpMethod.GET)();
  54. });
  55. it('should require the "path" option to be a non-empty String', function () {
  56. const throwable = v => () =>
  57. new Route({
  58. method: HttpMethod.GET,
  59. path: v,
  60. handler: () => 'Ok',
  61. });
  62. const error = v =>
  63. format(
  64. 'Option "path" must be a non-empty String, but %s was given.',
  65. v,
  66. );
  67. expect(throwable('')).to.throw(error('""'));
  68. expect(throwable(10)).to.throw(error('10'));
  69. expect(throwable(0)).to.throw(error('0'));
  70. expect(throwable(true)).to.throw(error('true'));
  71. expect(throwable(false)).to.throw(error('false'));
  72. expect(throwable({})).to.throw(error('Object'));
  73. expect(throwable([])).to.throw(error('Array'));
  74. expect(throwable(undefined)).to.throw(error('undefined'));
  75. expect(throwable(null)).to.throw(error('null'));
  76. expect(throwable(() => undefined)).to.throw(error('Function'));
  77. throwable('str')();
  78. });
  79. it('should require the "handler" option to be a Function', function () {
  80. const throwable = v => () =>
  81. new Route({
  82. method: HttpMethod.GET,
  83. path: ROOT_PATH,
  84. handler: v,
  85. });
  86. const error = v =>
  87. format('Option "handler" must be a Function, but %s was given.', v);
  88. expect(throwable('str')).to.throw(error('"str"'));
  89. expect(throwable('')).to.throw(error('""'));
  90. expect(throwable(10)).to.throw(error('10'));
  91. expect(throwable(0)).to.throw(error('0'));
  92. expect(throwable(true)).to.throw(error('true'));
  93. expect(throwable(false)).to.throw(error('false'));
  94. expect(throwable(null)).to.throw(error('null'));
  95. expect(throwable({})).to.throw(error('Object'));
  96. expect(throwable([])).to.throw(error('Array'));
  97. expect(throwable(undefined)).to.throw(error('undefined'));
  98. throwable(() => undefined)();
  99. });
  100. it('should require the "preHandler" option to be a Function or an Array', function () {
  101. const throwable = v => () =>
  102. new Route({
  103. method: HttpMethod.GET,
  104. path: ROOT_PATH,
  105. preHandler: v,
  106. handler: () => 'Ok',
  107. });
  108. const error = v =>
  109. format(
  110. 'Option "preHandler" must be a Function ' +
  111. 'or an Array, but %s was given.',
  112. v,
  113. );
  114. expect(throwable('str')).to.throw(error('"str"'));
  115. expect(throwable('')).to.throw(error('""'));
  116. expect(throwable(10)).to.throw(error('10'));
  117. expect(throwable(0)).to.throw(error('0'));
  118. expect(throwable(true)).to.throw(error('true'));
  119. expect(throwable(false)).to.throw(error('false'));
  120. expect(throwable({})).to.throw(error('Object'));
  121. expect(throwable(null)).to.throw(error('null'));
  122. throwable([])();
  123. throwable(() => undefined)();
  124. throwable(undefined)();
  125. });
  126. it('should require each element in the "preHandler" option to be a Function', function () {
  127. const throwable = v => () =>
  128. new Route({
  129. method: HttpMethod.GET,
  130. path: ROOT_PATH,
  131. preHandler: [v],
  132. handler: () => 'Ok',
  133. });
  134. const error = v =>
  135. format('Route pre-handler must be a Function, but %s was given.', v);
  136. expect(throwable('str')).to.throw(error('"str"'));
  137. expect(throwable('')).to.throw(error('""'));
  138. expect(throwable(10)).to.throw(error('10'));
  139. expect(throwable(0)).to.throw(error('0'));
  140. expect(throwable(true)).to.throw(error('true'));
  141. expect(throwable(false)).to.throw(error('false'));
  142. expect(throwable({})).to.throw(error('Object'));
  143. expect(throwable([])).to.throw(error('Array'));
  144. expect(throwable(null)).to.throw(error('null'));
  145. expect(throwable(undefined)).to.throw(error('undefined'));
  146. throwable(() => undefined)();
  147. });
  148. it('should require the "postHandler" option to be a Function or an Array', function () {
  149. const throwable = v => () =>
  150. new Route({
  151. method: HttpMethod.GET,
  152. path: ROOT_PATH,
  153. postHandler: v,
  154. handler: () => 'Ok',
  155. });
  156. const error = v =>
  157. format(
  158. 'Option "postHandler" must be a Function ' +
  159. 'or an Array, but %s was given.',
  160. v,
  161. );
  162. expect(throwable('str')).to.throw(error('"str"'));
  163. expect(throwable('')).to.throw(error('""'));
  164. expect(throwable(10)).to.throw(error('10'));
  165. expect(throwable(0)).to.throw(error('0'));
  166. expect(throwable(true)).to.throw(error('true'));
  167. expect(throwable(false)).to.throw(error('false'));
  168. expect(throwable({})).to.throw(error('Object'));
  169. expect(throwable(null)).to.throw(error('null'));
  170. throwable([])();
  171. throwable(() => undefined)();
  172. throwable(undefined)();
  173. });
  174. it('should require each element in the "postHandler" option to be a Function', function () {
  175. const throwable = v => () =>
  176. new Route({
  177. method: HttpMethod.GET,
  178. path: ROOT_PATH,
  179. postHandler: [v],
  180. handler: () => 'Ok',
  181. });
  182. const error = v =>
  183. format('Route post-handler must be a Function, but %s was given.', v);
  184. expect(throwable('str')).to.throw(error('"str"'));
  185. expect(throwable('')).to.throw(error('""'));
  186. expect(throwable(10)).to.throw(error('10'));
  187. expect(throwable(0)).to.throw(error('0'));
  188. expect(throwable(true)).to.throw(error('true'));
  189. expect(throwable(false)).to.throw(error('false'));
  190. expect(throwable({})).to.throw(error('Object'));
  191. expect(throwable([])).to.throw(error('Array'));
  192. expect(throwable(null)).to.throw(error('null'));
  193. expect(throwable(undefined)).to.throw(error('undefined'));
  194. throwable(() => undefined)();
  195. });
  196. it('should require the "meta" option to be a plain Object', function () {
  197. const throwable = v => () =>
  198. new Route({
  199. method: HttpMethod.GET,
  200. path: ROOT_PATH,
  201. handler: () => 'Ok',
  202. meta: v,
  203. });
  204. const error = v =>
  205. format('Option "meta" must be an Object, but %s was given.', v);
  206. expect(throwable('str')).to.throw(error('"str"'));
  207. expect(throwable('')).to.throw(error('""'));
  208. expect(throwable(10)).to.throw(error('10'));
  209. expect(throwable(0)).to.throw(error('0'));
  210. expect(throwable(true)).to.throw(error('true'));
  211. expect(throwable(false)).to.throw(error('false'));
  212. expect(throwable([])).to.throw(error('Array'));
  213. expect(throwable(null)).to.throw(error('null'));
  214. expect(throwable(() => undefined)).to.throw(error('Function'));
  215. throwable({foo: 'bar'})();
  216. throwable({})();
  217. throwable(undefined)();
  218. });
  219. it('should clone a given definition in the current instance', function () {
  220. const definition = {
  221. method: HttpMethod.GET,
  222. path: ROOT_PATH,
  223. handler: () => 'Ok',
  224. };
  225. const route = new Route(definition);
  226. const res = route.getDefinition();
  227. expect(res).to.be.eql(definition);
  228. expect(res).to.be.not.eq(definition);
  229. });
  230. it('should convert the "method" option to upper case', function () {
  231. const definition = {
  232. method: 'get',
  233. path: ROOT_PATH,
  234. handler: () => 'Ok',
  235. };
  236. const route = new Route(definition);
  237. const res = route.getDefinition();
  238. expect(res.method).to.be.eq('GET');
  239. });
  240. it('should add a single pre-handler to the hook registry', function () {
  241. const preHandler = () => undefined;
  242. const definition = {
  243. method: 'get',
  244. path: ROOT_PATH,
  245. handler: () => 'Ok',
  246. preHandler,
  247. };
  248. const route = new Route(definition);
  249. const hookReg = route.getHookRegistry();
  250. const res = hookReg.hasHook(RouterHookType.PRE_HANDLER, preHandler);
  251. expect(res).to.be.true;
  252. });
  253. it('should add each pre-handler from the array to the hook registry', function () {
  254. const preHandler1 = () => undefined;
  255. const preHandler2 = () => undefined;
  256. const definition = {
  257. method: 'get',
  258. path: ROOT_PATH,
  259. handler: () => 'Ok',
  260. preHandler: [preHandler1, preHandler2],
  261. };
  262. const route = new Route(definition);
  263. const hookReg = route.getHookRegistry();
  264. const res1 = hookReg.hasHook(RouterHookType.PRE_HANDLER, preHandler1);
  265. const res2 = hookReg.hasHook(RouterHookType.PRE_HANDLER, preHandler2);
  266. expect(res1).to.be.true;
  267. expect(res2).to.be.true;
  268. });
  269. it('should add a single post-handler to the hook registry', function () {
  270. const postHandler = () => undefined;
  271. const definition = {
  272. method: 'get',
  273. path: ROOT_PATH,
  274. handler: () => 'Ok',
  275. postHandler,
  276. };
  277. const route = new Route(definition);
  278. const hookReg = route.getHookRegistry();
  279. const res = hookReg.hasHook(RouterHookType.POST_HANDLER, postHandler);
  280. expect(res).to.be.true;
  281. });
  282. it('should add each post-handlers from the array to the hook registry', function () {
  283. const postHandler1 = () => undefined;
  284. const postHandler2 = () => undefined;
  285. const definition = {
  286. method: 'get',
  287. path: ROOT_PATH,
  288. handler: () => 'Ok',
  289. postHandler: [postHandler1, postHandler2],
  290. };
  291. const route = new Route(definition);
  292. const hookReg = route.getHookRegistry();
  293. const res1 = hookReg.hasHook(RouterHookType.POST_HANDLER, postHandler1);
  294. const res2 = hookReg.hasHook(RouterHookType.POST_HANDLER, postHandler2);
  295. expect(res1).to.be.true;
  296. expect(res2).to.be.true;
  297. });
  298. });
  299. describe('getDefinition', function () {
  300. it('should return a clone of the original route definition', function () {
  301. const definition = {
  302. method: HttpMethod.GET,
  303. path: '/myPath',
  304. handler: () => 'Ok',
  305. };
  306. const route = new Route(definition);
  307. expect(route.getDefinition()).to.be.eql(definition);
  308. });
  309. });
  310. describe('getHookRegistry', function () {
  311. it('should return the same hook registry instance on subsequent calls', function () {
  312. const route = new Route({
  313. method: HttpMethod.GET,
  314. path: ROOT_PATH,
  315. handler: () => 'Ok',
  316. });
  317. const res1 = route.getHookRegistry();
  318. const res2 = route.getHookRegistry();
  319. expect(res1).to.be.instanceOf(HookRegistry);
  320. expect(res2).to.be.eq(res1);
  321. });
  322. });
  323. describe('method', function () {
  324. it('should return a value of the "method" option', function () {
  325. const route = new Route({
  326. method: HttpMethod.GET,
  327. path: ROOT_PATH,
  328. handler: () => 'Ok',
  329. });
  330. expect(route.method).to.be.eq(HttpMethod.GET);
  331. });
  332. });
  333. describe('path', function () {
  334. it('should return a value of the "path" option', function () {
  335. const value = 'myPath';
  336. const route = new Route({
  337. method: HttpMethod.GET,
  338. path: value,
  339. handler: () => 'Ok',
  340. });
  341. expect(route.path).to.be.eq(value);
  342. });
  343. });
  344. describe('meta', function () {
  345. it('should return a value of the "meta" option', function () {
  346. const value = {foo: 'bar'};
  347. const route = new Route({
  348. method: HttpMethod.GET,
  349. path: ROOT_PATH,
  350. handler: () => 'Ok',
  351. meta: value,
  352. });
  353. expect(route.meta).to.be.eql(value);
  354. });
  355. it('should return an empty object if the "meta" option is not provided', function () {
  356. const route = new Route({
  357. method: HttpMethod.GET,
  358. path: ROOT_PATH,
  359. handler: () => 'Ok',
  360. });
  361. expect(route.meta).to.be.eql({});
  362. });
  363. it('should return an empty object if the "meta" option is undefined', function () {
  364. const route = new Route({
  365. method: HttpMethod.GET,
  366. path: ROOT_PATH,
  367. handler: () => 'Ok',
  368. meta: undefined,
  369. });
  370. expect(route.meta).to.be.eql({});
  371. });
  372. });
  373. describe('handler', function () {
  374. it('should return a value of the "handler" option', function () {
  375. const value = () => 'Ok';
  376. const route = new Route({
  377. method: HttpMethod.GET,
  378. path: ROOT_PATH,
  379. handler: value,
  380. });
  381. expect(route.handler).to.be.eq(value);
  382. });
  383. });
  384. describe('handle', function () {
  385. it('should invoke the handler with the given RequestContext and return its result', function () {
  386. const route = new Route({
  387. method: HttpMethod.GET,
  388. path: ROOT_PATH,
  389. handler(ctx) {
  390. expect(ctx).to.be.instanceof(RequestContext);
  391. return 'OK';
  392. },
  393. });
  394. const req = createRequestMock();
  395. const res = createResponseMock();
  396. const cont = new ServiceContainer();
  397. const ctx = new RequestContext(cont, req, res, route);
  398. const result = route.handle(ctx);
  399. expect(result).to.be.eq('OK');
  400. });
  401. });
  402. });