Files
oh-my-openagent/src/cli/install.test.ts
YeonGyu-Kim 6e9128e060 fix: add fetch mock to install test to prevent CI timeout
The first test case 'non-TUI mode: should show warning but continue when
OpenCode binary not found' was missing a globalThis.fetch mock, causing it
to make a real HTTP request to npm registry via fetchNpmDistTags().
The npm fetch timeout (5s) collided with the test timeout (5s), causing
flaky CI failures.

Added the same fetch mock pattern already used by the other two test cases.
Test runtime dropped from 5000ms+ to ~2ms.
2026-03-23 20:03:45 +09:00

160 lines
5.0 KiB
TypeScript

import { describe, expect, test, mock, beforeEach, afterEach, spyOn } from "bun:test"
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs"
import { tmpdir } from "node:os"
import { join } from "node:path"
import { install } from "./install"
import * as configManager from "./config-manager"
import type { InstallArgs } from "./types"
// Mock console methods to capture output
const mockConsoleLog = mock(() => {})
const mockConsoleError = mock(() => {})
describe("install CLI - binary check behavior", () => {
let tempDir: string
let originalEnv: string | undefined
let isOpenCodeInstalledSpy: ReturnType<typeof spyOn>
let getOpenCodeVersionSpy: ReturnType<typeof spyOn>
beforeEach(() => {
// given temporary config directory
tempDir = join(tmpdir(), `omo-test-${Date.now()}-${Math.random().toString(36).slice(2)}`)
mkdirSync(tempDir, { recursive: true })
originalEnv = process.env.OPENCODE_CONFIG_DIR
process.env.OPENCODE_CONFIG_DIR = tempDir
// Reset config context
configManager.resetConfigContext()
configManager.initConfigContext("opencode", null)
// Capture console output
console.log = mockConsoleLog
mockConsoleLog.mockClear()
})
afterEach(() => {
if (originalEnv !== undefined) {
process.env.OPENCODE_CONFIG_DIR = originalEnv
} else {
delete process.env.OPENCODE_CONFIG_DIR
}
if (existsSync(tempDir)) {
rmSync(tempDir, { recursive: true, force: true })
}
isOpenCodeInstalledSpy?.mockRestore()
getOpenCodeVersionSpy?.mockRestore()
})
test("non-TUI mode: should show warning but continue when OpenCode binary not found", async () => {
// given OpenCode binary is NOT installed
isOpenCodeInstalledSpy = spyOn(configManager, "isOpenCodeInstalled").mockResolvedValue(false)
getOpenCodeVersionSpy = spyOn(configManager, "getOpenCodeVersion").mockResolvedValue(null)
// given mock npm fetch
globalThis.fetch = mock(() =>
Promise.resolve({
ok: true,
json: () => Promise.resolve({ latest: "3.0.0" }),
} as Response)
) as unknown as typeof fetch
const args: InstallArgs = {
tui: false,
claude: "yes",
openai: "no",
gemini: "no",
copilot: "no",
opencodeZen: "no",
zaiCodingPlan: "no",
}
// when running install
const exitCode = await install(args)
// then should return success (0), not failure (1)
expect(exitCode).toBe(0)
// then should have printed a warning (not error)
const allCalls = mockConsoleLog.mock.calls.flat().join("\n")
expect(allCalls).toContain("[!]") // warning symbol
expect(allCalls).toContain("OpenCode")
})
test("non-TUI mode: should create opencode.json with plugin even when binary not found", async () => {
// given OpenCode binary is NOT installed
isOpenCodeInstalledSpy = spyOn(configManager, "isOpenCodeInstalled").mockResolvedValue(false)
getOpenCodeVersionSpy = spyOn(configManager, "getOpenCodeVersion").mockResolvedValue(null)
// given mock npm fetch
globalThis.fetch = mock(() =>
Promise.resolve({
ok: true,
json: () => Promise.resolve({ latest: "3.0.0" }),
} as Response)
) as unknown as typeof fetch
const args: InstallArgs = {
tui: false,
claude: "yes",
openai: "no",
gemini: "no",
copilot: "no",
opencodeZen: "no",
zaiCodingPlan: "no",
}
// when running install
const exitCode = await install(args)
// then should create opencode.json
const configPath = join(tempDir, "opencode.json")
expect(existsSync(configPath)).toBe(true)
// then opencode.json should have plugin entry
const config = JSON.parse(readFileSync(configPath, "utf-8"))
expect(config.plugin).toBeDefined()
expect(config.plugin.some((p: string) => p.includes("oh-my-opencode"))).toBe(true)
// then exit code should be 0 (success)
expect(exitCode).toBe(0)
})
test("non-TUI mode: should still succeed and complete all steps when binary exists", async () => {
// given OpenCode binary IS installed
isOpenCodeInstalledSpy = spyOn(configManager, "isOpenCodeInstalled").mockResolvedValue(true)
getOpenCodeVersionSpy = spyOn(configManager, "getOpenCodeVersion").mockResolvedValue("1.0.200")
// given mock npm fetch
globalThis.fetch = mock(() =>
Promise.resolve({
ok: true,
json: () => Promise.resolve({ latest: "3.0.0" }),
} as Response)
) as unknown as typeof fetch
const args: InstallArgs = {
tui: false,
claude: "yes",
openai: "no",
gemini: "no",
copilot: "no",
opencodeZen: "no",
zaiCodingPlan: "no",
}
// when running install
const exitCode = await install(args)
// then should return success
expect(exitCode).toBe(0)
// then should have printed success (OK symbol)
const allCalls = mockConsoleLog.mock.calls.flat().join("\n")
expect(allCalls).toContain("[OK]")
expect(allCalls).toContain("OpenCode 1.0.200")
})
})