From ad025ee0f835445f59078978b9d7ecd1c09e2ad5 Mon Sep 17 00:00:00 2001 From: YeonGyu-Kim Date: Sat, 4 Apr 2026 19:20:40 +0900 Subject: [PATCH] test: isolate flaky shared-state tests --- src/agents/utils.test.ts | 14 +- src/shared/connected-providers-cache.test.ts | 406 ++++++++++--------- src/shared/legacy-plugin-warning.test.ts | 131 +++--- 3 files changed, 314 insertions(+), 237 deletions(-) diff --git a/src/agents/utils.test.ts b/src/agents/utils.test.ts index a37a8c710..5f3af762f 100644 --- a/src/agents/utils.test.ts +++ b/src/agents/utils.test.ts @@ -1,6 +1,6 @@ /// -import { describe, test, expect, beforeEach, afterEach, spyOn } from "bun:test" +import { describe, test, expect, beforeEach, afterEach, spyOn, mock } from "bun:test" import { createBuiltinAgents } from "./builtin-agents" import type { AgentConfig } from "@opencode-ai/sdk" import { clearSkillCache } from "../features/opencode-skill-loader/skill-content" @@ -10,6 +10,18 @@ import * as shared from "../shared" const TEST_DEFAULT_MODEL = "anthropic/claude-opus-4-6" +beforeEach(() => { + mock.restore() + clearSkillCache() + connectedProvidersCache._resetMemCacheForTesting() +}) + +afterEach(() => { + clearSkillCache() + connectedProvidersCache._resetMemCacheForTesting() + mock.restore() +}) + describe("createBuiltinAgents with model overrides", () => { test("Sisyphus with default model has thinking config when all models available", async () => { // #given diff --git a/src/shared/connected-providers-cache.test.ts b/src/shared/connected-providers-cache.test.ts index 73c905d25..1f20255de 100644 --- a/src/shared/connected-providers-cache.test.ts +++ b/src/shared/connected-providers-cache.test.ts @@ -1,148 +1,171 @@ /// -import { beforeEach, afterEach, describe, expect, test } from "bun:test" +import { describe, expect, test } from "bun:test" import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs" import { tmpdir } from "node:os" import { join } from "node:path" -import { - createConnectedProvidersCacheStore, - findProviderModelMetadata, -} from "./connected-providers-cache" +import { createConnectedProvidersCacheStore, findProviderModelMetadata } from "./connected-providers-cache" -let fakeUserCacheRoot = "" -let testCacheDir = "" -let testCacheStore: ReturnType +function createTestCacheContext() { + const fakeUserCacheRoot = mkdtempSync(join(tmpdir(), "connected-providers-user-cache-")) + const testCacheDir = join(fakeUserCacheRoot, "oh-my-opencode") + const testCacheStore = createConnectedProvidersCacheStore(() => testCacheDir) + + return { + fakeUserCacheRoot, + testCacheDir, + testCacheStore, + } +} + +function cleanupTestCacheContext(fakeUserCacheRoot: string): void { + if (existsSync(fakeUserCacheRoot)) { + rmSync(fakeUserCacheRoot, { recursive: true, force: true }) + } +} describe("updateConnectedProvidersCache", () => { - beforeEach(() => { - fakeUserCacheRoot = mkdtempSync(join(tmpdir(), "connected-providers-user-cache-")) - testCacheDir = join(fakeUserCacheRoot, "oh-my-opencode") - testCacheStore = createConnectedProvidersCacheStore(() => testCacheDir) - }) - - afterEach(() => { - if (existsSync(fakeUserCacheRoot)) { - rmSync(fakeUserCacheRoot, { recursive: true, force: true }) - } - fakeUserCacheRoot = "" - testCacheDir = "" - }) - test("extracts models from provider.list().all response", async () => { - //#given - const mockClient = { - provider: { - list: async () => ({ - data: { - connected: ["openai", "anthropic"], - all: [ - { - id: "openai", - name: "OpenAI", - env: [], - models: { - "gpt-5.3-codex": { id: "gpt-5.3-codex", name: "GPT-5.3 Codex" }, - "gpt-5.4": { id: "gpt-5.4", name: "GPT-5.4" }, + const { testCacheStore, fakeUserCacheRoot } = createTestCacheContext() + + try { + //#given + const mockClient = { + provider: { + list: async () => ({ + data: { + connected: ["openai", "anthropic"], + all: [ + { + id: "openai", + name: "OpenAI", + env: [], + models: { + "gpt-5.3-codex": { id: "gpt-5.3-codex", name: "GPT-5.3 Codex" }, + "gpt-5.4": { id: "gpt-5.4", name: "GPT-5.4" }, + }, }, - }, - { - id: "anthropic", - name: "Anthropic", - env: [], - models: { - "claude-opus-4-6": { id: "claude-opus-4-6", name: "Claude Opus 4.6" }, - "claude-sonnet-4-6": { id: "claude-sonnet-4-6", name: "Claude Sonnet 4.6" }, + { + id: "anthropic", + name: "Anthropic", + env: [], + models: { + "claude-opus-4-6": { id: "claude-opus-4-6", name: "Claude Opus 4.6" }, + "claude-sonnet-4-6": { id: "claude-sonnet-4-6", name: "Claude Sonnet 4.6" }, + }, }, - }, - ], - }, - }), - }, + ], + }, + }), + }, + } + + //#when + await testCacheStore.updateConnectedProvidersCache(mockClient) + + //#then + const cache = testCacheStore.readProviderModelsCache() + expect(cache).not.toBeNull() + expect(cache!.connected).toEqual(["openai", "anthropic"]) + expect(cache!.models).toEqual({ + openai: [ + { id: "gpt-5.3-codex", name: "GPT-5.3 Codex" }, + { id: "gpt-5.4", name: "GPT-5.4" }, + ], + anthropic: [ + { id: "claude-opus-4-6", name: "Claude Opus 4.6" }, + { id: "claude-sonnet-4-6", name: "Claude Sonnet 4.6" }, + ], + }) + } finally { + cleanupTestCacheContext(fakeUserCacheRoot) } - - //#when - await testCacheStore.updateConnectedProvidersCache(mockClient) - - //#then - const cache = testCacheStore.readProviderModelsCache() - expect(cache).not.toBeNull() - expect(cache!.connected).toEqual(["openai", "anthropic"]) - expect(cache!.models).toEqual({ - openai: [ - { id: "gpt-5.3-codex", name: "GPT-5.3 Codex" }, - { id: "gpt-5.4", name: "GPT-5.4" }, - ], - anthropic: [ - { id: "claude-opus-4-6", name: "Claude Opus 4.6" }, - { id: "claude-sonnet-4-6", name: "Claude Sonnet 4.6" }, - ], - }) }) test("writes empty models when provider has no models", async () => { - //#given - const mockClient = { - provider: { - list: async () => ({ - data: { - connected: ["empty-provider"], - all: [ - { - id: "empty-provider", - name: "Empty", - env: [], - models: {}, - }, - ], - }, - }), - }, + const { testCacheStore, fakeUserCacheRoot } = createTestCacheContext() + + try { + //#given + const mockClient = { + provider: { + list: async () => ({ + data: { + connected: ["empty-provider"], + all: [ + { + id: "empty-provider", + name: "Empty", + env: [], + models: {}, + }, + ], + }, + }), + }, + } + + //#when + await testCacheStore.updateConnectedProvidersCache(mockClient) + + //#then + const cache = testCacheStore.readProviderModelsCache() + expect(cache).not.toBeNull() + expect(cache!.models).toEqual({}) + } finally { + cleanupTestCacheContext(fakeUserCacheRoot) } - - //#when - await testCacheStore.updateConnectedProvidersCache(mockClient) - - //#then - const cache = testCacheStore.readProviderModelsCache() - expect(cache).not.toBeNull() - expect(cache!.models).toEqual({}) }) test("writes empty models when all field is missing", async () => { - //#given - const mockClient = { - provider: { - list: async () => ({ - data: { - connected: ["openai"], - }, - }), - }, + const { testCacheStore, fakeUserCacheRoot } = createTestCacheContext() + + try { + //#given + const mockClient = { + provider: { + list: async () => ({ + data: { + connected: ["openai"], + }, + }), + }, + } + + //#when + await testCacheStore.updateConnectedProvidersCache(mockClient) + + //#then + const cache = testCacheStore.readProviderModelsCache() + expect(cache).not.toBeNull() + expect(cache!.models).toEqual({}) + } finally { + cleanupTestCacheContext(fakeUserCacheRoot) } - - //#when - await testCacheStore.updateConnectedProvidersCache(mockClient) - - //#then - const cache = testCacheStore.readProviderModelsCache() - expect(cache).not.toBeNull() - expect(cache!.models).toEqual({}) }) test("does nothing when client.provider.list is not available", async () => { - //#given - const mockClient = {} + const { testCacheStore, fakeUserCacheRoot } = createTestCacheContext() - //#when - await testCacheStore.updateConnectedProvidersCache(mockClient) + try { + //#given + const mockClient = {} - //#then - const cache = testCacheStore.readProviderModelsCache() - expect(cache).toBeNull() + //#when + await testCacheStore.updateConnectedProvidersCache(mockClient) + + //#then + const cache = testCacheStore.readProviderModelsCache() + expect(cache).toBeNull() + } finally { + cleanupTestCacheContext(fakeUserCacheRoot) + } }) test("does not remove unrelated files in the cache directory", async () => { + const { testCacheStore, fakeUserCacheRoot } = createTestCacheContext() + //#given const realCacheDir = join(fakeUserCacheRoot, "oh-my-opencode") const sentinelPath = join(realCacheDir, "connected-providers-cache.test-sentinel.json") @@ -179,88 +202,101 @@ describe("updateConnectedProvidersCache", () => { if (existsSync(sentinelPath)) { rmSync(sentinelPath, { force: true }) } + cleanupTestCacheContext(fakeUserCacheRoot) } }) test("findProviderModelMetadata returns rich cached metadata", async () => { - //#given - const mockClient = { - provider: { - list: async () => ({ - data: { - connected: ["openai"], - all: [ - { - id: "openai", - models: { - "gpt-5.4": { - id: "gpt-5.4", - name: "GPT-5.4", - temperature: false, - variants: { - low: {}, - high: {}, + const { testCacheStore, fakeUserCacheRoot } = createTestCacheContext() + + try { + //#given + const mockClient = { + provider: { + list: async () => ({ + data: { + connected: ["openai"], + all: [ + { + id: "openai", + models: { + "gpt-5.4": { + id: "gpt-5.4", + name: "GPT-5.4", + temperature: false, + variants: { + low: {}, + high: {}, + }, + limit: { output: 128000 }, }, - limit: { output: 128000 }, }, }, - }, - ], - }, - }), - }, + ], + }, + }), + }, + } + + await testCacheStore.updateConnectedProvidersCache(mockClient) + const cache = testCacheStore.readProviderModelsCache() + + //#when + const result = findProviderModelMetadata("openai", "gpt-5.4", cache) + + //#then + expect(result).toEqual({ + id: "gpt-5.4", + name: "GPT-5.4", + temperature: false, + variants: { + low: {}, + high: {}, + }, + limit: { output: 128000 }, + }) + } finally { + cleanupTestCacheContext(fakeUserCacheRoot) } - - await testCacheStore.updateConnectedProvidersCache(mockClient) - const cache = testCacheStore.readProviderModelsCache() - - //#when - const result = findProviderModelMetadata("openai", "gpt-5.4", cache) - - //#then - expect(result).toEqual({ - id: "gpt-5.4", - name: "GPT-5.4", - temperature: false, - variants: { - low: {}, - high: {}, - }, - limit: { output: 128000 }, - }) }) test("keeps normalized fallback ids when raw metadata id is not a string", async () => { - const mockClient = { - provider: { - list: async () => ({ - data: { - connected: ["openai"], - all: [ - { - id: "openai", - models: { - "o3-mini": { - id: 123, - name: "o3-mini", + const { testCacheStore, fakeUserCacheRoot } = createTestCacheContext() + + try { + const mockClient = { + provider: { + list: async () => ({ + data: { + connected: ["openai"], + all: [ + { + id: "openai", + models: { + "o3-mini": { + id: 123, + name: "o3-mini", + }, }, }, - }, - ], - }, - }), - }, + ], + }, + }), + }, + } + + await testCacheStore.updateConnectedProvidersCache(mockClient) + const cache = testCacheStore.readProviderModelsCache() + + expect(cache?.models.openai).toEqual([ + { id: "o3-mini", name: "o3-mini" }, + ]) + expect(findProviderModelMetadata("openai", "o3-mini", cache)).toEqual({ + id: "o3-mini", + name: "o3-mini", + }) + } finally { + cleanupTestCacheContext(fakeUserCacheRoot) } - - await testCacheStore.updateConnectedProvidersCache(mockClient) - const cache = testCacheStore.readProviderModelsCache() - - expect(cache?.models.openai).toEqual([ - { id: "o3-mini", name: "o3-mini" }, - ]) - expect(findProviderModelMetadata("openai", "o3-mini", cache)).toEqual({ - id: "o3-mini", - name: "o3-mini", - }) }) }) diff --git a/src/shared/legacy-plugin-warning.test.ts b/src/shared/legacy-plugin-warning.test.ts index 47e8a39a9..48ea8194f 100644 --- a/src/shared/legacy-plugin-warning.test.ts +++ b/src/shared/legacy-plugin-warning.test.ts @@ -1,81 +1,110 @@ -import { afterEach, beforeEach, describe, expect, it } from "bun:test" +import { describe, expect, it } from "bun:test" import { mkdirSync, rmSync, writeFileSync } from "node:fs" import { tmpdir } from "node:os" import { join } from "node:path" import { checkForLegacyPluginEntry } from "./legacy-plugin-warning" +function createTestConfigDir(): string { + const testConfigDir = join(tmpdir(), `omo-legacy-check-${Date.now()}-${Math.random().toString(36).slice(2)}`) + mkdirSync(testConfigDir, { recursive: true }) + return testConfigDir +} + +function cleanupTestConfigDir(testConfigDir: string): void { + rmSync(testConfigDir, { recursive: true, force: true }) +} + describe("checkForLegacyPluginEntry", () => { - let testConfigDir = "" - - beforeEach(() => { - testConfigDir = join(tmpdir(), `omo-legacy-check-${Date.now()}-${Math.random().toString(36).slice(2)}`) - mkdirSync(testConfigDir, { recursive: true }) - }) - - afterEach(() => { - rmSync(testConfigDir, { recursive: true, force: true }) - }) - it("detects a bare legacy plugin entry", () => { - // given - writeFileSync(join(testConfigDir, "opencode.json"), JSON.stringify({ plugin: ["oh-my-opencode"] }, null, 2)) + const testConfigDir = createTestConfigDir() - // when - const result = checkForLegacyPluginEntry(testConfigDir) + try { + // given + writeFileSync(join(testConfigDir, "opencode.json"), JSON.stringify({ plugin: ["oh-my-opencode"] }, null, 2)) - // then - expect(result.hasLegacyEntry).toBe(true) - expect(result.hasCanonicalEntry).toBe(false) - expect(result.legacyEntries).toEqual(["oh-my-opencode"]) - expect(result.configPath).toBe(join(testConfigDir, "opencode.json")) + // when + const result = checkForLegacyPluginEntry(testConfigDir) + + // then + expect(result.hasLegacyEntry).toBe(true) + expect(result.hasCanonicalEntry).toBe(false) + expect(result.legacyEntries).toEqual(["oh-my-opencode"]) + expect(result.configPath).toBe(join(testConfigDir, "opencode.json")) + } finally { + cleanupTestConfigDir(testConfigDir) + } }) it("detects a version-pinned legacy plugin entry", () => { - // given - writeFileSync(join(testConfigDir, "opencode.json"), JSON.stringify({ plugin: ["oh-my-opencode@3.10.0"] }, null, 2)) + const testConfigDir = createTestConfigDir() - // when - const result = checkForLegacyPluginEntry(testConfigDir) + try { + // given + writeFileSync(join(testConfigDir, "opencode.json"), JSON.stringify({ plugin: ["oh-my-opencode@3.10.0"] }, null, 2)) - // then - expect(result.hasLegacyEntry).toBe(true) - expect(result.hasCanonicalEntry).toBe(false) - expect(result.legacyEntries).toEqual(["oh-my-opencode@3.10.0"]) + // when + const result = checkForLegacyPluginEntry(testConfigDir) + + // then + expect(result.hasLegacyEntry).toBe(true) + expect(result.hasCanonicalEntry).toBe(false) + expect(result.legacyEntries).toEqual(["oh-my-opencode@3.10.0"]) + } finally { + cleanupTestConfigDir(testConfigDir) + } }) it("does not flag a canonical plugin entry", () => { - // given - writeFileSync(join(testConfigDir, "opencode.json"), JSON.stringify({ plugin: ["oh-my-openagent"] }, null, 2)) + const testConfigDir = createTestConfigDir() - // when - const result = checkForLegacyPluginEntry(testConfigDir) + try { + // given + writeFileSync(join(testConfigDir, "opencode.json"), JSON.stringify({ plugin: ["oh-my-openagent"] }, null, 2)) - // then - expect(result.hasLegacyEntry).toBe(false) - expect(result.hasCanonicalEntry).toBe(true) - expect(result.legacyEntries).toEqual([]) + // when + const result = checkForLegacyPluginEntry(testConfigDir) + + // then + expect(result.hasLegacyEntry).toBe(false) + expect(result.hasCanonicalEntry).toBe(true) + expect(result.legacyEntries).toEqual([]) + } finally { + cleanupTestConfigDir(testConfigDir) + } }) it("detects legacy entries in quoted jsonc config", () => { - // given - writeFileSync(join(testConfigDir, "opencode.jsonc"), '{\n "plugin": ["oh-my-opencode"]\n}\n') + const testConfigDir = createTestConfigDir() - // when - const result = checkForLegacyPluginEntry(testConfigDir) + try { + // given + writeFileSync(join(testConfigDir, "opencode.jsonc"), '{\n "plugin": ["oh-my-opencode"]\n}\n') - // then - expect(result.hasLegacyEntry).toBe(true) - expect(result.legacyEntries).toEqual(["oh-my-opencode"]) + // when + const result = checkForLegacyPluginEntry(testConfigDir) + + // then + expect(result.hasLegacyEntry).toBe(true) + expect(result.legacyEntries).toEqual(["oh-my-opencode"]) + } finally { + cleanupTestConfigDir(testConfigDir) + } }) it("returns no warning data when config is missing", () => { - // when - const result = checkForLegacyPluginEntry(testConfigDir) + const testConfigDir = createTestConfigDir() - // then - expect(result.hasLegacyEntry).toBe(false) - expect(result.hasCanonicalEntry).toBe(false) - expect(result.legacyEntries).toEqual([]) - expect(result.configPath).toBeNull() + try { + // when + const result = checkForLegacyPluginEntry(testConfigDir) + + // then + expect(result.hasLegacyEntry).toBe(false) + expect(result.hasCanonicalEntry).toBe(false) + expect(result.legacyEntries).toEqual([]) + expect(result.configPath).toBeNull() + } finally { + cleanupTestConfigDir(testConfigDir) + } }) })