"use strict"; var assert = require("chai").assert; var sinon = require("sinon"); var express = require("express"); var GET = require("./util/http-utils").GET; var delay = function (method, payload) { setTimeout(function () { method(payload); }, 10); }; var PromiseRouter = require("../lib/express-promise-router.js"); describe("express-promise-router", function () { var app; var serverListening; var server; var router; var bootstrap = function (router) { app = express(); app.use("/", router); if (serverListening) { throw "already bootstrapped"; } serverListening = new Promise(function (resolve, reject) { server = app.listen(12345, function (err) { if (err) { reject(err); } else { resolve(); } }); }); return serverListening; }; beforeEach(function () { router = new PromiseRouter(); }); afterEach(function () { if (serverListening) { return serverListening.then(function () { server.close(); app = undefined; server = undefined; serverListening = undefined; }); } }); it("should call next with an error when a returned promise is rejected", function () { var callback = sinon.spy(); router.use("/foo", function () { return new Promise(function (resolve, reject) { delay(reject, "some error"); }); }); router.use(function (err, req, res, next) { assert.equal("some error", err); callback(); res.send(); }); return bootstrap(router) .then(function () { return GET("/foo"); }) .then(function () { assert(callback.calledOnce); }); }); it('should call next without an error when a returned promise is resolved with "next"', function () { var errorCallback = sinon.spy(); var nextCallback = sinon.spy(); router.use("/foo", function () { return new Promise(function (resolve) { delay(resolve, "next"); }); }); router.use("/foo", function (req, res) { nextCallback(); res.send(); }); router.use(function (err, req, res, next) { errorCallback(); next(); }); return bootstrap(router) .then(function () { return GET("/foo"); }) .then(function () { assert(errorCallback.notCalled); assert(nextCallback.calledOnce); }); }); it('should not call next when a returned promise is resolved with anything other than "route" or "next"', function () { var callback = sinon.spy(); router.get("/foo", function (req, res) { return new Promise(function (resolve) { res.send(); delay(resolve, "something"); }); }); router.get("/bar", function (req, res) { return new Promise(function (resolve) { res.send(); delay(resolve, {}); }); }); router.use(function (req, res) { callback(); res.send(500); }); return bootstrap(router) .then(function () { return GET("/foo"); }) .then(function () { assert(callback.notCalled); return GET("/bar"); }) .then(function () { assert(callback.notCalled); }); }); it("should move to the next middleware when next is called without an error", function () { var callback = sinon.spy(); router.use("/foo", function (req, res, next) { next(); }); router.use("/foo", function (req, res, next) { callback(); res.send(); }); return bootstrap(router) .then(function () { return GET("/foo"); }) .then(function () { assert(callback.calledOnce); }); }); it("should move to the next error handler when next is called with an error", function () { var callback = sinon.spy(); var errorCallback = sinon.spy(); router.use("/foo", function (req, res, next) { next("an error"); }); router.use("/foo", function (req, res, next) { callback(); next(); }); router.use(function (err, req, res, next) { assert.equal("an error", err); errorCallback(); res.send(); }); return bootstrap(router) .then(function () { return GET("/foo"); }) .then(function () { assert(errorCallback.calledOnce); assert(callback.notCalled); }); }); it("should call chained handlers in the correct order", function () { var fn2 = sinon.spy(function (req, res) { res.send(); }); var fn1 = sinon.spy(function () { assert(fn2.notCalled); return Promise.resolve("next"); }); router.get("/foo", fn1, fn2); return bootstrap(router).then(function () { return GET("/foo"); }); }); it("should correctly call an array of handlers", function () { var fn2 = sinon.spy(function (req, res) { res.send(); }); var fn1 = sinon.spy(function () { assert(fn2.notCalled); return Promise.resolve("next"); }); router.get("/foo", [[fn1], [fn2]]); return bootstrap(router).then(function () { return GET("/foo"); }); }); it('should call next("route") if a returned promise is resolved with "route"', function () { var fn1 = function () { return Promise.resolve("route"); }; var fn2 = function () { assert.fail(); }; router.get("/foo", fn1, fn2); router.get("/foo", function (req, res) { res.send(); }); return bootstrap(router).then(function () { return GET("/foo"); }); }); it("should bind to RegExp routes", function () { var fn1 = function (req, res) { res.send(); }; router.get(/^\/foo/, fn1); return bootstrap(router).then(function () { return GET("/foo"); }); }); it('multiple calls to handlers that have used "next" should not interfere with each other', function () { var fn = sinon.spy(function (req, res, next) { if (fn.calledOnce) { next("error"); } else { setTimeout(function () { res.status(200).send("ok"); }, 15); } }); var errHandler = function (err, req, res, next) { if (err === "error") { res.send("fail"); } else { next(err); } }; router.get("/foo", fn, errHandler); return bootstrap(router) .then(function () { return GET("/foo"); }) .then(function (res) { assert.equal(res.body, "fail"); return GET("/foo"); }) .then(function (res) { assert.equal(res.body, "ok"); }); }); it("calls next if next is called even if the handler returns a promise", function () { var fn = function (req, res, next) { next(); return new Promise(function (resolve, reject) {}); }; var fn2 = function (req, res) { res.send("ok"); }; var errHandler = function (err, req, res, next) { res.send("error"); }; router.get("/foo", fn, fn2, errHandler); return bootstrap(router) .then(function () { return GET("/foo"); }) .then(function (res) { assert.equal(res.body, "ok"); }); }); it("calls next with an error if the returned promise is rejected with no reason", function () { var fn = function () { return new Promise(function (resolve, reject) { delay(reject, null); }); }; var errHandler = function (err, req, res, next) { res.send("error"); }; router.get("/foo", fn, errHandler); return bootstrap(router) .then(function () { return GET("/foo"); }) .then(function (res) { assert.equal(res.body, "error"); }); }); it("should handle resolved promises returned in req.param() calls", function () { router.param("id", function () { return new Promise(function (resolve) { delay(resolve, "next"); }); }); router.use("/foo/:id", function (req, res) { res.send("done"); }); return bootstrap(router) .then(function () { return GET("/foo/1"); }) .then(function (res) { assert.equal(res.body, "done"); }); }); it("should call next with unresolved promises returned in req.param() calls", function () { var assertOutput = "error in param"; router.param("id", function (req, res, next, id) { return new Promise(function (resolve, reject) { delay(reject, assertOutput); }); }); var fn = function (req, res) { res.send("done"); }; var errHandler = function (err, req, res, next) { res.send(err); }; router.use("/foo/:id", fn); router.use(errHandler); return bootstrap(router) .then(function () { return GET("/foo/1"); }) .then(function (res) { assert.equal(res.body, assertOutput); }); }); it("support array in routes values", function () { router.use(["/", "/foo/:bar"], function (req, res) { res.send("done"); }); return bootstrap(router) .then(function () { return GET("/"); }) .then(function (res) { assert.equal(res.body, "done"); return GET("/foo/1"); }) .then(function (res) { assert.equal(res.body, "done"); }); }); it("should throw sensible errors when handler is not a function", function () { assert.throws(function () { router.use("/foo/:id", null); }, /callback/); }); });