route.spec.js 14 KB

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