route.spec.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362
  1. import {expect} from 'chai';
  2. import {format} from '@e22m4u/js-format';
  3. import {Route, HttpMethod} from './route.js';
  4. import {RouterHookType} from '../hooks/index.js';
  5. import {ServiceContainer} from '@e22m4u/js-service';
  6. import {RequestContext} from '../request-context.js';
  7. import {createRequestMock, createResponseMock} from '../utils/index.js';
  8. describe('Route', function () {
  9. describe('constructor', function () {
  10. it('requires the "routeDef" parameter to be an Object', function () {
  11. const throwable = v => () => new Route(v);
  12. const error = v =>
  13. format('Route definition must be an Object, but %s was given.', v);
  14. expect(throwable('str')).to.throw(error('"str"'));
  15. expect(throwable('')).to.throw(error('""'));
  16. expect(throwable(10)).to.throw(error('10'));
  17. expect(throwable(0)).to.throw(error('0'));
  18. expect(throwable(true)).to.throw(error('true'));
  19. expect(throwable(false)).to.throw(error('false'));
  20. expect(throwable(null)).to.throw(error('null'));
  21. expect(throwable([])).to.throw(error('Array'));
  22. expect(throwable(undefined)).to.throw(error('undefined'));
  23. expect(throwable(() => undefined)).to.throw(error('Function'));
  24. throwable({
  25. method: HttpMethod.GET,
  26. path: '/',
  27. handler: () => undefined,
  28. })();
  29. });
  30. describe('the "method" option', function () {
  31. it('requires the "method" option to be a non-empty String', function () {
  32. const throwable = v => () =>
  33. new Route({
  34. method: v,
  35. path: '/',
  36. handler: () => undefined,
  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('sets the "method" option in upper case to the "method" property', function () {
  56. const route = new Route({
  57. method: 'post',
  58. path: '/',
  59. handler: () => undefined,
  60. });
  61. expect(route.method).to.be.eq('POST');
  62. });
  63. });
  64. describe('the "path" option', function () {
  65. it('requires the "path" option to be a String', function () {
  66. const throwable = v => () =>
  67. new Route({
  68. method: HttpMethod.GET,
  69. path: v,
  70. handler: () => undefined,
  71. });
  72. const error = v =>
  73. format('Option "path" must be a String, but %s was given.', v);
  74. expect(throwable(10)).to.throw(error('10'));
  75. expect(throwable(0)).to.throw(error('0'));
  76. expect(throwable(true)).to.throw(error('true'));
  77. expect(throwable(false)).to.throw(error('false'));
  78. expect(throwable(null)).to.throw(error('null'));
  79. expect(throwable({})).to.throw(error('Object'));
  80. expect(throwable([])).to.throw(error('Array'));
  81. expect(throwable(undefined)).to.throw(error('undefined'));
  82. expect(throwable(() => undefined)).to.throw(error('Function'));
  83. throwable('str')();
  84. throwable('')();
  85. });
  86. it('sets the "path" option to the "path" property', function () {
  87. const value = '/myPath';
  88. const route = new Route({
  89. method: HttpMethod.GET,
  90. path: value,
  91. handler: () => undefined,
  92. });
  93. expect(route.path).to.be.eq(value);
  94. });
  95. });
  96. describe('the "meta" option', function () {
  97. it('requires the "meta" option to be a plain Object', function () {
  98. const throwable = v => () =>
  99. new Route({
  100. method: HttpMethod.GET,
  101. path: 'path',
  102. handler: () => undefined,
  103. meta: v,
  104. });
  105. const error = v =>
  106. format('Option "meta" must be an Object, but %s was given.', v);
  107. expect(throwable('str')).to.throw(error('"str"'));
  108. expect(throwable('')).to.throw(error('""'));
  109. expect(throwable(10)).to.throw(error('10'));
  110. expect(throwable(0)).to.throw(error('0'));
  111. expect(throwable(true)).to.throw(error('true'));
  112. expect(throwable(false)).to.throw(error('false'));
  113. expect(throwable([])).to.throw(error('Array'));
  114. expect(throwable(null)).to.throw(error('null'));
  115. expect(throwable(() => undefined)).to.throw(error('Function'));
  116. throwable({foo: 'bar'})();
  117. throwable({})();
  118. throwable(undefined)();
  119. });
  120. it('sets the "meta" option to the "meta" property as a deep copy', function () {
  121. const metaData = {foo: {bar: {baz: 'qux'}}};
  122. const route = new Route({
  123. method: 'post',
  124. path: '/',
  125. handler: () => undefined,
  126. meta: metaData,
  127. });
  128. expect(route.meta).to.be.not.eq(metaData);
  129. expect(route.meta).to.be.eql(metaData);
  130. expect(route.meta.foo).to.be.not.eq(metaData.foo);
  131. expect(route.meta.foo).to.be.eql(metaData.foo);
  132. expect(route.meta.foo.bar).to.be.not.eq(metaData.foo.bar);
  133. expect(route.meta.foo.bar).to.be.eql(metaData.foo.bar);
  134. });
  135. it('sets an empty object to the "meta" property if the "meta" option is not provided', function () {
  136. const route = new Route({
  137. method: 'post',
  138. path: '/',
  139. handler: () => undefined,
  140. });
  141. expect(route.meta).to.be.eql({});
  142. });
  143. it('sets an empty object to the "meta" property if the "meta" option is undefined', function () {
  144. const route = new Route({
  145. method: 'post',
  146. path: '/',
  147. handler: () => undefined,
  148. meta: undefined,
  149. });
  150. expect(route.meta).to.be.eql({});
  151. });
  152. });
  153. describe('the "handler" option', function () {
  154. it('requires the "handler" option to be a non-empty String', function () {
  155. const throwable = v => () =>
  156. new Route({
  157. method: HttpMethod.GET,
  158. path: '/',
  159. handler: v,
  160. });
  161. const error = v =>
  162. format('Option "handler" must be a Function, but %s was given.', v);
  163. expect(throwable('str')).to.throw(error('"str"'));
  164. expect(throwable('')).to.throw(error('""'));
  165. expect(throwable(10)).to.throw(error('10'));
  166. expect(throwable(0)).to.throw(error('0'));
  167. expect(throwable(true)).to.throw(error('true'));
  168. expect(throwable(false)).to.throw(error('false'));
  169. expect(throwable(null)).to.throw(error('null'));
  170. expect(throwable({})).to.throw(error('Object'));
  171. expect(throwable([])).to.throw(error('Array'));
  172. expect(throwable(undefined)).to.throw(error('undefined'));
  173. throwable(() => undefined)();
  174. });
  175. it('sets the "handler" option to the "handler" property', function () {
  176. const value = () => undefined;
  177. const route = new Route({
  178. method: HttpMethod.GET,
  179. path: '/',
  180. handler: value,
  181. });
  182. expect(route.handler).to.be.eq(value);
  183. });
  184. });
  185. describe('the "preHandler" option', function () {
  186. it('requires the "preHandler" option to be a Function or an Array of Function', function () {
  187. const throwable1 = v => () =>
  188. new Route({
  189. method: HttpMethod.GET,
  190. path: '/',
  191. preHandler: v,
  192. handler: () => undefined,
  193. });
  194. const error = v =>
  195. format(
  196. 'The hook "preHandler" must be a Function, but %s was given.',
  197. v,
  198. );
  199. expect(throwable1('str')).to.throw(error('"str"'));
  200. expect(throwable1('')).to.throw(error('""'));
  201. expect(throwable1(10)).to.throw(error('10'));
  202. expect(throwable1(0)).to.throw(error('0'));
  203. expect(throwable1(true)).to.throw(error('true'));
  204. expect(throwable1(false)).to.throw(error('false'));
  205. expect(throwable1({})).to.throw(error('Object'));
  206. throwable1([])();
  207. throwable1(() => undefined)();
  208. throwable1(null)();
  209. throwable1(undefined)();
  210. const throwable2 = v => () =>
  211. new Route({
  212. method: HttpMethod.GET,
  213. path: '/',
  214. preHandler: [v],
  215. handler: () => undefined,
  216. });
  217. expect(throwable2('str')).to.throw(error('"str"'));
  218. expect(throwable2('')).to.throw(error('""'));
  219. expect(throwable2(10)).to.throw(error('10'));
  220. expect(throwable2(0)).to.throw(error('0'));
  221. expect(throwable2(true)).to.throw(error('true'));
  222. expect(throwable2(false)).to.throw(error('false'));
  223. expect(throwable2({})).to.throw(error('Object'));
  224. expect(throwable2(null)).to.throw(error('null'));
  225. expect(throwable2([])).to.throw(error('Array'));
  226. expect(throwable2(undefined)).to.throw(error('undefined'));
  227. throwable2(() => undefined)();
  228. });
  229. it('adds a Function to "preHandler" hooks', function () {
  230. const value = () => undefined;
  231. const route = new Route({
  232. method: HttpMethod.GET,
  233. path: '/',
  234. preHandler: value,
  235. handler: () => undefined,
  236. });
  237. expect(route.hookRegistry.hasHook(RouterHookType.PRE_HANDLER, value)).to
  238. .be.true;
  239. });
  240. it('adds a Function Array to "preHandler" hooks', function () {
  241. const value = [() => undefined, () => undefined];
  242. const route = new Route({
  243. method: HttpMethod.GET,
  244. path: '/',
  245. preHandler: value,
  246. handler: () => undefined,
  247. });
  248. expect(route.hookRegistry.hasHook(RouterHookType.PRE_HANDLER, value[0]))
  249. .to.be.true;
  250. expect(route.hookRegistry.hasHook(RouterHookType.PRE_HANDLER, value[1]))
  251. .to.be.true;
  252. });
  253. });
  254. describe('the "postHandler" option', function () {
  255. it('requires the "postHandler" option to be a Function or an Array of Function', function () {
  256. const throwable1 = v => () =>
  257. new Route({
  258. method: HttpMethod.GET,
  259. path: '/',
  260. handler: () => undefined,
  261. postHandler: v,
  262. });
  263. const error = v =>
  264. format(
  265. 'The hook "postHandler" must be a Function, but %s was given.',
  266. v,
  267. );
  268. expect(throwable1('str')).to.throw(error('"str"'));
  269. expect(throwable1('')).to.throw(error('""'));
  270. expect(throwable1(10)).to.throw(error('10'));
  271. expect(throwable1(0)).to.throw(error('0'));
  272. expect(throwable1(true)).to.throw(error('true'));
  273. expect(throwable1(false)).to.throw(error('false'));
  274. expect(throwable1({})).to.throw(error('Object'));
  275. throwable1([])();
  276. throwable1(() => undefined)();
  277. throwable1(null)();
  278. throwable1(undefined)();
  279. const throwable2 = v => () =>
  280. new Route({
  281. method: HttpMethod.GET,
  282. path: '/',
  283. handler: () => undefined,
  284. postHandler: [v],
  285. });
  286. expect(throwable2('str')).to.throw(error('"str"'));
  287. expect(throwable2('')).to.throw(error('""'));
  288. expect(throwable2(10)).to.throw(error('10'));
  289. expect(throwable2(0)).to.throw(error('0'));
  290. expect(throwable2(true)).to.throw(error('true'));
  291. expect(throwable2(false)).to.throw(error('false'));
  292. expect(throwable2({})).to.throw(error('Object'));
  293. expect(throwable2(null)).to.throw(error('null'));
  294. expect(throwable2([])).to.throw(error('Array'));
  295. expect(throwable2(undefined)).to.throw(error('undefined'));
  296. throwable2(() => undefined)();
  297. });
  298. it('adds a Function to "postHandler" hooks', function () {
  299. const value = () => undefined;
  300. const route = new Route({
  301. method: HttpMethod.GET,
  302. path: '/',
  303. handler: () => undefined,
  304. postHandler: value,
  305. });
  306. expect(route.hookRegistry.hasHook(RouterHookType.POST_HANDLER, value))
  307. .to.be.true;
  308. });
  309. it('adds a Function Array to "postHandler" hooks', function () {
  310. const value = [() => undefined, () => undefined];
  311. const route = new Route({
  312. method: HttpMethod.GET,
  313. path: '/',
  314. handler: () => undefined,
  315. postHandler: value,
  316. });
  317. expect(
  318. route.hookRegistry.hasHook(RouterHookType.POST_HANDLER, value[0]),
  319. ).to.be.true;
  320. expect(
  321. route.hookRegistry.hasHook(RouterHookType.POST_HANDLER, value[1]),
  322. ).to.be.true;
  323. });
  324. });
  325. });
  326. describe('handle', function () {
  327. it('invokes the handler with the given RequestContext and return its result', function () {
  328. const route = new Route({
  329. method: HttpMethod.GET,
  330. path: '/',
  331. handler(ctx) {
  332. expect(ctx).to.be.instanceof(RequestContext);
  333. return 'OK';
  334. },
  335. });
  336. const req = createRequestMock();
  337. const res = createResponseMock();
  338. const cont = new ServiceContainer();
  339. const ctx = new RequestContext(cont, req, res, route);
  340. const result = route.handle(ctx);
  341. expect(result).to.be.eq('OK');
  342. });
  343. });
  344. });