Roo-Code/src/utils/__tests__/git.test.ts
2025-05-24 13:39:36 -06:00

348 lines
11 KiB
TypeScript

/* eslint-disable @typescript-eslint/no-unsafe-function-type */
import { ExecException } from "child_process"
import { jest } from "@jest/globals"
import { searchCommits, getCommitInfo, getWorkingState } from "../git"
type ExecFunction = (
command: string,
options: { cwd?: string },
callback: (error: ExecException | null, result?: { stdout: string; stderr: string }) => void,
) => void
type PromisifiedExec = (command: string, options?: { cwd?: string }) => Promise<{ stdout: string; stderr: string }>
// Mock child_process.exec
jest.mock("child_process", () => ({
exec: jest.fn(),
}))
// Mock util.promisify to return our own mock function
jest.mock("util", () => ({
promisify: jest.fn((fn: ExecFunction): PromisifiedExec => {
return async (command: string, options?: { cwd?: string }) => {
// Call the original mock to maintain the mock implementation
return new Promise((resolve, reject) => {
fn(
command,
options || {},
(error: ExecException | null, result?: { stdout: string; stderr: string }) => {
if (error) {
reject(error)
} else {
resolve(result!)
}
},
)
})
}
}),
}))
// Mock extract-text
jest.mock("../../integrations/misc/extract-text", () => ({
truncateOutput: jest.fn((text) => text),
}))
describe("git utils", () => {
// Get the mock with proper typing
const { exec } = jest.requireMock("child_process") as { exec: jest.MockedFunction<ExecFunction> }
const cwd = "/test/path"
beforeEach(() => {
jest.clearAllMocks()
})
describe("searchCommits", () => {
const mockCommitData = [
"abc123def456",
"abc123",
"fix: test commit",
"John Doe",
"2024-01-06",
"def456abc789",
"def456",
"feat: new feature",
"Jane Smith",
"2024-01-05",
].join("\n")
it("should return commits when git is installed and repo exists", async () => {
// Set up mock responses
const responses = new Map([
["git --version", { stdout: "git version 2.39.2", stderr: "" }],
["git rev-parse --git-dir", { stdout: ".git", stderr: "" }],
[
'git log -n 10 --format="%H%n%h%n%s%n%an%n%ad" --date=short --grep="test" --regexp-ignore-case',
{ stdout: mockCommitData, stderr: "" },
],
])
exec.mockImplementation((command: string, options: { cwd?: string }, callback: Function) => {
// Find matching response
for (const [cmd, response] of responses) {
if (command === cmd) {
callback(null, response)
return
}
}
callback(new Error(`Unexpected command: ${command}`))
})
const result = await searchCommits("test", cwd)
// First verify the result is correct
expect(result).toHaveLength(2)
expect(result[0]).toEqual({
hash: "abc123def456",
shortHash: "abc123",
subject: "fix: test commit",
author: "John Doe",
date: "2024-01-06",
})
// Then verify all commands were called correctly
expect(exec).toHaveBeenCalledWith("git --version", {}, expect.any(Function))
expect(exec).toHaveBeenCalledWith("git rev-parse --git-dir", { cwd }, expect.any(Function))
expect(exec).toHaveBeenCalledWith(
'git log -n 10 --format="%H%n%h%n%s%n%an%n%ad" --date=short --grep="test" --regexp-ignore-case',
{ cwd },
expect.any(Function),
)
})
it("should return empty array when git is not installed", async () => {
exec.mockImplementation((command: string, options: { cwd?: string }, callback: Function) => {
if (command === "git --version") {
callback(new Error("git not found"))
return
}
callback(new Error("Unexpected command"))
})
const result = await searchCommits("test", cwd)
expect(result).toEqual([])
expect(exec).toHaveBeenCalledWith("git --version", {}, expect.any(Function))
})
it("should return empty array when not in a git repository", async () => {
const responses = new Map([
["git --version", { stdout: "git version 2.39.2", stderr: "" }],
["git rev-parse --git-dir", null], // null indicates error should be called
])
exec.mockImplementation((command: string, options: { cwd?: string }, callback: Function) => {
const response = responses.get(command)
if (response === null) {
callback(new Error("not a git repository"))
} else if (response) {
callback(null, response)
} else {
callback(new Error("Unexpected command"))
}
})
const result = await searchCommits("test", cwd)
expect(result).toEqual([])
expect(exec).toHaveBeenCalledWith("git --version", {}, expect.any(Function))
expect(exec).toHaveBeenCalledWith("git rev-parse --git-dir", { cwd }, expect.any(Function))
})
it("should handle hash search when grep search returns no results", async () => {
const responses = new Map([
["git --version", { stdout: "git version 2.39.2", stderr: "" }],
["git rev-parse --git-dir", { stdout: ".git", stderr: "" }],
[
'git log -n 10 --format="%H%n%h%n%s%n%an%n%ad" --date=short --grep="abc123" --regexp-ignore-case',
{ stdout: "", stderr: "" },
],
[
'git log -n 10 --format="%H%n%h%n%s%n%an%n%ad" --date=short --author-date-order abc123',
{ stdout: mockCommitData, stderr: "" },
],
])
exec.mockImplementation((command: string, options: { cwd?: string }, callback: Function) => {
for (const [cmd, response] of responses) {
if (command === cmd) {
callback(null, response)
return
}
}
callback(new Error("Unexpected command"))
})
const result = await searchCommits("abc123", cwd)
expect(result).toHaveLength(2)
expect(result[0]).toEqual({
hash: "abc123def456",
shortHash: "abc123",
subject: "fix: test commit",
author: "John Doe",
date: "2024-01-06",
})
})
})
describe("getCommitInfo", () => {
const mockCommitInfo = [
"abc123def456",
"abc123",
"fix: test commit",
"John Doe",
"2024-01-06",
"Detailed description",
].join("\n")
const mockStats = "1 file changed, 2 insertions(+), 1 deletion(-)"
const mockDiff = "@@ -1,1 +1,2 @@\n-old line\n+new line"
it("should return formatted commit info", async () => {
const responses = new Map([
["git --version", { stdout: "git version 2.39.2", stderr: "" }],
["git rev-parse --git-dir", { stdout: ".git", stderr: "" }],
[
'git show --format="%H%n%h%n%s%n%an%n%ad%n%b" --no-patch abc123',
{ stdout: mockCommitInfo, stderr: "" },
],
['git show --stat --format="" abc123', { stdout: mockStats, stderr: "" }],
['git show --format="" abc123', { stdout: mockDiff, stderr: "" }],
])
exec.mockImplementation((command: string, options: { cwd?: string }, callback: Function) => {
for (const [cmd, response] of responses) {
if (command.startsWith(cmd)) {
callback(null, response)
return
}
}
callback(new Error("Unexpected command"))
})
const result = await getCommitInfo("abc123", cwd)
expect(result).toContain("Commit: abc123")
expect(result).toContain("Author: John Doe")
expect(result).toContain("Files Changed:")
expect(result).toContain("Full Changes:")
})
it("should return error message when git is not installed", async () => {
exec.mockImplementation((command: string, options: { cwd?: string }, callback: Function) => {
if (command === "git --version") {
callback(new Error("git not found"))
return
}
callback(new Error("Unexpected command"))
})
const result = await getCommitInfo("abc123", cwd)
expect(result).toBe("Git is not installed")
})
it("should return error message when not in a git repository", async () => {
const responses = new Map([
["git --version", { stdout: "git version 2.39.2", stderr: "" }],
["git rev-parse --git-dir", null], // null indicates error should be called
])
exec.mockImplementation((command: string, options: { cwd?: string }, callback: Function) => {
const response = responses.get(command)
if (response === null) {
callback(new Error("not a git repository"))
} else if (response) {
callback(null, response)
} else {
callback(new Error("Unexpected command"))
}
})
const result = await getCommitInfo("abc123", cwd)
expect(result).toBe("Not a git repository")
})
})
describe("getWorkingState", () => {
const mockStatus = " M src/file1.ts\n?? src/file2.ts"
const mockDiff = "@@ -1,1 +1,2 @@\n-old line\n+new line"
it("should return working directory changes", async () => {
const responses = new Map([
["git --version", { stdout: "git version 2.39.2", stderr: "" }],
["git rev-parse --git-dir", { stdout: ".git", stderr: "" }],
["git status --short", { stdout: mockStatus, stderr: "" }],
["git diff HEAD", { stdout: mockDiff, stderr: "" }],
])
exec.mockImplementation((command: string, options: { cwd?: string }, callback: Function) => {
for (const [cmd, response] of responses) {
if (command === cmd) {
callback(null, response)
return
}
}
callback(new Error("Unexpected command"))
})
const result = await getWorkingState(cwd)
expect(result).toContain("Working directory changes:")
expect(result).toContain("src/file1.ts")
expect(result).toContain("src/file2.ts")
})
it("should return message when working directory is clean", async () => {
const responses = new Map([
["git --version", { stdout: "git version 2.39.2", stderr: "" }],
["git rev-parse --git-dir", { stdout: ".git", stderr: "" }],
["git status --short", { stdout: "", stderr: "" }],
])
exec.mockImplementation((command: string, options: { cwd?: string }, callback: Function) => {
for (const [cmd, response] of responses) {
if (command === cmd) {
callback(null, response)
return
}
}
callback(new Error("Unexpected command"))
})
const result = await getWorkingState(cwd)
expect(result).toBe("No changes in working directory")
})
it("should return error message when git is not installed", async () => {
exec.mockImplementation((command: string, options: { cwd?: string }, callback: Function) => {
if (command === "git --version") {
callback(new Error("git not found"))
return
}
callback(new Error("Unexpected command"))
})
const result = await getWorkingState(cwd)
expect(result).toBe("Git is not installed")
})
it("should return error message when not in a git repository", async () => {
const responses = new Map([
["git --version", { stdout: "git version 2.39.2", stderr: "" }],
["git rev-parse --git-dir", null], // null indicates error should be called
])
exec.mockImplementation((command: string, options: { cwd?: string }, callback: Function) => {
const response = responses.get(command)
if (response === null) {
callback(new Error("not a git repository"))
} else if (response) {
callback(null, response)
} else {
callback(new Error("Unexpected command"))
}
})
const result = await getWorkingState(cwd)
expect(result).toBe("Not a git repository")
})
})
})