Skip to content

Commit

Permalink
Adds endHandlers to be called before the callback.
Browse files Browse the repository at this point in the history
  • Loading branch information
rogelio-o committed Oct 19, 2018
1 parent 9a49242 commit 9d99c70
Show file tree
Hide file tree
Showing 9 changed files with 160 additions and 46 deletions.
14 changes: 12 additions & 2 deletions src/lib/App.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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);
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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 = {};

Expand Down
11 changes: 8 additions & 3 deletions src/lib/event/eventFinalHandler.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -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);
}
});
};
}
12 changes: 8 additions & 4 deletions src/lib/http/httpFinalHandler.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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);
}
});
};
}
3 changes: 3 additions & 0 deletions src/lib/types/IApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -123,4 +124,6 @@ export default interface IApp {
*/
addTemplateEngine(renderer: ITemplateRenderer, engineConfiguration?: {[name: string]: any}): IApp;

addEndHandler(handler: IEndHandler): void;

}
5 changes: 5 additions & 0 deletions src/lib/types/IEndHandler.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/**
* Handler called just before the provider callback is called.
*/
type IEndHandler = () => Promise<void>;
export default IEndHandler;
27 changes: 25 additions & 2 deletions test/event/eventFinalHandler.spec.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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();
});

});
129 changes: 95 additions & 34 deletions test/http/httpFinalHandler.spec.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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) => {
Expand All @@ -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("<!DOCTYPE html>");
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("<!DOCTYPE html>");
});

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();
});

});
2 changes: 1 addition & 1 deletion test/http/renderEngine/DevTemplateLoader.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ describe("DevTemplateLoader", () => {
beforeEach(() => {
mock({
"/base/path": {
"prueba.pug": new Buffer("Test content.", "utf8")
"prueba.pug": Buffer.from("Test content.", "utf8")
}
});
});
Expand Down
3 changes: 3 additions & 0 deletions test/utils/DefaultCallback.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ export default class DefaultCallback implements IRawCallback {

public finalize(): void {
this._isFinalized = true;
if (this._callback) {
this._callback();
}
}

}

0 comments on commit 9d99c70

Please sign in to comment.