/**
 * 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 NR_TEST_UTILS = require("nr-test-utils");
var library = NR_TEST_UTILS.require("@node-red/runtime/lib/api/library")

var mockLog = {
    log: sinon.stub(),
    debug: sinon.stub(),
    trace: sinon.stub(),
    warn: sinon.stub(),
    info: sinon.stub(),
    metric: sinon.stub(),
    audit: sinon.stub(),
    _: function() { return "abc"}
}

describe("runtime-api/library", function() {
    describe("getEntry", function() {
        before(function() {
            library.init({
                log: mockLog,
                library: {
                    getEntry: function(type,path) {
                        if (type === "known") {
                            return Promise.resolve("known");
                        } else if (type === "forbidden") {
                            var err = new Error("forbidden");
                            err.code = "forbidden";
                            var p = Promise.reject(err);
                            p.catch(()=>{});
                            return p;
                        } else if (type === "not_found") {
                            var err = new Error("forbidden");
                            err.code = "not_found";
                            var p = Promise.reject(err);
                            p.catch(()=>{});
                            return p;
                        } else if (type === "error") {
                            var err = new Error("error");
                            err.code = "unknown_error";
                            var p = Promise.reject(err);
                            p.catch(()=>{});
                            return p;
                        } else if (type === "blank") {
                            return Promise.reject();
                        }
                    }
                }
            })
        })
        it("returns a known entry", function(done) {
            library.getEntry({type: "known", path: "/abc"}).then(function(result) {
                result.should.eql("known")
                done();
            }).catch(done)
        })
        it("rejects a forbidden entry", function(done) {
            library.getEntry({type: "forbidden", path: "/abc"}).then(function(result) {
                done(new Error("did not reject"));
            }).catch(function(err) {
                err.should.have.property("code","forbidden");
                err.should.have.property("status",403);
                done();
            }).catch(done)
        })
        it("rejects an unknown entry", function(done) {
            library.getEntry({type: "not_found", path: "/abc"}).then(function(result) {
                done(new Error("did not reject"));
            }).catch(function(err) {
                err.should.have.property("code","not_found");
                err.should.have.property("status",404);
                done();
            }).catch(done)
        })
        it("rejects a blank (unknown) entry", function(done) {
            library.getEntry({type: "blank", path: "/abc"}).then(function(result) {
                done(new Error("did not reject"));
            }).catch(function(err) {
                err.should.have.property("code","not_found");
                err.should.have.property("status",404);
                done();
            }).catch(done)
        })
        it("rejects unexpected error", function(done) {
            library.getEntry({type: "error", path: "/abc"}).then(function(result) {
                done(new Error("did not reject"));
            }).catch(function(err) {
                err.should.have.property("status",400);
                done();
            }).catch(done)
        })
    })
    describe("saveEntry", function() {
        var opts;
        before(function() {
            library.init({
                log: mockLog,
                library: {
                    saveEntry: function(type,path,meta,body) {
                        opts = {type,path,meta,body};
                        if (type === "known") {
                            return Promise.resolve();
                        } else if (type === "forbidden") {
                            var err = new Error("forbidden");
                            err.code = "forbidden";
                            var p = Promise.reject(err);
                            p.catch(()=>{});
                            return p;
                        } else if (type === "not_found") {
                            var err = new Error("forbidden");
                            err.code = "not_found";
                            var p = Promise.reject(err);
                            p.catch(()=>{});
                            return p;
                        }
                    }
                }
            })
        })

        it("saves an entry", function(done) {
            library.saveEntry({type: "known", path: "/abc", meta: {a:1}, body:"123"}).then(function() {
                opts.should.have.property("type","known");
                opts.should.have.property("path","/abc");
                opts.should.have.property("meta",{a:1});
                opts.should.have.property("body","123");
                done();
            }).catch(done)
        })
        it("rejects a forbidden entry", function(done) {
            library.saveEntry({type: "forbidden", path: "/abc", meta: {a:1}, body:"123"}).then(function() {
                done(new Error("did not reject"));
            }).catch(function(err) {
                err.should.have.property("code","forbidden");
                err.should.have.property("status",403);
                done();
            }).catch(done)
        })
        it("rejects an unknown entry", function(done) {
            library.saveEntry({type: "not_found", path: "/abc", meta: {a:1}, body:"123"}).then(function() {
                done(new Error("did not reject"));
            }).catch(function(err) {
                err.should.have.property("status",400);
                done();
            }).catch(done)
        })
    })
    describe("getEntries", function() {
        var opts;
        before(function() {
            library.init({
                log: mockLog,
                storage: {
                    getAllFlows: function() {
                        return Promise.resolve({a:1});
                    }
                },
                nodes: {
                    getNodeExampleFlows: function() {
                        return {b:2};
                    }
                }
            });
        });
        it("returns all flows", function(done) {
            library.getEntries({type:"flows"}).then(function(result) {
                result.should.eql({a:1,d:{_examples_:{b:2}}});
                done();
            }).catch(done)
        });
        it("fails for non-flows (currently)", function(done) {
            library.getEntries({type:"functions"}).then(function(result) {
                done(new Error("did not reject"));
            }).catch(function(err) {
                done();
            }).catch(done)
        })
    })


});


/*

var should = require("should");
var sinon = require("sinon");
var fs = require("fs");
var fspath = require('path');
var request = require('supertest');
var express = require('express');
var bodyParser = require('body-parser');

var when = require('when');

var app;
var library = require("../../../../red/api/editor/library");
var auth = require("../../../../red/api/auth");

describe("api/editor/library", function() {

    function initLibrary(_flows,_libraryEntries,_examples,_exampleFlowPathFunction) {
        var flows = _flows;
        var libraryEntries = _libraryEntries;
        library.init(app,{
            log:{audit:function(){},_:function(){},warn:function(){}},
            storage: {
                init: function() {
                    return when.resolve();
                },
                getAllFlows: function() {
                    return when.resolve(flows);
                },
                getFlow: function(fn) {
                    if (flows[fn]) {
                        return when.resolve(flows[fn]);
                    } else if (fn.indexOf("..")!==-1) {
                        var err = new Error();
                        err.code = 'forbidden';
                        return when.reject(err);
                    } else {
                        return when.reject();
                    }
                },
                saveFlow: function(fn,data) {
                    if (fn.indexOf("..")!==-1) {
                        var err = new Error();
                        err.code = 'forbidden';
                        return when.reject(err);
                    }
                    flows[fn] = data;
                    return when.resolve();
                },
                getLibraryEntry: function(type,path) {
                    if (path.indexOf("..")!==-1) {
                        var err = new Error();
                        err.code = 'forbidden';
                        return when.reject(err);
                    }
                    if (libraryEntries[type] && libraryEntries[type][path]) {
                        return when.resolve(libraryEntries[type][path]);
                    } else {
                        return when.reject();
                    }
                },
                saveLibraryEntry: function(type,path,meta,body) {
                    if (path.indexOf("..")!==-1) {
                        var err = new Error();
                        err.code = 'forbidden';
                        return when.reject(err);
                    }
                    libraryEntries[type][path] = body;
                    return when.resolve();
                }
            },
            events: {
                on: function(){},
                removeListener: function(){}
            },
            nodes: {
                getNodeExampleFlows: function() {
                    return _examples;
                },
                getNodeExampleFlowPath: _exampleFlowPathFunction
            }
        });
    }

    describe("flows", function() {
        before(function() {
            app = express();
            app.use(bodyParser.json());
            app.get("/library/flows",library.getAll);
            app.post(new RegExp("/library/flows\/(.*)"),library.post);
            app.get(new RegExp("/library/flows\/(.*)"),library.get);
            app.response.sendFile = function (path) {
                app.response.json.call(this, {sendFile: path});
            };
            sinon.stub(fs,"statSync",function() { return true; });
        });
        after(function() {
            fs.statSync.restore();
        });
        it('returns empty result', function(done) {
            initLibrary({},{flows:{}});
            request(app)
                .get('/library/flows')
                .expect(200)
                .end(function(err,res) {
                    if (err) {
                        throw err;
                    }
                    res.body.should.not.have.property('f');
                    res.body.should.not.have.property('d');
                    done();
                });
        });

        it('returns 404 for non-existent entry', function(done) {
            initLibrary({},{flows:{}});
            request(app)
                .get('/library/flows/foo')
                .expect(404)
                .end(done);
        });


        it('can store and retrieve item', function(done) {
            initLibrary({},{flows:{}});
            var flow = '[]';
            request(app)
                .post('/library/flows/foo')
                .set('Content-Type', 'application/json')
                .send(flow)
                .expect(204).end(function (err, res) {
                    if (err) {
                        throw err;
                    }
                    request(app)
                        .get('/library/flows/foo')
                        .expect(200)
                        .end(function(err,res) {
                            if (err) {
                                throw err;
                            }
                            res.text.should.equal(flow);
                            done();
                        });
                });
        });

        it('lists a stored item', function(done) {
            initLibrary({f:["bar"]});
            request(app)
                .get('/library/flows')
                .expect(200)
                .end(function(err,res) {
                    if (err) {
                        throw err;
                    }
                    res.body.should.have.property('f');
                    should.deepEqual(res.body.f,['bar']);
                    done();
                });
        });

        it('returns 403 for malicious get attempt', function(done) {
            initLibrary({});
            // without the userDir override the malicious url would be
            // http://127.0.0.1:1880/library/flows/../../package to
            // obtain package.json from the node-red root.
            request(app)
                .get('/library/flows/../../../../../package')
                .expect(403)
                .end(done);
        });
        it('returns 403 for malicious post attempt', function(done) {
            initLibrary({});
            // without the userDir override the malicious url would be
            // http://127.0.0.1:1880/library/flows/../../package to
            // obtain package.json from the node-red root.
            request(app)
                .post('/library/flows/../../../../../package')
                .expect(403)
                .end(done);
        });
        it('includes examples flows if set', function(done) {
            var examples = {"d":{"node-module":{"f":["example-one"]}}};
            initLibrary({},{},examples);
            request(app)
                .get('/library/flows')
                .expect(200)
                .end(function(err,res) {
                    if (err) {
                        throw err;
                    }
                    res.body.should.have.property('d');
                    res.body.d.should.have.property('_examples_');
                    should.deepEqual(res.body.d._examples_,examples);
                    done();
                });
        });

        it('can retrieve an example flow', function(done) {
            var examples = {"d":{"node-module":{"f":["example-one"]}}};
            initLibrary({},{},examples,function(module,path) {
                return module + ':' + path
            });
            request(app)
                .get('/library/flows/_examples_/node-module/example-one')
                .expect(200)
                .end(function(err,res) {
                    if (err) {
                        throw err;
                    }
                    res.body.should.have.property('sendFile',
                        fspath.resolve('node-module') + ':example-one');
                    done();
                });
        });

        it('can retrieve an example flow in an org scoped package', function(done) {
            var examples = {"d":{"@org_scope/node_package":{"f":["example-one"]}}};
            initLibrary({},{},examples,function(module,path) {
                return module + ':' + path
            });
            request(app)
                .get('/library/flows/_examples_/@org_scope/node_package/example-one')
                .expect(200)
                .end(function(err,res) {
                    if (err) {
                        throw err;
                    }
                    res.body.should.have.property('sendFile',
                        fspath.resolve('@org_scope/node_package') +
                        ':example-one');
                    done();
                });
        });
    });

    describe("type", function() {
        before(function() {

            app = express();
            app.use(bodyParser.json());
            initLibrary({},{});
            auth.init({settings:{}});
            library.register("test");
        });

        it('returns empty result', function(done) {
            initLibrary({},{'test':{"":[]}});
            request(app)
                .get('/library/test')
                .expect(200)
                .end(function(err,res) {
                    if (err) {
                        throw err;
                    }
                    res.body.should.not.have.property('f');
                    done();
                });
        });

        it('returns 404 for non-existent entry', function(done) {
            initLibrary({},{});
            request(app)
                .get('/library/test/foo')
                .expect(404)
                .end(done);
        });

        it('can store and retrieve item', function(done) {
            initLibrary({},{'test':{}});
            var flow = {text:"test content"};
            request(app)
                .post('/library/test/foo')
                .set('Content-Type', 'application/json')
                .send(flow)
                .expect(204).end(function (err, res) {
                    if (err) {
                        throw err;
                    }
                    request(app)
                        .get('/library/test/foo')
                        .expect(200)
                        .end(function(err,res) {
                            if (err) {
                                throw err;
                            }
                            res.text.should.equal(flow.text);
                            done();
                        });
                });
        });

        it('lists a stored item', function(done) {
            initLibrary({},{'test':{'a':['abc','def']}});
                request(app)
                    .get('/library/test/a')
                    .expect(200)
                    .end(function(err,res) {
                        if (err) {
                            throw err;
                        }
                        // This response isn't strictly accurate - but it
                        // verifies the api returns what storage gave it
                        should.deepEqual(res.body,['abc','def']);
                        done();
                    });
        });


        it('returns 403 for malicious access attempt', function(done) {
            request(app)
                .get('/library/test/../../../../../../../../../../etc/passwd')
                .expect(403)
                .end(done);
        });

        it('returns 403 for malicious access attempt', function(done) {
            request(app)
                .get('/library/test/..\\..\\..\\..\\..\\..\\..\\..\\..\\..\\etc\\passwd')
                .expect(403)
                .end(done);
        });

        it('returns 403 for malicious access attempt', function(done) {
            request(app)
                .post('/library/test/../../../../../../../../../../etc/passwd')
                .set('Content-Type', 'text/plain')
                .send('root:x:0:0:root:/root:/usr/bin/tclsh')
                .expect(403)
                .end(done);
        });

    });
});

*/