FreeAllMedia/forbin

View on GitHub
es6/spec/forbin.spec.js

Summary

Maintainability
F
1 wk
Test Coverage
import Controller from "../../index";
import flowsync from "flowsync";
import sinon from "sinon";

describe("Controller(...options)", () => {
    describe(".actionNames", () => {
        let clientController;

        class ClientController extends Controller {
            create() {}
            update() {}
            delete() {}
        }

        beforeEach(() => {
            clientController = new ClientController("foo", "bar");
        });

        it("should return an array of action names for extended controllers", () => {
            clientController.actionNames.should.eql(["create", "update", "delete"]);
        });
    });

    describe("(constructor hooks)", () => {
    let clientController,
        prefilterSpy,
        initializeSpy,
        setupSpy,
        filterSpy,
        initializeValues;

    class ApplicationController extends Controller {
        prefilters(...options) {
            prefilterSpy(...options);
        }

        initialize(...options) {
            initializeSpy(...options);
        }

        setup(...options) {
            setupSpy(...options);
        }

        filters(...options) {
            filterSpy(...options);
        }
    }

    class ClientController extends ApplicationController {}

    beforeEach(() => {
        filterSpy = sinon.spy();
        initializeSpy = sinon.spy();
        prefilterSpy = sinon.spy();
        setupSpy = sinon.spy();

        initializeValues = ["foo", "bar"];
        clientController = new ClientController(...initializeValues);
    });

    describe(".initialize(...options)", () => {
        it("should be called during construction", () => {
            initializeSpy.called.should.be.true;
        });

        it("should be called with the constructor values", () => {
            initializeSpy.calledWithExactly(...initializeValues).should.be.true;
        });
    });

    describe(".setup(...options)", () => {
        it("should be called during construction", () => {
            setupSpy.called.should.be.true;
        });

        it("should be called with the constructor values", () => {
            setupSpy.calledWithExactly(...initializeValues).should.be.true;
        });
    });

    describe(".filters(...options)", () => {
        it("should be called during construction", () => {
            filterSpy.called.should.be.true;
        });

        it("should be called with the constructor values", () => {
            filterSpy.calledWithExactly(...initializeValues).should.be.true;
        });
    });

    describe(".prefilters(...options)", () => {
        it("should be called during construction", () => {
            prefilterSpy.called.should.be.true;
        });

        it("should be called with the constructor values", () => {
            prefilterSpy.calledWithExactly(...initializeValues).should.be.true;
        });

        it("should be called before filters", () => {
            sinon.assert.callOrder(prefilterSpy, filterSpy);
        });
    });
    });

    describe("(before and after filters)", () => {
        const logRequest = Symbol(),
            authenticate = Symbol(),
            shredResponse = Symbol(),
            logResponse = Symbol();

        let logRequestSpy,
            clientController,
            createSpy,
            updateSpy,
            deleteSpy,
            authenticateSpy,
            mockRequest,
            mockResponse,
            clock,
            callClientControllerActions,
            logResponseSpy,
            shredResponseSpy;

        class ApplicationController extends Controller {
            filters(...options) {}

            [logRequest] (request, response, next) {
                logRequestSpy(request, response, next);
            }

            [authenticate] (request, response, next) {
                authenticateSpy(request, response, next);
            }

            [logResponse] (request, response, next) {
                logResponseSpy(request, response, next);
            }

            [shredResponse] (request, response, next) {
                shredResponseSpy(request, response, next);
            }
        }

        class ClientController extends ApplicationController {
            update(request, response) {
                updateSpy(request, response);
            }

            create(request, response) {
                createSpy(request, response);
            }

            delete(request, response) {
                deleteSpy(request, response);
            }
        }

        before(() => {
            callClientControllerActions = (controller, callback) => {
                flowsync.series([
                    function createAction(next) {
                        mockResponse = {
                            end: () => {
                            }
                        };
                        controller.create(mockRequest, mockResponse);
                        clock.tick(1000);
                        next();
                    },
                    function updateAction(next) {
                        mockResponse = {
                            end: () => {
                            }
                        };
                        controller.update(mockRequest, mockResponse);
                        clock.tick(1000);
                        next();
                    },
                    function deleteAction(next) {
                        mockResponse = {
                            end: () => {
                            }
                        };
                        controller.delete(mockRequest, mockResponse);
                        clock.tick(1000);
                        next();
                    }],
                    callback
                );
            };
        });

        beforeEach(() => {
            clock = sinon.useFakeTimers();

            /* FILTER SPIES */
            logRequestSpy = sinon.spy(function logRequestSpy(request, response, next) {
                next();
            });

            authenticateSpy = sinon.spy(function authenticateSpy(request, response, next) {
                next();
            });

            logResponseSpy = sinon.spy(function logResponseSpy(request, response, next) {
                next();
            });

            shredResponseSpy = sinon.spy(function shredResponseSpy(request, response, next) {
                next();
            });

            /* ACTION SPIES */
            createSpy = sinon.spy(function createSpy(request, response) {
                response.end();
            });

            updateSpy = sinon.spy(function updateSpy(request, response) {
                response.end();
            });

            deleteSpy = sinon.spy(function deleteSpy(request, response) {
                response.end();
            });

            mockRequest = {};

            mockResponse = {
                end: () => {
                }
            };

            clientController = new ClientController("foo", "bar");
        });

        afterEach(() => {
            clock.restore();
        });

        describe(".before(...options)", () => {
            describe("(with just a filter function)", () => {
                beforeEach(() => {
                    clientController.before(clientController[logRequest]);
                });

                describe("(before all)", () => {
                    it("should call a filter method before create", done => {
                        mockResponse = {
                            end: () => {
                                sinon.assert.callOrder(logRequestSpy, createSpy);
                                done();
                            }
                        };
                        clientController.create(mockRequest, mockResponse);
                    });

                    it("should call a filter method before update", done => {
                        mockResponse = {
                            end: () => {
                                sinon.assert.callOrder(logRequestSpy, updateSpy);
                                done();
                            }
                        };
                        clientController.update(mockRequest, mockResponse);
                    });

                    it("should call a filter method before delete", done => {
                        mockResponse = {
                            end: () => {
                                sinon.assert.callOrder(logRequestSpy, deleteSpy);
                                done();
                            }
                        };
                        clientController.delete(mockRequest, mockResponse);
                    });
                });

                describe("(order)", () => {
                    beforeEach(() => {
                        clientController.before(clientController[authenticate]);
                    });

                    describe("(before all)", () => {
                        it("should call a filter before create action in the order they were added", done => {
                            mockResponse = {
                                end: () => {
                                    sinon.assert.callOrder(
                                        logRequestSpy,
                                        authenticateSpy,
                                        createSpy);
                                    done();
                                }
                            };

                            clientController.create(mockRequest, mockResponse);
                        });

                        it("should call a filter before update action in the order they were added", done => {
                            mockResponse = {
                                end: () => {
                                    sinon.assert.callOrder(
                                        logRequestSpy,
                                        authenticateSpy,
                                        updateSpy);
                                    done();
                                }
                            };

                            clientController.update(mockRequest, mockResponse);
                        });

                        it("should call a filter before delete action in the order they were added", done => {
                            mockResponse = {
                                end: () => {
                                    sinon.assert.callOrder(
                                        logRequestSpy,
                                        authenticateSpy,
                                        deleteSpy);
                                    done();
                                }
                            };

                            clientController.delete(mockRequest, mockResponse);
                        });
                    });
                });
            });

            describe("(when calling with the action and the filter)", () => {
                beforeEach(() => {
                    clientController.before(clientController.update, clientController[logRequest]);
                });

                describe("(before specific)", () => {
                    it("should set a filter method to be called before a specific action", done => {
                        mockResponse = {
                            end: () => {
                                sinon.assert.callOrder(
                                    logRequestSpy,
                                    updateSpy);
                                done();
                            }
                        }
                        clientController.update(mockRequest, mockResponse);
                    });

                    it("should set a filter method not to be called before another action", done => {
                        mockResponse = {
                            end: () => {
                                logRequestSpy.called.should.be.false;
                                done();
                            }
                        }
                        clientController.create(mockRequest, mockResponse);
                    });

                    describe("(order)", () => {
                        beforeEach(() => {
                            clientController.before(clientController.update, clientController[authenticate]);
                            clientController.before(clientController.delete, clientController[authenticate]);
                            clientController.before(clientController.delete, clientController[logRequest]);
                        });

                        it("should set a filter method to be called before delete in the order they were added", done => {
                            mockResponse = {
                                end: () => {
                                    sinon.assert.callOrder(
                                        authenticateSpy,
                                        logRequestSpy,
                                        deleteSpy);
                                    done();
                                }
                            }
                            clientController.delete(mockRequest, mockResponse);
                        });
                    });
                });
            });

            describe("(when calling with an action array and a filter)", () => {
                describe("(before specific array)", () => {
                    beforeEach(() => {
                        clientController.before([clientController.update, clientController.delete], clientController[logRequest]);
                    });

                    it("should set a filter method to be called before the create action as provided in the array", done => {
                        mockResponse = {
                            end: () => {
                                sinon.assert.callOrder(createSpy);
                                done();
                            }
                        }
                        clientController.create(mockRequest, mockResponse);
                    });

                    it("should set a filter method to be called before the update action as provided in the array", done => {
                        mockResponse = {
                            end: () => {
                                sinon.assert.callOrder(
                                    logRequestSpy,
                                    updateSpy);
                                done();
                            }
                        }
                        clientController.update(mockRequest, mockResponse);
                    });

                    it("should set a filter method to be called before the delete action as provided in the array", done => {
                        mockResponse = {
                            end: () => {
                                sinon.assert.callOrder(
                                    logRequestSpy,
                                    deleteSpy);
                                done();
                            }
                        }
                        clientController.delete(mockRequest, mockResponse);
                    });
                });
            });
        });

        //add sinon tick
        describe(".after(...options)", () => {
            describe("(with just a filter function)", () => {
                beforeEach(() => {
                    clientController.after(clientController[logResponse]);
                });

                describe("(after all)", () => {

                    it("should call a filter method after create", done => {
                        logResponseSpy = sinon.spy(() => {
                            sinon.assert.callOrder(createSpy, logResponseSpy);
                            done();
                        });

                        clientController.create(mockRequest, mockResponse);
                    });

                    it("should call a filter method after update", done => {
                        logResponseSpy = sinon.spy(() => {
                            sinon.assert.callOrder(updateSpy, logResponseSpy);
                            done();
                        });
                        clientController.update(mockRequest, mockResponse);
                    });

                    it("should call a filter method after delete", done => {
                        logResponseSpy = sinon.spy(() => {
                            sinon.assert.callOrder(deleteSpy, logResponseSpy);
                            done();
                        });
                        clientController.delete(mockRequest, mockResponse);
                    });
                });

                describe("(order)", () => {
                    beforeEach(() => {
                        clientController.after(clientController[shredResponse]);
                    });

                    describe("(after all)", () => {
                        it("should call a filter after create action in the order they were added", done => {
                            shredResponseSpy = sinon.spy(() => {
                                sinon.assert.callOrder(createSpy, logResponseSpy, shredResponseSpy);
                                done();
                            });

                            clientController.create(mockRequest, mockResponse);
                        });
                    });
                });
            });

            describe("(when calling with the action and the filter)", () => {
                beforeEach(() => {
                    clientController.after(clientController.update, clientController[logResponse]);
                });

                it("should set a filter method to be called after the specified action", done => {
                    logResponseSpy = sinon.spy(() => {
                        sinon.assert.callOrder(updateSpy, logResponseSpy);
                        done();
                    });
                    clientController.update(mockRequest, mockResponse);
                });

                describe("(order)", () => {
                    beforeEach(() => {
                        clientController.after(clientController.update, clientController[shredResponse]);
                        clientController.after(clientController.delete, clientController[shredResponse]);
                        clientController.after(clientController.delete, clientController[logResponse]);
                    });

                    it("should set a filter method to be called after a specific action in the order they were added", done => {
                        logResponseSpy = sinon.spy(() => {
                            sinon.assert.callOrder(deleteSpy, shredResponseSpy, logResponseSpy);
                            done();
                        });
                        clientController.delete(mockRequest, mockResponse);
                    });
                });
            });

            describe("(when calling with an action array and a filter)", () => {
                beforeEach(() => {
                    clientController.after([clientController.update, clientController.delete], clientController[logResponse]);
                });

                it("should set a filter method to be called after the delete action provided in the array", done => {
                    logResponseSpy = sinon.spy(() => {
                        sinon.assert.callOrder(deleteSpy, logResponseSpy);
                        done();
                    });
                    clientController.delete(mockRequest, mockResponse);
                });

                it("should set a filter method to be called after the delete action provided in the array", done => {
                    logResponseSpy = sinon.spy(() => {
                        sinon.assert.callOrder(updateSpy, logResponseSpy);
                        done();
                    });
                    clientController.update(mockRequest, mockResponse);
                });
            });
        });

        describe(".skip(...options)", () => {
            describe("(with just the filter)", () => {
                describe("(when was applied to all)", () => {
                    beforeEach(() => {
                        clientController.before(clientController[logRequest]);
                        clientController.before(clientController[logResponse]);
                        clientController.skip(clientController[logRequest]);
                        clientController.skip(clientController[logResponse]);
                    });

                    it("should skip a filter applied to all actions", done => {
                        callClientControllerActions(
                            clientController,
                            () => {
                                (logRequestSpy.called && logResponse.called).should.be.false;
                                done();
                            }
                        );
                    });
                });

                describe("(when was applied to just one)", () => {
                    beforeEach(() => {
                        clientController.before(clientController.update, clientController[logRequest]);
                        clientController.before(clientController.delete, clientController[logResponse]);
                        clientController.skip(clientController[logRequest]);
                        clientController.skip(clientController[logResponse]);
                    });

                    it("should skip a filter applied to one action", done => {
                        callClientControllerActions(
                            clientController,
                            () => {
                                (logRequestSpy.called && logResponse.called).should.be.false;
                                done();
                            }
                        );
                    });
                });
            });

            describe("(with the specific action to skip the filter)", () => {

                describe("(when initially applied to all)", () => {
                    beforeEach(() => {
                        clientController.before(clientController[logRequest]);
                        clientController.skip(clientController.update, clientController[logRequest]);
                    });

                    it("should skip the filter applied to all just for that action", done => {
                        callClientControllerActions(
                            clientController,
                            () => {
                                logRequestSpy.callCount.should.equals(2);
                                done();
                            }
                        );
                    });
                });

                describe("(when initially applied to a specific action)", () => {
                    beforeEach(() => {
                        clientController.before(clientController.update, clientController[logRequest]);
                        clientController.skip(clientController.update, clientController[logRequest]);
                    });

                    it("should skip the filter applied to that specific action", done => {
                        callClientControllerActions(
                            clientController,
                            () => {
                                logRequestSpy.called.should.be.false;
                                done();
                            }
                        );
                    });
                });

                describe("(when skip an array of actions)", () => {
                    beforeEach(() => {
                        clientController.before(clientController[logRequest]);
                        clientController.skip([clientController.update, clientController.delete], clientController[logRequest]);
                    });

                    it("should skip the filter applied to that specific action", done => {
                        callClientControllerActions(
                            clientController,
                            () => {
                                logRequestSpy.callCount.should.equals(1);
                                done();
                            }
                        );
                    });
                });
            });
        });

        describe("(binding)", () => {
            let clean = Symbol("clean"),
                throwIt = Symbol("throwIt"),
                actionSpy,
                actionObject,
                beforeFilterSpy,
                afterFilterSpy,
                beforeFilterObject,
                afterFilterObject,
                appleController;

            class AppleController extends Controller {
                filters() {
                    this.before(this[clean]);
                    this.after(this[throwIt]);
                }

                [clean](request, response, next) {
                    beforeFilterSpy(this);
                    next();
                }

                [throwIt](request, response, next) {
                    afterFilterSpy(this);
                    next();
                }

                eat(request, response) {
                    actionSpy(this);
                    response.end();
                }
            }

            before((done) => {
                actionSpy = sinon.spy((object) => {
                    actionObject = object;
                });

                beforeFilterSpy = sinon.spy((object) => {
                    beforeFilterObject = object;
                });

                afterFilterSpy = sinon.spy((object) => {
                    afterFilterObject = object;
                });

                appleController = new AppleController();
                appleController.eat({}, {end: done});
            });

            it("should allow to access this on the action", () => {
                actionObject.should.eql(appleController);
            });

            it("should allow to access this on the before filter", () => {
                beforeFilterObject.should.eql(appleController);
            });

            it("should allow to access this on the after filter", () => {
                afterFilterObject.should.eql(appleController);
            });
        });
    });
});