trie-router.spec.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471
  1. import {describe} from 'mocha';
  2. import {Route} from './route.js';
  3. import {expect} from './chai.js';
  4. import {HttpMethod} from './route.js';
  5. import {HookName} from './hooks/index.js';
  6. import {TrieRouter} from './trie-router.js';
  7. import {HookRegistry} from './hooks/index.js';
  8. import {DataSender} from './senders/index.js';
  9. import {ErrorSender} from './senders/index.js';
  10. import {createRequestMock} from './utils/index.js';
  11. import {createResponseMock} from './utils/index.js';
  12. import {RequestContext} from './request-context.js';
  13. describe('TrieRouter', function () {
  14. describe('defineRoute', function () {
  15. it('returns the Route instance', function () {
  16. const router = new TrieRouter();
  17. const path = '/path';
  18. const handler = () => 'ok';
  19. const res = router.defineRoute({method: HttpMethod.GET, path, handler});
  20. expect(res).to.be.instanceof(Route);
  21. expect(res.method).to.be.eq(HttpMethod.GET);
  22. expect(res.path).to.be.eq(path);
  23. expect(res.handler).to.be.eq(handler);
  24. });
  25. });
  26. describe('requestListener', function () {
  27. it('to be a function', function () {
  28. const router = new TrieRouter();
  29. expect(typeof router.requestListener).to.be.eq('function');
  30. });
  31. it('passes request context to the route handler', function (done) {
  32. const router = new TrieRouter();
  33. router.defineRoute({
  34. method: HttpMethod.GET,
  35. path: '/test',
  36. handler: ctx => {
  37. expect(ctx).to.be.instanceof(RequestContext);
  38. done();
  39. },
  40. });
  41. const req = createRequestMock({path: '/test'});
  42. const res = createResponseMock();
  43. router.requestListener(req, res);
  44. });
  45. it('passes path parameters to the request context', function (done) {
  46. const router = new TrieRouter();
  47. router.defineRoute({
  48. method: HttpMethod.GET,
  49. path: '/:p1-:p2',
  50. handler: ({params}) => {
  51. expect(params).to.be.eql({p1: 'foo', p2: 'bar'});
  52. done();
  53. },
  54. });
  55. const req = createRequestMock({path: '/foo-bar'});
  56. const res = createResponseMock();
  57. router.requestListener(req, res);
  58. });
  59. it('passes query parameters to the request context', function (done) {
  60. const router = new TrieRouter();
  61. router.defineRoute({
  62. method: HttpMethod.GET,
  63. path: '/',
  64. handler: ({query}) => {
  65. expect(query).to.be.eql({p1: 'foo', p2: 'bar'});
  66. done();
  67. },
  68. });
  69. const req = createRequestMock({path: '?p1=foo&p2=bar'});
  70. const res = createResponseMock();
  71. router.requestListener(req, res);
  72. });
  73. it('passes parsed cookie to the request context', function (done) {
  74. const router = new TrieRouter();
  75. router.defineRoute({
  76. method: HttpMethod.GET,
  77. path: '/',
  78. handler: ({cookie}) => {
  79. expect(cookie).to.be.eql({p1: 'foo', p2: 'bar'});
  80. done();
  81. },
  82. });
  83. const req = createRequestMock({headers: {cookie: 'p1=foo; p2=bar;'}});
  84. const res = createResponseMock();
  85. router.requestListener(req, res);
  86. });
  87. it('passes plain text body to the request context', function (done) {
  88. const router = new TrieRouter();
  89. const body = 'Lorem Ipsum is simply dummy text.';
  90. router.defineRoute({
  91. method: HttpMethod.POST,
  92. path: '/',
  93. handler: ctx => {
  94. expect(ctx.body).to.be.eq(body);
  95. done();
  96. },
  97. });
  98. const req = createRequestMock({method: HttpMethod.POST, body});
  99. const res = createResponseMock();
  100. router.requestListener(req, res);
  101. });
  102. it('passes parsed JSON body to the request context', function (done) {
  103. const router = new TrieRouter();
  104. const data = {p1: 'foo', p2: 'bar'};
  105. router.defineRoute({
  106. method: HttpMethod.POST,
  107. path: '/',
  108. handler: ({body}) => {
  109. expect(body).to.be.eql(data);
  110. done();
  111. },
  112. });
  113. const req = createRequestMock({method: HttpMethod.POST, body: data});
  114. const res = createResponseMock();
  115. router.requestListener(req, res);
  116. });
  117. it('passes headers to the request context', function (done) {
  118. const router = new TrieRouter();
  119. router.defineRoute({
  120. method: HttpMethod.GET,
  121. path: '/',
  122. handler: ({headers}) => {
  123. expect(headers).to.be.eql({
  124. host: 'localhost',
  125. foo: 'bar',
  126. });
  127. done();
  128. },
  129. });
  130. const req = createRequestMock({headers: {foo: 'bar'}});
  131. const res = createResponseMock();
  132. router.requestListener(req, res);
  133. });
  134. it('uses DataSender to send the response', function (done) {
  135. const router = new TrieRouter();
  136. const resBody = 'Lorem Ipsum is simply dummy text.';
  137. router.defineRoute({
  138. method: HttpMethod.GET,
  139. path: '/',
  140. handler: () => resBody,
  141. });
  142. const req = createRequestMock();
  143. const res = createResponseMock();
  144. router.setService(DataSender, {
  145. send(response, data) {
  146. expect(response).to.be.eq(res);
  147. expect(data).to.be.eq(resBody);
  148. done();
  149. },
  150. });
  151. router.requestListener(req, res);
  152. });
  153. it('uses ErrorSender to send the response', function (done) {
  154. const router = new TrieRouter();
  155. const error = new Error();
  156. router.defineRoute({
  157. method: HttpMethod.GET,
  158. path: '/',
  159. handler: () => {
  160. throw error;
  161. },
  162. });
  163. const req = createRequestMock();
  164. const res = createResponseMock();
  165. router.setService(ErrorSender, {
  166. send(request, response, err) {
  167. expect(request).to.be.eq(req);
  168. expect(response).to.be.eq(res);
  169. expect(err).to.be.eq(error);
  170. done();
  171. },
  172. });
  173. router.requestListener(req, res);
  174. });
  175. describe('hooks', function () {
  176. it('invokes entire "preHandler" hooks before the route handler', async function () {
  177. const router = new TrieRouter();
  178. const order = [];
  179. const body = 'OK';
  180. router.defineRoute({
  181. method: HttpMethod.GET,
  182. path: '/',
  183. preHandler: [
  184. () => {
  185. order.push('preHandler1');
  186. },
  187. () => {
  188. order.push('preHandler2');
  189. },
  190. ],
  191. handler: () => {
  192. order.push('handler');
  193. return body;
  194. },
  195. });
  196. const req = createRequestMock();
  197. const res = createResponseMock();
  198. router.requestListener(req, res);
  199. const result = await res.getBody();
  200. expect(result).to.be.eq(body);
  201. expect(order).to.be.eql(['preHandler1', 'preHandler2', 'handler']);
  202. });
  203. it('invokes entire "preHandler" hooks after the route handler', async function () {
  204. const router = new TrieRouter();
  205. const order = [];
  206. const body = 'OK';
  207. router.defineRoute({
  208. method: HttpMethod.GET,
  209. path: '/',
  210. handler: () => {
  211. order.push('handler');
  212. return body;
  213. },
  214. postHandler: [
  215. () => {
  216. order.push('postHandler1');
  217. },
  218. () => {
  219. order.push('postHandler2');
  220. },
  221. ],
  222. });
  223. const req = createRequestMock();
  224. const res = createResponseMock();
  225. router.requestListener(req, res);
  226. const result = await res.getBody();
  227. expect(result).to.be.eq(body);
  228. expect(order).to.be.eql(['handler', 'postHandler1', 'postHandler2']);
  229. });
  230. it('passes the request context to the "preHandler" hooks', async function () {
  231. const router = new TrieRouter();
  232. const order = [];
  233. const body = 'OK';
  234. router.defineRoute({
  235. method: HttpMethod.GET,
  236. path: '/',
  237. preHandler: [
  238. ctx => {
  239. order.push('preHandler1');
  240. expect(ctx).to.be.instanceof(RequestContext);
  241. },
  242. ctx => {
  243. order.push('preHandler2');
  244. expect(ctx).to.be.instanceof(RequestContext);
  245. },
  246. ],
  247. handler: ctx => {
  248. order.push('handler');
  249. expect(ctx).to.be.instanceof(RequestContext);
  250. return body;
  251. },
  252. });
  253. const req = createRequestMock();
  254. const res = createResponseMock();
  255. router.requestListener(req, res);
  256. const result = await res.getBody();
  257. expect(result).to.be.eq(body);
  258. expect(order).to.be.eql(['preHandler1', 'preHandler2', 'handler']);
  259. });
  260. it('passes the request context and return value from the route handler to the "postHandler" hooks', async function () {
  261. const router = new TrieRouter();
  262. const order = [];
  263. const body = 'OK';
  264. let requestContext;
  265. router.defineRoute({
  266. method: HttpMethod.GET,
  267. path: '/',
  268. handler: ctx => {
  269. order.push('handler');
  270. expect(ctx).to.be.instanceof(RequestContext);
  271. requestContext = ctx;
  272. return body;
  273. },
  274. postHandler: [
  275. (ctx, data) => {
  276. order.push('postHandler1');
  277. expect(ctx).to.be.eq(requestContext);
  278. expect(data).to.be.eq(body);
  279. },
  280. (ctx, data) => {
  281. order.push('postHandler2');
  282. expect(ctx).to.be.eq(requestContext);
  283. expect(data).to.be.eq(body);
  284. },
  285. ],
  286. });
  287. const req = createRequestMock();
  288. const res = createResponseMock();
  289. router.requestListener(req, res);
  290. const result = await res.getBody();
  291. expect(result).to.be.eq(body);
  292. expect(order).to.be.eql(['handler', 'postHandler1', 'postHandler2']);
  293. });
  294. it('invokes the route handler if entire "preHandler" hooks returns undefined or null', async function () {
  295. const router = new TrieRouter();
  296. const order = [];
  297. const body = 'OK';
  298. router.defineRoute({
  299. method: HttpMethod.GET,
  300. path: '/',
  301. preHandler: [
  302. () => {
  303. order.push('preHandler1');
  304. return undefined;
  305. },
  306. () => {
  307. order.push('preHandler2');
  308. return null;
  309. },
  310. ],
  311. handler: () => {
  312. order.push('handler');
  313. return body;
  314. },
  315. });
  316. const req = createRequestMock();
  317. const res = createResponseMock();
  318. router.requestListener(req, res);
  319. const result = await res.getBody();
  320. expect(result).to.be.eq(body);
  321. expect(order).to.be.eql(['preHandler1', 'preHandler2', 'handler']);
  322. });
  323. it('sends a returns value from the route handler if entire "postHandler" hooks returns undefined or null', async function () {
  324. const router = new TrieRouter();
  325. const order = [];
  326. const body = 'OK';
  327. router.defineRoute({
  328. method: HttpMethod.GET,
  329. path: '/',
  330. handler: () => {
  331. order.push('handler');
  332. return body;
  333. },
  334. postHandler: [
  335. () => {
  336. order.push('postHandler1');
  337. return undefined;
  338. },
  339. () => {
  340. order.push('postHandler2');
  341. return null;
  342. },
  343. ],
  344. });
  345. const req = createRequestMock();
  346. const res = createResponseMock();
  347. router.requestListener(req, res);
  348. const result = await res.getBody();
  349. expect(result).to.be.eq(body);
  350. expect(order).to.be.eql(['handler', 'postHandler1', 'postHandler2']);
  351. });
  352. it('sends a return value from the "preHandler" hook in the first priority', async function () {
  353. const router = new TrieRouter();
  354. const order = [];
  355. const preHandlerBody = 'foo';
  356. const handlerBody = 'bar';
  357. const postHandlerBody = 'baz';
  358. router.defineRoute({
  359. method: HttpMethod.GET,
  360. path: '/',
  361. preHandler() {
  362. order.push('preHandler');
  363. return preHandlerBody;
  364. },
  365. handler: () => {
  366. order.push('handler');
  367. return handlerBody;
  368. },
  369. postHandler() {
  370. order.push('postHandler');
  371. return postHandlerBody;
  372. },
  373. });
  374. const req = createRequestMock();
  375. const res = createResponseMock();
  376. router.requestListener(req, res);
  377. const result = await res.getBody();
  378. expect(result).to.be.eq(preHandlerBody);
  379. expect(result).not.to.be.eq(handlerBody);
  380. expect(result).not.to.be.eq(postHandlerBody);
  381. expect(order).to.be.eql(['preHandler']);
  382. });
  383. it('sends a return value from the "postHandler" hook in the second priority', async function () {
  384. const router = new TrieRouter();
  385. const order = [];
  386. const handlerBody = 'foo';
  387. const postHandlerBody = 'bar';
  388. router.defineRoute({
  389. method: HttpMethod.GET,
  390. path: '/',
  391. preHandler() {
  392. order.push('preHandler');
  393. },
  394. handler: () => {
  395. order.push('handler');
  396. return handlerBody;
  397. },
  398. postHandler() {
  399. order.push('postHandler');
  400. return postHandlerBody;
  401. },
  402. });
  403. const req = createRequestMock();
  404. const res = createResponseMock();
  405. router.requestListener(req, res);
  406. const result = await res.getBody();
  407. expect(result).not.to.be.eq(handlerBody);
  408. expect(result).to.be.eq(postHandlerBody);
  409. expect(order).to.be.eql(['preHandler', 'handler', 'postHandler']);
  410. });
  411. it('sends a return value from the root handler in the third priority', async function () {
  412. const router = new TrieRouter();
  413. const order = [];
  414. const body = 'OK';
  415. router.defineRoute({
  416. method: HttpMethod.GET,
  417. path: '/',
  418. preHandler() {
  419. order.push('preHandler');
  420. },
  421. handler: () => {
  422. order.push('handler');
  423. return body;
  424. },
  425. postHandler() {
  426. order.push('postHandler');
  427. },
  428. });
  429. const req = createRequestMock();
  430. const res = createResponseMock();
  431. router.requestListener(req, res);
  432. const result = await res.getBody();
  433. expect(result).to.be.eq(body);
  434. expect(order).to.be.eql(['preHandler', 'handler', 'postHandler']);
  435. });
  436. });
  437. });
  438. describe('addHook', function () {
  439. it('adds the given hook to the HookRegistry and returns itself', function () {
  440. const router = new TrieRouter();
  441. const reg = router.getService(HookRegistry);
  442. const name = HookName.PRE_HANDLER;
  443. const hook = () => undefined;
  444. expect(reg.hasHook(name, hook)).to.be.false;
  445. const res = router.addHook(name, hook);
  446. expect(res).to.be.eq(router);
  447. expect(reg.hasHook(name, hook)).to.be.true;
  448. });
  449. });
  450. });