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:
parent
fecc488848
commit
7255fec8b3
@ -1,79 +1,79 @@
|
|||||||
/// <reference types="bun-types" />
|
/// <reference types="bun-types" />
|
||||||
|
|
||||||
import { describe, expect, mock, test } from "bun:test"
|
import { describe, expect, test, spyOn, beforeEach, afterEach } from "bun:test"
|
||||||
|
import * as childProcess from "node:child_process"
|
||||||
const execSyncMock = mock(() => {
|
import * as fs from "node:fs"
|
||||||
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")
|
|
||||||
|
|
||||||
describe("collectGitDiffStats", () => {
|
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
|
//#given
|
||||||
|
const { collectGitDiffStats } = await import("./collect-git-diff-stats")
|
||||||
const directory = "/tmp/safe-repo;touch /tmp/pwn"
|
const directory = "/tmp/safe-repo;touch /tmp/pwn"
|
||||||
|
|
||||||
//#when
|
//#when
|
||||||
const result = collectGitDiffStats(directory)
|
const result = collectGitDiffStats(directory)
|
||||||
|
|
||||||
//#then
|
//#then
|
||||||
expect(execSyncMock).not.toHaveBeenCalled()
|
expect(execSyncSpy).not.toHaveBeenCalled()
|
||||||
expect(execFileSyncMock).toHaveBeenCalledTimes(3)
|
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 }]
|
.calls[0]! as unknown as [string, string[], { cwd?: string }]
|
||||||
expect(firstCallFile).toBe("git")
|
expect(firstCallFile).toBe("git")
|
||||||
expect(firstCallArgs).toEqual(["diff", "--numstat", "HEAD"])
|
expect(firstCallArgs).toEqual(["diff", "--numstat", "HEAD"])
|
||||||
expect(firstCallOpts.cwd).toBe(directory)
|
expect(firstCallOpts.cwd).toBe(directory)
|
||||||
expect(firstCallArgs.join(" ")).not.toContain(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 }]
|
.calls[1]! as unknown as [string, string[], { cwd?: string }]
|
||||||
expect(secondCallFile).toBe("git")
|
expect(secondCallFile).toBe("git")
|
||||||
expect(secondCallArgs).toEqual(["status", "--porcelain"])
|
expect(secondCallArgs).toEqual(["status", "--porcelain"])
|
||||||
expect(secondCallOpts.cwd).toBe(directory)
|
expect(secondCallOpts.cwd).toBe(directory)
|
||||||
expect(secondCallArgs.join(" ")).not.toContain(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 }]
|
.calls[2]! as unknown as [string, string[], { cwd?: string }]
|
||||||
expect(thirdCallFile).toBe("git")
|
expect(thirdCallFile).toBe("git")
|
||||||
expect(thirdCallArgs).toEqual(["ls-files", "--others", "--exclude-standard"])
|
expect(thirdCallArgs).toEqual(["ls-files", "--others", "--exclude-standard"])
|
||||||
expect(thirdCallOpts.cwd).toBe(directory)
|
expect(thirdCallOpts.cwd).toBe(directory)
|
||||||
expect(thirdCallArgs.join(" ")).not.toContain(directory)
|
expect(thirdCallArgs.join(" ")).not.toContain(directory)
|
||||||
|
|
||||||
expect(readFileSyncMock).toHaveBeenCalled()
|
expect(readFileSyncSpy).toHaveBeenCalled()
|
||||||
|
|
||||||
expect(result).toEqual([
|
expect(result).toEqual([
|
||||||
{
|
{
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user