"use strict";
var decoratorsLib = require("../../decorators");
var yok_1 = require("../../yok");
var chai_1 = require("chai");
var Future = require("fibers/future");
var Promise = require("bluebird");
describe("decorators", function () {
    var moduleName = "moduleName", propertyName = "propertyName", generatePublicApiFromExportedPromiseDecorator = function () {
        chai_1.assert.deepEqual($injector.publicApi.__modules__[moduleName], undefined);
        var promisifiedResult = decoratorsLib.exportedPromise(moduleName);
        promisifiedResult({}, propertyName, {});
    };
    beforeEach(function () {
        $injector = new yok_1.Yok();
    });
    after(function () {
        $injector = new yok_1.Yok();
    });
    describe("exportedPromise", function () {
        it("returns function", function () {
            var result = decoratorsLib.exportedPromise("test");
            chai_1.assert.equal(typeof (result), "function");
        });
        it("does not change original method", function () {
            var promisifiedResult = decoratorsLib.exportedPromise(moduleName);
            var expectedResult = { "originalObject": "originalValue" };
            var actualResult = promisifiedResult({}, "myTest1", expectedResult);
            chai_1.assert.deepEqual(actualResult, expectedResult);
        });
        it("adds method to public api", function () {
            generatePublicApiFromExportedPromiseDecorator();
            chai_1.assert.deepEqual(typeof ($injector.publicApi.__modules__[moduleName][propertyName]), "function");
        });
        it("returns Promise", function (done) {
            var expectedResult = "result";
            $injector.register(moduleName, { propertyName: function () { return expectedResult; } });
            generatePublicApiFromExportedPromiseDecorator();
            var promise = $injector.publicApi.__modules__[moduleName][propertyName]();
            chai_1.assert.equal(typeof (promise.then), "function");
            promise.then(function (val) {
                chai_1.assert.deepEqual(val, expectedResult);
            }).then(done).catch(done);
        });
        it("returns Promise, which is resolved to correct value (function without arguments)", function (done) {
            var expectedResult = "result";
            $injector.register(moduleName, { propertyName: function () { return expectedResult; } });
            generatePublicApiFromExportedPromiseDecorator();
            var promise = $injector.publicApi.__modules__[moduleName][propertyName]();
            promise.then(function (val) {
                chai_1.assert.deepEqual(val, expectedResult);
            }).then(done).catch(done);
        });
        it("returns Promise, which is resolved to correct value (function with arguments)", function (done) {
            var expectedArgs = ["result", "result1", "result2"];
            $injector.register(moduleName, { propertyName: function (functionArgs) { return functionArgs; } });
            generatePublicApiFromExportedPromiseDecorator();
            var promise = $injector.publicApi.__modules__[moduleName][propertyName](expectedArgs);
            promise.then(function (val) {
                chai_1.assert.deepEqual(val, expectedArgs);
            }).then(done).catch(done);
        });
        it("returns Promise, which is resolved to correct value (function returning IFuture without arguments)", function (done) {
            var expectedResult = "result";
            $injector.register(moduleName, { propertyName: function () { return Future.fromResult(expectedResult); } });
            generatePublicApiFromExportedPromiseDecorator();
            var promise = $injector.publicApi.__modules__[moduleName][propertyName]();
            promise.then(function (val) {
                chai_1.assert.deepEqual(val, expectedResult);
            }).then(done).catch(done);
        });
        it("returns Promise, which is resolved to correct value (function returning IFuture with arguments)", function (done) {
            var expectedArgs = ["result", "result1", "result2"];
            $injector.register(moduleName, { propertyName: function (args) { return Future.fromResult(args); } });
            generatePublicApiFromExportedPromiseDecorator();
            var promise = $injector.publicApi.__modules__[moduleName][propertyName](expectedArgs);
            promise.then(function (val) {
                chai_1.assert.deepEqual(val, expectedArgs);
            }).then(done).catch(done);
        });
        it("rejects Promise, which is resolved to correct error (function without arguments throws)", function (done) {
            var expectedError = new Error("Test msg");
            $injector.register(moduleName, { propertyName: function () { throw expectedError; } });
            generatePublicApiFromExportedPromiseDecorator();
            var promise = $injector.publicApi.__modules__[moduleName][propertyName]();
            promise.then(function (result) {
                throw new Error("Then method MUST not be called when promise is rejected!");
            }, function (err) {
                chai_1.assert.deepEqual(err, expectedError);
            }).then(done).catch(done);
        });
        it("rejects Promise, which is resolved to correct error (function returning IFuture without arguments throws)", function (done) {
            var expectedError = new Error("Test msg");
            $injector.register(moduleName, { propertyName: function () { return (function () { throw expectedError; }).future()(); } });
            generatePublicApiFromExportedPromiseDecorator();
            var promise = $injector.publicApi.__modules__[moduleName][propertyName]();
            promise.then(function (result) {
                throw new Error("Then method MUST not be called when promise is rejected!");
            }, function (err) {
                chai_1.assert.deepEqual(err.message, expectedError.message);
            }).then(done).catch(done);
        });
        it("returns Promises, which are resolved to correct value (function returning IFuture<T>[] without arguments)", function (done) {
            var expectedResults = ["result1", "result2", "result3"];
            $injector.register(moduleName, { propertyName: function () { return _.map(expectedResults, function (expectedResult) { return Future.fromResult(expectedResult); }); } });
            generatePublicApiFromExportedPromiseDecorator();
            var promises = $injector.publicApi.__modules__[moduleName][propertyName]();
            Promise.all(promises)
                .then(function (promiseResults) {
                _.each(promiseResults, function (val, index) {
                    chai_1.assert.deepEqual(val, expectedResults[index]);
                });
            })
                .then(done)
                .catch(done);
        });
        it("rejects Promises, which are resolved to correct error (function returning IFuture<T>[] without arguments throws)", function (done) {
            var expectedErrors = [new Error("result1"), new Error("result2"), new Error("result3")];
            $injector.register(moduleName, { propertyName: function () { return _.map(expectedErrors, function (expectedError) { return (function () { throw expectedError; }).future()(); }); } });
            generatePublicApiFromExportedPromiseDecorator();
            new Promise(function (onFulfilled, onRejected) {
                var promises = $injector.publicApi.__modules__[moduleName][propertyName]();
                _.each(promises, function (promise, index) { return promise.then(function (result) {
                    onRejected(new Error("Then method MUST not be called when promise is rejected!. Result of promise is: " + result));
                }, function (err) {
                    if (err.message !== expectedErrors[index].message) {
                        onRejected(new Error("Error message of rejected promise is not the expected one: expected: \"" + expectedErrors[index].message + "\", but was: \"" + err.message + "\"."));
                    }
                    if (index + 1 === expectedErrors.length) {
                        onFulfilled();
                    }
                }); });
            }).then(done).catch(done);
        });
        it("rejects only Promises which throw, resolves the others correctly (function returning IFuture<T>[] without arguments)", function (done) {
            var expectedResults = ["result1", new Error("result2")];
            $injector.register(moduleName, { propertyName: function () { return _.map(expectedResults, function (expectedResult) { return Future.fromResult(expectedResult); }); } });
            generatePublicApiFromExportedPromiseDecorator();
            new Promise(function (onFulfilled, onRejected) {
                var promises = $injector.publicApi.__modules__[moduleName][propertyName]();
                _.each(promises, function (promise, index) { return promise.then(function (val) {
                    chai_1.assert.deepEqual(val, expectedResults[index]);
                    if (index + 1 === expectedResults.length) {
                        onFulfilled();
                    }
                }, function (err) {
                    chai_1.assert.deepEqual(err.message, expectedResults[index].message);
                    if (index + 1 === expectedResults.length) {
                        onFulfilled();
                    }
                }); });
            }).then(done).catch(done);
        });
        describe("postAction", function () {
            var isPostActionExecuted = false;
            var isActionExecuted = false;
            var expectedResults;
            var postAction = function () {
                chai_1.assert.isTrue(isActionExecuted, "Post Action MUST be executed AFTER all actions are executed.");
                isPostActionExecuted = true;
            };
            var getPromisesWithPostAction = function () {
                var promisifiedResultFunction = decoratorsLib.exportedPromise(moduleName, postAction);
                promisifiedResultFunction({}, propertyName, {});
                return $injector.publicApi.__modules__[moduleName][propertyName]();
            };
            var assertResults = function (result) {
                chai_1.assert.deepEqual(result, expectedResults);
                chai_1.assert.isTrue(isPostActionExecuted, "Post action must be executed after all promises are resolved.");
                chai_1.assert.isTrue(isActionExecuted, "All actions must be executed after the promise is resolved.");
            };
            beforeEach(function () {
                isPostActionExecuted = false;
                isActionExecuted = false;
            });
            it("executes postAction after all promises are resolved (function returning IFuture<T>)", function (done) {
                expectedResults = "result";
                $injector.register(moduleName, {
                    propertyName: function () {
                        return (function () {
                            chai_1.assert.isFalse(isPostActionExecuted, "Post action MUST NOT be called before all actions are executed.");
                            isActionExecuted = true;
                            return expectedResults;
                        }).future()();
                    }
                });
                getPromisesWithPostAction()
                    .then(assertResults)
                    .then(done)
                    .catch(done);
            });
            it("executes postAction after all promises are resolved (function returning IFuture<T>[])", function (done) {
                expectedResults = ["result1", "result2", "result3"];
                $injector.register(moduleName, {
                    propertyName: function () { return _.map(expectedResults, function (expectedResult, index) {
                        return (function () {
                            chai_1.assert.isFalse(isPostActionExecuted, "Post action MUST NOT be called before all actions are executed.");
                            isActionExecuted = (index + 1) === expectedResults.length;
                            return expectedResult;
                        }).future()();
                    }); }
                });
                Promise.all(getPromisesWithPostAction())
                    .then(assertResults)
                    .then(done)
                    .catch(done);
            });
            it("executes postAction after a promise is rejected (function returning IFuture<T> that throws)", function (done) {
                expectedResults = "result";
                var errorMessage = "This future throws";
                $injector.register(moduleName, {
                    propertyName: function () {
                        return (function () {
                            chai_1.assert.isFalse(isPostActionExecuted, "Post action MUST NOT be called before all actions are executed.");
                            isActionExecuted = true;
                            throw new Error(errorMessage);
                        }).future()();
                    }
                });
                getPromisesWithPostAction()
                    .then(function (result) {
                    throw new Error("Then method MUST not be called when promise is rejected!");
                }, function (err) {
                    chai_1.assert.deepEqual(err.message, errorMessage, "Error message of rejection should be the specified one.");
                    chai_1.assert.isTrue(isPostActionExecuted, "Post action must be executed after all promises are resolved.");
                    chai_1.assert.isTrue(isActionExecuted, "All actions must be executed after the promise is resolved.");
                })
                    .then(done)
                    .catch(done);
            });
            it("executes postAction after all promises are rejected (function returning IFuture<T>[] that throws)", function (done) {
                expectedResults = ["result1", "result2", "result3"];
                var errorMessage = "This future throws.";
                $injector.register(moduleName, {
                    propertyName: function () { return _.map(expectedResults, function (expectedResult, index) {
                        return (function () {
                            chai_1.assert.isFalse(isPostActionExecuted, "Post action MUST NOT be called before all actions are executed.");
                            isActionExecuted = (index + 1) === expectedResults.length;
                            throw new Error(errorMessage);
                        }).future()();
                    }); }
                });
                var caughtErrors = 0;
                var mainPromise = new Promise(function (onFulfilled, onRejected) {
                    _.each(getPromisesWithPostAction(), function (promise) { return promise.then(function (result) {
                        throw new Error("Then method MUST not be called when promise is rejected!");
                    }, function (err) {
                        caughtErrors++;
                        chai_1.assert.deepEqual(err.message, errorMessage, "Error message of rejection should be the specified one.");
                        if (caughtErrors === expectedResults.length) {
                            onFulfilled();
                        }
                    }); });
                });
                mainPromise
                    .then(function (result) {
                    chai_1.assert.isTrue(isPostActionExecuted, "Post action must be executed after all promises are resolved.");
                    chai_1.assert.isTrue(isActionExecuted, "All actions must be executed after the promise is resolved.");
                    done();
                })
                    .catch(done);
            });
            it("executes postAction after all some promises are rejected and others are resolved (function returning IFuture<T>[] where some of the future throw)", function (done) {
                var calledActionsCount = 0;
                expectedResults = ["result1", "result2", "result3", "result4"];
                var errorMessage = "This future throws.";
                $injector.register(moduleName, {
                    propertyName: function () { return _.map(expectedResults, function (expectedResult) {
                        return (function () {
                            chai_1.assert.isFalse(isPostActionExecuted, "Post action MUST NOT be called before all actions are executed.");
                            calledActionsCount++;
                            isActionExecuted = calledActionsCount === expectedResults.length;
                            if (calledActionsCount % 2 === 0) {
                                throw new Error(errorMessage);
                            }
                            else {
                                return expectedResult;
                            }
                        }).future()();
                    }); }
                });
                var caughtErrors = 0, resolvedPromises = 0;
                var mainPromise = new Promise(function (onFulfilled, onRejected) {
                    _.each(getPromisesWithPostAction(), function (promise, index) { return promise.then(function (result) {
                        resolvedPromises++;
                        chai_1.assert.deepEqual(result, expectedResults[index]);
                        if ((caughtErrors + resolvedPromises) === expectedResults.length) {
                            onFulfilled();
                        }
                    }, function (err) {
                        caughtErrors++;
                        chai_1.assert.deepEqual(err.message, errorMessage, "Error message of rejection should be the specified one.");
                        if ((caughtErrors + resolvedPromises) === expectedResults.length) {
                            onFulfilled();
                        }
                    }); });
                });
                mainPromise
                    .then(function (result) {
                    chai_1.assert.isTrue(isPostActionExecuted, "Post action must be executed after all promises are resolved.");
                    chai_1.assert.isTrue(isActionExecuted, "All actions must be executed after the promise is resolved.");
                    done();
                })
                    .catch(done);
            });
        });
    });
    describe("exported", function () {
        var expectedResults = [
            "string result",
            1,
            { a: 1, b: "2" },
            ["string 1", "string2"],
            true,
            undefined,
            null
        ];
        var generatePublicApiFromExportedDecorator = function () {
            chai_1.assert.deepEqual($injector.publicApi.__modules__[moduleName], undefined);
            var resultFunction = decoratorsLib.exported(moduleName);
            resultFunction({}, propertyName, {});
        };
        it("returns function", function () {
            var result = decoratorsLib.exported("test");
            chai_1.assert.equal(typeof (result), "function");
        });
        it("does not change original method", function () {
            var exportedFunctionResult = decoratorsLib.exported(moduleName);
            var expectedResult = { "originalObject": "originalValue" };
            var actualResult = exportedFunctionResult({}, "myTest1", expectedResult);
            chai_1.assert.deepEqual(actualResult, expectedResult);
        });
        _.each(expectedResults, function (expectedResult) {
            it("returns correct result when function returns " + (_.isArray(expectedResult) ? "array" : typeof (expectedResult)), function () {
                $injector.register(moduleName, { propertyName: function () { return expectedResult; } });
                generatePublicApiFromExportedDecorator();
                var actualResult = $injector.publicApi.__modules__[moduleName][propertyName]();
                chai_1.assert.deepEqual(actualResult, expectedResult);
            });
            it("passes correct arguments to original function, when argument type is: " + (_.isArray(expectedResult) ? "array" : typeof (expectedResult)), function () {
                $injector.register(moduleName, { propertyName: function (arg) { return arg; } });
                generatePublicApiFromExportedDecorator();
                var actualResult = $injector.publicApi.__modules__[moduleName][propertyName](expectedResult);
                chai_1.assert.deepEqual(actualResult, expectedResult);
            });
        });
        it("when function throws, raises the error only when the public API is called, not when decorator is applied", function () {
            var errorMessage = "This is error message";
            $injector.register(moduleName, { propertyName: function () { throw new Error(errorMessage); } });
            generatePublicApiFromExportedDecorator();
            chai_1.assert.throws(function () { return $injector.publicApi.__modules__[moduleName][propertyName](); }, errorMessage);
        });
        it("throws error when passed function returns IFuture", function () {
            $injector.register(moduleName, { propertyName: function () { return Future.fromResult(expectedResults); } });
            generatePublicApiFromExportedDecorator();
            chai_1.assert.throws(function () { return $injector.publicApi.__modules__[moduleName][propertyName](); }, "Cannot use exported decorator with function returning IFuture<T>.");
        });
    });
});
