/**
 * Copyright JS Foundation and other contributors, http://js.foundation
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 **/

var should = require("should");
var sinon = require("sinon");
var path = require("path");
var fs = require("fs-extra");

var NR_TEST_UTILS = require("nr-test-utils");

var loader = NR_TEST_UTILS.require("@node-red/registry/lib/loader");

var localfilesystem = NR_TEST_UTILS.require("@node-red/registry/lib/localfilesystem");
var registry = NR_TEST_UTILS.require("@node-red/registry/lib/registry");

var nodes = NR_TEST_UTILS.require("@node-red/registry");

var resourcesDir = path.resolve(path.join(__dirname,"resources","local"));

describe("red/nodes/registry/loader",function() {
    var stubs = [];
    before(function() {
        sinon.stub(localfilesystem,"init");
    });
    after(function() {
        localfilesystem.init.restore();
    });
    afterEach(function() {
        while(stubs.length) {
            stubs.pop().restore();
        }
    })

    describe("#load",function() {
        it("load empty set without settings available", function(done) {
            stubs.push(sinon.stub(localfilesystem,"getNodeFiles").callsFake(function(){ return {};}));
            stubs.push(sinon.stub(registry,"saveNodeList").callsFake(function(){ return {};}));
            loader.init({nodes:nodes,log:{info:function(){},_:function(){}},settings:{available:function(){return false;}}});
            loader.load(true).then(function() {
                localfilesystem.getNodeFiles.called.should.be.true();
                localfilesystem.getNodeFiles.lastCall.args[0].should.be.true();
                registry.saveNodeList.called.should.be.false();
                done();
            })
        });
        it("load empty set with settings available triggers registery save", function(done) {
            stubs.push(sinon.stub(localfilesystem,"getNodeFiles").callsFake(function(){ return {};}));
            stubs.push(sinon.stub(registry,"saveNodeList").callsFake(function(){ return {};}));
            loader.init({nodes:nodes,log:{info:function(){},_:function(){}},settings:{available:function(){return true;}}});
            loader.load(true).then(function() {
                registry.saveNodeList.called.should.be.true();
                done();
            }).catch(function(err) {
                done(err);
            })
        });

        it("load core node files scanned by lfs - single node single file", function(done) {
            stubs.push(sinon.stub(localfilesystem,"getNodeFiles").callsFake(function(){
                var result = {};
                result["node-red"] = {
                    "name": "node-red",
                    "version": "1.2.3",
                    "nodes": {
                        "TestNode1": {
                            "file": path.join(resourcesDir,"TestNode1","TestNode1.js"),
                            "module": "node-red",
                            "name": "TestNode1"
                        }
                    }
                };
                return result;
            }));

            stubs.push(sinon.stub(registry,"saveNodeList").callsFake(function(){ return }));
            stubs.push(sinon.stub(registry,"addModule").callsFake(function(){ return }));
            // This module isn't already loaded
            stubs.push(sinon.stub(registry,"getNodeInfo").callsFake(function(){ return null; }));

            stubs.push(sinon.stub(nodes,"registerType"));
            loader.init({nodes:nodes,log:{info:function(){},_:function(){}},settings:{available:function(){return true;}}});
            loader.load().then(function(result) {
                registry.addModule.called.should.be.true();
                var module = registry.addModule.lastCall.args[0];
                module.should.have.property("name","node-red");
                module.should.have.property("version","1.2.3");
                module.should.have.property("nodes");
                module.nodes.should.have.property("TestNode1");
                module.nodes.TestNode1.should.have.property("id","node-red/TestNode1");
                module.nodes.TestNode1.should.have.property("module","node-red");
                module.nodes.TestNode1.should.have.property("name","TestNode1");
                module.nodes.TestNode1.should.have.property("file");
                module.nodes.TestNode1.should.have.property("template");
                module.nodes.TestNode1.should.have.property("enabled",true);
                module.nodes.TestNode1.should.have.property("loaded",true);
                module.nodes.TestNode1.should.have.property("types");
                module.nodes.TestNode1.types.should.have.a.length(1);
                module.nodes.TestNode1.types[0].should.eql('test-node-1');
                module.nodes.TestNode1.should.have.property("config");
                module.nodes.TestNode1.config.should.not.eql("");
                module.nodes.TestNode1.should.have.property("help");
                module.nodes.TestNode1.help.should.have.property("en-US");
                module.nodes.TestNode1.should.have.property("namespace","node-red");

                nodes.registerType.calledOnce.should.be.true();
                nodes.registerType.lastCall.args[0].should.eql('node-red/TestNode1');
                nodes.registerType.lastCall.args[1].should.eql('test-node-1');

                done();
            }).catch(function(err) {
                done(err);
            });
        });

        it("load core node files scanned by lfs - ignore html if disableEditor true", function(done) {
            stubs.push(sinon.stub(localfilesystem,"getNodeFiles").callsFake(function(){
                var result = {};
                result["node-red"] = {
                    "name": "node-red",
                    "version": "1.2.3",
                    "nodes": {
                        "TestNode1": {
                            "file": path.join(resourcesDir,"TestNode1","TestNode1.js"),
                            "module": "node-red",
                            "name": "TestNode1"
                        }
                    }
                };
                return result;
            }));

            stubs.push(sinon.stub(registry,"saveNodeList").callsFake(function(){ return }));
            stubs.push(sinon.stub(registry,"addModule").callsFake(function(){ return }));
            // This module isn't already loaded
            stubs.push(sinon.stub(registry,"getNodeInfo").callsFake(function(){ return null; }));

            stubs.push(sinon.stub(nodes,"registerType"));
            loader.init({nodes:nodes,log:{info:function(){},_:function(){}},settings:{disableEditor: true, available:function(){return true;}}});
            loader.load().then(function(result) {
                registry.addModule.called.should.be.true();
                var module = registry.addModule.lastCall.args[0];
                module.should.have.property("name","node-red");
                module.should.have.property("version","1.2.3");
                module.should.have.property("nodes");
                module.nodes.should.have.property("TestNode1");
                module.nodes.TestNode1.should.have.property("id","node-red/TestNode1");
                module.nodes.TestNode1.should.have.property("module","node-red");
                module.nodes.TestNode1.should.have.property("name","TestNode1");
                module.nodes.TestNode1.should.have.property("file");
                module.nodes.TestNode1.should.have.property("template");
                module.nodes.TestNode1.should.have.property("enabled",true);
                module.nodes.TestNode1.should.have.property("loaded",true);
                // With disableEditor true, the types property is not populated by the
                // html file - but instead is populated as nodes register themselves.
                // But for this test, we have stubbed out registerType, so we won't get any types
                // module.nodes.TestNode1.should.have.property("types");
                // module.nodes.TestNode1.types.should.have.a.length(1);
                // module.nodes.TestNode1.types[0].should.eql('test-node-1');

                // With disableEditor set, config should be blank
                module.nodes.TestNode1.should.have.property("config");
                module.nodes.TestNode1.config.should.eql("");

                // help should be an empty object
                module.nodes.TestNode1.should.have.property("help");
                module.nodes.TestNode1.help.should.eql({})
                module.nodes.TestNode1.should.have.property("namespace","node-red");

                nodes.registerType.calledOnce.should.be.true();
                nodes.registerType.lastCall.args[0].should.eql('node-red/TestNode1');
                nodes.registerType.lastCall.args[1].should.eql('test-node-1');

                done();
            }).catch(function(err) {
                done(err);
            });
        });

        it("load core node files scanned by lfs - multiple nodes single file", function(done) {
            stubs.push(sinon.stub(localfilesystem,"getNodeFiles").callsFake(function(){
                var result = {};
                result["node-red"] = {
                    "name": "node-red",
                    "version": "4.5.6",
                    "nodes": {
                        "MultipleNodes1": {
                            "file": path.join(resourcesDir,"MultipleNodes1","MultipleNodes1.js"),
                            "module": "node-red",
                            "name": "MultipleNodes1"
                        }
                    }
                };
                return result;
            }));

            stubs.push(sinon.stub(registry,"saveNodeList").callsFake(function(){ return }));
            stubs.push(sinon.stub(registry,"addModule").callsFake(function(){ return }));
            // This module isn't already loaded
            stubs.push(sinon.stub(registry,"getNodeInfo").callsFake(function(){ return null; }));
            stubs.push(sinon.stub(nodes,"registerType"));
            loader.init({nodes:nodes,log:{info:function(){},_:function(){}},settings:{available:function(){return true;}}});
            loader.load().then(function(result) {

                registry.addModule.called.should.be.true();
                var module = registry.addModule.lastCall.args[0];
                module.should.have.property("name","node-red");
                module.should.have.property("version","4.5.6");
                module.should.have.property("nodes");
                module.nodes.should.have.property("MultipleNodes1");
                module.nodes.MultipleNodes1.should.have.property("id","node-red/MultipleNodes1");
                module.nodes.MultipleNodes1.should.have.property("module","node-red");
                module.nodes.MultipleNodes1.should.have.property("name","MultipleNodes1");
                module.nodes.MultipleNodes1.should.have.property("file");
                module.nodes.MultipleNodes1.should.have.property("template");
                module.nodes.MultipleNodes1.should.have.property("enabled",true);
                module.nodes.MultipleNodes1.should.have.property("loaded",true);
                module.nodes.MultipleNodes1.should.have.property("types");
                module.nodes.MultipleNodes1.types.should.have.a.length(2);
                module.nodes.MultipleNodes1.types[0].should.eql('test-node-multiple-1a');
                module.nodes.MultipleNodes1.types[1].should.eql('test-node-multiple-1b');
                module.nodes.MultipleNodes1.should.have.property("config");
                module.nodes.MultipleNodes1.should.have.property("help");
                module.nodes.MultipleNodes1.should.have.property("namespace","node-red");

                nodes.registerType.calledTwice.should.be.true();
                nodes.registerType.firstCall.args[0].should.eql('node-red/MultipleNodes1');
                nodes.registerType.firstCall.args[1].should.eql('test-node-multiple-1a');
                nodes.registerType.secondCall.args[0].should.eql('node-red/MultipleNodes1');
                nodes.registerType.secondCall.args[1].should.eql('test-node-multiple-1b');


                done();
            }).catch(function(err) {
                done(err);
            });
        });


        it("load core node files scanned by lfs - node with promise", function(done) {
            stubs.push(sinon.stub(localfilesystem,"getNodeFiles").callsFake(function(){
                var result = {};
                result["node-red"] = {
                    "name": "node-red",
                    "version":"2.4.6",
                    "nodes": {
                        "TestNode2": {
                            "file": path.join(resourcesDir,"TestNode2","TestNode2.js"),
                            "module": "node-red",
                            "name": "TestNode2"
                        }
                    }
                };
                return result;
            }));

            stubs.push(sinon.stub(registry,"saveNodeList").callsFake(function(){ return }));
            stubs.push(sinon.stub(registry,"addModule").callsFake(function(){ return }));
            // This module isn't already loaded
            stubs.push(sinon.stub(registry,"getNodeInfo").callsFake(function(){ return null; }));

            stubs.push(sinon.stub(nodes,"registerType"));
            loader.init({nodes:nodes,log:{info:function(){},_:function(){}},settings:{available:function(){return true;}}});
            loader.load().then(function(result) {

                registry.addModule.called.should.be.true();
                var module = registry.addModule.lastCall.args[0];
                module.should.have.property("name","node-red");
                module.should.have.property("version","2.4.6");
                module.should.have.property("nodes");
                module.nodes.should.have.property("TestNode2");
                module.nodes.TestNode2.should.have.property("id","node-red/TestNode2");
                module.nodes.TestNode2.should.have.property("module","node-red");
                module.nodes.TestNode2.should.have.property("name","TestNode2");
                module.nodes.TestNode2.should.have.property("file");
                module.nodes.TestNode2.should.have.property("template");
                module.nodes.TestNode2.should.have.property("enabled",true);
                module.nodes.TestNode2.should.have.property("loaded",true);
                module.nodes.TestNode2.should.have.property("types");
                module.nodes.TestNode2.types.should.have.a.length(1);
                module.nodes.TestNode2.types[0].should.eql('test-node-2');
                module.nodes.TestNode2.should.have.property("config");
                module.nodes.TestNode2.should.have.property("help");
                module.nodes.TestNode2.should.have.property("namespace","node-red");
                module.nodes.TestNode2.should.not.have.property('err');

                nodes.registerType.calledOnce.should.be.true();
                nodes.registerType.lastCall.args[0].should.eql('node-red/TestNode2');
                nodes.registerType.lastCall.args[1].should.eql('test-node-2');

                done();
            }).catch(function(err) {
                done(err);
            });
        });


        it("load core node files scanned by lfs - node with rejecting promise", function(done) {
            stubs.push(sinon.stub(localfilesystem,"getNodeFiles").callsFake(function(){
                var result = {};
                result["node-red"] = {
                    "name": "node-red",
                    "version":"1.2.3",
                    "nodes": {
                        "TestNode3": {
                            "file": path.join(resourcesDir,"TestNode3","TestNode3.js"),
                            "module": "node-red",
                            "name": "TestNode3"
                        }
                    }
                };
                return result;
            }));

            stubs.push(sinon.stub(registry,"saveNodeList").callsFake(function(){ return }));
            stubs.push(sinon.stub(registry,"addModule").callsFake(function(){ return }));
            // This module isn't already loaded
            stubs.push(sinon.stub(registry,"getNodeInfo").callsFake(function(){ return null; }));

            stubs.push(sinon.stub(nodes,"registerType"));
            loader.init({nodes:nodes,log:{info:function(){},_:function(){}},settings:{available:function(){return true;}}});
            loader.load().then(function(result) {
                registry.addModule.called.should.be.true();
                var module = registry.addModule.lastCall.args[0];
                module.should.have.property("name","node-red");
                module.should.have.property("version","1.2.3");
                module.should.have.property("nodes");
                module.nodes.should.have.property("TestNode3");
                module.nodes.TestNode3.should.have.property("id","node-red/TestNode3");
                module.nodes.TestNode3.should.have.property("module","node-red");
                module.nodes.TestNode3.should.have.property("name","TestNode3");
                module.nodes.TestNode3.should.have.property("file");
                module.nodes.TestNode3.should.have.property("template");
                module.nodes.TestNode3.should.have.property("enabled",true);
                module.nodes.TestNode3.should.have.property("loaded",false);
                module.nodes.TestNode3.should.have.property("types");
                module.nodes.TestNode3.types.should.have.a.length(1);
                module.nodes.TestNode3.types[0].should.eql('test-node-3');
                module.nodes.TestNode3.should.have.property("config");
                module.nodes.TestNode3.should.have.property("help");
                module.nodes.TestNode3.should.have.property("namespace","node-red");
                module.nodes.TestNode3.should.have.property('err','fail');

                nodes.registerType.called.should.be.false();

                done();
            }).catch(function(err) {
                done(err);
            });
        });

        it("load core node files scanned by lfs - missing file", function(done) {
            stubs.push(sinon.stub(localfilesystem,"getNodeFiles").callsFake(function(){
                var result = {};
                result["node-red"] = {
                    "name": "node-red",
                    "version":"1.2.3",
                    "nodes": {
                        "DoesNotExist": {
                            "file": path.join(resourcesDir,"doesnotexist"),
                            "module": "node-red",
                            "name": "DoesNotExist"
                        }
                    }
                };
                return result;
            }));

            stubs.push(sinon.stub(registry,"saveNodeList").callsFake(function(){ return }));
            stubs.push(sinon.stub(registry,"addModule").callsFake(function(){ return }));
            // This module isn't already loaded
            stubs.push(sinon.stub(registry,"getNodeInfo").callsFake(function(){ return null; }));

            stubs.push(sinon.stub(nodes,"registerType"));
            loader.init({nodes:nodes,log:{info:function(){},_:function(){}},settings:{available:function(){return true;}}});
            loader.load().then(function(result) {
                registry.addModule.called.should.be.true();
                var module = registry.addModule.lastCall.args[0];
                module.should.have.property("name","node-red");
                module.should.have.property("version","1.2.3");
                module.should.have.property("nodes");
                module.nodes.should.have.property("DoesNotExist");
                module.nodes.DoesNotExist.should.have.property("id","node-red/DoesNotExist");
                module.nodes.DoesNotExist.should.have.property("module","node-red");
                module.nodes.DoesNotExist.should.have.property("name","DoesNotExist");
                module.nodes.DoesNotExist.should.have.property("file");
                module.nodes.DoesNotExist.should.have.property("template");
                module.nodes.DoesNotExist.should.have.property("enabled",true);
                module.nodes.DoesNotExist.should.have.property("loaded",false);
                module.nodes.DoesNotExist.should.have.property("types");
                module.nodes.DoesNotExist.types.should.have.a.length(0);
                module.nodes.DoesNotExist.should.have.property("config","");
                module.nodes.DoesNotExist.should.have.property("help",{});
                module.nodes.DoesNotExist.should.have.property("namespace","node-red");
                module.nodes.DoesNotExist.should.have.property('err');

                nodes.registerType.called.should.be.false();

                done();
            }).catch(function(err) {
                done(err);
            });
        });

        // it("load core node files scanned by lfs - missing html file", function(done) {
        //     // This is now an okay situation
        //     stubs.push(sinon.stub(localfilesystem,"getNodeFiles").callsFake(function(){
        //         var result = {};
        //         result["node-red"] = {
        //             "name": "node-red",
        //             "version": "1.2.3",
        //             "nodes": {
        //                 "DuffNode": {
        //                     "file": path.join(resourcesDir,"DuffNode","DuffNode.js"),
        //                     "module": "node-red",
        //                     "name": "DuffNode"
        //                 }
        //             }
        //         };
        //         return result;
        //     }));
        //
        //     stubs.push(sinon.stub(registry,"saveNodeList").callsFake(function(){ return }));
        //     stubs.push(sinon.stub(registry,"addModule").callsFake(function(){ return }));
        //     // This module isn't already loaded
        //     stubs.push(sinon.stub(registry,"getNodeInfo").callsFake(function(){ return null; }));
        //
        //     stubs.push(sinon.stub(nodes,"registerType"));
        //     loader.init({nodes:nodes,log:{info:function(){},_:function(){}},settings:{available:function(){return true;}}});
        //     loader.load().then(function(result) {
        //
        //         registry.addModule.called.should.be.true();
        //         var module = registry.addModule.lastCall.args[0];
        //         module.should.have.property("name","node-red");
        //         module.should.have.property("version","1.2.3");
        //         module.should.have.property("nodes");
        //         module.nodes.should.have.property("DuffNode");
        //         module.nodes.DuffNode.should.have.property("id","node-red/DuffNode");
        //         module.nodes.DuffNode.should.have.property("module","node-red");
        //         module.nodes.DuffNode.should.have.property("name","DuffNode");
        //         module.nodes.DuffNode.should.have.property("file");
        //         module.nodes.DuffNode.should.have.property("template");
        //         module.nodes.DuffNode.should.have.property("enabled",true);
        //         module.nodes.DuffNode.should.have.property("loaded",false);
        //         module.nodes.DuffNode.should.have.property("types");
        //         module.nodes.DuffNode.types.should.have.a.length(0);
        //         module.nodes.DuffNode.should.have.property("config","");
        //         module.nodes.DuffNode.should.have.property("help",{});
        //         module.nodes.DuffNode.should.have.property("namespace","node-red");
        //         module.nodes.DuffNode.should.have.property('err');
        //         module.nodes.DuffNode.err.should.endWith("DuffNode.html does not exist");
        //
        //         nodes.registerType.called.should.be.false();
        //
        //         done();
        //     }).catch(function(err) {
        //         done(err);
        //     });
        // });
    });

    describe("#addModule",function() {
        it("throws error if settings unavailable", function() {
            loader.init({nodes:nodes,log:{info:function(){},_:function(){}},settings:{available:function(){return false;}}});
            /*jshint immed: false */
            (function(){
                loader.addModule("test-module");
            }).should.throw("Settings unavailable");
        });

        it("returns rejected error if module already loaded", function(done) {
            stubs.push(sinon.stub(registry,"getModuleInfo").callsFake(function(){return{}}));
            loader.init({nodes:nodes,log:{info:function(){},_:function(){}},settings:{available:function(){return true;}}});

            loader.addModule("test-module").catch(function(err) {
                err.code.should.eql("module_already_loaded");
                done();
            });
        });
        it("returns rejected error if module not found", function(done) {
            stubs.push(sinon.stub(registry,"getModuleInfo").callsFake(function(){return null}));
            stubs.push(sinon.stub(localfilesystem,"getModuleFiles").callsFake(function() {
                throw new Error("failure");
            }));
            loader.init({nodes:nodes,log:{info:function(){},_:function(){}},settings:{available:function(){return true;}}});
            loader.addModule("test-module").catch(function(err) {
                err.message.should.eql("failure");
                done();
            });

        });

        it("loads module by name", function(done) {
            // This module isn't already loaded
            stubs.push(sinon.stub(registry,"getNodeInfo").callsFake(function(){ return null; }));
            stubs.push(sinon.stub(registry,"getModuleInfo").callsFake(function(){ return null; }));
            stubs.push(sinon.stub(localfilesystem,"getModuleFiles").callsFake(function(){
                var result = {};
                result["TestNodeModule"] = {
                    "name": "TestNodeModule",
                    "version": "1.2.3",
                    "nodes": {
                        "TestNode1": {
                            "file": path.join(resourcesDir,"TestNodeModule","node_modules","TestNodeModule","TestNodeModule.js"),
                            "module": "TestNodeModule",
                            "name": "TestNode1",
                            "version": "1.2.3"
                        }
                    }
                };
                return result;
            }));

            stubs.push(sinon.stub(registry,"saveNodeList").callsFake(function(){ return "a node list" }));
            stubs.push(sinon.stub(registry,"addModule").callsFake(function(){ return }));
            stubs.push(sinon.stub(nodes,"registerType"));
            loader.init({nodes:nodes,log:{info:function(){},_:function(){}},settings:{available:function(){return true;}}});
            loader.addModule("TestNodeModule").then(function(result) {
                result.should.eql("TestNodeModule");

                registry.addModule.called.should.be.true();
                var module = registry.addModule.lastCall.args[0];
                module.should.have.property("name","TestNodeModule");
                module.should.have.property("version","1.2.3");
                module.should.have.property("nodes");
                module.nodes.should.have.property("TestNode1");
                module.nodes.TestNode1.should.have.property("id","TestNodeModule/TestNode1");
                module.nodes.TestNode1.should.have.property("module","TestNodeModule");
                module.nodes.TestNode1.should.have.property("name","TestNode1");
                module.nodes.TestNode1.should.have.property("file");
                module.nodes.TestNode1.should.have.property("template");
                module.nodes.TestNode1.should.have.property("enabled",true);
                module.nodes.TestNode1.should.have.property("loaded",true);
                module.nodes.TestNode1.should.have.property("types");
                module.nodes.TestNode1.types.should.have.a.length(1);
                module.nodes.TestNode1.types[0].should.eql('test-node-mod-1');
                module.nodes.TestNode1.should.have.property("config");
                module.nodes.TestNode1.should.have.property("help");
                module.nodes.TestNode1.should.have.property("namespace","TestNodeModule");
                module.nodes.TestNode1.should.not.have.property('err');

                nodes.registerType.calledOnce.should.be.true();
                done();
            }).catch(function(err) {
                done(err);
            });
        });

        it("skips module that fails version check", function(done) {
            // This module isn't already loaded
            stubs.push(sinon.stub(registry,"getNodeInfo").callsFake(function(){ return null; }));
            stubs.push(sinon.stub(registry,"getModuleInfo").callsFake(function(){ return null; }));
            stubs.push(sinon.stub(localfilesystem,"getModuleFiles").callsFake(function(){
                var result = {};
                result["TestNodeModule"] = {
                    "name": "TestNodeModule",
                    "version": "1.2.3",
                    "redVersion":"999.0.0",
                    "nodes": {
                        "TestNode1": {
                            "file": path.join(resourcesDir,"TestNodeModule","node_modules","TestNodeModule","TestNodeModule.js"),
                            "module": "TestNodeModule",
                            "name": "TestNode1",
                            "version": "1.2.3"
                        }
                    }
                };
                return result;
            }));

            stubs.push(sinon.stub(registry,"saveNodeList").callsFake(function(){ return "a node list" }));
            stubs.push(sinon.stub(registry,"addModule").callsFake(function(){ return }));
            stubs.push(sinon.stub(nodes,"registerType"));
            loader.init({log:{"_":function(){},warn:function(){}},nodes:nodes,version: function() { return "0.12.0"}, settings:{available:function(){return true;}}});
            loader.addModule("TestNodeModule").then(function(result) {
                result.should.eql("TestNodeModule");
                registry.addModule.called.should.be.false();
                nodes.registerType.called.should.be.false();
                done();
            }).catch(function(err) {
                done(err);
            });
        });

        it.skip('registers a message catalog');


    });
    describe("#loadNodeSet",function() {
        it("no-ops the load if node is not enabled", function(done) {
            stubs.push(sinon.stub(nodes,"registerType"));
            loader.loadNodeSet({
                "file": path.join(resourcesDir,"TestNode1","TestNode1.js"),
                "module": "node-red",
                "name": "TestNode1",
                "enabled": false
            }).then(function(node) {
                node.enabled.should.be.false();
                nodes.registerType.called.should.be.false();
                done();
            }).catch(function(err) {
                done(err);
            });
        });

        it("handles node that errors on require", function(done) {
            stubs.push(sinon.stub(nodes,"registerType"));
            loader.loadNodeSet({
                "file": path.join(resourcesDir,"TestNode4","TestNode4.js"),
                "module": "node-red",
                "name": "TestNode4",
                "enabled": true
            }).then(function(node) {
                node.enabled.should.be.true();
                nodes.registerType.called.should.be.false();
                node.should.have.property('err');
                node.err.toString().should.eql("Error: fail to require (line:1)");

                done();
            }).catch(function(err) {
                done(err);
            });
        });
    });
    describe("#getNodeHelp",function() {
        it("returns preloaded help", function() {
            loader.getNodeHelp({
                help:{
                    en:"foo"
                }
            },"en").should.eql("foo");
        });
        it("loads help, caching result", function() {
            stubs.push(sinon.stub(fs,"readFileSync").callsFake(function(path) {
                return 'bar';
            }))
            var node = {
                template: "/tmp/node/directory/file.html",
                help:{
                    en:"foo"
                }
            };
            loader.getNodeHelp(node,"fr").should.eql("bar");
            node.help['fr'].should.eql("bar");
            fs.readFileSync.calledOnce.should.be.true();
            fs.readFileSync.lastCall.args[0].should.eql(path.normalize("/tmp/node/directory/locales/fr/file.html"));
            loader.getNodeHelp(node,"fr").should.eql("bar");
            fs.readFileSync.calledOnce.should.be.true();
        });
        it("loads help, defaulting to en-US content", function() {
            stubs.push(sinon.stub(fs,"readFileSync").callsFake(function(path) {
                throw new Error("not found");
            }))
            var node = {
                template: "/tmp/node/directory/file.html",
                help:{}
            };
            node.help['en-US'] = 'foo';

            loader.getNodeHelp(node,"fr").should.eql("foo");
            node.help['fr'].should.eql("foo");
            fs.readFileSync.calledOnce.should.be.true();
            fs.readFileSync.lastCall.args[0].should.eql(path.normalize("/tmp/node/directory/locales/fr/file.html"));
            loader.getNodeHelp(node,"fr").should.eql("foo");
            fs.readFileSync.calledOnce.should.be.true();
        });
        it("loads help, defaulting to en-US content for extra nodes", function() {
            stubs.push(sinon.stub(fs,"readFileSync").callsFake(function(path) {
                if (path.indexOf("en-US") >= 0) {
                    return 'bar';
                }
                throw new Error("not found");
            }));
            var node = {
                template: "/tmp/node/directory/file.html",
                help:{}
            };
            delete node.help['en-US'];

            loader.getNodeHelp(node,"fr").should.eql("bar");
            node.help['fr'].should.eql("bar");
            fs.readFileSync.calledTwice.should.be.true();
            fs.readFileSync.firstCall.args[0].should.eql(path.normalize("/tmp/node/directory/locales/fr/file.html"));
            fs.readFileSync.lastCall.args[0].should.eql(path.normalize("/tmp/node/directory/locales/en-US/file.html"));
            loader.getNodeHelp(node,"fr").should.eql("bar");
            fs.readFileSync.calledTwice.should.be.true();
        });
        it("fails to load en-US help content", function() {
            stubs.push(sinon.stub(fs,"readFileSync").callsFake(function(path) {
                throw new Error("not found");
            }));
            var node = {
                template: "/tmp/node/directory/file.html",
                help:{}
            };
            delete node.help['en-US'];

            should.not.exist(loader.getNodeHelp(node,"en-US"));
            should.not.exist(node.help['en-US']);
            fs.readFileSync.calledTwice.should.be.true();
            fs.readFileSync.firstCall.args[0].should.eql(path.normalize("/tmp/node/directory/locales/en-US/file.html"));
            fs.readFileSync.lastCall.args[0].should.eql(path.normalize("/tmp/node/directory/locales/en/file.html"));
            should.not.exist(loader.getNodeHelp(node,"en-US"));
            fs.readFileSync.callCount.should.eql(4);
        });

    });
});