| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627 |
- import {expect} from 'chai';
- import {TrieRouter} from './trie-router.js';
- import {Route, HttpMethod} from './route.js';
- import {RequestContext} from './request-context.js';
- import {ServerResponse, IncomingMessage} from 'http';
- import {DataSender, ErrorSender} from './senders/index.js';
- import {HookRegistry, RouterHookType} from './hooks/index.js';
- import {createRequestMock, createResponseMock} from './utils/index.js';
- describe('TrieRouter', function () {
- describe('defineRoute', function () {
- it('returns the Route instance', function () {
- const router = new TrieRouter();
- const path = '/path';
- const handler = () => 'ok';
- const res = router.defineRoute({method: HttpMethod.GET, path, handler});
- expect(res).to.be.instanceof(Route);
- expect(res.method).to.be.eq(HttpMethod.GET);
- expect(res.path).to.be.eq(path);
- expect(res.handler).to.be.eq(handler);
- });
- });
- describe('requestListener', function () {
- it('should be a function', function () {
- const router = new TrieRouter();
- expect(typeof router.requestListener).to.be.eq('function');
- });
- it('provides the request context to the route handler', function (done) {
- const router = new TrieRouter();
- router.defineRoute({
- method: HttpMethod.GET,
- path: '/test',
- handler: ctx => {
- expect(ctx).to.be.instanceof(RequestContext);
- done();
- },
- });
- const req = createRequestMock({path: '/test'});
- const res = createResponseMock();
- router.requestListener(req, res);
- });
- it('provides path parameters to the request context', function (done) {
- const router = new TrieRouter();
- router.defineRoute({
- method: HttpMethod.GET,
- path: '/:p1-:p2',
- handler: ({params}) => {
- expect(params).to.be.eql({p1: 'foo', p2: 'bar'});
- done();
- },
- });
- const req = createRequestMock({path: '/foo-bar'});
- const res = createResponseMock();
- router.requestListener(req, res);
- });
- it('provides query parameters to the request context', function (done) {
- const router = new TrieRouter();
- router.defineRoute({
- method: HttpMethod.GET,
- path: '/',
- handler: ({query}) => {
- expect(query).to.be.eql({p1: 'foo', p2: 'bar'});
- done();
- },
- });
- const req = createRequestMock({path: '?p1=foo&p2=bar'});
- const res = createResponseMock();
- router.requestListener(req, res);
- });
- it('provides parsed cookies to the request context', function (done) {
- const router = new TrieRouter();
- router.defineRoute({
- method: HttpMethod.GET,
- path: '/',
- handler: ({cookies}) => {
- expect(cookies).to.be.eql({p1: 'foo', p2: 'bar'});
- done();
- },
- });
- const req = createRequestMock({headers: {cookie: 'p1=foo; p2=bar;'}});
- const res = createResponseMock();
- router.requestListener(req, res);
- });
- it('provides the plain text body to the request context', function (done) {
- const router = new TrieRouter();
- const body = 'Lorem Ipsum is simply dummy text.';
- router.defineRoute({
- method: HttpMethod.POST,
- path: '/',
- handler: ctx => {
- expect(ctx.body).to.be.eq(body);
- done();
- },
- });
- const req = createRequestMock({method: HttpMethod.POST, body});
- const res = createResponseMock();
- router.requestListener(req, res);
- });
- it('provides the parsed JSON body to the request context', function (done) {
- const router = new TrieRouter();
- const data = {p1: 'foo', p2: 'bar'};
- router.defineRoute({
- method: HttpMethod.POST,
- path: '/',
- handler: ({body}) => {
- expect(body).to.be.eql(data);
- done();
- },
- });
- const req = createRequestMock({method: HttpMethod.POST, body: data});
- const res = createResponseMock();
- router.requestListener(req, res);
- });
- it('provides request headers to the request context', function (done) {
- const router = new TrieRouter();
- router.defineRoute({
- method: HttpMethod.GET,
- path: '/',
- handler: ({headers}) => {
- expect(headers).to.be.eql({
- host: 'localhost',
- foo: 'bar',
- });
- done();
- },
- });
- const req = createRequestMock({headers: {foo: 'bar'}});
- const res = createResponseMock();
- router.requestListener(req, res);
- });
- it('provides the route to the request context', function (done) {
- const router = new TrieRouter();
- const metaData = {foo: {bar: {baz: 'qux'}}};
- const currentRoute = router.defineRoute({
- method: HttpMethod.GET,
- path: '/',
- meta: metaData,
- handler: ({route}) => {
- expect(route).to.be.eq(currentRoute);
- done();
- },
- });
- const req = createRequestMock();
- const res = createResponseMock();
- router.requestListener(req, res);
- });
- it('provides access to route meta via the request context', function (done) {
- const router = new TrieRouter();
- const metaData = {role: 'admin'};
- router.defineRoute({
- method: HttpMethod.GET,
- path: '/',
- meta: metaData,
- handler: ({meta}) => {
- expect(meta).to.eql(metaData);
- done();
- },
- });
- const req = createRequestMock();
- const res = createResponseMock();
- router.requestListener(req, res);
- });
- it('uses the DataSender to send the server response', function (done) {
- const router = new TrieRouter();
- const resBody = 'Lorem Ipsum is simply dummy text.';
- router.defineRoute({
- method: HttpMethod.GET,
- path: '/',
- handler: () => resBody,
- });
- const req = createRequestMock();
- const res = createResponseMock();
- router.setService(DataSender, {
- send(response, data) {
- expect(response).to.be.eq(res);
- expect(data).to.be.eq(resBody);
- done();
- },
- });
- router.requestListener(req, res);
- });
- it('uses the ErrorSender to send the server response', function (done) {
- const router = new TrieRouter();
- const error = new Error();
- router.defineRoute({
- method: HttpMethod.GET,
- path: '/',
- handler: () => {
- throw error;
- },
- });
- const req = createRequestMock();
- const res = createResponseMock();
- router.setService(ErrorSender, {
- send(request, response, err) {
- expect(request).to.be.eq(req);
- expect(response).to.be.eq(res);
- expect(err).to.be.eq(error);
- done();
- },
- });
- router.requestListener(req, res);
- });
- describe('hooks', function () {
- it('invokes entire "preHandler" hooks before the route handler', async function () {
- const router = new TrieRouter();
- const order = [];
- const body = 'OK';
- router.defineRoute({
- method: HttpMethod.GET,
- path: '/',
- preHandler: [
- () => {
- order.push('preHandler1');
- },
- () => {
- order.push('preHandler2');
- },
- ],
- handler: () => {
- order.push('handler');
- return body;
- },
- });
- const req = createRequestMock();
- const res = createResponseMock();
- router.requestListener(req, res);
- const result = await res.getBody();
- expect(result).to.be.eq(body);
- expect(order).to.be.eql(['preHandler1', 'preHandler2', 'handler']);
- });
- it('invokes entire "preHandler" hooks after the route handler', async function () {
- const router = new TrieRouter();
- const order = [];
- const body = 'OK';
- router.defineRoute({
- method: HttpMethod.GET,
- path: '/',
- handler: () => {
- order.push('handler');
- return body;
- },
- postHandler: [
- () => {
- order.push('postHandler1');
- },
- () => {
- order.push('postHandler2');
- },
- ],
- });
- const req = createRequestMock();
- const res = createResponseMock();
- router.requestListener(req, res);
- const result = await res.getBody();
- expect(result).to.be.eq(body);
- expect(order).to.be.eql(['handler', 'postHandler1', 'postHandler2']);
- });
- it('provides the request context to the "preHandler" hooks', async function () {
- const router = new TrieRouter();
- const order = [];
- const body = 'OK';
- router.defineRoute({
- method: HttpMethod.GET,
- path: '/',
- preHandler: [
- ctx => {
- order.push('preHandler1');
- expect(ctx).to.be.instanceof(RequestContext);
- },
- ctx => {
- order.push('preHandler2');
- expect(ctx).to.be.instanceof(RequestContext);
- },
- ],
- handler: ctx => {
- order.push('handler');
- expect(ctx).to.be.instanceof(RequestContext);
- return body;
- },
- });
- const req = createRequestMock();
- const res = createResponseMock();
- router.requestListener(req, res);
- const result = await res.getBody();
- expect(result).to.be.eq(body);
- expect(order).to.be.eql(['preHandler1', 'preHandler2', 'handler']);
- });
- it('provides the request context and return value from the route handler to the "postHandler" hooks', async function () {
- const router = new TrieRouter();
- const order = [];
- const body = 'OK';
- let requestContext;
- router.defineRoute({
- method: HttpMethod.GET,
- path: '/',
- handler: ctx => {
- order.push('handler');
- expect(ctx).to.be.instanceof(RequestContext);
- requestContext = ctx;
- return body;
- },
- postHandler: [
- (ctx, data) => {
- order.push('postHandler1');
- expect(ctx).to.be.eq(requestContext);
- expect(data).to.be.eq(body);
- },
- (ctx, data) => {
- order.push('postHandler2');
- expect(ctx).to.be.eq(requestContext);
- expect(data).to.be.eq(body);
- },
- ],
- });
- const req = createRequestMock();
- const res = createResponseMock();
- router.requestListener(req, res);
- const result = await res.getBody();
- expect(result).to.be.eq(body);
- expect(order).to.be.eql(['handler', 'postHandler1', 'postHandler2']);
- });
- it('invokes the route handler if entire "preHandler" hooks returns undefined or null', async function () {
- const router = new TrieRouter();
- const order = [];
- const body = 'OK';
- router.defineRoute({
- method: HttpMethod.GET,
- path: '/',
- preHandler: [
- () => {
- order.push('preHandler1');
- return undefined;
- },
- () => {
- order.push('preHandler2');
- return null;
- },
- ],
- handler: () => {
- order.push('handler');
- return body;
- },
- });
- const req = createRequestMock();
- const res = createResponseMock();
- router.requestListener(req, res);
- const result = await res.getBody();
- expect(result).to.be.eq(body);
- expect(order).to.be.eql(['preHandler1', 'preHandler2', 'handler']);
- });
- it('sends a returns value from the route handler if entire "postHandler" hooks returns undefined or null', async function () {
- const router = new TrieRouter();
- const order = [];
- const body = 'OK';
- router.defineRoute({
- method: HttpMethod.GET,
- path: '/',
- handler: () => {
- order.push('handler');
- return body;
- },
- postHandler: [
- () => {
- order.push('postHandler1');
- return undefined;
- },
- () => {
- order.push('postHandler2');
- return null;
- },
- ],
- });
- const req = createRequestMock();
- const res = createResponseMock();
- router.requestListener(req, res);
- const result = await res.getBody();
- expect(result).to.be.eq(body);
- expect(order).to.be.eql(['handler', 'postHandler1', 'postHandler2']);
- });
- it('sends a return value from the "preHandler" hook in the first priority', async function () {
- const router = new TrieRouter();
- const order = [];
- const preHandlerBody = 'foo';
- const handlerBody = 'bar';
- const postHandlerBody = 'baz';
- router.defineRoute({
- method: HttpMethod.GET,
- path: '/',
- preHandler() {
- order.push('preHandler');
- return preHandlerBody;
- },
- handler: () => {
- order.push('handler');
- return handlerBody;
- },
- postHandler() {
- order.push('postHandler');
- return postHandlerBody;
- },
- });
- const req = createRequestMock();
- const res = createResponseMock();
- router.requestListener(req, res);
- const result = await res.getBody();
- expect(result).to.be.eq(preHandlerBody);
- expect(result).not.to.be.eq(handlerBody);
- expect(result).not.to.be.eq(postHandlerBody);
- expect(order).to.be.eql(['preHandler']);
- });
- it('sends a return value from the "postHandler" hook in the second priority', async function () {
- const router = new TrieRouter();
- const order = [];
- const handlerBody = 'foo';
- const postHandlerBody = 'bar';
- router.defineRoute({
- method: HttpMethod.GET,
- path: '/',
- preHandler() {
- order.push('preHandler');
- },
- handler: () => {
- order.push('handler');
- return handlerBody;
- },
- postHandler() {
- order.push('postHandler');
- return postHandlerBody;
- },
- });
- const req = createRequestMock();
- const res = createResponseMock();
- router.requestListener(req, res);
- const result = await res.getBody();
- expect(result).not.to.be.eq(handlerBody);
- expect(result).to.be.eq(postHandlerBody);
- expect(order).to.be.eql(['preHandler', 'handler', 'postHandler']);
- });
- it('sends a return value from the root handler in the third priority', async function () {
- const router = new TrieRouter();
- const order = [];
- const body = 'OK';
- router.defineRoute({
- method: HttpMethod.GET,
- path: '/',
- preHandler() {
- order.push('preHandler');
- },
- handler: () => {
- order.push('handler');
- return body;
- },
- postHandler() {
- order.push('postHandler');
- },
- });
- const req = createRequestMock();
- const res = createResponseMock();
- router.requestListener(req, res);
- const result = await res.getBody();
- expect(result).to.be.eq(body);
- expect(order).to.be.eql(['preHandler', 'handler', 'postHandler']);
- });
- });
- });
- describe('_handleRequest', function () {
- it('should register the request context in the request-scope ServiceContainer', function (done) {
- const router = new TrieRouter();
- router.defineRoute({
- method: HttpMethod.GET,
- path: '/',
- handler(ctx) {
- const res = ctx.container.getRegistered(RequestContext);
- expect(res).to.be.eq(ctx);
- expect(res).to.be.not.eq(router.container);
- done();
- },
- });
- const req = createRequestMock();
- const res = createResponseMock();
- router.requestListener(req, res);
- });
- it('should register the IncomingMessage in the request-scope ServiceContainer', function (done) {
- const router = new TrieRouter();
- const req = createRequestMock();
- const res = createResponseMock();
- router.defineRoute({
- method: HttpMethod.GET,
- path: '/',
- handler(ctx) {
- const result = ctx.container.getRegistered(IncomingMessage);
- expect(result).to.be.eq(req);
- done();
- },
- });
- router.requestListener(req, res);
- });
- it('should register the ServerResponse in the request-scope ServiceContainer', function (done) {
- const router = new TrieRouter();
- const req = createRequestMock();
- const res = createResponseMock();
- router.defineRoute({
- method: HttpMethod.GET,
- path: '/',
- handler(ctx) {
- const result = ctx.container.getRegistered(ServerResponse);
- expect(result).to.be.eq(res);
- done();
- },
- });
- router.requestListener(req, res);
- });
- it('should send parsing error response instead of throwing error', async function () {
- const router = new TrieRouter();
- router.defineRoute({
- method: HttpMethod.POST,
- path: '/',
- handler() {},
- });
- const req = createRequestMock({
- method: HttpMethod.POST,
- headers: {'content-type': 'application/json'},
- body: 'invalid',
- });
- const res = createResponseMock();
- router.requestListener(req, res);
- const body = await res.getBody();
- expect(res.statusCode).to.be.eq(400);
- expect(JSON.parse(body)).to.be.eql({
- error: {
- message: `Unexpected token 'i', "invalid" is not valid JSON`,
- },
- });
- });
- it('should not invoke the main handler if a preHandler sends the response asynchronously', async function () {
- let handlerCalled = false;
- const router = new TrieRouter();
- router.defineRoute({
- method: 'GET',
- path: '/',
- preHandler(ctx) {
- return new Promise(resolve => {
- setTimeout(() => {
- ctx.response.setHeader('Content-Type', 'text/plain');
- ctx.response.end('Response from preHandler');
- resolve(undefined);
- }, 10);
- });
- },
- handler() {
- handlerCalled = true;
- return 'Response from main handler';
- },
- });
- const req = createRequestMock({method: 'GET', path: '/'});
- const res = createResponseMock();
- await router._handleRequest(req, res);
- const responseBody = await res.getBody();
- expect(responseBody).to.equal('Response from preHandler');
- expect(handlerCalled).to.be.false;
- });
- });
- describe('addHook', function () {
- it('adds the given hook to the HookRegistry and returns itself', function () {
- const router = new TrieRouter();
- const reg = router.getService(HookRegistry);
- const type = RouterHookType.PRE_HANDLER;
- const hook = () => undefined;
- expect(reg.hasHook(type, hook)).to.be.false;
- const res = router.addHook(type, hook);
- expect(res).to.be.eq(router);
- expect(reg.hasHook(type, hook)).to.be.true;
- });
- });
- describe('addPreHandler', function () {
- it('adds the given pre-handler hook to the HookRegistry and returns itself', function () {
- const router = new TrieRouter();
- const reg = router.getService(HookRegistry);
- const hook = () => undefined;
- expect(reg.hasHook(RouterHookType.PRE_HANDLER, hook)).to.be.false;
- const res = router.addPreHandler(hook);
- expect(res).to.be.eq(router);
- expect(reg.hasHook(RouterHookType.PRE_HANDLER, hook)).to.be.true;
- });
- });
- describe('addPostHandler', function () {
- it('adds the given post-handler hook to the HookRegistry and returns itself', function () {
- const router = new TrieRouter();
- const reg = router.getService(HookRegistry);
- const hook = () => undefined;
- expect(reg.hasHook(RouterHookType.POST_HANDLER, hook)).to.be.false;
- const res = router.addPostHandler(hook);
- expect(res).to.be.eq(router);
- expect(reg.hasHook(RouterHookType.POST_HANDLER, hook)).to.be.true;
- });
- });
- });
|