diff --git a/src/hooks/auto-update-checker/cache.test.ts b/src/hooks/auto-update-checker/cache.test.ts index 371aef936..a8785cdf9 100644 --- a/src/hooks/auto-update-checker/cache.test.ts +++ b/src/hooks/auto-update-checker/cache.test.ts @@ -1,4 +1,4 @@ -import { afterAll, afterEach, beforeEach, describe, expect, it, mock } from "bun:test" +import { afterEach, beforeEach, describe, expect, it, mock } from "bun:test" import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs" import { join } from "node:path" @@ -6,19 +6,30 @@ const TEST_CACHE_DIR = join(import.meta.dir, "__test-cache__") const TEST_OPENCODE_CACHE_DIR = join(TEST_CACHE_DIR, "opencode") const TEST_USER_CONFIG_DIR = "/tmp/opencode-config" -mock.module("./constants", () => ({ - CACHE_DIR: TEST_OPENCODE_CACHE_DIR, - USER_CONFIG_DIR: TEST_USER_CONFIG_DIR, - PACKAGE_NAME: "oh-my-opencode", -})) +let importCounter = 0 -mock.module("../../shared/logger", () => ({ - log: () => {}, -})) +async function importFreshCacheModule(): Promise { + mock.module("./constants", () => ({ + CACHE_DIR: TEST_OPENCODE_CACHE_DIR, + USER_CONFIG_DIR: TEST_USER_CONFIG_DIR, + PACKAGE_NAME: "oh-my-opencode", + NPM_REGISTRY_URL: "https://registry.npmjs.org/-/package/oh-my-opencode/dist-tags", + NPM_FETCH_TIMEOUT: 5000, + VERSION_FILE: join(TEST_OPENCODE_CACHE_DIR, "version"), + USER_OPENCODE_CONFIG: join(TEST_USER_CONFIG_DIR, "opencode.json"), + USER_OPENCODE_CONFIG_JSONC: join(TEST_USER_CONFIG_DIR, "opencode.jsonc"), + INSTALLED_PACKAGE_JSON: join(TEST_OPENCODE_CACHE_DIR, "node_modules", "oh-my-opencode", "package.json"), + getWindowsAppdataDir: () => null, + })) -afterAll(() => { + mock.module("../../shared/logger", () => ({ + log: () => {}, + })) + + const cacheModule = await import(`./cache?test=${importCounter++}`) mock.restore() -}) + return cacheModule +} function resetTestCache(): void { if (existsSync(TEST_CACHE_DIR)) { @@ -66,7 +77,7 @@ describe("invalidatePackage", () => { }) it("invalidates the installed package from the OpenCode cache directory", async () => { - const { invalidatePackage } = await import("./cache") + const { invalidatePackage } = await importFreshCacheModule() const result = invalidatePackage() diff --git a/src/hooks/auto-update-checker/checker/sync-package-json.test.ts b/src/hooks/auto-update-checker/checker/sync-package-json.test.ts index b808699d3..4f5bd42a5 100644 --- a/src/hooks/auto-update-checker/checker/sync-package-json.test.ts +++ b/src/hooks/auto-update-checker/checker/sync-package-json.test.ts @@ -1,30 +1,34 @@ -import { afterAll, afterEach, beforeEach, describe, expect, it, mock } from "bun:test" +import { afterEach, beforeEach, describe, expect, it, mock } from "bun:test" import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs" import { join } from "node:path" import type { PluginEntryInfo } from "./plugin-entry" const TEST_CACHE_DIR = join(import.meta.dir, "__test-sync-cache__") -mock.module("../constants", () => ({ - CACHE_DIR: TEST_CACHE_DIR, - PACKAGE_NAME: "oh-my-opencode", - NPM_REGISTRY_URL: "https://registry.npmjs.org/-/package/oh-my-opencode/dist-tags", - NPM_FETCH_TIMEOUT: 5000, - VERSION_FILE: join(TEST_CACHE_DIR, "version"), - USER_CONFIG_DIR: "/tmp/opencode-config", - USER_OPENCODE_CONFIG: "/tmp/opencode-config/opencode.json", - USER_OPENCODE_CONFIG_JSONC: "/tmp/opencode-config/opencode.jsonc", - INSTALLED_PACKAGE_JSON: join(TEST_CACHE_DIR, "node_modules", "oh-my-opencode", "package.json"), - getWindowsAppdataDir: () => null, -})) +let importCounter = 0 -mock.module("../../../shared/logger", () => ({ - log: () => {}, -})) +async function importFreshSyncPackageJsonModule(): Promise { + mock.module("../constants", () => ({ + CACHE_DIR: TEST_CACHE_DIR, + PACKAGE_NAME: "oh-my-opencode", + NPM_REGISTRY_URL: "https://registry.npmjs.org/-/package/oh-my-opencode/dist-tags", + NPM_FETCH_TIMEOUT: 5000, + VERSION_FILE: join(TEST_CACHE_DIR, "version"), + USER_CONFIG_DIR: "/tmp/opencode-config", + USER_OPENCODE_CONFIG: "/tmp/opencode-config/opencode.json", + USER_OPENCODE_CONFIG_JSONC: "/tmp/opencode-config/opencode.jsonc", + INSTALLED_PACKAGE_JSON: join(TEST_CACHE_DIR, "node_modules", "oh-my-opencode", "package.json"), + getWindowsAppdataDir: () => null, + })) -afterAll(() => { + mock.module("../../../shared/logger", () => ({ + log: () => {}, + })) + + const syncPackageJsonModule = await import(`./sync-package-json?test=${importCounter++}`) mock.restore() -}) + return syncPackageJsonModule +} function resetTestCache(currentVersion = "3.10.0"): void { if (existsSync(TEST_CACHE_DIR)) { @@ -62,7 +66,7 @@ describe("syncCachePackageJsonToIntent", () => { describe("#given cache package.json with pinned semver version", () => { describe("#when opencode.json intent is latest tag", () => { it("#then updates package.json to use latest", async () => { - const { syncCachePackageJsonToIntent } = await import("./sync-package-json") + const { syncCachePackageJsonToIntent } = await importFreshSyncPackageJsonModule() const pluginInfo: PluginEntryInfo = { entry: "oh-my-opencode@latest", @@ -81,7 +85,7 @@ describe("syncCachePackageJsonToIntent", () => { describe("#when opencode.json intent is next tag", () => { it("#then updates package.json to use next", async () => { - const { syncCachePackageJsonToIntent } = await import("./sync-package-json") + const { syncCachePackageJsonToIntent } = await importFreshSyncPackageJsonModule() const pluginInfo: PluginEntryInfo = { entry: "oh-my-opencode@next", @@ -100,7 +104,7 @@ describe("syncCachePackageJsonToIntent", () => { describe("#when opencode.json has no version (implies latest)", () => { it("#then updates package.json to use latest", async () => { - const { syncCachePackageJsonToIntent } = await import("./sync-package-json") + const { syncCachePackageJsonToIntent } = await importFreshSyncPackageJsonModule() const pluginInfo: PluginEntryInfo = { entry: "oh-my-opencode", @@ -121,7 +125,7 @@ describe("syncCachePackageJsonToIntent", () => { describe("#given cache package.json already matches intent", () => { it("#then returns synced false with no error", async () => { resetTestCache("latest") - const { syncCachePackageJsonToIntent } = await import("./sync-package-json") + const { syncCachePackageJsonToIntent } = await importFreshSyncPackageJsonModule() const pluginInfo: PluginEntryInfo = { entry: "oh-my-opencode@latest", @@ -141,7 +145,7 @@ describe("syncCachePackageJsonToIntent", () => { describe("#given cache package.json does not exist", () => { it("#then returns file_not_found error", async () => { cleanupTestCache() - const { syncCachePackageJsonToIntent } = await import("./sync-package-json") + const { syncCachePackageJsonToIntent } = await importFreshSyncPackageJsonModule() const pluginInfo: PluginEntryInfo = { entry: "oh-my-opencode@latest", @@ -166,7 +170,7 @@ describe("syncCachePackageJsonToIntent", () => { JSON.stringify({ dependencies: { other: "1.0.0" } }, null, 2) ) - const { syncCachePackageJsonToIntent } = await import("./sync-package-json") + const { syncCachePackageJsonToIntent } = await importFreshSyncPackageJsonModule() const pluginInfo: PluginEntryInfo = { entry: "oh-my-opencode@latest", @@ -185,7 +189,7 @@ describe("syncCachePackageJsonToIntent", () => { describe("#given user explicitly changed from one semver to another", () => { it("#then updates package.json to new version", async () => { resetTestCache("3.9.0") - const { syncCachePackageJsonToIntent } = await import("./sync-package-json") + const { syncCachePackageJsonToIntent } = await importFreshSyncPackageJsonModule() const pluginInfo: PluginEntryInfo = { entry: "oh-my-opencode@3.10.0", @@ -204,7 +208,7 @@ describe("syncCachePackageJsonToIntent", () => { describe("#given cache package.json with other dependencies", () => { it("#then other dependencies are preserved when updating plugin version", async () => { - const { syncCachePackageJsonToIntent } = await import("./sync-package-json") + const { syncCachePackageJsonToIntent } = await importFreshSyncPackageJsonModule() const pluginInfo: PluginEntryInfo = { entry: "oh-my-opencode@latest", @@ -230,7 +234,7 @@ describe("syncCachePackageJsonToIntent", () => { mkdirSync(TEST_CACHE_DIR, { recursive: true }) writeFileSync(join(TEST_CACHE_DIR, "package.json"), "{ invalid json }") - const { syncCachePackageJsonToIntent } = await import("./sync-package-json") + const { syncCachePackageJsonToIntent } = await importFreshSyncPackageJsonModule() const pluginInfo: PluginEntryInfo = { entry: "oh-my-opencode@latest", @@ -268,7 +272,7 @@ describe("syncCachePackageJsonToIntent", () => { })) try { - const { syncCachePackageJsonToIntent } = await import("./sync-package-json") + const { syncCachePackageJsonToIntent } = await importFreshSyncPackageJsonModule() const pluginInfo: PluginEntryInfo = { entry: "oh-my-opencode@latest", @@ -318,7 +322,7 @@ describe("syncCachePackageJsonToIntent", () => { })) try { - const { syncCachePackageJsonToIntent } = await import("./sync-package-json") + const { syncCachePackageJsonToIntent } = await importFreshSyncPackageJsonModule() const pluginInfo: PluginEntryInfo = { entry: "oh-my-opencode@latest", diff --git a/src/hooks/auto-update-checker/hook/background-update-check.test.ts b/src/hooks/auto-update-checker/hook/background-update-check.test.ts index 9ba424d0f..e9f177d46 100644 --- a/src/hooks/auto-update-checker/hook/background-update-check.test.ts +++ b/src/hooks/auto-update-checker/hook/background-update-check.test.ts @@ -1,5 +1,5 @@ import type { PluginInput } from "@opencode-ai/plugin" -import { afterAll, beforeEach, describe, expect, it, mock } from "bun:test" +import { beforeEach, describe, expect, it, mock } from "bun:test" type PluginEntry = { entry: string @@ -35,28 +35,29 @@ const mockShowAutoUpdatedToast = mock( const mockSyncCachePackageJsonToIntent = mock(() => false) -mock.module("../checker", () => ({ - findPluginEntry: mockFindPluginEntry, - getCachedVersion: mockGetCachedVersion, - getLatestVersion: mockGetLatestVersion, - revertPinnedVersion: mock(() => false), - syncCachePackageJsonToIntent: mockSyncCachePackageJsonToIntent, -})) -mock.module("../version-channel", () => ({ extractChannel: mockExtractChannel })) -mock.module("../cache", () => ({ invalidatePackage: mockInvalidatePackage })) -mock.module("../../../cli/config-manager", () => ({ runBunInstallWithDetails: mockRunBunInstallWithDetails })) -mock.module("./update-toasts", () => ({ - showUpdateAvailableToast: mockShowUpdateAvailableToast, - showAutoUpdatedToast: mockShowAutoUpdatedToast, -})) -mock.module("../../../shared/logger", () => ({ log: () => {} })) +let importCounter = 0 -afterAll(() => { +async function importFreshBackgroundUpdateCheck(): Promise { + mock.module("../checker", () => ({ + findPluginEntry: mockFindPluginEntry, + getCachedVersion: mockGetCachedVersion, + getLatestVersion: mockGetLatestVersion, + revertPinnedVersion: mock(() => false), + syncCachePackageJsonToIntent: mockSyncCachePackageJsonToIntent, + })) + mock.module("../version-channel", () => ({ extractChannel: mockExtractChannel })) + mock.module("../cache", () => ({ invalidatePackage: mockInvalidatePackage })) + mock.module("../../../cli/config-manager", () => ({ runBunInstallWithDetails: mockRunBunInstallWithDetails })) + mock.module("./update-toasts", () => ({ + showUpdateAvailableToast: mockShowUpdateAvailableToast, + showAutoUpdatedToast: mockShowAutoUpdatedToast, + })) + mock.module("../../../shared/logger", () => ({ log: () => {} })) + + const backgroundUpdateCheckModule = await import(`./background-update-check?test=${importCounter++}`) mock.restore() -}) - -const modulePath = "./background-update-check?test" -const { runBackgroundUpdateCheck } = await import(modulePath) + return backgroundUpdateCheckModule +} describe("runBackgroundUpdateCheck", () => { const mockCtx = { directory: "/test" } as PluginInput @@ -85,6 +86,7 @@ describe("runBackgroundUpdateCheck", () => { describe("#given no plugin entry found", () => { it("returns early without showing any toast", async () => { //#given + const { runBackgroundUpdateCheck } = await importFreshBackgroundUpdateCheck() mockFindPluginEntry.mockReturnValue(null) //#when await runBackgroundUpdateCheck(mockCtx, true, getToastMessage) @@ -99,6 +101,7 @@ describe("runBackgroundUpdateCheck", () => { describe("#given no version available", () => { it("returns early when neither cached nor pinned version exists", async () => { //#given + const { runBackgroundUpdateCheck } = await importFreshBackgroundUpdateCheck() mockFindPluginEntry.mockReturnValue(createPluginEntry({ entry: "oh-my-opencode" })) mockGetCachedVersion.mockReturnValue(null) //#when @@ -114,6 +117,7 @@ describe("runBackgroundUpdateCheck", () => { describe("#given latest version fetch fails", () => { it("returns early without toasts", async () => { //#given + const { runBackgroundUpdateCheck } = await importFreshBackgroundUpdateCheck() mockGetLatestVersion.mockResolvedValue(null) //#when await runBackgroundUpdateCheck(mockCtx, true, getToastMessage) @@ -128,6 +132,7 @@ describe("runBackgroundUpdateCheck", () => { describe("#given already on latest version", () => { it("returns early without any action", async () => { //#given + const { runBackgroundUpdateCheck } = await importFreshBackgroundUpdateCheck() mockGetCachedVersion.mockReturnValue("3.4.0") mockGetLatestVersion.mockResolvedValue("3.4.0") //#when @@ -143,6 +148,7 @@ describe("runBackgroundUpdateCheck", () => { describe("#given update available with autoUpdate disabled", () => { it("shows update notification but does not install", async () => { //#given + const { runBackgroundUpdateCheck } = await importFreshBackgroundUpdateCheck() const autoUpdate = false //#when await runBackgroundUpdateCheck(mockCtx, autoUpdate, getToastMessage) @@ -156,6 +162,7 @@ describe("runBackgroundUpdateCheck", () => { describe("#given user has pinned a specific version", () => { it("shows pinned-version toast without auto-updating", async () => { //#given + const { runBackgroundUpdateCheck } = await importFreshBackgroundUpdateCheck() mockFindPluginEntry.mockReturnValue(createPluginEntry({ isPinned: true, pinnedVersion: "3.4.0" })) //#when await runBackgroundUpdateCheck(mockCtx, true, getToastMessage) @@ -167,6 +174,7 @@ describe("runBackgroundUpdateCheck", () => { it("toast message mentions version pinned", async () => { //#given + const { runBackgroundUpdateCheck } = await importFreshBackgroundUpdateCheck() let capturedToastMessage: ToastMessageGetter | undefined mockFindPluginEntry.mockReturnValue(createPluginEntry({ isPinned: true, pinnedVersion: "3.4.0" })) mockShowUpdateAvailableToast.mockImplementation( @@ -191,6 +199,7 @@ describe("runBackgroundUpdateCheck", () => { describe("#given unpinned with auto-update and install succeeds", () => { it("syncs cache, invalidates, installs, and shows auto-updated toast", async () => { //#given + const { runBackgroundUpdateCheck } = await importFreshBackgroundUpdateCheck() mockRunBunInstallWithDetails.mockResolvedValue({ success: true }) //#when await runBackgroundUpdateCheck(mockCtx, true, getToastMessage) @@ -204,6 +213,7 @@ describe("runBackgroundUpdateCheck", () => { it("syncs before invalidate and install (correct order)", async () => { //#given + const { runBackgroundUpdateCheck } = await importFreshBackgroundUpdateCheck() const callOrder: string[] = [] mockSyncCachePackageJsonToIntent.mockImplementation(() => { callOrder.push("sync") @@ -226,6 +236,7 @@ describe("runBackgroundUpdateCheck", () => { describe("#given unpinned with auto-update and install fails", () => { it("falls back to notification-only toast", async () => { //#given + const { runBackgroundUpdateCheck } = await importFreshBackgroundUpdateCheck() mockRunBunInstallWithDetails.mockResolvedValue({ success: false }) //#when await runBackgroundUpdateCheck(mockCtx, true, getToastMessage) @@ -239,6 +250,7 @@ describe("runBackgroundUpdateCheck", () => { describe("#given sync fails with file_not_found", () => { it("aborts update and shows notification-only toast", async () => { //#given + const { runBackgroundUpdateCheck } = await importFreshBackgroundUpdateCheck() mockSyncCachePackageJsonToIntent.mockReturnValue({ synced: false, error: "file_not_found", @@ -258,6 +270,7 @@ describe("runBackgroundUpdateCheck", () => { describe("#given sync fails with plugin_not_in_deps", () => { it("aborts update and shows notification-only toast", async () => { //#given + const { runBackgroundUpdateCheck } = await importFreshBackgroundUpdateCheck() mockSyncCachePackageJsonToIntent.mockReturnValue({ synced: false, error: "plugin_not_in_deps", @@ -277,6 +290,7 @@ describe("runBackgroundUpdateCheck", () => { describe("#given sync fails with parse_error", () => { it("aborts update and shows notification-only toast", async () => { //#given + const { runBackgroundUpdateCheck } = await importFreshBackgroundUpdateCheck() mockSyncCachePackageJsonToIntent.mockReturnValue({ synced: false, error: "parse_error", @@ -296,6 +310,7 @@ describe("runBackgroundUpdateCheck", () => { describe("#given sync fails with write_error", () => { it("aborts update and shows notification-only toast", async () => { //#given + const { runBackgroundUpdateCheck } = await importFreshBackgroundUpdateCheck() mockSyncCachePackageJsonToIntent.mockReturnValue({ synced: false, error: "write_error", diff --git a/src/hooks/auto-update-checker/hook/workspace-resolution.test.ts b/src/hooks/auto-update-checker/hook/workspace-resolution.test.ts index 720832b26..bc722bdfc 100644 --- a/src/hooks/auto-update-checker/hook/workspace-resolution.test.ts +++ b/src/hooks/auto-update-checker/hook/workspace-resolution.test.ts @@ -1,7 +1,8 @@ import type { PluginInput } from "@opencode-ai/plugin" -import { afterAll, afterEach, beforeEach, describe, expect, it, mock } from "bun:test" +import { afterEach, beforeEach, describe, expect, it, mock, spyOn } from "bun:test" import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs" import { join } from "node:path" +import * as shared from "../../../shared" type PluginEntry = { entry: string @@ -45,68 +46,71 @@ const mockRunBunInstallWithDetails = mock( } ) -mock.module("../checker", () => ({ - findPluginEntry: mockFindPluginEntry, - getCachedVersion: mockGetCachedVersion, - getLatestVersion: mockGetLatestVersion, - revertPinnedVersion: mock(() => false), - syncCachePackageJsonToIntent: mockSyncCachePackageJsonToIntent, -})) -mock.module("../version-channel", () => ({ extractChannel: mockExtractChannel })) -mock.module("../cache", () => ({ invalidatePackage: mockInvalidatePackage })) -mock.module("../../../cli/config-manager", () => ({ - runBunInstallWithDetails: mockRunBunInstallWithDetails, -})) -mock.module("./update-toasts", () => ({ - showUpdateAvailableToast: mockShowUpdateAvailableToast, - showAutoUpdatedToast: mockShowAutoUpdatedToast, -})) -mock.module("../../../shared/logger", () => ({ log: () => {} })) -mock.module("../../../shared", () => ({ - getOpenCodeCacheDir: () => TEST_CACHE_DIR, - getOpenCodeConfigPaths: () => ({ +let importCounter = 0 +let getOpenCodeCacheDirSpy: { mockRestore: () => void } | undefined +let getOpenCodeConfigPathsSpy: { mockRestore: () => void } | undefined + +async function importFreshBackgroundUpdateCheck(): Promise { + mock.module("../checker", () => ({ + findPluginEntry: mockFindPluginEntry, + getCachedVersion: mockGetCachedVersion, + getLatestVersion: mockGetLatestVersion, + revertPinnedVersion: mock(() => false), + syncCachePackageJsonToIntent: mockSyncCachePackageJsonToIntent, + })) + mock.module("../version-channel", () => ({ extractChannel: mockExtractChannel })) + mock.module("../cache", () => ({ invalidatePackage: mockInvalidatePackage })) + mock.module("../../../cli/config-manager", () => ({ + runBunInstallWithDetails: mockRunBunInstallWithDetails, + })) + mock.module("./update-toasts", () => ({ + showUpdateAvailableToast: mockShowUpdateAvailableToast, + showAutoUpdatedToast: mockShowAutoUpdatedToast, + })) + mock.module("../../../shared/logger", () => ({ log: () => {} })) + getOpenCodeCacheDirSpy = spyOn(shared, "getOpenCodeCacheDir").mockReturnValue(TEST_CACHE_DIR) + getOpenCodeConfigPathsSpy = spyOn(shared, "getOpenCodeConfigPaths").mockReturnValue({ configDir: TEST_CONFIG_DIR, configJson: join(TEST_CONFIG_DIR, "opencode.json"), configJsonc: join(TEST_CONFIG_DIR, "opencode.jsonc"), packageJson: join(TEST_CONFIG_DIR, "package.json"), omoConfig: join(TEST_CONFIG_DIR, "oh-my-opencode.json"), - }), - getOpenCodeConfigDir: () => TEST_CONFIG_DIR, -})) + }) -// Mock constants BEFORE importing the module -const ORIGINAL_PACKAGE_NAME = "oh-my-opencode" -mock.module("../constants", () => ({ - PACKAGE_NAME: ORIGINAL_PACKAGE_NAME, - CACHE_DIR: TEST_CACHE_DIR, - USER_CONFIG_DIR: TEST_CONFIG_DIR, -})) + mock.module("../constants", () => ({ + PACKAGE_NAME: "oh-my-opencode", + CACHE_DIR: TEST_CACHE_DIR, + USER_CONFIG_DIR: TEST_CONFIG_DIR, + NPM_REGISTRY_URL: "https://registry.npmjs.org/-/package/oh-my-opencode/dist-tags", + NPM_FETCH_TIMEOUT: 5000, + VERSION_FILE: join(TEST_CACHE_DIR, "version"), + USER_OPENCODE_CONFIG: join(TEST_CONFIG_DIR, "opencode.json"), + USER_OPENCODE_CONFIG_JSONC: join(TEST_CONFIG_DIR, "opencode.jsonc"), + INSTALLED_PACKAGE_JSON: join(TEST_CACHE_DIR, "node_modules", "oh-my-opencode", "package.json"), + getWindowsAppdataDir: () => null, + })) -// Need to mock getOpenCodeCacheDir and getOpenCodeConfigPaths before importing the module -mock.module("../../../shared/data-path", () => ({ - getDataDir: () => join(TEST_DIR, "data"), - getOpenCodeStorageDir: () => join(TEST_DIR, "data", "opencode", "storage"), - getCacheDir: () => TEST_DIR, - getOmoOpenCodeCacheDir: () => join(TEST_DIR, "oh-my-opencode"), - getOpenCodeCacheDir: () => TEST_CACHE_DIR, -})) -mock.module("../../../shared/opencode-config-dir", () => ({ - getOpenCodeConfigDir: () => TEST_CONFIG_DIR, - getOpenCodeConfigPaths: () => ({ - configDir: TEST_CONFIG_DIR, - configJson: join(TEST_CONFIG_DIR, "opencode.json"), - configJsonc: join(TEST_CONFIG_DIR, "opencode.jsonc"), - packageJson: join(TEST_CONFIG_DIR, "package.json"), - omoConfig: join(TEST_CONFIG_DIR, "oh-my-opencode.json"), - }), -})) + mock.module("../../../shared/data-path", () => ({ + getDataDir: () => join(TEST_DIR, "data"), + getOpenCodeStorageDir: () => join(TEST_DIR, "data", "opencode", "storage"), + getCacheDir: () => TEST_DIR, + getOmoOpenCodeCacheDir: () => join(TEST_DIR, "oh-my-opencode"), + getOpenCodeCacheDir: () => TEST_CACHE_DIR, + })) + mock.module("../../../shared/opencode-config-dir", () => ({ + getOpenCodeConfigDir: () => TEST_CONFIG_DIR, + getOpenCodeConfigPaths: () => ({ + configDir: TEST_CONFIG_DIR, + configJson: join(TEST_CONFIG_DIR, "opencode.json"), + configJsonc: join(TEST_CONFIG_DIR, "opencode.jsonc"), + packageJson: join(TEST_CONFIG_DIR, "package.json"), + omoConfig: join(TEST_CONFIG_DIR, "oh-my-opencode.json"), + }), + })) -afterAll(() => { - mock.restore() -}) - -const modulePath = "./background-update-check?test" -const { runBackgroundUpdateCheck } = await import(modulePath) + const backgroundUpdateCheckModule = await import(`./background-update-check?test=${importCounter++}`) + return backgroundUpdateCheckModule +} describe("workspace resolution", () => { const mockCtx = { directory: "/test" } as PluginInput @@ -138,6 +142,11 @@ describe("workspace resolution", () => { }) afterEach(() => { + getOpenCodeCacheDirSpy?.mockRestore() + getOpenCodeConfigPathsSpy?.mockRestore() + getOpenCodeCacheDirSpy = undefined + getOpenCodeConfigPathsSpy = undefined + mock.restore() if (existsSync(TEST_DIR)) { rmSync(TEST_DIR, { recursive: true, force: true }) } @@ -146,6 +155,7 @@ describe("workspace resolution", () => { describe("#given config-dir install exists but cache-dir does not", () => { it("installs to config-dir, not cache-dir", async () => { //#given - config-dir has installation, cache-dir does not + const { runBackgroundUpdateCheck } = await importFreshBackgroundUpdateCheck() mkdirSync(join(TEST_CONFIG_DIR, "node_modules", "oh-my-opencode"), { recursive: true }) writeFileSync( join(TEST_CONFIG_DIR, "package.json"), @@ -171,6 +181,7 @@ describe("workspace resolution", () => { describe("#given both config-dir and cache-dir exist", () => { it("prefers config-dir over cache-dir", async () => { //#given - both directories have installations + const { runBackgroundUpdateCheck } = await importFreshBackgroundUpdateCheck() mkdirSync(join(TEST_CONFIG_DIR, "node_modules", "oh-my-opencode"), { recursive: true }) writeFileSync( join(TEST_CONFIG_DIR, "package.json"), @@ -203,6 +214,7 @@ describe("workspace resolution", () => { describe("#given only cache-dir install exists", () => { it("falls back to cache-dir", async () => { //#given - only cache-dir has installation + const { runBackgroundUpdateCheck } = await importFreshBackgroundUpdateCheck() mkdirSync(join(TEST_CACHE_DIR, "node_modules", "oh-my-opencode"), { recursive: true }) writeFileSync( join(TEST_CACHE_DIR, "package.json"),