/**
 * 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 projects = NR_TEST_UTILS.require("@node-red/runtime/lib/api/projects")

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/settings", function() {
    describe("available", function() {
        it("resolves true if projects available", function(done) {
            projects.init({
                storage: {
                    projects: {}
                }
            });
            projects.available().then(function(result) {
                result.should.be.true();
                done();
            }).catch(done);
        })
        it("resolves false if projects unavailable", function(done) {
            projects.init({
                storage: {
                }
            });
            projects.available().then(function(result) {
                result.should.be.false();
                done();
            }).catch(done);
        })

    });
    describe("listProjects", function() {
        var runtime = {
            log: mockLog(),
            storage: {projects: {
            listProjects: sinon.spy(function(user) {
                if (user === "error") {
                    var err = new Error("error");
                    err.code = "error";
                    var p = Promise.reject(err);
                    p.catch(()=>{});
                    return p;
                } else {
                    return Promise.resolve([1,2,3]);
                }
            }),
            getActiveProject: function(user) {
                if (user === "noActive") {
                    return null;
                }
                return {name:"aProject"};
            }
        }}};
        before(function() {
            projects.init(runtime);
        })
        it("lists the projects, without an active project", function(done) {
            projects.listProjects({user:"noActive"}).then(function(result) {
                result.should.have.property('projects',[1,2,3]);
                result.should.not.have.property('active');
                done();
            }).catch(done);
        });
        it("lists the projects, with an active project", function(done) {
            projects.listProjects({user:"foo"}).then(function(result) {
                result.should.have.property('projects',[1,2,3]);
                result.should.have.property('active','aProject');
                done();
            }).catch(done);
        });
        it("rejects with internal error", function(done) {
            projects.listProjects({user:"error"}).then(function(result) {
                done(new Error("Did not reject internal error"));
            }).catch(function(err) {
                err.should.have.property('code','error');
                err.should.have.property('status',400);
                done();
            }).catch(done);
        });

    });
    describe("createProject", function() {
        var runtime = {
        log: mockLog(),
        storage: {projects: {
            createProject: sinon.spy(function(user,project) {
                if (user === "error") {
                    var err = new Error("error");
                    err.code = "error";
                    var p = Promise.reject(err);
                    p.catch(()=>{});
                    return p;
                } else {
                    return Promise.resolve(project);
                }
            })
        }}};
        before(function() {
            projects.init(runtime);
        })
        it("create project", function(done) {
            projects.createProject({user:"known",project:{a:1}}).then(function(result) {
                result.should.eql({a:1});
                done();
            }).catch(done);
        });
        it("rejects with internal error", function(done) {
            projects.createProject({user:"error"}).then(function(result) {
                done(new Error("Did not reject internal error"));
            }).catch(function(err) {
                err.should.have.property('code','error');
                // err.should.have.property('status',400);
                done();
            }).catch(done);
        });
    });
    describe("initialiseProject", function() {
        var runtime = {
        log: mockLog(),
        storage: {projects: {
            initialiseProject: sinon.spy(function(user,id,project) {
                if (user === "error") {
                    var err = new Error("error");
                    err.code = "error";
                    var p = Promise.reject(err);
                    p.catch(()=>{});
                    return p;
                } else {
                    return Promise.resolve({id,project});
                }
            })
        }}};
        before(function() {
            projects.init(runtime);
        })
        it("intialise project", function(done) {
            projects.initialiseProject({user:"known",id:123, project:{a:1}}).then(function(result) {
                result.should.eql({id:123, project:{a:1}});
                done();
            }).catch(done);
        });
        it("rejects with internal error", function(done) {
            projects.initialiseProject({user:"error"}).then(function(result) {
                done(new Error("Did not reject internal error"));
            }).catch(function(err) {
                err.should.have.property('code','error');
                // err.should.have.property('status',400);
                done();
            }).catch(done);
        });
    });

    describe("getActiveProject", function() {
        var runtime = {
        log: mockLog(),
        storage: {projects: {
            getActiveProject: sinon.spy(function(user) {
                if (user === "error") {
                    var err = new Error("error");
                    err.code = "error";
                    var p = Promise.reject(err);
                    p.catch(()=>{});
                    return p;
                } else {
                    return Promise.resolve("123");
                }
            })
        }}};
        before(function() {
            projects.init(runtime);
        })
        it("returns active project", function(done) {
            projects.getActiveProject({user:"known"}).then(function(result) {
                result.should.eql("123");
                done();
            }).catch(done);
        });
        it("rejects with internal error", function(done) {
            projects.getActiveProject({user:"error"}).then(function(result) {
                done(new Error("Did not reject internal error"));
            }).catch(function(err) {
                err.should.have.property('code','error');
                // err.should.have.property('status',400);
                done();
            }).catch(done);
        });
    });

    describe("setActiveProject", function() {
        var activeProject;
        var runtime;
        beforeEach(function() {
            runtime = {
        log: mockLog(),
        storage: {projects: {
                getActiveProject: sinon.spy(function() { return activeProject;}),
                setActiveProject: sinon.spy(function(user,id) {
                    if (user === "error") {
                        var err = new Error("error");
                        err.code = "error";
                        var p = Promise.reject(err);
                        p.catch(()=>{});
                        return p;
                    } else {
                        return Promise.resolve({user,id});
                    }
                })
            }}};
            projects.init(runtime);
        })
        it("sets project if current project is unset", function(done) {
            activeProject = null;
            projects.setActiveProject({user:"known",id:123}).then(function(result) {
                result.should.eql({user:"known",id:123});
                done();
            }).catch(done);
        });
        it("sets project if current project is different", function(done) {
            activeProject = {name:456};
            projects.setActiveProject({user:"known",id:123}).then(function(result) {
                result.should.eql({user:"known",id:123});
                done();
            }).catch(done);
        });
        it("no-ops if current project is same", function(done) {
            activeProject = {name:123};
            projects.setActiveProject({user:"known",id:123}).then(function(result) {
                (result === undefined).should.be.true();
                done();
            }).catch(done);
        });
        it("rejects with internal error", function(done) {
            projects.setActiveProject({user:"error"}).then(function(result) {
                done(new Error("Did not reject internal error"));
            }).catch(function(err) {
                err.should.have.property('code','error');
                // err.should.have.property('status',400);
                done();
            }).catch(done);
        });
    });

    describe("getProject", function() {
        var runtime = {
        log: mockLog(),
        storage: {projects: {
            getProject: sinon.spy(function(user,id) {
                if (user === "error") {
                    var err = new Error("error");
                    err.code = "error";
                    var p = Promise.reject(err);
                    p.catch(()=>{});
                    return p;
                } else {
                    return Promise.resolve({user,id});
                }
            })
        }}};
        before(function() {
            projects.init(runtime);
        })
        it("returns project", function(done) {
            projects.getProject({user:"known",id:123}).then(function(result) {
                result.should.eql({user:"known",id:123});
                done();
            }).catch(done);
        });
        it("rejects with internal error", function(done) {
            projects.getProject({user:"error"}).then(function(result) {
                done(new Error("Did not reject internal error"));
            }).catch(function(err) {
                err.should.have.property('code','error');
                // err.should.have.property('status',400);
                done();
            }).catch(done);
        });
    });
    describe("updateProject", function() {
        var runtime = {
        log: mockLog(),
        storage: {projects: {
            updateProject: sinon.spy(function(user,id,project) {
                if (user === "error") {
                    var err = new Error("error");
                    err.code = "error";
                    var p = Promise.reject(err);
                    p.catch(()=>{});
                    return p;
                } else {
                    return Promise.resolve({user,id,project});
                }
            })
        }}};
        before(function() {
            projects.init(runtime);
        })
        it("updates project", function(done) {
            projects.updateProject({user:"known",id:123,project:{a:1}}).then(function(result) {
                result.should.eql({user:"known",id:123,project:{a:1}});
                done();
            }).catch(done);
        });
        it("rejects with internal error", function(done) {
            projects.updateProject({user:"error",id:123,project:{a:1}}).then(function(result) {
                done(new Error("Did not reject internal error"));
            }).catch(function(err) {
                err.should.have.property('code','error');
                // err.should.have.property('status',400);
                done();
            }).catch(done);
        });
    });

    describe("deleteProject", function() {
        var runtime = {
        log: mockLog(),
        storage: {projects: {
            deleteProject: sinon.spy(function(user,id) {
                if (user === "error") {
                    var err = new Error("error");
                    err.code = "error";
                    var p = Promise.reject(err);
                    p.catch(()=>{});
                    return p;
                } else {
                    return Promise.resolve({user,id});
                }
            })
        }}};
        before(function() {
            projects.init(runtime);
        })
        it("deletes project", function(done) {
            projects.deleteProject({user:"known",id:123}).then(function(result) {
                result.should.eql({user:"known",id:123});
                done();
            }).catch(done);
        });
        it("rejects with internal error", function(done) {
            projects.deleteProject({user:"error",id:123}).then(function(result) {
                done(new Error("Did not reject internal error"));
            }).catch(function(err) {
                err.should.have.property('code','error');
                // err.should.have.property('status',400);
                done();
            }).catch(done);
        });
    });

    describe("getStatus", function() {
        var runtime = {
        log: mockLog(),
        storage: {projects: {
            getStatus: sinon.spy(function(user,id,remote) {
                if (user === "error") {
                    var err = new Error("error");
                    err.code = "error";
                    var p = Promise.reject(err);
                    p.catch(()=>{});
                    return p;
                } else {
                    return Promise.resolve({user,id,remote});
                }
            })
        }}};
        before(function() {
            projects.init(runtime);
        })
        it("gets status", function(done) {
            projects.getStatus({user:"known",id:123,remote:{a:1}}).then(function(result) {
                result.should.eql({user:"known",id:123,remote:{a:1}});
                done();
            }).catch(done);
        });
        it("rejects with internal error", function(done) {
            projects.getStatus({user:"error",id:123,project:{a:1}}).then(function(result) {
                done(new Error("Did not reject internal error"));
            }).catch(function(err) {
                err.should.have.property('code','error');
                // err.should.have.property('status',400);
                done();
            }).catch(done);
        });
    });
    describe("getBranches", function() {
        var runtime = {
        log: mockLog(),
        storage: {projects: {
            getBranches: sinon.spy(function(user,id,remote) {
                if (user === "error") {
                    var err = new Error("error");
                    err.code = "error";
                    var p = Promise.reject(err);
                    p.catch(()=>{});
                    return p;
                } else {
                    return Promise.resolve({user,id,remote});
                }
            })
        }}};
        before(function() {
            projects.init(runtime);
        })
        it("gets branches", function(done) {
            projects.getBranches({user:"known",id:123,remote:{a:1}}).then(function(result) {
                result.should.eql({user:"known",id:123,remote:{a:1}});
                done();
            }).catch(done);
        });
        it("rejects with internal error", function(done) {
            projects.getBranches({user:"error",id:123,remote:{a:1}}).then(function(result) {
                done(new Error("Did not reject internal error"));
            }).catch(function(err) {
                err.should.have.property('code','error');
                // err.should.have.property('status',400);
                done();
            }).catch(done);
        });
    });
    describe("getBranchStatus", function() {
        var runtime = {
        log: mockLog(),
        storage: {projects: {
            getBranchStatus: sinon.spy(function(user,id,branch) {
                if (user === "error") {
                    var err = new Error("error");
                    err.code = "error";
                    var p = Promise.reject(err);
                    p.catch(()=>{});
                    return p;
                } else {
                    return Promise.resolve({user,id,branch});
                }
            })
        }}};
        before(function() {
            projects.init(runtime);
        })
        it("gets branch status", function(done) {
            projects.getBranchStatus({user:"known",id:123,branch:{a:1}}).then(function(result) {
                result.should.eql({user:"known",id:123,branch:{a:1}});
                done();
            }).catch(done);
        });
        it("rejects with internal error", function(done) {
            projects.getBranchStatus({user:"error",id:123,branch:{a:1}}).then(function(result) {
                done(new Error("Did not reject internal error"));
            }).catch(function(err) {
                err.should.have.property('code','error');
                // err.should.have.property('status',400);
                done();
            }).catch(done);
        });
    });

    describe("setBranch", function() {
        var runtime = {
        log: mockLog(),
        storage: {projects: {
            setBranch: sinon.spy(function(user,id,branch,create) {
                if (user === "error") {
                    var err = new Error("error");
                    err.code = "error";
                    var p = Promise.reject(err);
                    p.catch(()=>{});
                    return p;
                } else {
                    return Promise.resolve({user,id,branch,create});
                }
            })
        }}};
        before(function() {
            projects.init(runtime);
        })
        it("commits", function(done) {
            projects.setBranch({user:"known",id:123,branch:{a:1},create:true}).then(function(result) {
                result.should.eql({user:"known",id:123,branch:{a:1},create:true});
                done();
            }).catch(done);
        });
        it("rejects with internal error", function(done) {
            projects.setBranch({user:"error",id:123,branch:{a:1},create:true}).then(function(result) {
                done(new Error("Did not reject internal error"));
            }).catch(function(err) {
                err.should.have.property('code','error');
                // err.should.have.property('status',400);
                done();
            }).catch(done);
        });
    });

    describe("deleteBranch", function() {
        var runtime = {
        log: mockLog(),
        storage: {projects: {
            deleteBranch: sinon.spy(function(user,id,branch,something,force) {
                if (user === "error") {
                    var err = new Error("error");
                    err.code = "error";
                    var p = Promise.reject(err);
                    p.catch(()=>{});
                    return p;
                } else {
                    return Promise.resolve({user,id,branch,force});
                }
            })
        }}};
        before(function() {
            projects.init(runtime);
        })
        it("commits", function(done) {
            projects.deleteBranch({user:"known",id:123,branch:{a:1},force:true}).then(function(result) {
                result.should.eql({user:"known",id:123,branch:{a:1},force:true});
                done();
            }).catch(done);
        });
        it("rejects with internal error", function(done) {
            projects.deleteBranch({user:"error",id:123,branch:{a:1},force:true}).then(function(result) {
                done(new Error("Did not reject internal error"));
            }).catch(function(err) {
                err.should.have.property('code','error');
                // err.should.have.property('status',400);
                done();
            }).catch(done);
        });
    });

    describe("commit", function() {
        var runtime = {
        log: mockLog(),
        storage: {projects: {
            commit: sinon.spy(function(user,id,message) {
                if (user === "error") {
                    var err = new Error("error");
                    err.code = "error";
                    var p = Promise.reject(err);
                    p.catch(()=>{});
                    return p;
                } else {
                    return Promise.resolve({user,id,message});
                }
            })
        }}};
        before(function() {
            projects.init(runtime);
        })
        it("commits", function(done) {
            projects.commit({user:"known",id:123,message:{a:1}}).then(function(result) {
                result.should.eql({user:"known",id:123,message:{message:{a:1}}});
                done();
            }).catch(done);
        });
        it("rejects with internal error", function(done) {
            projects.commit({user:"error",id:123,message:{a:1}}).then(function(result) {
                done(new Error("Did not reject internal error"));
            }).catch(function(err) {
                err.should.have.property('code','error');
                // err.should.have.property('status',400);
                done();
            }).catch(done);
        });
    });
    describe("getCommit", function() {
        var runtime = {
        log: mockLog(),
        storage: {projects: {
            getCommit: sinon.spy(function(user,id,sha) {
                if (user === "error") {
                    var err = new Error("error");
                    err.code = "error";
                    var p = Promise.reject(err);
                    p.catch(()=>{});
                    return p;
                } else {
                    return Promise.resolve({user,id,sha});
                }
            })
        }}};
        before(function() {
            projects.init(runtime);
        })
        it("gets commit", function(done) {
            projects.getCommit({user:"known",id:123,sha:{a:1}}).then(function(result) {
                result.should.eql({user:"known",id:123,sha:{a:1}});
                done();
            }).catch(done);
        });
        it("rejects with internal error", function(done) {
            projects.getCommit({user:"error",id:123,sha:{a:1}}).then(function(result) {
                done(new Error("Did not reject internal error"));
            }).catch(function(err) {
                err.should.have.property('code','error');
                // err.should.have.property('status',400);
                done();
            }).catch(done);
        });
    });

    describe("getCommits", function() {
        var runtime = {
        log: mockLog(),
        storage: {projects: {
            getCommits: sinon.spy(function(user,id,options) {
                if (user === "error") {
                    var err = new Error("error");
                    err.code = "error";
                    var p = Promise.reject(err);
                    p.catch(()=>{});
                    return p;
                } else {
                    return Promise.resolve({user,id,options});
                }
            })
        }}};
        before(function() {
            projects.init(runtime);
        })
        it("gets commits with default limit/before", function(done) {
            projects.getCommits({user:"known",id:123}).then(function(result) {
                result.should.eql({user:"known",id:123,options:{limit:20,before:undefined}});
                done();
            }).catch(done);
        });
        it("gets commits with provided limit/before", function(done) {
            projects.getCommits({user:"known",id:123,limit:10,before:456}).then(function(result) {
                result.should.eql({user:"known",id:123,options:{limit:10,before:456}});
                done();
            }).catch(done);
        });
        it("rejects with internal error", function(done) {
            projects.getCommits({user:"error"}).then(function(result) {
                done(new Error("Did not reject internal error"));
            }).catch(function(err) {
                err.should.have.property('code','error');
                // err.should.have.property('status',400);
                done();
            }).catch(done);
        });
    });

    describe("abortMerge", function() {
        var runtime = {
        log: mockLog(),
        storage: {projects: {
            abortMerge: sinon.spy(function(user,id) {
                if (user === "error") {
                    var err = new Error("error");
                    err.code = "error";
                    var p = Promise.reject(err);
                    p.catch(()=>{});
                    return p;
                } else {
                    return Promise.resolve({user,id});
                }
            })
        }}};
        before(function() {
            projects.init(runtime);
        })
        it("aborts merge", function(done) {
            projects.abortMerge({user:"known",id:123}).then(function(result) {
                result.should.eql({user:"known",id:123});
                done();
            }).catch(done);
        });
        it("rejects with internal error", function(done) {
            projects.abortMerge({user:"error"}).then(function(result) {
                done(new Error("Did not reject internal error"));
            }).catch(function(err) {
                err.should.have.property('code','error');
                // err.should.have.property('status',400);
                done();
            }).catch(done);
        });
    });

    describe("resolveMerge", function() {
        var runtime = {
        log: mockLog(),
        storage: {projects: {
            resolveMerge: sinon.spy(function(user,id,path,resolution) {
                if (user === "error") {
                    var err = new Error("error");
                    err.code = "error";
                    var p = Promise.reject(err);
                    p.catch(()=>{});
                    return p;
                } else {
                    return Promise.resolve({user,id,path,resolution});
                }
            })
        }}};
        before(function() {
            projects.init(runtime);
        })
        it("resolves merge", function(done) {
            projects.resolveMerge({user:"known",id:123,path:"/abc",resolution:{a:1}}).then(function(result) {
                result.should.eql({user:"known",id:123,path:"/abc",resolution:{a:1}});
                done();
            }).catch(done);
        });
        it("rejects with internal error", function(done) {
            projects.resolveMerge({user:"error",id:123,path:"/abc",resolution:{a:1}}).then(function(result) {
                done(new Error("Did not reject internal error"));
            }).catch(function(err) {
                err.should.have.property('code','error');
                // err.should.have.property('status',400);
                done();
            }).catch(done);
        });
    });

    describe("getFiles", function() {
        var runtime = {
        log: mockLog(),
        storage: {projects: {
            getFiles: sinon.spy(function(user,id) {
                if (user === "error") {
                    var err = new Error("error");
                    err.code = "error";
                    var p = Promise.reject(err);
                    p.catch(()=>{});
                    return p;
                } else {
                    return Promise.resolve({user,id});
                }
            })
        }}};
        before(function() {
            projects.init(runtime);
        })
        it("gets files", function(done) {
            projects.getFiles({user:"known",id:123}).then(function(result) {
                result.should.eql({user:"known",id:123});
                done();
            }).catch(done);
        });
        it("rejects with internal error", function(done) {
            projects.getFiles({user:"error"}).then(function(result) {
                done(new Error("Did not reject internal error"));
            }).catch(function(err) {
                err.should.have.property('code','error');
                // err.should.have.property('status',400);
                done();
            }).catch(done);
        });
    });

    describe("getFile", function() {
        var runtime = {
        log: mockLog(),
        storage: {projects: {
            getFile: sinon.spy(function(user,id,path,tree) {
                if (user === "error") {
                    var err = new Error("error");
                    err.code = "error";
                    var p = Promise.reject(err);
                    p.catch(()=>{});
                    return p;
                } else {
                    return Promise.resolve({user,id,path,tree});
                }
            })
        }}};
        before(function() {
            projects.init(runtime);
        })
        it("gets file", function(done) {
            projects.getFile({user:"known",id:123,path:"/abc",tree:{a:1}}).then(function(result) {
                result.should.eql({user:"known",id:123,path:"/abc",tree:{a:1}});
                done();
            }).catch(done);
        });
        it("rejects with internal error", function(done) {
            projects.getFile({user:"error",id:123,path:"/abc",tree:{a:1}}).then(function(result) {
                done(new Error("Did not reject internal error"));
            }).catch(function(err) {
                err.should.have.property('code','error');
                // err.should.have.property('status',400);
                done();
            }).catch(done);
        });
    });

    describe("stageFile", function() {
        var runtime = {
        log: mockLog(),
        storage: {projects: {
            stageFile: sinon.spy(function(user,id,path) {
                if (user === "error") {
                    var err = new Error("error");
                    err.code = "error";
                    var p = Promise.reject(err);
                    p.catch(()=>{});
                    return p;
                } else {
                    return Promise.resolve({user,id,path});
                }
            })
        }}};
        before(function() {
            projects.init(runtime);
        })
        it("stages a file", function(done) {
            projects.stageFile({user:"known",id:123,path:{a:1}}).then(function(result) {
                result.should.eql({user:"known",id:123,path:{a:1}});
                done();
            }).catch(done);
        });
        it("rejects with internal error", function(done) {
            projects.stageFile({user:"error",id:123,path:{a:1}}).then(function(result) {
                done(new Error("Did not reject internal error"));
            }).catch(function(err) {
                err.should.have.property('code','error');
                // err.should.have.property('status',400);
                done();
            }).catch(done);
        });
    });
    describe("unstageFile", function() {
        var runtime = {
        log: mockLog(),
        storage: {projects: {
            unstageFile: sinon.spy(function(user,id,path) {
                if (user === "error") {
                    var err = new Error("error");
                    err.code = "error";
                    var p = Promise.reject(err);
                    p.catch(()=>{});
                    return p;
                } else {
                    return Promise.resolve({user,id,path});
                }
            })
        }}};
        before(function() {
            projects.init(runtime);
        })
        it("unstages a file", function(done) {
            projects.unstageFile({user:"known",id:123,path:{a:1}}).then(function(result) {
                result.should.eql({user:"known",id:123,path:{a:1}});
                done();
            }).catch(done);
        });
        it("rejects with internal error", function(done) {
            projects.unstageFile({user:"error",id:123,path:{a:1}}).then(function(result) {
                done(new Error("Did not reject internal error"));
            }).catch(function(err) {
                err.should.have.property('code','error');
                // err.should.have.property('status',400);
                done();
            }).catch(done);
        });
    });

    describe("revertFile", function() {
        var runtime = {
        log: mockLog(),
        storage: {projects: {
            revertFile: sinon.spy(function(user,id,path) {
                if (user === "error") {
                    var err = new Error("error");
                    err.code = "error";
                    var p = Promise.reject(err);
                    p.catch(()=>{});
                    return p;
                } else {
                    return Promise.resolve({user,id,path});
                }
            })
        }}};
        before(function() {
            projects.init(runtime);
        })
        it("reverts a file", function(done) {
            projects.revertFile({user:"known",id:123,path:{a:1}}).then(function(result) {
                result.should.eql({user:"known",id:123,path:{a:1}});
                done();
            }).catch(done);
        });
        it("rejects with internal error", function(done) {
            projects.revertFile({user:"error",id:123,path:{a:1}}).then(function(result) {
                done(new Error("Did not reject internal error"));
            }).catch(function(err) {
                err.should.have.property('code','error');
                // err.should.have.property('status',400);
                done();
            }).catch(done);
        });
    });

    describe("getFileDiff", function() {
        var runtime = {
        log: mockLog(),
        storage: {projects: {
            getFileDiff: sinon.spy(function(user,id,path,type) {
                if (user === "error") {
                    var err = new Error("error");
                    err.code = "error";
                    var p = Promise.reject(err);
                    p.catch(()=>{});
                    return p;
                } else {
                    return Promise.resolve({user,id,path,type});
                }
            })
        }}};
        before(function() {
            projects.init(runtime);
        })
        it("gets file diff", function(done) {
            projects.getFileDiff({user:"known",id:123,path:{a:1},type:"abc"}).then(function(result) {
                result.should.eql({user:"known",id:123,path:{a:1},type:"abc"});
                done();
            }).catch(done);
        });
        it("rejects with internal error", function(done) {
            projects.getFileDiff({user:"error",id:123,path:{a:1},type:"abc"}).then(function(result) {
                done(new Error("Did not reject internal error"));
            }).catch(function(err) {
                err.should.have.property('code','error');
                // err.should.have.property('status',400);
                done();
            }).catch(done);
        });
    });

    describe("getRemotes", function() {
        var runtime = {
        log: mockLog(),
        storage: {projects: {
            getRemotes: sinon.spy(function(user,id) {
                if (user === "error") {
                    var err = new Error("error");
                    err.code = "error";
                    var p = Promise.reject(err);
                    p.catch(()=>{});
                    return p;
                } else {
                    return Promise.resolve({user,id});
                }
            })
        }}};
        before(function() {
            projects.init(runtime);
        })
        it("gets remotes", function(done) {
            projects.getRemotes({user:"known",id:123}).then(function(result) {
                result.should.eql({user:"known",id:123});
                done();
            }).catch(done);
        });
        it("rejects with internal error", function(done) {
            projects.getRemotes({user:"error"}).then(function(result) {
                done(new Error("Did not reject internal error"));
            }).catch(function(err) {
                err.should.have.property('code','error');
                // err.should.have.property('status',400);
                done();
            }).catch(done);
        });
    });

    describe("addRemote", function() {
        var runtime = {
        log: mockLog(),
        storage: {projects: {
            addRemote: sinon.spy(function(user,id,remote) {
                if (user === "error") {
                    var err = new Error("error");
                    err.code = "error";
                    var p = Promise.reject(err);
                    p.catch(()=>{});
                    return p;
                } else {
                    return Promise.resolve({user,id,remote});
                }
            })
        }}};
        before(function() {
            projects.init(runtime);
        })
        it("adds a remote", function(done) {
            projects.addRemote({user:"known",id:123,remote:{a:1}}).then(function(result) {
                result.should.eql({user:"known",id:123,remote:{a:1}});
                done();
            }).catch(done);
        });
        it("rejects with internal error", function(done) {
            projects.addRemote({user:"error",id:123,remote:{a:1}}).then(function(result) {
                done(new Error("Did not reject internal error"));
            }).catch(function(err) {
                err.should.have.property('code','error');
                // err.should.have.property('status',400);
                done();
            }).catch(done);
        });
    });

    describe("removeRemote", function() {
        var runtime = {
        log: mockLog(),
        storage: {projects: {
            removeRemote: sinon.spy(function(user,id,remote) {
                if (user === "error") {
                    var err = new Error("error");
                    err.code = "error";
                    var p = Promise.reject(err);
                    p.catch(()=>{});
                    return p;
                } else {
                    return Promise.resolve({user,id,remote});
                }
            })
        }}};
        before(function() {
            projects.init(runtime);
        })
        it("removes a remote", function(done) {
            projects.removeRemote({user:"known",id:123,remote:{a:1}}).then(function(result) {
                result.should.eql({user:"known",id:123,remote:{a:1}});
                done();
            }).catch(done);
        });
        it("rejects with internal error", function(done) {
            projects.removeRemote({user:"error",id:123,remote:{a:1}}).then(function(result) {
                done(new Error("Did not reject internal error"));
            }).catch(function(err) {
                err.should.have.property('code','error');
                // err.should.have.property('status',400);
                done();
            }).catch(done);
        });
    });

    describe("updateRemote", function() {
        var runtime = {
        log: mockLog(),
        storage: {projects: {
            updateRemote: sinon.spy(function(user,id,name,remote) {
                if (user === "error") {
                    var err = new Error("error");
                    err.code = "error";
                    var p = Promise.reject(err);
                    p.catch(()=>{});
                    return p;
                } else {
                    return Promise.resolve({user,id,name,remote});
                }
            })
        }}};
        before(function() {
            projects.init(runtime);
        })
        it("updates a remote", function(done) {
            projects.updateRemote({user:"known",id:123,remote:{name:"abc",a:1}}).then(function(result) {
                result.should.eql({user:"known",id:123,name:"abc",remote:{name:"abc",a:1}});
                done();
            }).catch(done);
        });
        it("rejects with internal error", function(done) {
            projects.updateRemote({user:"error",id:123,remote:{name:"abc",a:1}}).then(function(result) {
                done(new Error("Did not reject internal error"));
            }).catch(function(err) {
                err.should.have.property('code','error');
                // err.should.have.property('status',400);
                done();
            }).catch(done);
        });
    });
    describe("pull", function() {
        var runtime = {
        log: mockLog(),
        storage: {projects: {
            pull: sinon.spy(function(user,id,remote,track,allowUnrelatedHistories) {
                if (user === "error") {
                    var err = new Error("error");
                    err.code = "error";
                    var p = Promise.reject(err);
                    p.catch(()=>{});
                    return p;
                } else {
                    return Promise.resolve({user,id,remote,track,allowUnrelatedHistories});
                }
            })
        }}};
        before(function() {
            projects.init(runtime);
        })
        it("pulls", function(done) {
            projects.pull({user:"known",id:123,remote:"abc",track:false,allowUnrelatedHistories:true}).then(function(result) {
                result.should.eql({user:"known",id:123,remote:"abc",track:false,allowUnrelatedHistories:true});
                done();
            }).catch(done);
        });
        it("rejects with internal error", function(done) {
            projects.pull({user:"error",id:123,remote:"abc",track:false,allowUnrelatedHistories:true}).then(function(result) {
                done(new Error("Did not reject internal error"));
            }).catch(function(err) {
                err.should.have.property('code','error');
                // err.should.have.property('status',400);
                done();
            }).catch(done);
        });
    });
    describe("push", function() {
        var runtime = {
        log: mockLog(),
        storage: {projects: {
            push: sinon.spy(function(user,id,remote,track) {
                if (user === "error") {
                    var err = new Error("error");
                    err.code = "error";
                    var p = Promise.reject(err);
                    p.catch(()=>{});
                    return p;
                } else {
                    return Promise.resolve({user,id,remote,track});
                }
            })
        }}};
        before(function() {
            projects.init(runtime);
        })
        it("pulls", function(done) {
            projects.push({user:"known",id:123,remote:"abc",track:false}).then(function(result) {
                result.should.eql({user:"known",id:123,remote:"abc",track:false});
                done();
            }).catch(done);
        });
        it("rejects with internal error", function(done) {
            projects.push({user:"error",id:123,remote:"abc",track:false}).then(function(result) {
                done(new Error("Did not reject internal error"));
            }).catch(function(err) {
                err.should.have.property('code','error');
                // err.should.have.property('status',400);
                done();
            }).catch(done);
        });
    });


});