@codecovevienna/gittt-cli
Version:
Tracking time with CLI into a git repository
536 lines (421 loc) • 16.7 kB
text/typescript
import { assert, expect } from "chai";
import path from "path";
import proxyquire from "proxyquire";
import { StatusResult } from "simple-git/promise";
import sinon from "sinon";
import { FileHelper, GitHelper, LogHelper } from "../../helper/";
import { LogResult, DefaultLogFields } from "simple-git";
const sandboxDir = "./sandbox";
const configDir: string = path.join(sandboxDir, ".git-time-tracker");
const configFileName = "config.json";
const timerFileName = "timer.json";
const projectsDir = "projects";
LogHelper.DEBUG = false;
LogHelper.silence = true;
describe("GitHelper", function () {
before(function () {
proxyquire.noCallThru();
});
it("should create instance", async function () {
const fileProxy: any = proxyquire("../../helper/file", {});
const mockedFileHelper: FileHelper = new fileProxy
.FileHelper(configDir, configFileName, timerFileName, projectsDir);
const proxy: any = proxyquire("../../helper/git", {
"simple-git/promise": (): any => {
return {};
},
});
const gitHelper: GitHelper = new proxy.GitHelper(configDir, mockedFileHelper);
// type of proxy object is not GitHelper, so just check for definition
assert.isDefined(gitHelper);
});
it("should log changes", async function () {
const fileProxy: any = proxyquire("../../helper/file", {});
const mockedFileHelper: FileHelper = new fileProxy
.FileHelper(configDir, configFileName, timerFileName, projectsDir);
const proxy: any = proxyquire("../../helper/git", {
"simple-git/promise": (): any => {
return {
log: (): LogResult => {
return {
all: [
{
// eslint-disable-next-line @typescript-eslint/naming-convention
author_email: "mock@mail.com",
// eslint-disable-next-line @typescript-eslint/naming-convention
author_name: "mockAuthor",
body: "mockedBody",
date: Date.UTC.toString(),
hash: "mockedHash",
message: "mockMessage",
refs: "mockedRefs",
},
],
latest: {
// eslint-disable-next-line @typescript-eslint/naming-convention
author_email: "mock@mail.com",
// eslint-disable-next-line @typescript-eslint/naming-convention
author_name: "mockAuthor",
body: "mockedBody",
date: Date.UTC.toString(),
hash: "mockedHash",
message: "mockMessage",
refs: "mockedRefs",
},
total: 0,
};
},
};
},
});
const instance: GitHelper = new proxy.GitHelper(configDir, mockedFileHelper);
const logs: ReadonlyArray<DefaultLogFields> = await instance.logChanges();
expect(logs.length).to.eq(1);
});
it("should push changes", async function () {
const fileProxy: any = proxyquire("../../helper/file", {});
const mockedFileHelper: FileHelper = new fileProxy
.FileHelper(configDir, configFileName, timerFileName, projectsDir);
const pullSpy = sinon.spy();
const pushSpy = sinon.spy();
const proxy: any = proxyquire("../../helper/git", {
"simple-git/promise": (): any => {
return {
pull: pullSpy,
push: pushSpy,
};
},
});
const instance: GitHelper = new proxy.GitHelper(configDir, mockedFileHelper);
await instance.pushChanges();
assert.isTrue(pullSpy.calledOnce);
assert.isTrue(pushSpy.calledOnce);
});
it("should commit changes", async function () {
const fileProxy: any = proxyquire("../../helper/file", {});
const mockedFileHelper: FileHelper = new fileProxy
.FileHelper(configDir, configFileName, timerFileName, projectsDir);
const pullSpy = sinon.spy();
const addSpy = sinon.spy();
const commitSpy = sinon.spy();
const proxy: any = proxyquire("../../helper/git", {
"simple-git/promise": (): any => {
return {
add: addSpy,
commit: commitSpy,
pull: pullSpy,
};
},
});
const instance: GitHelper = new proxy.GitHelper(configDir, mockedFileHelper);
await instance.commitChanges();
assert.isTrue(pullSpy.calledOnce);
assert.isTrue(addSpy.calledOnce);
assert.isTrue(commitSpy.calledOnce);
});
it("should commit changes with message", async function () {
const fileProxy: any = proxyquire("../../helper/file", {});
const mockedFileHelper: FileHelper = new fileProxy
.FileHelper(configDir, configFileName, timerFileName, projectsDir);
const pullSpy = sinon.spy();
const addSpy = sinon.spy();
const commitSpy = sinon.spy();
const proxy: any = proxyquire("../../helper/git", {
"simple-git/promise": (): any => {
return {
add: addSpy,
commit: commitSpy,
pull: pullSpy,
};
},
});
const instance: GitHelper = new proxy.GitHelper(configDir, mockedFileHelper);
await instance.commitChanges("message");
assert.isTrue(pullSpy.calledOnce);
assert.isTrue(addSpy.calledOnce);
assert.isTrue(commitSpy.calledWith("message"));
});
it("should init repo", async function () {
const fileProxy: any = proxyquire("../../helper/file", {});
const mockedFileHelper: FileHelper = new fileProxy
.FileHelper(configDir, configFileName, timerFileName, projectsDir);
const initSpy = sinon.spy();
const addRemoteSpy = sinon.spy();
const proxy: any = proxyquire("../../helper/git", {
"simple-git/promise": (): any => {
return {
addRemote: addRemoteSpy,
checkIsRepo: sinon.stub().resolves(false),
init: initSpy,
};
},
});
const instance: GitHelper = new proxy.GitHelper(configDir, mockedFileHelper);
await instance.initRepo("url");
assert.isTrue(initSpy.calledOnce);
assert.isTrue(addRemoteSpy.calledWith("origin", "url"));
});
it("should pull repo [reset: default, choice: 0]", async function () {
const fileProxy: any = proxyquire("../../helper/file", {});
const mockedFileHelper: FileHelper = new fileProxy
.FileHelper(configDir, configFileName, timerFileName, projectsDir);
const invalidateCacheSpy = sinon.spy(mockedFileHelper, "invalidateCache");
const resetSpy = sinon.spy();
const pullStub = sinon.stub()
.onCall(0).rejects(new Error("Mocked error"))
.onCall(1).resolves();
const proxy: any = proxyquire("../../helper/git", {
"./": {
QuestionHelper: class {
public static chooseOverrideLocalChanges = sinon.stub().resolves(0);
},
LogHelper
},
"simple-git/promise": (): any => {
return {
pull: pullStub,
reset: resetSpy,
};
},
});
const instance: GitHelper = new proxy.GitHelper(configDir, mockedFileHelper);
await instance.pullRepo();
assert.isTrue(resetSpy.calledWith(["--hard", "origin/master"]));
expect(pullStub.callCount).to.eq(2);
assert.isTrue(invalidateCacheSpy.calledOnce);
});
it("should pull repo [reset: default, choice: 1]", async function () {
const fileProxy: any = proxyquire("../../helper/file", {});
const mockedFileHelper: FileHelper = new fileProxy
.FileHelper(configDir, configFileName, timerFileName, projectsDir);
const invalidateCacheSpy = sinon.spy(mockedFileHelper, "invalidateCache");
const addSpy = sinon.spy();
const commitSpy = sinon.spy();
const rawSpy = sinon.spy();
const proxy: any = proxyquire("../../helper/git", {
"./": {
QuestionHelper: class {
public static chooseOverrideLocalChanges = sinon.stub().resolves(1);
},
LogHelper
},
"simple-git/promise": (): any => {
return {
add: addSpy,
commit: commitSpy,
pull: sinon.stub().rejects(new Error("Mocked error")),
raw: rawSpy,
status: sinon.stub().resolves({} as StatusResult),
};
},
});
const instance: GitHelper = new proxy.GitHelper(configDir, mockedFileHelper);
await instance.pullRepo();
assert.isTrue(addSpy.calledWith("./*"));
assert.isTrue(commitSpy.calledWith("Setup commit"));
assert.isTrue(rawSpy.calledWith(["push", "origin", "master", "--force"]));
assert.isTrue(invalidateCacheSpy.calledOnce);
});
it("should pull repo [reset: true, override: 1]", async function () {
const fileProxy: any = proxyquire("../../helper/file", {});
const mockedFileHelper: FileHelper = new fileProxy
.FileHelper(configDir, configFileName, timerFileName, projectsDir);
const invalidateCacheSpy = sinon.spy(mockedFileHelper, "invalidateCache");
const resetSpy = sinon.spy();
const pullSpy = sinon.spy();
const proxy: any = proxyquire("../../helper/git", {
"./": {
QuestionHelper: class {
public static chooseOverrideLocalChanges = sinon.stub().resolves(1);
},
LogHelper
},
"simple-git/promise": (): any => {
return {
pull: pullSpy,
reset: resetSpy,
};
},
});
const instance: GitHelper = new proxy.GitHelper(configDir, mockedFileHelper);
await instance.pullRepo(true);
assert.isTrue(resetSpy.calledWith(["--hard", "origin/master"]));
assert.isTrue(pullSpy.calledWith("origin", "master"));
// Nothing updated, so no need to invalidate config cache
assert.isTrue(invalidateCacheSpy.notCalled);
});
it("should pull repo [no master branch]", async function () {
const fileProxy: any = proxyquire("../../helper/file", {});
const mockedFileHelper: FileHelper = new fileProxy
.FileHelper(configDir, configFileName, timerFileName, projectsDir);
const invalidateCacheSpy = sinon.spy(mockedFileHelper, "invalidateCache");
sinon.stub(mockedFileHelper, "initReadme").resolves();
const addSpy = sinon.spy();
const commitSpy = sinon.spy();
const rawSpy = sinon.spy();
const pullStub = sinon.stub()
.onCall(0).rejects(new Error("fatal: couldn't find remote ref master\n"))
.onCall(1).resolves();
const proxy: any = proxyquire("../../helper/git", {
"simple-git/promise": (): any => {
return {
add: addSpy,
commit: commitSpy,
pull: pullStub,
raw: rawSpy,
status: sinon.stub().resolves({} as StatusResult),
};
},
});
const instance: GitHelper = new proxy.GitHelper(configDir, mockedFileHelper);
await instance.pullRepo();
assert.isTrue(pullStub.calledWith("origin", "master"));
assert.isTrue(addSpy.calledWith("./*"));
assert.isTrue(commitSpy.calledWith("Setup commit"));
assert.isTrue(rawSpy.calledWith(["push", "origin", "master", "--force"]));
assert.isTrue(invalidateCacheSpy.calledOnce);
});
it("should fail to pull repo [error in add]", async function () {
const fileProxy: any = proxyquire("../../helper/file", {});
const mockedFileHelper: FileHelper = new fileProxy
.FileHelper(configDir, configFileName, timerFileName, projectsDir);
sinon.stub(mockedFileHelper, "initReadme").resolves();
const proxy: any = proxyquire("../../helper/git", {
"simple-git/promise": (): any => {
return {
// rejecting pull is the fastest way to get to the error
add: sinon.stub().rejects(new Error("Mocked error")),
pull: sinon.stub().rejects(new Error("fatal: couldn't find remote ref master\n")),
status: sinon.stub().resolves({} as StatusResult),
};
},
});
const instance: GitHelper = new proxy.GitHelper(configDir, mockedFileHelper);
try {
await instance.pullRepo();
} catch (err: any) {
assert.isDefined(err);
}
});
it("should exit by choice", async function () {
const fileProxy: any = proxyquire("../../helper/file", {});
const mockedFileHelper: FileHelper = new fileProxy
.FileHelper(configDir, configFileName, timerFileName, projectsDir);
const exitStub = sinon.stub(process, "exit");
const proxy: any = proxyquire("../../helper/git", {
"./": {
QuestionHelper: class {
public static chooseOverrideLocalChanges = sinon.stub().resolves(2);
},
LogHelper
},
"simple-git/promise": (): any => {
return {
pull: sinon.stub().rejects(new Error("Mocked error")),
};
},
});
const instance: GitHelper = new proxy.GitHelper(configDir, mockedFileHelper);
await instance.pullRepo();
assert.isTrue(exitStub.called);
exitStub.restore();
});
it("should fail to pull repo [unknown override option]", async function () {
const fileProxy: any = proxyquire("../../helper/file", {});
const mockedFileHelper: FileHelper = new fileProxy
.FileHelper(configDir, configFileName, timerFileName, projectsDir);
const proxy: any = proxyquire("../../helper/git", {
"./": {
QuestionHelper: class {
public static chooseOverrideLocalChanges = sinon.stub().resolves(1337);
},
LogHelper
},
"simple-git/promise": (): any => {
return {
pull: sinon.stub().rejects(new Error("Mocked error")),
};
},
});
const instance: GitHelper = new proxy.GitHelper(configDir, mockedFileHelper);
try {
await instance.pullRepo();
} catch (err: any) {
assert.isDefined(err);
}
});
describe("getCurrentBranch", function () {
it("should get current branch", async function () {
const fileProxy: any = proxyquire("../../helper/file", {});
const mockedFileHelper: FileHelper = new fileProxy
.FileHelper(configDir, configFileName, timerFileName, projectsDir);
const shellExecStub = sinon.stub()
.returns({
code: 0,
stderr: "",
stdout: "1337-awesome-new-feature",
})
const proxy: any = proxyquire("../../helper/git", {
shelljs: {
exec: shellExecStub,
},
});
const instance: GitHelper = new proxy.GitHelper(configDir, mockedFileHelper);
const currentBranch = await instance.getCurrentBranch();
expect(currentBranch).to.eq("1337-awesome-new-feature")
});
it("should fail to get current branch [no git repo]", async function () {
const fileProxy: any = proxyquire("../../helper/file", {});
const mockedFileHelper: FileHelper = new fileProxy
.FileHelper(configDir, configFileName, timerFileName, projectsDir);
const shellExecStub = sinon.stub()
.returns({
code: 128,
stderr: "",
stdout: "",
})
const proxy: any = proxyquire("../../helper/git", {
shelljs: {
exec: shellExecStub,
},
});
const instance: GitHelper = new proxy.GitHelper(configDir, mockedFileHelper);
const currentBranch = await instance.getCurrentBranch();
expect(currentBranch).to.be.undefined;
});
it("should fail to get current branch [generic shell exec error]", async function () {
const fileProxy: any = proxyquire("../../helper/file", {});
const mockedFileHelper: FileHelper = new fileProxy
.FileHelper(configDir, configFileName, timerFileName, projectsDir);
const shellExecStub = sinon.stub()
.returns({
code: 1,
stderr: "",
stdout: "Generic error",
})
const proxy: any = proxyquire("../../helper/git", {
shelljs: {
exec: shellExecStub,
},
});
const instance: GitHelper = new proxy.GitHelper(configDir, mockedFileHelper);
const currentBranch = await instance.getCurrentBranch();
expect(currentBranch).to.be.undefined;
});
it("should fail to get current branch [shelljs throws]", async function () {
const fileProxy: any = proxyquire("../../helper/file", {});
const mockedFileHelper: FileHelper = new fileProxy
.FileHelper(configDir, configFileName, timerFileName, projectsDir);
const shellExecStub = sinon.stub()
.throws("Module not found")
const proxy: any = proxyquire("../../helper/git", {
shelljs: {
exec: shellExecStub,
},
});
const instance: GitHelper = new proxy.GitHelper(configDir, mockedFileHelper);
const currentBranch = await instance.getCurrentBranch();
expect(currentBranch).to.be.undefined;
});
})
});