remove mocking from implementation and use jest to mock git cli instead

This commit is contained in:
Felix Wiedenbach 2021-10-02 14:08:16 +02:00
parent 332e429a41
commit b094707324
3 changed files with 191 additions and 163 deletions

View File

@ -12,7 +12,7 @@ class GitHelper {
} }
getRefRegex(branch) { getRefRegex(branch) {
return new RegExp("s*([a-z,0-9]+[.][.][a-z,0-9]+) " + branch, "g"); return new RegExp(`s*([a-z,0-9]+[.][.][a-z,0-9]+) ${branch}`, "g");
} }
async execShell(command) { async execShell(command) {
@ -22,10 +22,10 @@ class GitHelper {
} }
async isGitRepo(moduleFolder) { async isGitRepo(moduleFolder) {
const res = await this.execShell(`cd ${moduleFolder} && git remote -v`); const { stderr } = await this.execShell(`cd ${moduleFolder} && git remote -v`);
if (res.stderr) { if (stderr) {
Log.error(`Failed to fetch git data for ${moduleFolder}: ${res.stderr}`); Log.error(`Failed to fetch git data for ${moduleFolder}: ${stderr}`);
return false; return false;
} }
@ -68,33 +68,27 @@ class GitHelper {
isBehindInStatus: false isBehindInStatus: false
}; };
let res;
if (repo.module === "default") { if (repo.module === "default") {
// the hash is only needed for the mm repo // the hash is only needed for the mm repo
res = await this.execShell(`cd ${repo.folder} && git rev-parse HEAD`); const { stderr, stdout } = await this.execShell(`cd ${repo.folder} && git rev-parse HEAD`);
if (res.stderr) { if (stderr) {
Log.error(`Failed to get current commit hash for ${repo.module}: ${res.stderr}`); Log.error(`Failed to get current commit hash for ${repo.module}: ${stderr}`);
} }
gitInfo.hash = res.stdout; gitInfo.hash = stdout;
} }
if (repo.res) { const { stderr, stdout } = await this.execShell(`cd ${repo.folder} && git status -sb`);
// mocking
res = repo.res;
} else {
res = await this.execShell(`cd ${repo.folder} && git status -sb`);
}
if (res.stderr) { if (stderr) {
Log.error(`Failed to get git status for ${repo.module}: ${res.stderr}`); Log.error(`Failed to get git status for ${repo.module}: ${stderr}`);
// exit without git status info // exit without git status info
return; return;
} }
// only the first line of stdout is evaluated // only the first line of stdout is evaluated
let status = res.stdout.split("\n")[0]; let status = stdout.split("\n")[0];
// examples for status: // examples for status:
// ## develop...origin/develop // ## develop...origin/develop
// ## master...origin/master [behind 8] // ## master...origin/master [behind 8]
@ -119,14 +113,7 @@ class GitHelper {
} }
async getRepoInfo(repo) { async getRepoInfo(repo) {
let gitInfo; const gitInfo = await this.getStatusInfo(repo);
if (repo.gitInfo) {
// mocking
gitInfo = repo.gitInfo;
} else {
gitInfo = await this.getStatusInfo(repo);
}
if (!gitInfo) { if (!gitInfo) {
return; return;
@ -136,20 +123,13 @@ class GitHelper {
return gitInfo; return gitInfo;
} }
let res; const { stderr } = await this.execShell(`cd ${repo.folder} && git fetch --dry-run`);
if (repo.res) {
// mocking
res = repo.res;
} else {
res = await this.execShell(`cd ${repo.folder} && git fetch --dry-run`);
}
// example output: // example output:
// From https://github.com/MichMich/MagicMirror // From https://github.com/MichMich/MagicMirror
// e40ddd4..06389e3 develop -> origin/develop // e40ddd4..06389e3 develop -> origin/develop
// here the result is in stderr (this is a git default, don't ask why ...) // here the result is in stderr (this is a git default, don't ask why ...)
const matches = res.stderr.match(this.getRefRegex(gitInfo.current)); const matches = stderr.match(this.getRefRegex(gitInfo.current));
if (!matches || !matches[0]) { if (!matches || !matches[0]) {
// no refs found, nothing to do // no refs found, nothing to do
@ -158,8 +138,8 @@ class GitHelper {
// get behind with refs // get behind with refs
try { try {
res = await this.execShell(`cd ${repo.folder} && git rev-list --ancestry-path --count ${matches[0]}`); const { stdout } = await this.execShell(`cd ${repo.folder} && git rev-list --ancestry-path --count ${matches[0]}`);
gitInfo.behind = parseInt(res.stdout); gitInfo.behind = parseInt(stdout);
return gitInfo; return gitInfo;
} catch (err) { } catch (err) {
@ -167,29 +147,19 @@ class GitHelper {
} }
} }
async getStatus() {
const gitResultList = [];
for (let repo of this.gitRepos) {
const gitInfo = await this.getStatusInfo(repo);
if (gitInfo) {
gitResultList.push(gitInfo);
}
}
return gitResultList;
}
async getRepos() { async getRepos() {
const gitResultList = []; const gitResultList = [];
for (const repo of this.gitRepos) { for (const repo of this.gitRepos) {
try {
const gitInfo = await this.getRepoInfo(repo); const gitInfo = await this.getRepoInfo(repo);
if (gitInfo) { if (gitInfo) {
gitResultList.push(gitInfo); gitResultList.push(gitInfo);
} }
} catch (e) {
Log.error(`Failed to retrieve repo info for ${repo.module}: ${e}`);
}
} }
return gitResultList; return gitResultList;

View File

@ -0,0 +1,45 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Updatenotification custom module returns status information 1`] = `
Object {
"behind": 7,
"current": "master",
"hash": "",
"isBehindInStatus": false,
"module": "MMM-Fuel",
"tracking": "origin/master",
}
`;
exports[`Updatenotification custom module returns status information without hash 1`] = `
Object {
"behind": 7,
"current": "master",
"hash": "",
"isBehindInStatus": false,
"module": "MMM-Fuel",
"tracking": "origin/master",
}
`;
exports[`Updatenotification default returns status information 1`] = `
Object {
"behind": 5,
"current": "develop",
"hash": "332e429a41f1a2339afd4f0ae96dd125da6beada",
"isBehindInStatus": false,
"module": "default",
"tracking": "origin/develop",
}
`;
exports[`Updatenotification default returns status information early if isBehindInStatus 1`] = `
Object {
"behind": 5,
"current": "develop",
"hash": "332e429a41f1a2339afd4f0ae96dd125da6beada",
"isBehindInStatus": true,
"module": "default",
"tracking": "origin/develop",
}
`;

View File

@ -1,126 +1,139 @@
const path = require("path"); jest.mock("util", () => ({
const git_Helper = require("../../../modules/default/updatenotification/git_helper.js"); ...jest.requireActual("util"),
const gitHelper = new git_Helper.gitHelper(); promisify: jest.fn()
gitHelper.add("default"); }));
const test1 = { jest.mock("fs", () => ({
module: "test1", ...jest.requireActual("fs"),
folder: "", statSync: jest.fn()
res: { }));
stdout: "## master...origin/master [behind 8]",
stderr: ""
},
gitInfo: {
module: "default",
// commits behind:
behind: 0,
// branch name:
current: "develop",
// current hash:
hash: "",
// remote branch:
tracking: "",
isBehindInStatus: false
}
};
const test2 = { jest.mock("logger", () => ({
module: "test2", ...jest.requireActual("logger"),
folder: "", error: jest.fn(),
res: { info: jest.fn()
stdout: "## develop...origin/develop", }));
stderr: ""
}
};
const test3 = {
module: "test3",
folder: "",
res: {
stdout: "",
stderr: "error"
},
gitInfo: {
module: "default",
// commits behind:
behind: 2,
// branch name:
current: "develop",
// current hash:
hash: "",
// remote branch:
tracking: "",
isBehindInStatus: true
}
};
const test4 = {
module: "default",
folder: path.join(__dirname, "../../.."),
res: {
stdout: "",
stderr: " e40ddd4..06389e3 develop -> origin/develop"
},
gitInfo: {
module: "default",
// commits behind:
behind: 0,
// branch name:
current: "develop",
// current hash:
hash: "",
// remote branch:
tracking: "",
isBehindInStatus: false
}
};
describe("Updatenotification", function () { describe("Updatenotification", function () {
it("should return valid output for git status", async function () { const execMock = jest.fn();
const arr = await gitHelper.getStatus();
expect(arr.length).toBe(1);
const gitInfo = arr[0];
expect(gitInfo.current).not.toBe("");
expect(gitInfo.hash).not.toBe("");
}, 15000);
it("should return behind=8 for test1", async function () { let gitHelper;
const gitInfo = await gitHelper.getStatusInfo(test1);
expect(gitInfo.behind).toBe(8); let gitRemoteOut;
expect(gitInfo.isBehindInStatus).toBe(true); let gitRevParseOut;
let gitStatusOut;
let gitFetchOut;
let gitRevListOut;
let gitRemoteErr;
let gitRevParseErr;
let gitStatusErr;
let gitFetchErr;
let gitRevListErr;
beforeAll(async function () {
const { promisify } = require("util");
promisify.mockReturnValue(execMock);
const GitHelper = require(`../../../modules/default/updatenotification/git_helper`);
gitHelper = new GitHelper();
}); });
it("should return behind=0 for test2", async function () { beforeEach(function () {
const gitInfo = await gitHelper.getStatusInfo(test2); gitRemoteOut = "";
expect(gitInfo.behind).toBe(0); gitRevParseOut = "";
expect(gitInfo.isBehindInStatus).toBe(false); gitStatusOut = "";
gitFetchOut = "";
gitRevListOut = "";
gitRemoteErr = "";
gitRevParseErr = "";
gitStatusErr = "";
gitFetchErr = "";
gitRevListErr = "";
execMock.mockImplementation(function (command) {
if (command.includes("git remote -v")) {
return { stdout: gitRemoteOut, stderr: gitRemoteErr };
} else if (command.includes("git rev-parse HEAD")) {
return { stdout: gitRevParseOut, stderr: gitRevParseErr };
} else if (command.includes("git status -sb")) {
return { stdout: gitStatusOut, stderr: gitStatusErr };
} else if (command.includes("git fetch --dry-run")) {
return { stdout: gitFetchOut, stderr: gitFetchErr };
} else if (command.includes("git rev-list --ancestry-path --count")) {
return { stdout: gitRevListOut, stderr: gitRevListErr };
}
});
}); });
it("should return empty status object for test3", async function () { afterEach(async function () {
const gitInfo = await gitHelper.getStatusInfo(test3); gitHelper.gitRepos = [];
expect(gitInfo).toBe(undefined);
jest.clearAllMocks();
}); });
it("should return empty repo object for test2", async function () { describe("default", () => {
// no gitInfo provided in res, so returns undefined const moduleName = "default";
const gitInfo = await gitHelper.getRepoInfo(test2);
expect(gitInfo).toBe(undefined); beforeEach(async function () {
gitRemoteOut = "origin\tgit@github.com:MichMich/MagicMirror.git (fetch)\norigin\tgit@github.com:MichMich/MagicMirror.git (push)\n";
gitRevParseOut = "332e429a41f1a2339afd4f0ae96dd125da6beada";
gitStatusOut = "## develop...origin/develop\n M tests/unit/functions/updatenotification_spec.js\n";
gitFetchErr = "From github.com:MichMich/MagicMirror\n60e0377..332e429 develop -> origin/develop\n";
gitRevListOut = "5";
await gitHelper.add(moduleName);
}); });
it("should return empty repo object for test1", async function () { it("returns status information", async function () {
// no regex match for refs in empty string, so returns undefined const repos = await gitHelper.getRepos();
const gitInfo = await gitHelper.getRepoInfo(test1); expect(repos[0]).toMatchSnapshot();
expect(gitInfo).toBe(undefined); expect(execMock).toHaveBeenCalledTimes(5);
}); });
it("should return empty repo object for test4", async function () { it("returns status information early if isBehindInStatus", async function () {
// git ref list throws error, so returns undefined gitStatusOut = "## develop...origin/develop [behind 5]";
const gitInfo = await gitHelper.getRepoInfo(test4);
expect(gitInfo).toBe(undefined); const repos = await gitHelper.getRepos();
expect(repos[0]).toMatchSnapshot();
expect(execMock).toHaveBeenCalledTimes(3);
}); });
it("should return behind=2 for test3", async function () { it("excludes repo if status can't be retrieved", async function () {
const gitInfo = await gitHelper.getRepoInfo(test3); const errorMessage = "Failed to retrieve status";
expect(gitInfo.behind).toBe(2); execMock.mockRejectedValueOnce(errorMessage);
const repos = await gitHelper.getRepos();
expect(repos.length).toBe(0);
const { error } = require("logger");
expect(error).toHaveBeenCalledWith(`Failed to retrieve repo info for ${moduleName}: Failed to retrieve status`);
});
it("excludes repo if refs don't match regex", async function () {
gitFetchErr = "";
const repos = await gitHelper.getRepos();
expect(repos.length).toBe(0);
});
});
describe("custom module", () => {
const moduleName = "MMM-Fuel";
beforeEach(async function () {
gitRemoteOut = `origin\thttps://github.com/fewieden/${moduleName}.git (fetch)\norigin\thttps://github.com/fewieden/${moduleName}.git (push)\n`;
gitRevParseOut = "9d8310163da94441073a93cead711ba43e8888d0";
gitStatusOut = "## master...origin/master";
gitFetchErr = `From https://github.com/fewieden/${moduleName}\n19f7faf..9d83101 master -> origin/master`;
gitRevListOut = "7";
await gitHelper.add(moduleName);
});
it("returns status information without hash", async function () {
const repos = await gitHelper.getRepos();
expect(repos[0]).toMatchSnapshot();
expect(execMock).toHaveBeenCalledTimes(4);
});
}); });
}); });