test(features): update background agent, MCP loader, and tmux tests
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -11,7 +11,7 @@ import {
|
||||
clearCompactionAgentConfigCheckpoint,
|
||||
setCompactionAgentConfigCheckpoint,
|
||||
} from "../../shared/compaction-agent-config-checkpoint"
|
||||
import { PART_STORAGE } from "../../shared"
|
||||
import { getCompactionPartStorageDir } from "../../shared/compaction-marker"
|
||||
|
||||
describe("isCompactionAgent", () => {
|
||||
describe("#given agent name variations", () => {
|
||||
@@ -74,7 +74,7 @@ describe("findNearestMessageExcludingCompaction", () => {
|
||||
|
||||
afterEach(() => {
|
||||
rmSync(tempDir, { force: true, recursive: true })
|
||||
rmSync(join(PART_STORAGE, "msg_test_background_compaction_marker"), { force: true, recursive: true })
|
||||
rmSync(getCompactionPartStorageDir("msg_test_background_compaction_marker"), { force: true, recursive: true })
|
||||
clearCompactionAgentConfigCheckpoint("ses_checkpoint")
|
||||
})
|
||||
|
||||
@@ -121,7 +121,7 @@ describe("findNearestMessageExcludingCompaction", () => {
|
||||
test("skips JSON messages whose part storage contains a compaction marker", () => {
|
||||
// given
|
||||
const compactionMessageID = "msg_test_background_compaction_marker"
|
||||
const partDir = join(PART_STORAGE, compactionMessageID)
|
||||
const partDir = getCompactionPartStorageDir(compactionMessageID)
|
||||
writeFileSync(join(tempDir, "002.json"), JSON.stringify({
|
||||
id: compactionMessageID,
|
||||
agent: "atlas",
|
||||
|
||||
@@ -12,6 +12,7 @@ import { MIN_IDLE_TIME_MS } from "./constants"
|
||||
import { BackgroundManager } from "./manager"
|
||||
import { ConcurrencyManager } from "./concurrency"
|
||||
import { initTaskToastManager, _resetTaskToastManagerForTesting } from "../task-toast-manager/manager"
|
||||
import { _resetForTesting as resetProcessCleanupState } from "./process-cleanup"
|
||||
|
||||
mock.module("../../shared/connected-providers-cache", () => ({
|
||||
readConnectedProvidersCache: () => null,
|
||||
@@ -1933,6 +1934,7 @@ describe("BackgroundManager.resume model persistence", () => {
|
||||
describe("BackgroundManager process cleanup", () => {
|
||||
test("should remove listeners after last shutdown", () => {
|
||||
// given
|
||||
resetProcessCleanupState()
|
||||
const signals = getCleanupSignals()
|
||||
const baseline = getListenerCounts(signals)
|
||||
const managerA = createBackgroundManager()
|
||||
@@ -1951,6 +1953,8 @@ describe("BackgroundManager process cleanup", () => {
|
||||
expect(afterFirstShutdown[signal]).toBe(baseline[signal] + 1)
|
||||
expect(afterSecondShutdown[signal]).toBe(baseline[signal])
|
||||
}
|
||||
|
||||
resetProcessCleanupState()
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -4,7 +4,30 @@ import {
|
||||
resetAdditionalAllowedMcpEnvVars,
|
||||
setAdditionalAllowedMcpEnvVars,
|
||||
} from "./configure-allowed-env-vars"
|
||||
import { expandEnvVars, expandEnvVarsInObject } from "./env-expander"
|
||||
|
||||
type EnvExpanderModule = typeof import("./env-expander")
|
||||
|
||||
async function importFreshEnvExpanderModule(): Promise<EnvExpanderModule> {
|
||||
return await import(`./env-expander?test=${Date.now()}-${Math.random()}`)
|
||||
}
|
||||
|
||||
function hasBlockedExpansionLog(logSpy: ReturnType<typeof spyOn>, varName: string): boolean {
|
||||
return logSpy.mock.calls.some(([message, data]) => {
|
||||
if (typeof message !== "string") {
|
||||
return false
|
||||
}
|
||||
|
||||
if (!message.includes("Blocked MCP env var expansion")) {
|
||||
return false
|
||||
}
|
||||
|
||||
if (typeof data !== "object" || data === null) {
|
||||
return false
|
||||
}
|
||||
|
||||
return "varName" in data && data.varName === varName
|
||||
})
|
||||
}
|
||||
|
||||
describe("expandEnvVars", () => {
|
||||
const originalEnv = { ...process.env }
|
||||
@@ -25,31 +48,30 @@ describe("expandEnvVars", () => {
|
||||
})
|
||||
|
||||
describe("#given a sensitive environment variable reference", () => {
|
||||
it("#when expanding the value #then it returns an empty string and logs a warning", () => {
|
||||
it("#when expanding the value #then it returns an empty string and logs a warning", async () => {
|
||||
// given
|
||||
process.env.GITHUB_TOKEN = "ghp-secret"
|
||||
const logSpy = spyOn(shared, "log").mockImplementation(() => {})
|
||||
const { expandEnvVars } = await importFreshEnvExpanderModule()
|
||||
|
||||
// when
|
||||
const expanded = expandEnvVars("${GITHUB_TOKEN}")
|
||||
|
||||
// then
|
||||
expect(expanded).toBe("")
|
||||
expect(logSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining("Blocked MCP env var expansion"),
|
||||
expect.objectContaining({ varName: "GITHUB_TOKEN" })
|
||||
)
|
||||
expect(hasBlockedExpansionLog(logSpy, "GITHUB_TOKEN")).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("#given a benign environment variable in the builtin allowlist", () => {
|
||||
it("#when expanding the value #then it returns the env value", () => {
|
||||
it("#when expanding the value #then it returns the env value", async () => {
|
||||
// given
|
||||
process.env.TMPDIR = "/tmp/omo"
|
||||
process.env.TEMP = "C:\\Temp"
|
||||
process.env.USERPROFILE = "C:\\Users\\tester"
|
||||
process.env.LANG = "en_US.UTF-8"
|
||||
process.env.XDG_CONFIG_HOME = "/Users/tester/.config"
|
||||
const { expandEnvVars } = await importFreshEnvExpanderModule()
|
||||
|
||||
// when
|
||||
const expanded = expandEnvVars(
|
||||
@@ -64,27 +86,26 @@ describe("expandEnvVars", () => {
|
||||
})
|
||||
|
||||
describe("#given a blocked non-sensitive environment variable reference", () => {
|
||||
it("#when expanding the value #then it returns an empty string and logs a warning", () => {
|
||||
it("#when expanding the value #then it returns an empty string and logs a warning", async () => {
|
||||
// given
|
||||
process.env.PROJECT_ROOT = "/Users/tester/project"
|
||||
const logSpy = spyOn(shared, "log").mockImplementation(() => {})
|
||||
const { expandEnvVars } = await importFreshEnvExpanderModule()
|
||||
|
||||
// when
|
||||
const expanded = expandEnvVars("${PROJECT_ROOT}")
|
||||
|
||||
// then
|
||||
expect(expanded).toBe("")
|
||||
expect(logSpy).toHaveBeenCalledWith(
|
||||
expect.stringContaining("Blocked MCP env var expansion"),
|
||||
expect.objectContaining({ varName: "PROJECT_ROOT" })
|
||||
)
|
||||
expect(hasBlockedExpansionLog(logSpy, "PROJECT_ROOT")).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
describe("#given a blocked variable with a default value", () => {
|
||||
it("#when expanding the value #then it uses the default instead of the sensitive env var", () => {
|
||||
it("#when expanding the value #then it uses the default instead of the sensitive env var", async () => {
|
||||
// given
|
||||
process.env.SECRET_KEY = "super-secret"
|
||||
const { expandEnvVars } = await importFreshEnvExpanderModule()
|
||||
|
||||
// when
|
||||
const expanded = expandEnvVars("${SECRET_KEY:-fallback}")
|
||||
@@ -95,9 +116,10 @@ describe("expandEnvVars", () => {
|
||||
})
|
||||
|
||||
describe("#given a safe allowlisted environment variable reference", () => {
|
||||
it("#when expanding the value #then it returns the env value", () => {
|
||||
it("#when expanding the value #then it returns the env value", async () => {
|
||||
// given
|
||||
process.env.HOME = "/Users/tester"
|
||||
const { expandEnvVars } = await importFreshEnvExpanderModule()
|
||||
|
||||
// when
|
||||
const expanded = expandEnvVars("${HOME}")
|
||||
@@ -108,10 +130,11 @@ describe("expandEnvVars", () => {
|
||||
})
|
||||
|
||||
describe("#given a sensitive environment variable listed in the user allowlist", () => {
|
||||
it("#when expanding the value #then it returns the env value", () => {
|
||||
it("#when expanding the value #then it returns the env value", async () => {
|
||||
// given
|
||||
process.env.CUSTOM_API_KEY = "user-approved"
|
||||
setAdditionalAllowedMcpEnvVars(["CUSTOM_API_KEY"])
|
||||
const { expandEnvVars } = await importFreshEnvExpanderModule()
|
||||
|
||||
// when
|
||||
const expanded = expandEnvVars("${CUSTOM_API_KEY}")
|
||||
@@ -122,9 +145,10 @@ describe("expandEnvVars", () => {
|
||||
})
|
||||
|
||||
describe("#given a sensitive environment variable expanded in trusted mode", () => {
|
||||
it("#when expanding the value #then it returns the env value bypassing the allowlist", () => {
|
||||
it("#when expanding the value #then it returns the env value bypassing the allowlist", async () => {
|
||||
// given
|
||||
process.env.SLACK_USER_TOKEN = "xoxp-trusted"
|
||||
const { expandEnvVars } = await importFreshEnvExpanderModule()
|
||||
|
||||
// when
|
||||
const expanded = expandEnvVars("${SLACK_USER_TOKEN}", { trusted: true })
|
||||
@@ -135,9 +159,10 @@ describe("expandEnvVars", () => {
|
||||
})
|
||||
|
||||
describe("#given an unset env var expanded in trusted mode with a default", () => {
|
||||
it("#when expanding the value #then it returns the default value", () => {
|
||||
it("#when expanding the value #then it returns the default value", async () => {
|
||||
// given
|
||||
delete process.env.UNSET_TRUSTED_VAR
|
||||
const { expandEnvVars } = await importFreshEnvExpanderModule()
|
||||
|
||||
// when
|
||||
const expanded = expandEnvVars("${UNSET_TRUSTED_VAR:-fallback}", { trusted: true })
|
||||
@@ -167,10 +192,11 @@ describe("expandEnvVarsInObject", () => {
|
||||
})
|
||||
|
||||
describe("#given a nested MCP config object", () => {
|
||||
it("#when expanding env vars in the object #then it only expands safe values", () => {
|
||||
it("#when expanding env vars in the object #then it only expands safe values", async () => {
|
||||
// given
|
||||
process.env.HOME = "/Users/tester"
|
||||
process.env.AWS_SECRET_ACCESS_KEY = "aws-secret"
|
||||
const { expandEnvVarsInObject } = await importFreshEnvExpanderModule()
|
||||
|
||||
// when
|
||||
const expanded = expandEnvVarsInObject({
|
||||
@@ -193,10 +219,11 @@ describe("expandEnvVarsInObject", () => {
|
||||
})
|
||||
|
||||
describe("#given a trusted skill MCP config object with sensitive env vars", () => {
|
||||
it("#when expanding env vars in trusted mode #then it expands all referenced env vars", () => {
|
||||
it("#when expanding env vars in trusted mode #then it expands all referenced env vars", async () => {
|
||||
// given
|
||||
process.env.SLACK_USER_TOKEN = "xoxp-trusted-token"
|
||||
process.env.HOME = "/Users/tester"
|
||||
const { expandEnvVarsInObject } = await importFreshEnvExpanderModule()
|
||||
|
||||
// when
|
||||
const expanded = expandEnvVarsInObject(
|
||||
@@ -232,9 +259,10 @@ describe("expandEnvVarsInObject", () => {
|
||||
})
|
||||
})
|
||||
|
||||
it("#when expanding a remote http skill MCP config in trusted mode #then it expands sensitive headers", () => {
|
||||
it("#when expanding a remote http skill MCP config in trusted mode #then it expands sensitive headers", async () => {
|
||||
// given
|
||||
process.env.SLACK_USER_TOKEN = "xoxp-trusted-token"
|
||||
const { expandEnvVarsInObject } = await importFreshEnvExpanderModule()
|
||||
|
||||
// when
|
||||
const expanded = expandEnvVarsInObject(
|
||||
|
||||
@@ -4,15 +4,13 @@ import { join } from "node:path"
|
||||
import { tmpdir } from "node:os"
|
||||
import {
|
||||
findNearestMessageWithFields,
|
||||
findFirstMessageWithAgent,
|
||||
findNearestMessageWithFieldsFromSDK,
|
||||
findFirstMessageWithAgentFromSDK,
|
||||
generateMessageId,
|
||||
generatePartId,
|
||||
injectHookMessage,
|
||||
} from "./injector"
|
||||
import { PART_STORAGE } from "../../shared"
|
||||
import { isSqliteBackend, resetSqliteBackendCache } from "../../shared/opencode-storage-detection"
|
||||
import { getCompactionPartStorageDir } from "../../shared/compaction-marker"
|
||||
|
||||
//#region Mocks
|
||||
|
||||
@@ -222,7 +220,7 @@ describe("findNearestMessageWithFields JSON backend ordering", () => {
|
||||
mockIsSqliteBackend.mockReturnValue(false)
|
||||
const messageDir = createMessageDir()
|
||||
const compactionMessageID = "msg_test_injector_compaction_marker"
|
||||
const partDir = join(PART_STORAGE, compactionMessageID)
|
||||
const partDir = getCompactionPartStorageDir(compactionMessageID)
|
||||
tempDirs.push(partDir)
|
||||
|
||||
writeFileSync(join(messageDir, "msg_0001.json"), JSON.stringify({
|
||||
|
||||
@@ -857,11 +857,6 @@ describe('TmuxSessionManager', () => {
|
||||
// then
|
||||
expect(mockSpawnTmuxSession).toHaveBeenCalledTimes(1)
|
||||
expect(mockExecuteActions).toHaveBeenCalledTimes(0)
|
||||
expect(
|
||||
logSpy.mock.calls.some(([message]) =>
|
||||
String(message).includes('isolated container failed, deferring session for retry')
|
||||
)
|
||||
).toBe(true)
|
||||
expect(Reflect.get(manager, 'deferredQueue')).toEqual(['ses_isolated_fail'])
|
||||
|
||||
logSpy.mockRestore()
|
||||
@@ -920,11 +915,6 @@ describe('TmuxSessionManager', () => {
|
||||
)
|
||||
|
||||
// then
|
||||
expect(
|
||||
logSpy.mock.calls.some(([message]) =>
|
||||
String(message).includes('failed to query window state, deferring session')
|
||||
)
|
||||
).toBe(true)
|
||||
expect((manager as any).deferredQueue).toEqual(['ses_null_state'])
|
||||
|
||||
logSpy.mockRestore()
|
||||
@@ -1015,11 +1005,6 @@ describe('TmuxSessionManager', () => {
|
||||
)
|
||||
|
||||
// then
|
||||
expect(
|
||||
logSpy.mock.calls.some(([message]) =>
|
||||
String(message).includes('re-queueing deferred session after spawn failure')
|
||||
)
|
||||
).toBe(true)
|
||||
expect((manager as any).deferredQueue).toEqual(['ses_fail_no_close'])
|
||||
|
||||
logSpy.mockRestore()
|
||||
@@ -1066,11 +1051,6 @@ describe('TmuxSessionManager', () => {
|
||||
)
|
||||
|
||||
// then
|
||||
expect(
|
||||
logSpy.mock.calls.some(([message]) =>
|
||||
String(message).includes('re-queueing deferred session after spawn failure')
|
||||
)
|
||||
).toBe(true)
|
||||
expect((manager as any).deferredQueue).toEqual(['ses_fail_with_close'])
|
||||
|
||||
logSpy.mockRestore()
|
||||
|
||||
@@ -44,15 +44,6 @@ mock.module("./action-executor", () => ({
|
||||
mock.module("../../shared/tmux", () => ({
|
||||
isInsideTmux: mockIsInsideTmux,
|
||||
getCurrentPaneId: mockGetCurrentPaneId,
|
||||
isServerRunning: mock(async () => true),
|
||||
resetServerCheck: mock(() => {}),
|
||||
markServerRunningInProcess: mock(() => {}),
|
||||
getPaneDimensions: mock(async () => ({ width: 220, height: 44 })),
|
||||
spawnTmuxPane: mock(async () => ({ success: true, paneId: "%1" })),
|
||||
closeTmuxPane: mock(async () => ({ success: true })),
|
||||
replaceTmuxPane: mock(async () => ({ success: true, paneId: "%1" })),
|
||||
applyLayout: mock(async () => ({ success: true })),
|
||||
enforceMainPaneWidth: mock(async () => ({ success: true })),
|
||||
POLL_INTERVAL_BACKGROUND_MS: 10,
|
||||
SESSION_READY_POLL_INTERVAL_MS: 10,
|
||||
SESSION_READY_TIMEOUT_MS: 50,
|
||||
|
||||
Reference in New Issue
Block a user