test(auto-update): isolate cached version resolution

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
deopa0402
2026-04-29 00:53:21 +09:00
committed by 전나맛있는어쩌고힙한하루보내세요
parent 37d9d613b6
commit 9758168676
2 changed files with 46 additions and 37 deletions
@@ -1,7 +1,8 @@
import { afterEach, beforeEach, describe, expect, it, mock } from "bun:test" import { afterEach, beforeEach, describe, expect, it } from "bun:test"
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs" import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs"
import { tmpdir } from "node:os" import { tmpdir } from "node:os"
import { join } from "node:path" import { join } from "node:path"
import { getCachedVersion } from "./cached-version"
// Hold mutable mock state so beforeEach can swap the cache root for each test. // Hold mutable mock state so beforeEach can swap the cache root for each test.
const mockState: { candidates: string[]; walkUpResult: string | null } = { const mockState: { candidates: string[]; walkUpResult: string | null } = {
@@ -9,27 +10,14 @@ const mockState: { candidates: string[]; walkUpResult: string | null } = {
walkUpResult: null, walkUpResult: null,
} }
mock.module("../constants", () => ({ function getIsolatedCachedVersion(): string | null {
INSTALLED_PACKAGE_JSON_CANDIDATES: new Proxy([], { return getCachedVersion({
get(_, prop) { packageJsonCandidates: mockState.candidates,
const current = mockState.candidates findPackageJson: () => null,
// Forward array methods/properties to the mutable candidates list currentDir: null,
// so getCachedVersion's `for (... of ...)` sees fresh data per test. execDir: null,
const value = (unsafeTestValue<Record<PropertyKey, unknown>>(current))[prop] })
if (typeof value === "function") { }
return (value as (...args: unknown[]) => unknown).bind(current)
}
return value
},
}),
}))
mock.module("./package-json-locator", () => ({
findPackageJsonUp: () => mockState.walkUpResult,
}))
import { getCachedVersion } from "./cached-version"
import { unsafeTestValue } from "../../../../test-support/unsafe-test-value"
describe("getCachedVersion (GH-3257)", () => { describe("getCachedVersion (GH-3257)", () => {
let cacheRoot: string let cacheRoot: string
@@ -54,7 +42,7 @@ describe("getCachedVersion (GH-3257)", () => {
mkdirSync(pkgDir, { recursive: true }) mkdirSync(pkgDir, { recursive: true })
writeFileSync(join(pkgDir, "package.json"), JSON.stringify({ name: "oh-my-opencode", version: "3.16.0" })) writeFileSync(join(pkgDir, "package.json"), JSON.stringify({ name: "oh-my-opencode", version: "3.16.0" }))
expect(getCachedVersion()).toBe("3.16.0") expect(getIsolatedCachedVersion()).toBe("3.16.0")
}) })
it("returns the version when the package is installed under oh-my-openagent", () => { it("returns the version when the package is installed under oh-my-openagent", () => {
@@ -65,7 +53,7 @@ describe("getCachedVersion (GH-3257)", () => {
mkdirSync(pkgDir, { recursive: true }) mkdirSync(pkgDir, { recursive: true })
writeFileSync(join(pkgDir, "package.json"), JSON.stringify({ name: "oh-my-openagent", version: "3.16.0" })) writeFileSync(join(pkgDir, "package.json"), JSON.stringify({ name: "oh-my-openagent", version: "3.16.0" }))
expect(getCachedVersion()).toBe("3.16.0") expect(getIsolatedCachedVersion()).toBe("3.16.0")
}) })
it("prefers oh-my-opencode when both are installed", () => { it("prefers oh-my-opencode when both are installed", () => {
@@ -77,11 +65,11 @@ describe("getCachedVersion (GH-3257)", () => {
mkdirSync(aliasDir, { recursive: true }) mkdirSync(aliasDir, { recursive: true })
writeFileSync(join(aliasDir, "package.json"), JSON.stringify({ name: "oh-my-openagent", version: "3.15.0" })) writeFileSync(join(aliasDir, "package.json"), JSON.stringify({ name: "oh-my-openagent", version: "3.15.0" }))
expect(getCachedVersion()).toBe("3.16.0") expect(getIsolatedCachedVersion()).toBe("3.16.0")
}) })
it("returns null when neither candidate exists and fallbacks find nothing", () => { it("returns null when neither candidate exists and fallbacks find nothing", () => {
expect(getCachedVersion()).toBeNull() expect(getIsolatedCachedVersion()).toBeNull()
}) })
it("prefers the loaded module's package.json over flat-install candidates", () => { it("prefers the loaded module's package.json over flat-install candidates", () => {
@@ -100,6 +88,13 @@ describe("getCachedVersion (GH-3257)", () => {
mkdirSync(flatDir, { recursive: true }) mkdirSync(flatDir, { recursive: true })
writeFileSync(join(flatDir, "package.json"), JSON.stringify({ name: "oh-my-opencode", version: "3.17.6" })) writeFileSync(join(flatDir, "package.json"), JSON.stringify({ name: "oh-my-opencode", version: "3.17.6" }))
expect(getCachedVersion()).toBe("3.17.5") expect(
getCachedVersion({
packageJsonCandidates: mockState.candidates,
findPackageJson: () => mockState.walkUpResult,
currentDir: sandboxDir,
execDir: null,
})
).toBe("3.17.5")
}) })
}) })
@@ -6,13 +6,23 @@ import type { PackageJson } from "../types"
import { INSTALLED_PACKAGE_JSON_CANDIDATES } from "../constants" import { INSTALLED_PACKAGE_JSON_CANDIDATES } from "../constants"
import { findPackageJsonUp } from "./package-json-locator" import { findPackageJsonUp } from "./package-json-locator"
interface CachedVersionOptions {
packageJsonCandidates?: readonly string[]
findPackageJson?: (startPath: string) => string | null
currentDir?: string | null
execDir?: string | null
}
function readPackageVersion(packageJsonPath: string): string | null { function readPackageVersion(packageJsonPath: string): string | null {
const content = fs.readFileSync(packageJsonPath, "utf-8") const content = fs.readFileSync(packageJsonPath, "utf-8")
const pkg = JSON.parse(content) as PackageJson const pkg = JSON.parse(content) as PackageJson
return pkg.version ?? null return pkg.version ?? null
} }
export function getCachedVersion(): string | null { export function getCachedVersion(options: CachedVersionOptions = {}): string | null {
const packageJsonCandidates = options.packageJsonCandidates ?? INSTALLED_PACKAGE_JSON_CANDIDATES
const findPackageJson = options.findPackageJson ?? findPackageJsonUp
// Walk up from the loaded module first. OpenCode loads plugins from a // Walk up from the loaded module first. OpenCode loads plugins from a
// per-plugin sandbox at <CACHE_DIR>/<plugin-entry>/node_modules/<pkg>/, while // per-plugin sandbox at <CACHE_DIR>/<plugin-entry>/node_modules/<pkg>/, while
// a parallel flat install at <CACHE_DIR>/node_modules/<pkg>/ can drift // a parallel flat install at <CACHE_DIR>/node_modules/<pkg>/ can drift
@@ -20,16 +30,18 @@ export function getCachedVersion(): string | null {
// first means the toast can announce a version the runtime isn't running. // first means the toast can announce a version the runtime isn't running.
// The module-relative walk-up always reflects what is actually loaded. // The module-relative walk-up always reflects what is actually loaded.
try { try {
const currentDir = path.dirname(fileURLToPath(import.meta.url)) const currentDir = options.currentDir === undefined ? path.dirname(fileURLToPath(import.meta.url)) : options.currentDir
const pkgPath = findPackageJsonUp(currentDir) if (currentDir) {
if (pkgPath) { const pkgPath = findPackageJson(currentDir)
return readPackageVersion(pkgPath) if (pkgPath) {
return readPackageVersion(pkgPath)
}
} }
} catch (err) { } catch (err) {
log("[auto-update-checker] Failed to resolve version from current directory:", err) log("[auto-update-checker] Failed to resolve version from current directory:", err)
} }
for (const candidate of INSTALLED_PACKAGE_JSON_CANDIDATES) { for (const candidate of packageJsonCandidates) {
try { try {
if (fs.existsSync(candidate)) { if (fs.existsSync(candidate)) {
return readPackageVersion(candidate) return readPackageVersion(candidate)
@@ -40,10 +52,12 @@ export function getCachedVersion(): string | null {
} }
try { try {
const execDir = path.dirname(fs.realpathSync(process.execPath)) const execDir = options.execDir === undefined ? path.dirname(fs.realpathSync(process.execPath)) : options.execDir
const pkgPath = findPackageJsonUp(execDir) if (execDir) {
if (pkgPath) { const pkgPath = findPackageJson(execDir)
return readPackageVersion(pkgPath) if (pkgPath) {
return readPackageVersion(pkgPath)
}
} }
} catch (err) { } catch (err) {
log("[auto-update-checker] Failed to resolve version from execPath:", err) log("[auto-update-checker] Failed to resolve version from execPath:", err)