From 9d99c70ab4c8bb0d77955bc440b99e956d3b970c Mon Sep 17 00:00:00 2001 From: rogelio-o Date: Fri, 19 Oct 2018 13:43:43 +0200 Subject: [PATCH] Adds endHandlers to be called before the callback. --- src/lib/App.ts | 14 +- src/lib/event/eventFinalHandler.ts | 11 +- src/lib/http/httpFinalHandler.ts | 12 +- src/lib/types/IApp.ts | 3 + src/lib/types/IEndHandler.ts | 5 + test/event/eventFinalHandler.spec.ts | 27 +++- test/http/httpFinalHandler.spec.ts | 129 +++++++++++++----- .../renderEngine/DevTemplateLoader.spec.ts | 2 +- test/utils/DefaultCallback.ts | 3 + 9 files changed, 160 insertions(+), 46 deletions(-) create mode 100644 src/lib/types/IEndHandler.ts diff --git a/src/lib/App.ts b/src/lib/App.ts index 3007851..b1c90ce 100644 --- a/src/lib/App.ts +++ b/src/lib/App.ts @@ -18,6 +18,7 @@ import IHttpResponse from "./types/http/IHttpResponse"; import IHttpRoute from "./types/http/IHttpRoute"; import ITemplateRenderer from "./types/http/renderEngine/ITemplateRenderer"; import IApp from "./types/IApp"; +import IEndHandler from "./types/IEndHandler"; import IRawCallback from "./types/IRawCallback"; import IRawEvent from "./types/IRawEvent"; import IRouter from "./types/IRouter"; @@ -29,8 +30,11 @@ export default class App implements IApp { private _settings: object; private _router: IRouter; + private _endHandlers: IEndHandler[]; constructor(settings?: object) { + this._endHandlers = []; + this._settings = {}; this.initEnvConfiguration(); this.initParamsConfiguration(settings); @@ -65,14 +69,16 @@ export default class App implements IApp { const res: IHttpResponse = new HttpResponse(this, req, callback); const done = httpFinalHandler(req, res, { env: this.get(configuration.ENVIRONMENT), - onerror: this.logError.bind(this, req) + onerror: this.logError.bind(this, req), + endHandlers: this._endHandlers }); this._router.httpHandle(req, res, done); } else { const req: IEventRequest = new EventRequest(event); const done = eventFinalHandler(req, callback, { env: this.get(configuration.ENVIRONMENT), - onerror: this.logError.bind(this, req) + onerror: this.logError.bind(this, req), + endHandlers: this._endHandlers }); this._router.eventHandle(req, done); } @@ -112,6 +118,10 @@ export default class App implements IApp { return this; } + public addEndHandler(handler: IEndHandler): void { + this._endHandlers.push(handler); + } + private initEnvConfiguration(): void { const settings = {}; diff --git a/src/lib/event/eventFinalHandler.ts b/src/lib/event/eventFinalHandler.ts index 676e7e6..dbe843f 100644 --- a/src/lib/event/eventFinalHandler.ts +++ b/src/lib/event/eventFinalHandler.ts @@ -1,3 +1,4 @@ +import IEndHandler from "../types/IEndHandler"; import IEventRequest from "./../types/event/IEventRequest"; import INext from "./../types/INext"; import IRawCallback from "./../types/IRawCallback"; @@ -17,13 +18,17 @@ export default function eventFinalHandler(req: IEventRequest, callback: IRawCall const onerror = opts.onerror; return (err?: Error) => { + const endHandlers: IEndHandler[] = opts.endHandlers || []; + // schedule onerror callback if (onerror) { setImmediate(() => onerror(err, req)); } - if (callback) { - callback.finalize(err); - } + Promise.all(endHandlers.map((f) => f())).then(() => { + if (callback) { + callback.finalize(err); + } + }); }; } diff --git a/src/lib/http/httpFinalHandler.ts b/src/lib/http/httpFinalHandler.ts index 4b9c853..652bc4e 100644 --- a/src/lib/http/httpFinalHandler.ts +++ b/src/lib/http/httpFinalHandler.ts @@ -1,5 +1,6 @@ import * as escapeHtml from "escape-html"; import * as statuses from "statuses"; +import IEndHandler from "../types/IEndHandler"; import HttpError from "./../exceptions/HttpError"; import IHttpRequest from "./../types/http/IHttpRequest"; import IHttpResponse from "./../types/http/IHttpResponse"; @@ -144,9 +145,12 @@ export default function httpFinalHandler(req: IHttpRequest, res: IHttpResponse, setImmediate(() => onerror(err, req, res)); } - if (!res.isSent) { - // send response - send(req, res, status, headers, msg); - } + const endHandlers: IEndHandler[] = opts.endHandlers || []; + Promise.all(endHandlers.map((f) => f())).then(() => { + if (!res.isSent) { + // send response + send(req, res, status, headers, msg); + } + }); }; } diff --git a/src/lib/types/IApp.ts b/src/lib/types/IApp.ts index b2acf06..e6b5780 100644 --- a/src/lib/types/IApp.ts +++ b/src/lib/types/IApp.ts @@ -4,6 +4,7 @@ import IHttpHandler from "./http/IHttpHandler"; import IHttpPlaceholderHandler from "./http/IHttpPlaceholderHandler"; import IHttpRoute from "./http/IHttpRoute"; import ITemplateRenderer from "./http/renderEngine/ITemplateRenderer"; +import IEndHandler from "./IEndHandler"; import IRawCallback from "./IRawCallback"; import IRawEvent from "./IRawEvent"; import IRouter from "./IRouter"; @@ -123,4 +124,6 @@ export default interface IApp { */ addTemplateEngine(renderer: ITemplateRenderer, engineConfiguration?: {[name: string]: any}): IApp; + addEndHandler(handler: IEndHandler): void; + } diff --git a/src/lib/types/IEndHandler.ts b/src/lib/types/IEndHandler.ts new file mode 100644 index 0000000..929a5c8 --- /dev/null +++ b/src/lib/types/IEndHandler.ts @@ -0,0 +1,5 @@ +/** + * Handler called just before the provider callback is called. + */ +type IEndHandler = () => Promise; +export default IEndHandler; diff --git a/test/event/eventFinalHandler.spec.ts b/test/event/eventFinalHandler.spec.ts index 800a4e8..6dea2a4 100644 --- a/test/event/eventFinalHandler.spec.ts +++ b/test/event/eventFinalHandler.spec.ts @@ -1,5 +1,6 @@ /* tslint:disable:no-unused-expression */ import * as Chai from "chai"; +import { SinonStub, stub } from "sinon"; import eventFinalHandler from "./../../src/lib/event/eventFinalHandler"; import EventRequest from "./../../src/lib/event/EventRequest"; import DefaultCallback from "./../utils/DefaultCallback"; @@ -40,12 +41,34 @@ describe("eventFinalHandler", () => { handler(); }); - it("should calls the finalize method of raw callback if it exists.", () => { + it("should calls the finalize method of raw callback if it exists.", (done) => { const rawCallback: DefaultCallback = new DefaultCallback(); const handler = eventFinalHandler(req, rawCallback, null); + + rawCallback.setCallback(() => { + Chai.expect(rawCallback.isFinalized).to.be.true; + done(); + }); + handler(); + }); - Chai.expect(rawCallback.isFinalized).to.be.true; + it("should call the endHandlers.", (done) => { + const rawCallback: DefaultCallback = new DefaultCallback(); + const endHandler: SinonStub = stub(); + endHandler.returns(Promise.resolve()); + + rawCallback.setCallback(() => { + Chai.expect(endHandler.calledOnce).to.be.true; + done(); + }); + + const handler = eventFinalHandler(req, rawCallback, { + endHandlers: [ + endHandler + ] + }); + handler(); }); }); diff --git a/test/http/httpFinalHandler.spec.ts b/test/http/httpFinalHandler.spec.ts index cfc62bf..7757bcd 100644 --- a/test/http/httpFinalHandler.spec.ts +++ b/test/http/httpFinalHandler.spec.ts @@ -1,5 +1,6 @@ /* tslint:disable:no-unused-expression */ import * as Chai from "chai"; +import { SinonStub, stub } from "sinon"; import App from "./../../src/lib/App"; import HttpError from "./../../src/lib/exceptions/HttpError"; import httpFinalHandler from "./../../src/lib/http/httpFinalHandler"; @@ -30,23 +31,35 @@ describe("httpFinalHandler", () => { res = new HttpResponse(app, req, callback); }); - it("should send a response with the headers: Content-Security-Policy, X-Content-Type-Options, Content-Type and Content-Length.", () => { - const handler = httpFinalHandler(req, res, {}); - handler(); - Chai.expect(callback.successResult.headers["Content-Security-Policy"]).to.be.not.empty; - Chai.expect(callback.successResult.headers["X-Content-Type-Options"]).to.be.not.empty; - Chai.expect(callback.successResult.headers["Content-Type"]).to.be.not.empty; - Chai.expect(callback.successResult.headers["Content-Length"]).to.be.not.empty; + afterEach(() => { + callback.setCallback(null); }); - it("should not send a response if a response is prevously sent.", () => { - res.send(""); - Chai.expect(callback.successResult).to.be.not.undefined; - callback.successResult.body = null; + it("should send a response with the headers: Content-Security-Policy, X-Content-Type-Options, Content-Type and Content-Length.", (done) => { + callback.setCallback(() => { + Chai.expect(callback.successResult.headers["Content-Security-Policy"]).to.be.not.empty; + Chai.expect(callback.successResult.headers["X-Content-Type-Options"]).to.be.not.empty; + Chai.expect(callback.successResult.headers["Content-Type"]).to.be.not.empty; + Chai.expect(callback.successResult.headers["Content-Length"]).to.be.not.empty; + done(); + }); const handler = httpFinalHandler(req, res, {}); handler(); - Chai.expect(callback.successResult.body).to.be.null; + }); + + it("should not send a response if a response is prevously sent.", (done) => { + callback.setCallback(() => { + Chai.expect(callback.successResult).to.be.not.undefined; + callback.successResult.body = null; + + const handler = httpFinalHandler(req, res, {}); + handler(); + + Chai.expect(callback.successResult.body).to.be.null; + done(); + }); + res.send(""); }); it("should call #onerror handler if it is set in the #options.", (done) => { @@ -58,73 +71,121 @@ describe("httpFinalHandler", () => { handler(); }); - it("should set the response status code as 404 and acording message if #err is undefined.", () => { + it("should set the response status code as 404 and acording message if #err is undefined.", (done) => { + callback.setCallback(() => { + Chai.expect(callback.successResult.statusCode).to.be.equals(404); + Chai.expect(JSON.parse(callback.successResult.body).message).to.be.equals("Cannot GET /blog/1"); + Chai.expect(JSON.parse(callback.successResult.body).error).to.be.equals(404); + done(); + }); + const handler = httpFinalHandler(req, res, {}); handler(); - Chai.expect(callback.successResult.statusCode).to.be.equals(404); - console.log(callback.successResult.body); - Chai.expect(JSON.parse(callback.successResult.body).message).to.be.equals("Cannot GET /blog/1"); - Chai.expect(JSON.parse(callback.successResult.body).error).to.be.equals(404); }); - it("should set the response status code, headers and body from #err if it is not undefined.", () => { + it("should set the response status code, headers and body from #err if it is not undefined.", (done) => { + callback.setCallback(() => { + Chai.expect(callback.successResult.statusCode).to.be.equals(403); + Chai.expect(JSON.parse(callback.successResult.body).message).to.be.equals("Test msg"); + Chai.expect(JSON.parse(callback.successResult.body).error).to.be.equals(403); + Chai.expect(callback.successResult.headers.errorHeader).to.be.equals("test"); + done(); + }); + const handler = httpFinalHandler(req, res, {}); const error = new HttpError("Test msg", 403); error.headers = {errorHeader: "test"}; handler(error); - Chai.expect(callback.successResult.statusCode).to.be.equals(403); - Chai.expect(JSON.parse(callback.successResult.body).message).to.be.equals("Test msg"); - Chai.expect(JSON.parse(callback.successResult.body).error).to.be.equals(403); - Chai.expect(callback.successResult.headers.errorHeader).to.be.equals("test"); }); - it("should send a HTML response and HTML as content type if the request accepts HTML.", () => { + it("should send a HTML response and HTML as content type if the request accepts HTML.", (done) => { + callback.setCallback(() => { + Chai.expect(callback.successResult.body).to.contain(""); + done(); + }); + event.headers = Object.assign({}, event.headers); event.headers.Accept = "application/json,text/html"; req = new HttpRequest(app, event); const handler = httpFinalHandler(req, res, {}); handler(); - Chai.expect(callback.successResult.body).to.contain(""); }); - it("should send a JSON response and JSON as content type if the request accepts JSON and it does not accept HTML.", () => { + it("should send a JSON response and JSON as content type if the request accepts JSON and it does not accept HTML.", (done) => { + callback.setCallback(() => { + Chai.expect(JSON.parse(callback.successResult.body)).to.be.a("object"); + done(); + }); + const handler = httpFinalHandler(req, res, {}); handler(); - Chai.expect(JSON.parse(callback.successResult.body)).to.be.a("object"); }); - it("should send a PLAIN response and PLAIN as content type if the request does not accept JSON neither HTML.", () => { + it("should send a PLAIN response and PLAIN as content type if the request does not accept JSON neither HTML.", (done) => { + callback.setCallback(() => { + Chai.expect(callback.successResult.body).to.be.equals("Cannot GET /blog/1"); + done(); + }); + event.headers = Object.assign({}, event.headers); event.headers.Accept = "application/pdf"; req = new HttpRequest(app, event); const handler = httpFinalHandler(req, res, {}); handler(); - Chai.expect(callback.successResult.body).to.be.equals("Cannot GET /blog/1"); }); - it("should set status code from res if it is higher than 400, lower than 600 and the error is not of type HttpError.", () => { + it("should set status code from res if it is higher than 400, lower than 600 and the error is not of type HttpError.", (done) => { + callback.setCallback(() => { + Chai.expect(callback.successResult.statusCode).to.be.equals(404); + done(); + }); + const handler = httpFinalHandler(req, res, {}); res.status(404); const error = new Error("No HTTP error."); handler(error); - Chai.expect(callback.successResult.statusCode).to.be.equals(404); }); - it("should set status code 500 if the res status code is lower than 400 or higher than 600 and the error is not of type HttpError.", () => { + it("should set status code 500 if the res status code is lower than 400 or higher than 600 and the error is not of type HttpError.", (done) => { + callback.setCallback(() => { + Chai.expect(callback.successResult.statusCode).to.be.equals(500); + done(); + }); + const handler = httpFinalHandler(req, res, {}); res.status(300); const error = new Error("No HTTP error."); handler(error); - Chai.expect(callback.successResult.statusCode).to.be.equals(500); }); - it("should set status code 500 if the status code of HttpError is not set.", () => { + it("should set status code 500 if the status code of HttpError is not set.", (done) => { + callback.setCallback(() => { + Chai.expect(callback.successResult.statusCode).to.be.equals(500); + done(); + }); + const handler = httpFinalHandler(req, res, {}); const error = new HttpError("HTTP error.", null); handler(error); - Chai.expect(callback.successResult.statusCode).to.be.equals(500); + }); + + it("should call the endHandlers.", (done) => { + const endHandler: SinonStub = stub(); + endHandler.returns(Promise.resolve()); + + callback.setCallback(() => { + Chai.expect(endHandler.calledOnce).to.be.true; + done(); + }); + + const handler = httpFinalHandler(req, res, { + endHandlers: [ + endHandler + ] + }); + handler(); }); }); diff --git a/test/http/renderEngine/DevTemplateLoader.spec.ts b/test/http/renderEngine/DevTemplateLoader.spec.ts index 24860bd..444037e 100644 --- a/test/http/renderEngine/DevTemplateLoader.spec.ts +++ b/test/http/renderEngine/DevTemplateLoader.spec.ts @@ -14,7 +14,7 @@ describe("DevTemplateLoader", () => { beforeEach(() => { mock({ "/base/path": { - "prueba.pug": new Buffer("Test content.", "utf8") + "prueba.pug": Buffer.from("Test content.", "utf8") } }); }); diff --git a/test/utils/DefaultCallback.ts b/test/utils/DefaultCallback.ts index 54d1342..7711844 100644 --- a/test/utils/DefaultCallback.ts +++ b/test/utils/DefaultCallback.ts @@ -42,6 +42,9 @@ export default class DefaultCallback implements IRawCallback { public finalize(): void { this._isFinalized = true; + if (this._callback) { + this._callback(); + } } }