Skip to content

Commit 90f1c1f

Browse files
🧪 Add tests for git utilities
Co-authored-by: sunnylqm <615282+sunnylqm@users.noreply.github.com>
1 parent 2be3698 commit 90f1c1f

2 files changed

Lines changed: 118 additions & 0 deletions

File tree

‎src/utils/git.ts‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { spawnSync } from 'child_process';
12
import fs from 'fs';
23
import git from 'isomorphic-git';
34
import path from 'path';
@@ -48,3 +49,11 @@ export async function getCommitInfo(): Promise<CommitInfo | undefined> {
4849
return;
4950
}
5051
}
52+
53+
export function getCurrentCommit() {
54+
const result = spawnSync('git', ['rev-parse', 'HEAD']);
55+
if (result.status !== 0) {
56+
throw new Error('Not a git repository');
57+
}
58+
return result.stdout.toString().trim();
59+
}

‎tests/git.test.ts‎

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
import { describe, it, expect, mock, afterEach, beforeEach, spyOn } from 'bun:test';
2+
3+
// Define the mock functions so we can manipulate them in tests
4+
const spawnSyncMock = mock(() => ({
5+
status: 0,
6+
stdout: Buffer.from('mock-hash-123\n')
7+
}));
8+
9+
mock.module('child_process', () => ({
10+
spawnSync: spawnSyncMock
11+
}));
12+
13+
const listRemotesMock = mock(async () => [{ remote: 'origin', url: 'https://github.com/test/repo.git' }]);
14+
const logMock = mock(async () => [{
15+
oid: 'mock-commit-hash',
16+
commit: {
17+
message: 'mock commit message',
18+
author: { name: 'Test Author' },
19+
committer: { name: 'Test Committer', timestamp: 1625097600 }
20+
}
21+
}]);
22+
23+
mock.module('isomorphic-git', () => ({
24+
default: {
25+
listRemotes: listRemotesMock,
26+
log: logMock
27+
}
28+
}));
29+
30+
import { getCurrentCommit, getCommitInfo } from '../src/utils/git';
31+
32+
describe('git utils', () => {
33+
afterEach(() => {
34+
spawnSyncMock.mockClear();
35+
listRemotesMock.mockClear();
36+
logMock.mockClear();
37+
});
38+
39+
describe('getCurrentCommit', () => {
40+
it('should return the commit hash when git command succeeds', () => {
41+
spawnSyncMock.mockImplementationOnce(() => ({
42+
status: 0,
43+
stdout: Buffer.from('abcdef1234567890\n')
44+
}));
45+
46+
const commit = getCurrentCommit();
47+
expect(commit).toBe('abcdef1234567890');
48+
expect(spawnSyncMock).toHaveBeenCalledWith('git', ['rev-parse', 'HEAD']);
49+
});
50+
51+
it('should throw an error when git command fails', () => {
52+
spawnSyncMock.mockImplementationOnce(() => ({
53+
status: 128,
54+
stdout: Buffer.from(''),
55+
stderr: Buffer.from('fatal: not a git repository (or any of the parent directories): .git\n')
56+
}));
57+
58+
expect(() => getCurrentCommit()).toThrow('Not a git repository');
59+
expect(spawnSyncMock).toHaveBeenCalledWith('git', ['rev-parse', 'HEAD']);
60+
});
61+
});
62+
63+
describe('getCommitInfo', () => {
64+
it('should return correct commit info', async () => {
65+
const info = await getCommitInfo();
66+
67+
expect(info).toBeDefined();
68+
expect(info?.hash).toBe('mock-commit-hash');
69+
expect(info?.message).toBe('mock commit message');
70+
expect(info?.author).toBe('Test Author');
71+
expect(info?.timestamp).toBe('1625097600');
72+
expect(info?.origin).toBe('https://github.com/test/repo.git');
73+
74+
expect(listRemotesMock).toHaveBeenCalled();
75+
expect(logMock).toHaveBeenCalled();
76+
});
77+
78+
it('should handle missing author name by falling back to committer name', async () => {
79+
logMock.mockImplementationOnce(async () => [{
80+
oid: 'mock-commit-hash-2',
81+
commit: {
82+
message: 'another message',
83+
author: { name: '' },
84+
committer: { name: 'Fallback Committer', timestamp: 1625098000 }
85+
}
86+
}]);
87+
88+
const info = await getCommitInfo();
89+
expect(info?.author).toBe('Fallback Committer');
90+
});
91+
92+
it('should return undefined and log error when git operations fail', async () => {
93+
const originalConsoleError = console.error;
94+
const consoleErrorMock = mock();
95+
console.error = consoleErrorMock;
96+
97+
listRemotesMock.mockImplementationOnce(async () => {
98+
throw new Error('Git operation failed');
99+
});
100+
101+
const info = await getCommitInfo();
102+
103+
expect(info).toBeUndefined();
104+
expect(consoleErrorMock).toHaveBeenCalled();
105+
106+
console.error = originalConsoleError;
107+
});
108+
});
109+
});

0 commit comments

Comments
 (0)