fix(tests): resolve 5 cross-file test isolation failures
- model-fallback hook: mock selectFallbackProvider and add _resetForTesting() to test-setup.ts to clear module-level state between files - fallback-retry-handler: add afterAll(mock.restore) and use mockReturnValueOnce to prevent connected-providers mock leaking to subsequent test files - opencode-config-dir: use win32.join for Windows APPDATA path construction so tests pass on macOS (path.join uses POSIX semantics regardless of process.platform override) - system-loaded-version: use resolveSymlink from file-utils instead of realpathSync to handle macOS /var -> /private/var symlink consistently All 4456 tests pass (0 failures) on full bun test suite.
This commit is contained in:
@@ -1,9 +1,10 @@
|
|||||||
import { afterEach, describe, expect, it } from "bun:test"
|
import { afterEach, describe, expect, it } from "bun:test"
|
||||||
import { mkdirSync, mkdtempSync, realpathSync, rmSync, symlinkSync, writeFileSync } from "node:fs"
|
import { mkdirSync, mkdtempSync, rmSync, symlinkSync, writeFileSync } from "node:fs"
|
||||||
import { tmpdir } from "node:os"
|
import { tmpdir } from "node:os"
|
||||||
import { dirname, join } from "node:path"
|
import { dirname, join } from "node:path"
|
||||||
|
|
||||||
import { PACKAGE_NAME } from "../constants"
|
import { PACKAGE_NAME } from "../constants"
|
||||||
|
import { resolveSymlink } from "../../../shared/file-utils"
|
||||||
|
|
||||||
const systemLoadedVersionModulePath = "./system-loaded-version?system-loaded-version-test"
|
const systemLoadedVersionModulePath = "./system-loaded-version?system-loaded-version-test"
|
||||||
|
|
||||||
@@ -125,7 +126,7 @@ describe("system loaded version", () => {
|
|||||||
const loadedVersion = getLoadedPluginVersion()
|
const loadedVersion = getLoadedPluginVersion()
|
||||||
|
|
||||||
//#then
|
//#then
|
||||||
expect(loadedVersion.cacheDir).toBe(realpathSync(symlinkConfigDir))
|
expect(loadedVersion.cacheDir).toBe(resolveSymlink(symlinkConfigDir))
|
||||||
expect(loadedVersion.expectedVersion).toBe("4.5.6")
|
expect(loadedVersion.expectedVersion).toBe("4.5.6")
|
||||||
expect(loadedVersion.loadedVersion).toBe("4.5.6")
|
expect(loadedVersion.loadedVersion).toBe("4.5.6")
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { existsSync, readFileSync, realpathSync } from "node:fs"
|
import { existsSync, readFileSync } from "node:fs"
|
||||||
import { homedir } from "node:os"
|
import { homedir } from "node:os"
|
||||||
import { join } from "node:path"
|
import { join } from "node:path"
|
||||||
|
import { resolveSymlink } from "../../../shared/file-utils"
|
||||||
import { getLatestVersion } from "../../../hooks/auto-update-checker/checker"
|
import { getLatestVersion } from "../../../hooks/auto-update-checker/checker"
|
||||||
import { extractChannel } from "../../../hooks/auto-update-checker"
|
import { extractChannel } from "../../../hooks/auto-update-checker"
|
||||||
import { PACKAGE_NAME } from "../constants"
|
import { PACKAGE_NAME } from "../constants"
|
||||||
@@ -38,12 +38,7 @@ function resolveOpenCodeCacheDir(): string {
|
|||||||
|
|
||||||
function resolveExistingDir(dirPath: string): string {
|
function resolveExistingDir(dirPath: string): string {
|
||||||
if (!existsSync(dirPath)) return dirPath
|
if (!existsSync(dirPath)) return dirPath
|
||||||
|
return resolveSymlink(dirPath)
|
||||||
try {
|
|
||||||
return realpathSync(dirPath)
|
|
||||||
} catch {
|
|
||||||
return dirPath
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function readPackageJson(filePath: string): PackageJsonShape | null {
|
function readPackageJson(filePath: string): PackageJsonShape | null {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { describe, test, expect, mock, beforeEach } from "bun:test"
|
import { afterAll, beforeEach, describe, expect, mock, test } from "bun:test"
|
||||||
|
|
||||||
mock.module("../../shared", () => ({
|
mock.module("../../shared", () => ({
|
||||||
log: mock(() => {}),
|
log: mock(() => {}),
|
||||||
@@ -82,6 +82,10 @@ function createDefaultArgs(taskOverrides: Partial<BackgroundTask> = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
describe("tryFallbackRetry", () => {
|
describe("tryFallbackRetry", () => {
|
||||||
|
afterAll(() => {
|
||||||
|
mock.restore()
|
||||||
|
})
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
;(shouldRetryError as any).mockImplementation(() => true)
|
;(shouldRetryError as any).mockImplementation(() => true)
|
||||||
;(selectFallbackProvider as any).mockImplementation((providers: string[]) => providers[0])
|
;(selectFallbackProvider as any).mockImplementation((providers: string[]) => providers[0])
|
||||||
@@ -274,8 +278,8 @@ describe("tryFallbackRetry", () => {
|
|||||||
|
|
||||||
describe("#given disconnected fallback providers with connected preferred provider", () => {
|
describe("#given disconnected fallback providers with connected preferred provider", () => {
|
||||||
test("keeps fallback entry and selects connected preferred provider", () => {
|
test("keeps fallback entry and selects connected preferred provider", () => {
|
||||||
;(readProviderModelsCache as any).mockReturnValue({ connected: ["provider-a"] })
|
;(readProviderModelsCache as any).mockReturnValueOnce({ connected: ["provider-a"] })
|
||||||
;(selectFallbackProvider as any).mockImplementation(
|
;(selectFallbackProvider as any).mockImplementationOnce(
|
||||||
(_providers: string[], preferredProviderID?: string) => preferredProviderID ?? "provider-b",
|
(_providers: string[], preferredProviderID?: string) => preferredProviderID ?? "provider-b",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,24 @@ const { beforeEach, describe, expect, mock, test } = require("bun:test")
|
|||||||
|
|
||||||
const readConnectedProvidersCacheMock = mock(() => null)
|
const readConnectedProvidersCacheMock = mock(() => null)
|
||||||
const readProviderModelsCacheMock = mock(() => null)
|
const readProviderModelsCacheMock = mock(() => null)
|
||||||
|
const selectFallbackProviderMock = mock((providers: string[], preferredProviderID?: string) => {
|
||||||
|
const connectedProviders = readConnectedProvidersCacheMock()
|
||||||
|
if (connectedProviders) {
|
||||||
|
const connectedSet = new Set(connectedProviders.map((provider: string) => provider.toLowerCase()))
|
||||||
|
|
||||||
|
for (const provider of providers) {
|
||||||
|
if (connectedSet.has(provider.toLowerCase())) {
|
||||||
|
return provider
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (preferredProviderID && connectedSet.has(preferredProviderID.toLowerCase())) {
|
||||||
|
return preferredProviderID
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return providers[0] || preferredProviderID || "opencode"
|
||||||
|
})
|
||||||
const transformModelForProviderMock = mock((provider: string, model: string) => {
|
const transformModelForProviderMock = mock((provider: string, model: string) => {
|
||||||
if (provider === "github-copilot") {
|
if (provider === "github-copilot") {
|
||||||
return model
|
return model
|
||||||
@@ -31,6 +49,10 @@ mock.module("../../shared/provider-model-id-transform", () => ({
|
|||||||
transformModelForProvider: transformModelForProviderMock,
|
transformModelForProvider: transformModelForProviderMock,
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
mock.module("../../shared/model-error-classifier", () => ({
|
||||||
|
selectFallbackProvider: selectFallbackProviderMock,
|
||||||
|
}))
|
||||||
|
|
||||||
import {
|
import {
|
||||||
clearPendingModelFallback,
|
clearPendingModelFallback,
|
||||||
createModelFallbackHook,
|
createModelFallbackHook,
|
||||||
@@ -44,6 +66,7 @@ describe("model fallback hook", () => {
|
|||||||
readProviderModelsCacheMock.mockReturnValue(null)
|
readProviderModelsCacheMock.mockReturnValue(null)
|
||||||
readConnectedProvidersCacheMock.mockClear()
|
readConnectedProvidersCacheMock.mockClear()
|
||||||
readProviderModelsCacheMock.mockClear()
|
readProviderModelsCacheMock.mockClear()
|
||||||
|
selectFallbackProviderMock.mockClear()
|
||||||
|
|
||||||
clearPendingModelFallback("ses_model_fallback_main")
|
clearPendingModelFallback("ses_model_fallback_main")
|
||||||
clearPendingModelFallback("ses_model_fallback_ghcp")
|
clearPendingModelFallback("ses_model_fallback_ghcp")
|
||||||
|
|||||||
@@ -274,3 +274,13 @@ export function createModelFallbackHook(args?: { toast?: FallbackToast; onApplie
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets all module-global state for testing.
|
||||||
|
* Clears pending fallbacks, toast keys, and session chains.
|
||||||
|
*/
|
||||||
|
export function _resetForTesting(): void {
|
||||||
|
pendingModelFallbacks.clear()
|
||||||
|
lastToastKey.clear()
|
||||||
|
sessionFallbackChains.clear()
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { describe, test, expect, beforeEach, afterEach } from "bun:test"
|
import { describe, test, expect, beforeEach, afterEach } from "bun:test"
|
||||||
import { homedir } from "node:os"
|
import { homedir } from "node:os"
|
||||||
import { join, resolve } from "node:path"
|
import { join, resolve, win32 } from "node:path"
|
||||||
import {
|
import {
|
||||||
getOpenCodeConfigDir,
|
getOpenCodeConfigDir,
|
||||||
getOpenCodeConfigPaths,
|
getOpenCodeConfigPaths,
|
||||||
@@ -241,9 +241,10 @@ describe("opencode-config-dir", () => {
|
|||||||
// when getOpenCodeConfigDir is called with binary="opencode-desktop"
|
// when getOpenCodeConfigDir is called with binary="opencode-desktop"
|
||||||
const result = getOpenCodeConfigDir({ binary: "opencode-desktop", version: "1.0.200", checkExisting: false })
|
const result = getOpenCodeConfigDir({ binary: "opencode-desktop", version: "1.0.200", checkExisting: false })
|
||||||
|
|
||||||
// then returns %APPDATA%/ai.opencode.desktop
|
// then returns %APPDATA%/ai.opencode.desktop using Windows path semantics
|
||||||
expect(result).toBe(join("C:\\Users\\TestUser\\AppData\\Roaming", TAURI_APP_IDENTIFIER))
|
expect(result).toBe(win32.join("C:\\Users\\TestUser\\AppData\\Roaming", TAURI_APP_IDENTIFIER))
|
||||||
})
|
})
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe("dev build detection", () => {
|
describe("dev build detection", () => {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { existsSync, realpathSync } from "node:fs"
|
import { existsSync, realpathSync } from "node:fs"
|
||||||
import { homedir } from "node:os"
|
import { homedir } from "node:os"
|
||||||
import { join, resolve } from "node:path"
|
import { join, resolve, win32 } from "node:path"
|
||||||
|
|
||||||
import type {
|
import type {
|
||||||
OpenCodeBinaryType,
|
OpenCodeBinaryType,
|
||||||
@@ -31,7 +31,7 @@ function getTauriConfigDir(identifier: string): string {
|
|||||||
|
|
||||||
case "win32": {
|
case "win32": {
|
||||||
const appData = process.env.APPDATA || join(homedir(), "AppData", "Roaming")
|
const appData = process.env.APPDATA || join(homedir(), "AppData", "Roaming")
|
||||||
return join(appData, identifier)
|
return win32.join(appData, identifier)
|
||||||
}
|
}
|
||||||
|
|
||||||
case "linux":
|
case "linux":
|
||||||
@@ -71,7 +71,10 @@ export function getOpenCodeConfigDir(options: OpenCodeConfigDirOptions): string
|
|||||||
}
|
}
|
||||||
|
|
||||||
const identifier = isDevBuild(version) ? TAURI_APP_IDENTIFIER_DEV : TAURI_APP_IDENTIFIER
|
const identifier = isDevBuild(version) ? TAURI_APP_IDENTIFIER_DEV : TAURI_APP_IDENTIFIER
|
||||||
const tauriDir = resolveConfigPath(getTauriConfigDir(identifier))
|
const tauriDirBase = getTauriConfigDir(identifier)
|
||||||
|
const tauriDir = process.platform === "win32"
|
||||||
|
? (win32.isAbsolute(tauriDirBase) ? win32.normalize(tauriDirBase) : win32.resolve(tauriDirBase))
|
||||||
|
: resolveConfigPath(tauriDirBase)
|
||||||
|
|
||||||
if (checkExisting) {
|
if (checkExisting) {
|
||||||
const legacyDir = getCliConfigDir()
|
const legacyDir = getCliConfigDir()
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
import { beforeEach } from "bun:test"
|
import { beforeEach } from "bun:test"
|
||||||
import { _resetForTesting } from "./src/features/claude-code-session-state/state"
|
import { _resetForTesting as resetClaudeSessionState } from "./src/features/claude-code-session-state/state"
|
||||||
|
import { _resetForTesting as resetModelFallbackState } from "./src/hooks/model-fallback/hook"
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
_resetForTesting()
|
resetClaudeSessionState()
|
||||||
|
resetModelFallbackState()
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user