test(hooks): replace global module and timer overrides
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
/// <reference types="bun-types" />
|
||||
|
||||
import { afterEach, describe, expect, it, mock } from "bun:test"
|
||||
import { afterEach, describe, expect, it, mock, spyOn } from "bun:test"
|
||||
|
||||
import * as spawnWithTimeoutModule from "../spawn-with-timeout"
|
||||
|
||||
const originalWhich = Bun.which
|
||||
|
||||
@@ -13,18 +15,18 @@ describe("getGhCliInfo", () => {
|
||||
it("falls back to gh --version when Bun.which cannot find gh", async () => {
|
||||
// given
|
||||
Bun.which = mock(() => null)
|
||||
mock.module("../spawn-with-timeout", () => ({
|
||||
spawnWithTimeout: mock((command: string[]) => {
|
||||
spyOn(spawnWithTimeoutModule, "spawnWithTimeout").mockImplementation(
|
||||
(command: string[]) => {
|
||||
if (command.join(" ") === "gh --version") {
|
||||
return Promise.resolve({ stdout: "gh version 2.82.1\n", stderr: "", exitCode: 0, timedOut: false })
|
||||
}
|
||||
|
||||
return Promise.resolve({ stdout: "", stderr: "not logged in", exitCode: 1, timedOut: false })
|
||||
}),
|
||||
}))
|
||||
const { getGhCliInfo } = await import("./tools-gh")
|
||||
}
|
||||
)
|
||||
|
||||
// when
|
||||
const { getGhCliInfo } = await import("./tools-gh")
|
||||
const info = await getGhCliInfo()
|
||||
|
||||
// then
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { spawnWithTimeout } from "../spawn-with-timeout"
|
||||
import * as spawnWithTimeoutModule from "../spawn-with-timeout"
|
||||
|
||||
export interface GhCliInfo {
|
||||
installed: boolean
|
||||
@@ -21,7 +21,7 @@ async function checkBinaryExists(binary: string): Promise<{ exists: boolean; pat
|
||||
|
||||
async function getGhVersion(): Promise<string | null> {
|
||||
try {
|
||||
const result = await spawnWithTimeout(["gh", "--version"], { stdout: "pipe", stderr: "pipe" })
|
||||
const result = await spawnWithTimeoutModule.spawnWithTimeout(["gh", "--version"], { stdout: "pipe", stderr: "pipe" })
|
||||
if (result.timedOut || result.exitCode !== 0) return null
|
||||
|
||||
const matchedVersion = result.stdout.match(/gh version (\S+)/)
|
||||
@@ -38,7 +38,7 @@ async function getGhAuthStatus(): Promise<{
|
||||
error: string | null
|
||||
}> {
|
||||
try {
|
||||
const result = await spawnWithTimeout(
|
||||
const result = await spawnWithTimeoutModule.spawnWithTimeout(
|
||||
["gh", "auth", "status"],
|
||||
{ stdout: "pipe", stderr: "pipe", env: { ...process.env, GH_NO_UPDATE_NOTIFIER: "1" } }
|
||||
)
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { afterEach, beforeEach, describe, expect, test } from "bun:test"
|
||||
import { spawnSync } from "node:child_process"
|
||||
import * as fs from "node:fs"
|
||||
import * as os from "node:os"
|
||||
import * as path from "node:path"
|
||||
import { PACKAGE_NAME } from "../constants"
|
||||
import { LEGACY_PLUGIN_NAME, PLUGIN_NAME } from "../../../shared/plugin-identity"
|
||||
import { findPluginEntry } from "./plugin-entry"
|
||||
|
||||
type PluginEntryResult = {
|
||||
entry: string
|
||||
@@ -17,25 +17,33 @@ function runFindPluginEntry(
|
||||
directory: string,
|
||||
envOverrides: Record<string, string | undefined> = {},
|
||||
): { status: number | null; stdout: string; stderr: string } {
|
||||
const command = [
|
||||
`import { findPluginEntry } from ${JSON.stringify("./src/hooks/auto-update-checker/checker/plugin-entry")};`,
|
||||
`const result = findPluginEntry(${JSON.stringify(directory)});`,
|
||||
"console.log(JSON.stringify(result));",
|
||||
].join("")
|
||||
const originalValues = new Map<string, string | undefined>()
|
||||
for (const key of Object.keys(envOverrides)) {
|
||||
originalValues.set(key, process.env[key])
|
||||
}
|
||||
|
||||
const execution = spawnSync(process.execPath, ["-e", command], {
|
||||
cwd: process.cwd(),
|
||||
env: {
|
||||
...process.env,
|
||||
...envOverrides,
|
||||
},
|
||||
encoding: "utf-8",
|
||||
})
|
||||
try {
|
||||
for (const [key, value] of Object.entries(envOverrides)) {
|
||||
if (value === undefined) {
|
||||
delete process.env[key]
|
||||
} else {
|
||||
process.env[key] = value
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
status: execution.status,
|
||||
stdout: execution.stdout,
|
||||
stderr: execution.stderr,
|
||||
return {
|
||||
status: 0,
|
||||
stdout: JSON.stringify(findPluginEntry(directory)),
|
||||
stderr: "",
|
||||
}
|
||||
} finally {
|
||||
for (const [key, value] of originalValues) {
|
||||
if (value === undefined) {
|
||||
delete process.env[key]
|
||||
} else {
|
||||
process.env[key] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -74,20 +74,18 @@ while :; do
|
||||
:
|
||||
done
|
||||
`)
|
||||
const originalSetTimeout = globalThis.setTimeout
|
||||
globalThis.setTimeout = ((fn: (...args: unknown[]) => void, _ms?: number) => {
|
||||
const immediateSetTimeout = ((fn: (...args: unknown[]) => void, _ms?: number) => {
|
||||
fn()
|
||||
return unsafeTestValue<ReturnType<typeof setTimeout>>(0)
|
||||
}) as typeof setTimeout
|
||||
|
||||
try {
|
||||
// when
|
||||
const result = await runCommentChecker(createMockInput(), binaryPath)
|
||||
// then
|
||||
expect(result).toEqual({ hasComments: false, message: "" })
|
||||
} finally {
|
||||
globalThis.setTimeout = originalSetTimeout
|
||||
}
|
||||
// when
|
||||
const result = await runCommentChecker(createMockInput(), binaryPath, undefined, {
|
||||
setTimeoutFn: immediateSetTimeout,
|
||||
})
|
||||
|
||||
// then
|
||||
expect(result).toEqual({ hasComments: false, message: "" })
|
||||
})
|
||||
|
||||
test("returns empty result on timeout", async () => {
|
||||
@@ -102,20 +100,18 @@ while :; do
|
||||
:
|
||||
done
|
||||
`)
|
||||
const originalSetTimeout = globalThis.setTimeout
|
||||
globalThis.setTimeout = ((fn: (...args: unknown[]) => void, _ms?: number) => {
|
||||
const immediateSetTimeout = ((fn: (...args: unknown[]) => void, _ms?: number) => {
|
||||
fn()
|
||||
return unsafeTestValue<ReturnType<typeof setTimeout>>(0)
|
||||
}) as typeof setTimeout
|
||||
|
||||
try {
|
||||
// when
|
||||
const result = await runCommentChecker(createMockInput(), binaryPath)
|
||||
// then
|
||||
expect(result).toEqual({ hasComments: false, message: "" })
|
||||
} finally {
|
||||
globalThis.setTimeout = originalSetTimeout
|
||||
}
|
||||
// when
|
||||
const result = await runCommentChecker(createMockInput(), binaryPath, undefined, {
|
||||
setTimeoutFn: immediateSetTimeout,
|
||||
})
|
||||
|
||||
// then
|
||||
expect(result).toEqual({ hasComments: false, message: "" })
|
||||
})
|
||||
|
||||
test("keeps non-timeout flow unchanged", async () => {
|
||||
|
||||
@@ -44,6 +44,13 @@ function findCommentCheckerPathSync(): string | null {
|
||||
// Cached resolved path
|
||||
let resolvedCliPath: string | null = null
|
||||
let initPromise: Promise<string | null> | null = null
|
||||
const stableSetTimeout = globalThis.setTimeout
|
||||
const stableClearTimeout = globalThis.clearTimeout
|
||||
|
||||
type TimerOverrides = {
|
||||
setTimeoutFn?: typeof setTimeout
|
||||
clearTimeoutFn?: typeof clearTimeout
|
||||
}
|
||||
|
||||
/**
|
||||
* Asynchronously get comment-checker binary path.
|
||||
@@ -116,7 +123,12 @@ export type { HookInput, CheckResult }
|
||||
* @param cliPath Optional explicit path to CLI binary
|
||||
* @param customPrompt Optional custom prompt to replace default warning message
|
||||
*/
|
||||
export async function runCommentChecker(input: HookInput, cliPath?: string, customPrompt?: string): Promise<CheckResult> {
|
||||
export async function runCommentChecker(
|
||||
input: HookInput,
|
||||
cliPath?: string,
|
||||
customPrompt?: string,
|
||||
timerOverrides: TimerOverrides = {},
|
||||
): Promise<CheckResult> {
|
||||
const binaryPath = cliPath ?? resolvedCliPath ?? getCommentCheckerPathSync()
|
||||
|
||||
if (!binaryPath) {
|
||||
@@ -135,6 +147,8 @@ export async function runCommentChecker(input: HookInput, cliPath?: string, cust
|
||||
stdout: "pipe",
|
||||
stderr: "pipe",
|
||||
}),
|
||||
setTimeoutFn: timerOverrides.setTimeoutFn ?? stableSetTimeout,
|
||||
clearTimeoutFn: timerOverrides.clearTimeoutFn ?? stableClearTimeout,
|
||||
},
|
||||
)
|
||||
return result
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
/// <reference path="../../../bun-test.d.ts" />
|
||||
|
||||
import { afterAll, beforeAll, beforeEach, describe, expect, mock, test } from "bun:test"
|
||||
import { afterEach, beforeEach, describe, expect, mock, spyOn, test } from "bun:test"
|
||||
|
||||
import * as tmuxRunner from "../../shared/tmux/runner"
|
||||
import * as tmuxPathResolver from "../../tools/interactive-bash/tmux-path-resolver"
|
||||
|
||||
type MockTmuxCommandResult = {
|
||||
success: boolean
|
||||
@@ -21,28 +24,17 @@ const runTmuxCommandMock = mock(
|
||||
)
|
||||
|
||||
const getTmuxPathMock = mock(async (): Promise<string | null> => "/mock/tmux")
|
||||
|
||||
let tmuxModule: typeof import("../tmux")
|
||||
|
||||
beforeAll(async () => {
|
||||
mock.module("../../shared/tmux/runner", () => ({
|
||||
runTmuxCommand: runTmuxCommandMock,
|
||||
}))
|
||||
|
||||
mock.module("../../tools/interactive-bash/tmux-path-resolver", () => ({
|
||||
getTmuxPath: getTmuxPathMock,
|
||||
}))
|
||||
|
||||
tmuxModule = await import("../tmux")
|
||||
})
|
||||
const tmuxModule = await import("../tmux")
|
||||
|
||||
beforeEach(() => {
|
||||
runTmuxCommandMock.mockReset()
|
||||
getTmuxPathMock.mockReset()
|
||||
getTmuxPathMock.mockResolvedValue("/mock/tmux")
|
||||
spyOn(tmuxRunner, "runTmuxCommand").mockImplementation(runTmuxCommandMock)
|
||||
spyOn(tmuxPathResolver, "getTmuxPath").mockImplementation(getTmuxPathMock)
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
afterEach(() => {
|
||||
mock.restore()
|
||||
})
|
||||
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import { runTmuxCommand } from "../shared/tmux/runner"
|
||||
import { getTmuxPath } from "../tools/interactive-bash/tmux-path-resolver"
|
||||
import * as tmuxRunner from "../shared/tmux/runner"
|
||||
import * as tmuxPathResolver from "../tools/interactive-bash/tmux-path-resolver"
|
||||
|
||||
async function runOpenClawTmuxCommand(args: string[]) {
|
||||
const tmuxPath = await getTmuxPath()
|
||||
const tmuxPath = await tmuxPathResolver.getTmuxPath()
|
||||
if (!tmuxPath) {
|
||||
return null
|
||||
}
|
||||
|
||||
return runTmuxCommand(tmuxPath, args)
|
||||
return tmuxRunner.runTmuxCommand(tmuxPath, args)
|
||||
}
|
||||
|
||||
export function getCurrentTmuxSession(): string | null {
|
||||
|
||||
Reference in New Issue
Block a user