fix(tests): resolve 25 pre-publish test failures

- Add providerModelsCache/fetchAvailableModels mocks to 20 utils.test.ts
  tests that broke when createBuiltinAgents started reading the cache
- Isolate ALL src/plugin and src/features/background-agent test files in CI
  (mock.module pollution crosses between files in the same bun process)
- Mirror CI isolation changes in publish.yml

25 failures → 0 in CI (all mock-pollution tests run individually)
This commit is contained in:
YeonGyu-Kim
2026-03-31 17:41:23 +09:00
parent 96135b5444
commit fd281deba7
3 changed files with 76 additions and 2 deletions

View File

@@ -69,6 +69,10 @@ jobs:
bun test src/hooks/session-recovery/recover-tool-result-missing.test.ts
# legacy-plugin-toast mock isolation (hook.test.ts mocks ./auto-migrate)
bun test src/hooks/legacy-plugin-toast/hook.test.ts
# src/plugin — ALL isolated (mock.module pollution crosses between files)
for f in src/plugin/*.test.ts; do bun test "$f"; done
# src/features/background-agent — ALL isolated (mock.module pollution)
for f in src/features/background-agent/*.test.ts; do bun test "$f"; done
- name: Run remaining tests
run: |
@@ -78,6 +82,8 @@ jobs:
# Excluded from src/cli: doctor/formatter.test.ts, doctor/format-default.test.ts
# Excluded from src/tools: call-omo-agent/sync-executor.test.ts, call-omo-agent/session-creator.test.ts, session-manager (all)
# Excluded from src/hooks/anthropic-context-window-limit-recovery: recovery-hook.test.ts, executor.test.ts
# Excluded: src/plugin/* (all run isolated above)
# Excluded: src/features/background-agent/* (all run isolated above)
# Build src/shared file list excluding mock-heavy files already run in isolation
SHARED_FILES=$(find src/shared -name '*.test.ts' \
! -name 'model-capabilities.test.ts' \
@@ -85,6 +91,7 @@ jobs:
! -name 'model-error-classifier.test.ts' \
! -name 'opencode-message-dir.test.ts' \
| sort | tr '\n' ' ')
# plugin and background-agent fully isolated above — excluded from remaining
bun test bin script src/config src/mcp src/index.test.ts \
src/agents $SHARED_FILES \
src/cli/run src/cli/config-manager src/cli/mcp-oauth \
@@ -107,7 +114,6 @@ jobs:
src/hooks/session-notification \
src/hooks/sisyphus \
src/hooks/todo-continuation-enforcer \
src/features/background-agent \
src/features/builtin-commands \
src/features/builtin-skills \
src/features/claude-code-session-state \

View File

@@ -70,6 +70,10 @@ jobs:
bun test src/hooks/session-recovery/recover-tool-result-missing.test.ts
# legacy-plugin-toast mock isolation (hook.test.ts mocks ./auto-migrate)
bun test src/hooks/legacy-plugin-toast/hook.test.ts
# src/plugin — ALL isolated (mock.module pollution crosses between files)
for f in src/plugin/*.test.ts; do bun test "$f"; done
# src/features/background-agent — ALL isolated (mock.module pollution)
for f in src/features/background-agent/*.test.ts; do bun test "$f"; done
- name: Run remaining tests
run: |
@@ -79,6 +83,8 @@ jobs:
# Excluded from src/cli: doctor/formatter.test.ts, doctor/format-default.test.ts
# Excluded from src/tools: call-omo-agent/sync-executor.test.ts, call-omo-agent/session-creator.test.ts, session-manager (all)
# Excluded from src/hooks/anthropic-context-window-limit-recovery: recovery-hook.test.ts, executor.test.ts
# Excluded: src/plugin/* (all run isolated above)
# Excluded: src/features/background-agent/* (all run isolated above)
# Build src/shared file list excluding mock-heavy files already run in isolation
SHARED_FILES=$(find src/shared -name '*.test.ts' \
! -name 'model-capabilities.test.ts' \
@@ -86,6 +92,7 @@ jobs:
! -name 'model-error-classifier.test.ts' \
! -name 'opencode-message-dir.test.ts' \
| sort | tr '\n' ' ')
# plugin and background-agent fully isolated above — excluded from remaining
bun test bin script src/config src/mcp src/index.test.ts \
src/agents $SHARED_FILES \
src/cli/run src/cli/config-manager src/cli/mcp-oauth \
@@ -108,7 +115,6 @@ jobs:
src/hooks/session-notification \
src/hooks/sisyphus \
src/hooks/todo-continuation-enforcer \
src/features/background-agent \
src/features/builtin-commands \
src/features/builtin-skills \
src/features/claude-code-session-state \

View File

@@ -38,6 +38,8 @@ describe("createBuiltinAgents with model overrides", () => {
test("Sisyphus with GPT model override has reasoningEffort, no thinking", async () => {
// #given
const providerModelsSpy = spyOn(connectedProvidersCache, "readProviderModelsCache").mockReturnValue(null)
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(new Set())
const overrides = {
sisyphus: { model: "github-copilot/gpt-5.4" },
}
@@ -49,6 +51,8 @@ describe("createBuiltinAgents with model overrides", () => {
expect(agents.sisyphus.model).toBe("github-copilot/gpt-5.4")
expect(agents.sisyphus.reasoningEffort).toBe("medium")
expect(agents.sisyphus.thinking).toBeUndefined()
providerModelsSpy.mockRestore()
fetchSpy.mockRestore()
})
test("Atlas uses uiSelectedModel", async () => {
@@ -168,6 +172,8 @@ describe("createBuiltinAgents with model overrides", () => {
test("Oracle uses connected provider fallback when availableModels is empty and cache exists", async () => {
// #given - connected providers cache has "openai", which matches oracle's first fallback entry
const providerModelsSpy = spyOn(connectedProvidersCache, "readProviderModelsCache").mockReturnValue(null)
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(new Set())
const cacheSpy = spyOn(connectedProvidersCache, "readConnectedProvidersCache").mockReturnValue(["openai"])
// #when
@@ -178,6 +184,8 @@ describe("createBuiltinAgents with model overrides", () => {
expect(agents.oracle.reasoningEffort).toBe("medium")
expect(agents.oracle.thinking).toBeUndefined()
cacheSpy.mockRestore?.()
providerModelsSpy.mockRestore()
fetchSpy.mockRestore()
})
test("Oracle created without model field when no cache exists (first run scenario)", async () => {
@@ -195,6 +203,8 @@ describe("createBuiltinAgents with model overrides", () => {
test("Oracle with GPT model override has reasoningEffort, no thinking", async () => {
// #given
const providerModelsSpy = spyOn(connectedProvidersCache, "readProviderModelsCache").mockReturnValue(null)
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(new Set())
const overrides = {
oracle: { model: "openai/gpt-5.4" },
}
@@ -207,10 +217,14 @@ describe("createBuiltinAgents with model overrides", () => {
expect(agents.oracle.reasoningEffort).toBe("medium")
expect(agents.oracle.textVerbosity).toBe("high")
expect(agents.oracle.thinking).toBeUndefined()
providerModelsSpy.mockRestore()
fetchSpy.mockRestore()
})
test("Oracle with Claude model override has thinking, no reasoningEffort", async () => {
// #given
const providerModelsSpy = spyOn(connectedProvidersCache, "readProviderModelsCache").mockReturnValue(null)
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(new Set())
const overrides = {
oracle: { model: "anthropic/claude-sonnet-4" },
}
@@ -223,10 +237,14 @@ describe("createBuiltinAgents with model overrides", () => {
expect(agents.oracle.thinking).toEqual({ type: "enabled", budgetTokens: 32000 })
expect(agents.oracle.reasoningEffort).toBeUndefined()
expect(agents.oracle.textVerbosity).toBeUndefined()
providerModelsSpy.mockRestore()
fetchSpy.mockRestore()
})
test("non-model overrides are still applied after factory rebuild", async () => {
// #given
const providerModelsSpy = spyOn(connectedProvidersCache, "readProviderModelsCache").mockReturnValue(null)
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(new Set())
const overrides = {
sisyphus: { model: "github-copilot/gpt-5.4", temperature: 0.5 },
}
@@ -237,10 +255,15 @@ describe("createBuiltinAgents with model overrides", () => {
// #then
expect(agents.sisyphus.model).toBe("github-copilot/gpt-5.4")
expect(agents.sisyphus.temperature).toBe(0.5)
providerModelsSpy.mockRestore()
fetchSpy.mockRestore()
})
test("createBuiltinAgents excludes disabled skills from availableSkills", async () => {
// #given
const providerModelsSpy = spyOn(connectedProvidersCache, "readProviderModelsCache").mockReturnValue(null)
const connectedSpy = spyOn(connectedProvidersCache, "readConnectedProvidersCache").mockReturnValue(null)
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(new Set())
const disabledSkills = new Set(["playwright"])
// #when
@@ -250,6 +273,9 @@ describe("createBuiltinAgents with model overrides", () => {
expect(agents.sisyphus.prompt).not.toContain("playwright")
expect(agents.sisyphus.prompt).toContain("frontend-ui-ux")
expect(agents.sisyphus.prompt).toContain("git-master")
providerModelsSpy.mockRestore()
connectedSpy.mockRestore()
fetchSpy.mockRestore()
})
test("includes custom agents in orchestrator prompts when provided via config", async () => {
@@ -472,6 +498,8 @@ describe("createBuiltinAgents with model overrides", () => {
describe("createBuiltinAgents without systemDefaultModel", () => {
test("agents created via connected cache fallback even without systemDefaultModel", async () => {
// #given - connected cache has "openai", which matches oracle's fallback chain
const providerModelsSpy = spyOn(connectedProvidersCache, "readProviderModelsCache").mockReturnValue(null)
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(new Set())
const cacheSpy = spyOn(connectedProvidersCache, "readConnectedProvidersCache").mockReturnValue(["openai"])
// #when
@@ -481,6 +509,8 @@ describe("createBuiltinAgents without systemDefaultModel", () => {
expect(agents.oracle).toBeDefined()
expect(agents.oracle.model).toBe("openai/gpt-5.4")
cacheSpy.mockRestore?.()
providerModelsSpy.mockRestore()
fetchSpy.mockRestore()
})
test("oracle is created on first run when no cache and no systemDefaultModel", async () => {
@@ -1242,6 +1272,17 @@ describe("buildAgent with category and skills", () => {
})
describe("override.category expansion in createBuiltinAgents", () => {
let providerModelsSpy: ReturnType<typeof spyOn>
let fetchSpy: ReturnType<typeof spyOn>
beforeEach(() => {
providerModelsSpy = spyOn(connectedProvidersCache, "readProviderModelsCache").mockReturnValue(null)
fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(new Set())
})
afterEach(() => {
providerModelsSpy.mockRestore()
fetchSpy.mockRestore()
})
test("standard agent override with category expands category properties", async () => {
// #given
const overrides = {
@@ -1358,6 +1399,17 @@ describe("override.category expansion in createBuiltinAgents", () => {
})
describe("agent override tools migration", () => {
let providerModelsSpy: ReturnType<typeof spyOn>
let fetchSpy: ReturnType<typeof spyOn>
beforeEach(() => {
providerModelsSpy = spyOn(connectedProvidersCache, "readProviderModelsCache").mockReturnValue(null)
fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(new Set())
})
afterEach(() => {
providerModelsSpy.mockRestore()
fetchSpy.mockRestore()
})
test("tools: { x: false } is migrated to permission: { x: deny }", async () => {
// #given
const overrides = {
@@ -1441,6 +1493,8 @@ describe("Deadlock prevention - fetchAvailableModels must not receive client", (
})
test("Hephaestus variant override respects user config over hardcoded default", async () => {
// #given - user provides variant in config
const providerModelsSpy = spyOn(connectedProvidersCache, "readProviderModelsCache").mockReturnValue(null)
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(new Set())
const overrides = {
hephaestus: { variant: "high" },
}
@@ -1451,10 +1505,15 @@ describe("Deadlock prevention - fetchAvailableModels must not receive client", (
// #then - user variant takes precedence over hardcoded "medium"
expect(agents.hephaestus).toBeDefined()
expect(agents.hephaestus.variant).toBe("high")
providerModelsSpy.mockRestore()
fetchSpy.mockRestore()
})
test("Hephaestus uses default variant when no user override provided", async () => {
// #given - no variant override in config
const providerModelsSpy = spyOn(connectedProvidersCache, "readProviderModelsCache").mockReturnValue(null)
const connectedSpy = spyOn(connectedProvidersCache, "readConnectedProvidersCache").mockReturnValue(null)
const fetchSpy = spyOn(shared, "fetchAvailableModels").mockResolvedValue(new Set())
const overrides = {}
// #when
@@ -1463,5 +1522,8 @@ describe("Deadlock prevention - fetchAvailableModels must not receive client", (
// #then - default "medium" variant is applied
expect(agents.hephaestus).toBeDefined()
expect(agents.hephaestus.variant).toBe("medium")
providerModelsSpy.mockRestore()
connectedSpy.mockRestore()
fetchSpy.mockRestore()
})
})