test(git-worktree): fix test pollution from incomplete fs mock
Replace mock.module with spyOn + mockRestore to prevent fs module pollution across test files. mock.module replaces the entire module and caused 69 test failures in other files that depend on fs.
This commit is contained in:
@@ -1,79 +1,79 @@
|
||||
/// <reference types="bun-types" />
|
||||
|
||||
import { describe, expect, mock, test } from "bun:test"
|
||||
|
||||
const execSyncMock = mock(() => {
|
||||
throw new Error("execSync should not be called")
|
||||
})
|
||||
|
||||
const execFileSyncMock = mock((file: string, args: string[], _opts: { cwd?: string }) => {
|
||||
if (file !== "git") throw new Error(`unexpected file: ${file}`)
|
||||
const subcommand = args[0]
|
||||
|
||||
if (subcommand === "diff") {
|
||||
return "1\t2\tfile.ts\n"
|
||||
}
|
||||
|
||||
if (subcommand === "status") {
|
||||
return " M file.ts\n?? new-file.ts\n"
|
||||
}
|
||||
|
||||
if (subcommand === "ls-files") {
|
||||
return "new-file.ts\n"
|
||||
}
|
||||
|
||||
throw new Error(`unexpected args: ${args.join(" ")}`)
|
||||
})
|
||||
|
||||
const readFileSyncMock = mock((_path: string, _encoding: string) => {
|
||||
return "line1\nline2\nline3\nline4\nline5\nline6\nline7\nline8\nline9\nline10\n"
|
||||
})
|
||||
|
||||
mock.module("node:child_process", () => ({
|
||||
execSync: execSyncMock,
|
||||
execFileSync: execFileSyncMock,
|
||||
}))
|
||||
|
||||
mock.module("node:fs", () => ({
|
||||
readFileSync: readFileSyncMock,
|
||||
}))
|
||||
|
||||
const { collectGitDiffStats } = await import("./collect-git-diff-stats")
|
||||
import { describe, expect, test, spyOn, beforeEach, afterEach } from "bun:test"
|
||||
import * as childProcess from "node:child_process"
|
||||
import * as fs from "node:fs"
|
||||
|
||||
describe("collectGitDiffStats", () => {
|
||||
test("uses execFileSync with arg arrays (no shell injection)", () => {
|
||||
let execFileSyncSpy: ReturnType<typeof spyOn>
|
||||
let execSyncSpy: ReturnType<typeof spyOn>
|
||||
let readFileSyncSpy: ReturnType<typeof spyOn>
|
||||
|
||||
beforeEach(() => {
|
||||
execSyncSpy = spyOn(childProcess, "execSync").mockImplementation(() => {
|
||||
throw new Error("execSync should not be called")
|
||||
})
|
||||
|
||||
execFileSyncSpy = spyOn(childProcess, "execFileSync").mockImplementation(
|
||||
((file: string, args: string[], _opts: { cwd?: string }) => {
|
||||
if (file !== "git") throw new Error(`unexpected file: ${file}`)
|
||||
const subcommand = args[0]
|
||||
|
||||
if (subcommand === "diff") return "1\t2\tfile.ts\n"
|
||||
if (subcommand === "status") return " M file.ts\n?? new-file.ts\n"
|
||||
if (subcommand === "ls-files") return "new-file.ts\n"
|
||||
|
||||
throw new Error(`unexpected args: ${args.join(" ")}`)
|
||||
}) as typeof childProcess.execFileSync
|
||||
)
|
||||
|
||||
readFileSyncSpy = spyOn(fs, "readFileSync").mockImplementation(
|
||||
((_path: unknown, _encoding: unknown) => {
|
||||
return "line1\nline2\nline3\nline4\nline5\nline6\nline7\nline8\nline9\nline10\n"
|
||||
}) as typeof fs.readFileSync
|
||||
)
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
execSyncSpy.mockRestore()
|
||||
execFileSyncSpy.mockRestore()
|
||||
readFileSyncSpy.mockRestore()
|
||||
})
|
||||
|
||||
test("uses execFileSync with arg arrays (no shell injection)", async () => {
|
||||
//#given
|
||||
const { collectGitDiffStats } = await import("./collect-git-diff-stats")
|
||||
const directory = "/tmp/safe-repo;touch /tmp/pwn"
|
||||
|
||||
//#when
|
||||
const result = collectGitDiffStats(directory)
|
||||
|
||||
//#then
|
||||
expect(execSyncMock).not.toHaveBeenCalled()
|
||||
expect(execFileSyncMock).toHaveBeenCalledTimes(3)
|
||||
expect(execSyncSpy).not.toHaveBeenCalled()
|
||||
expect(execFileSyncSpy).toHaveBeenCalledTimes(3)
|
||||
|
||||
const [firstCallFile, firstCallArgs, firstCallOpts] = execFileSyncMock.mock
|
||||
const [firstCallFile, firstCallArgs, firstCallOpts] = execFileSyncSpy.mock
|
||||
.calls[0]! as unknown as [string, string[], { cwd?: string }]
|
||||
expect(firstCallFile).toBe("git")
|
||||
expect(firstCallArgs).toEqual(["diff", "--numstat", "HEAD"])
|
||||
expect(firstCallOpts.cwd).toBe(directory)
|
||||
expect(firstCallArgs.join(" ")).not.toContain(directory)
|
||||
|
||||
const [secondCallFile, secondCallArgs, secondCallOpts] = execFileSyncMock.mock
|
||||
const [secondCallFile, secondCallArgs, secondCallOpts] = execFileSyncSpy.mock
|
||||
.calls[1]! as unknown as [string, string[], { cwd?: string }]
|
||||
expect(secondCallFile).toBe("git")
|
||||
expect(secondCallArgs).toEqual(["status", "--porcelain"])
|
||||
expect(secondCallOpts.cwd).toBe(directory)
|
||||
expect(secondCallArgs.join(" ")).not.toContain(directory)
|
||||
|
||||
const [thirdCallFile, thirdCallArgs, thirdCallOpts] = execFileSyncMock.mock
|
||||
const [thirdCallFile, thirdCallArgs, thirdCallOpts] = execFileSyncSpy.mock
|
||||
.calls[2]! as unknown as [string, string[], { cwd?: string }]
|
||||
expect(thirdCallFile).toBe("git")
|
||||
expect(thirdCallArgs).toEqual(["ls-files", "--others", "--exclude-standard"])
|
||||
expect(thirdCallOpts.cwd).toBe(directory)
|
||||
expect(thirdCallArgs.join(" ")).not.toContain(directory)
|
||||
|
||||
expect(readFileSyncMock).toHaveBeenCalled()
|
||||
expect(readFileSyncSpy).toHaveBeenCalled()
|
||||
|
||||
expect(result).toEqual([
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user