mirror of
https://mirror.skon.top/github.com/code-yeongyu/oh-my-opencode
synced 2026-04-24 02:57:42 +08:00
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:
8
.github/workflows/ci.yml
vendored
8
.github/workflows/ci.yml
vendored
@@ -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 \
|
||||
|
||||
8
.github/workflows/publish.yml
vendored
8
.github/workflows/publish.yml
vendored
@@ -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 \
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user