Compare commits
4 Commits
fix/subage
...
fix/inheri
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
271929a9e4 | ||
|
|
945329e261 | ||
|
|
f27733eae2 | ||
|
|
e9c9cb696d |
@@ -1,6 +1,10 @@
|
||||
import { afterEach, describe, expect, it, mock } from "bun:test"
|
||||
import { afterEach, afterAll, describe, expect, it, mock } from "bun:test"
|
||||
import type { DoctorResult } from "./types"
|
||||
|
||||
const realFormatDefault = await import("./format-default")
|
||||
const realFormatStatus = await import("./format-status")
|
||||
const realFormatVerbose = await import("./format-verbose")
|
||||
|
||||
function createDoctorResult(): DoctorResult {
|
||||
return {
|
||||
results: [
|
||||
@@ -44,6 +48,12 @@ describe("formatter", () => {
|
||||
mock.restore()
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
mock.module("./format-default", () => ({ ...realFormatDefault }))
|
||||
mock.module("./format-status", () => ({ ...realFormatStatus }))
|
||||
mock.module("./format-verbose", () => ({ ...realFormatVerbose }))
|
||||
})
|
||||
|
||||
describe("formatDoctorOutput", () => {
|
||||
it("dispatches to default formatter for default mode", async () => {
|
||||
//#given
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { afterEach, describe, expect, it, mock } from "bun:test"
|
||||
import { afterEach, afterAll, describe, expect, it, mock } from "bun:test"
|
||||
import type { CheckDefinition, CheckResult, DoctorResult, SystemInfo, ToolsSummary } from "./types"
|
||||
|
||||
const realChecks = await import("./checks")
|
||||
const realFormatter = await import("./formatter")
|
||||
|
||||
function createSystemInfo(): SystemInfo {
|
||||
return {
|
||||
opencodeVersion: "1.0.200",
|
||||
@@ -47,6 +50,11 @@ describe("runner", () => {
|
||||
mock.restore()
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
mock.module("./checks", () => ({ ...realChecks }))
|
||||
mock.module("./formatter", () => ({ ...realFormatter }))
|
||||
})
|
||||
|
||||
describe("runCheck", () => {
|
||||
it("returns fail result with issue when check throws", async () => {
|
||||
//#given
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { describe, it, expect, beforeEach, afterEach, mock } from "bun:test"
|
||||
import { describe, it, expect, beforeEach, afterEach, afterAll, mock } from "bun:test"
|
||||
|
||||
const realMcpOauthProvider = await import("../../features/mcp-oauth/provider")
|
||||
|
||||
const mockLogin = mock(() => Promise.resolve({ accessToken: "test-token", expiresAt: 1710000000 }))
|
||||
|
||||
@@ -11,6 +13,10 @@ mock.module("../../features/mcp-oauth/provider", () => ({
|
||||
},
|
||||
}))
|
||||
|
||||
afterAll(() => {
|
||||
mock.module("../../features/mcp-oauth/provider", () => ({ ...realMcpOauthProvider }))
|
||||
})
|
||||
|
||||
const { login } = await import("./login")
|
||||
|
||||
describe("login command", () => {
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import { describe, it, expect, mock, spyOn, beforeEach, afterEach } from "bun:test"
|
||||
import { describe, it, expect, mock, spyOn, beforeEach, afterEach, afterAll } from "bun:test"
|
||||
import type { RunResult } from "./types"
|
||||
import { createJsonOutputManager } from "./json-output"
|
||||
import { resolveSession } from "./session-resolver"
|
||||
import { executeOnCompleteHook } from "./on-complete-hook"
|
||||
import type { OpencodeClient } from "./types"
|
||||
|
||||
const realSdk = await import("@opencode-ai/sdk")
|
||||
const realPortUtils = await import("../../shared/port-utils")
|
||||
|
||||
const mockServerClose = mock(() => {})
|
||||
const mockCreateOpencode = mock(() =>
|
||||
Promise.resolve({
|
||||
@@ -17,16 +20,23 @@ const mockIsPortAvailable = mock(() => Promise.resolve(true))
|
||||
const mockGetAvailableServerPort = mock(() => Promise.resolve({ port: 9999, wasAutoSelected: false }))
|
||||
|
||||
mock.module("@opencode-ai/sdk", () => ({
|
||||
...realSdk,
|
||||
createOpencode: mockCreateOpencode,
|
||||
createOpencodeClient: mockCreateOpencodeClient,
|
||||
}))
|
||||
|
||||
mock.module("../../shared/port-utils", () => ({
|
||||
...realPortUtils,
|
||||
isPortAvailable: mockIsPortAvailable,
|
||||
getAvailableServerPort: mockGetAvailableServerPort,
|
||||
DEFAULT_SERVER_PORT: 4096,
|
||||
}))
|
||||
|
||||
afterAll(() => {
|
||||
mock.module("@opencode-ai/sdk", () => ({ ...realSdk }))
|
||||
mock.module("../../shared/port-utils", () => ({ ...realPortUtils }))
|
||||
})
|
||||
|
||||
const { createServerConnection } = await import("./server-connection")
|
||||
|
||||
interface MockWriteStream {
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
import { describe, it, expect, mock, beforeEach, afterEach } from "bun:test"
|
||||
import { describe, it, expect, mock, beforeEach, afterEach, afterAll } from "bun:test"
|
||||
|
||||
const realSdk = await import("@opencode-ai/sdk")
|
||||
const realPortUtils = await import("../../shared/port-utils")
|
||||
|
||||
const originalConsole = globalThis.console
|
||||
|
||||
@@ -15,16 +18,23 @@ const mockGetAvailableServerPort = mock(() => Promise.resolve({ port: 4096, wasA
|
||||
const mockConsoleLog = mock(() => {})
|
||||
|
||||
mock.module("@opencode-ai/sdk", () => ({
|
||||
...realSdk,
|
||||
createOpencode: mockCreateOpencode,
|
||||
createOpencodeClient: mockCreateOpencodeClient,
|
||||
}))
|
||||
|
||||
mock.module("../../shared/port-utils", () => ({
|
||||
...realPortUtils,
|
||||
isPortAvailable: mockIsPortAvailable,
|
||||
getAvailableServerPort: mockGetAvailableServerPort,
|
||||
DEFAULT_SERVER_PORT: 4096,
|
||||
}))
|
||||
|
||||
afterAll(() => {
|
||||
mock.module("@opencode-ai/sdk", () => ({ ...realSdk }))
|
||||
mock.module("../../shared/port-utils", () => ({ ...realPortUtils }))
|
||||
})
|
||||
|
||||
const { createServerConnection } = await import("./server-connection")
|
||||
|
||||
describe("createServerConnection", () => {
|
||||
|
||||
@@ -6,15 +6,20 @@ import { tmpdir } from "os"
|
||||
const TEST_DIR = join(tmpdir(), "mcp-loader-test-" + Date.now())
|
||||
const TEST_HOME = join(TEST_DIR, "home")
|
||||
|
||||
const realOs = await import("os")
|
||||
const realShared = await import("../../shared")
|
||||
|
||||
describe("getSystemMcpServerNames", () => {
|
||||
beforeEach(() => {
|
||||
mkdirSync(TEST_DIR, { recursive: true })
|
||||
mkdirSync(TEST_HOME, { recursive: true })
|
||||
mock.module("os", () => ({
|
||||
...realOs,
|
||||
homedir: () => TEST_HOME,
|
||||
tmpdir,
|
||||
}))
|
||||
mock.module("../../shared", () => ({
|
||||
...realShared,
|
||||
getClaudeConfigDir: () => join(TEST_HOME, ".claude"),
|
||||
}))
|
||||
})
|
||||
@@ -22,6 +27,9 @@ describe("getSystemMcpServerNames", () => {
|
||||
afterEach(() => {
|
||||
mock.restore()
|
||||
rmSync(TEST_DIR, { recursive: true, force: true })
|
||||
|
||||
mock.module("os", () => ({ ...realOs }))
|
||||
mock.module("../../shared", () => ({ ...realShared }))
|
||||
})
|
||||
|
||||
it("returns empty set when no .mcp.json files exist", async () => {
|
||||
|
||||
@@ -53,26 +53,28 @@ async function loadSourcePath(options: {
|
||||
const stat = await fs.stat(absolutePath).catch(() => null)
|
||||
if (!stat) return []
|
||||
|
||||
const realBasePath = await fs.realpath(absolutePath).catch(() => absolutePath)
|
||||
|
||||
if (stat.isFile()) {
|
||||
if (!isMarkdownPath(absolutePath)) return []
|
||||
if (!isMarkdownPath(realBasePath)) return []
|
||||
const loaded = await loadSkillFromPath({
|
||||
skillPath: absolutePath,
|
||||
resolvedPath: dirname(absolutePath),
|
||||
defaultName: inferSkillNameFromFileName(absolutePath),
|
||||
skillPath: realBasePath,
|
||||
resolvedPath: dirname(realBasePath),
|
||||
defaultName: inferSkillNameFromFileName(realBasePath),
|
||||
scope: "config",
|
||||
})
|
||||
if (!loaded) return []
|
||||
return filterByGlob([loaded], dirname(absolutePath), options.globPattern)
|
||||
return filterByGlob([loaded], dirname(realBasePath), options.globPattern)
|
||||
}
|
||||
|
||||
if (!stat.isDirectory()) return []
|
||||
|
||||
const directorySkills = await loadSkillsFromDir({
|
||||
skillsDir: absolutePath,
|
||||
skillsDir: realBasePath,
|
||||
scope: "config",
|
||||
maxDepth: options.recursive ? MAX_RECURSIVE_DEPTH : 0,
|
||||
})
|
||||
return filterByGlob(directorySkills, absolutePath, options.globPattern)
|
||||
return filterByGlob(directorySkills, realBasePath, options.globPattern)
|
||||
}
|
||||
|
||||
export async function discoverConfigSourceSkills(options: {
|
||||
|
||||
@@ -1,8 +1,13 @@
|
||||
import { describe, it, expect, beforeEach, afterEach, mock, spyOn } from "bun:test"
|
||||
import { describe, it, expect, beforeEach, afterEach, mock, spyOn, afterAll } from "bun:test"
|
||||
import { SkillMcpManager } from "./manager"
|
||||
import type { SkillMcpClientInfo, SkillMcpServerContext } from "./types"
|
||||
import type { ClaudeCodeMcpServer } from "../claude-code-mcp-loader/types"
|
||||
|
||||
const realStreamableHttp = await import(
|
||||
"@modelcontextprotocol/sdk/client/streamableHttp.js"
|
||||
)
|
||||
const realMcpOauthProvider = await import("../mcp-oauth/provider")
|
||||
|
||||
// Mock the MCP SDK transports to avoid network calls
|
||||
const mockHttpConnect = mock(() => Promise.reject(new Error("Mocked HTTP connection failure")))
|
||||
const mockHttpClose = mock(() => Promise.resolve())
|
||||
@@ -37,6 +42,13 @@ mock.module("../mcp-oauth/provider", () => ({
|
||||
},
|
||||
}))
|
||||
|
||||
afterAll(() => {
|
||||
mock.module("@modelcontextprotocol/sdk/client/streamableHttp.js", () => ({
|
||||
...realStreamableHttp,
|
||||
}))
|
||||
mock.module("../mcp-oauth/provider", () => ({ ...realMcpOauthProvider }))
|
||||
})
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
import { describe, test, expect, mock, beforeEach } from 'bun:test'
|
||||
import { describe, test, expect, mock, beforeEach, afterAll } from 'bun:test'
|
||||
import type { TmuxConfig } from '../../config/schema'
|
||||
import type { WindowState, PaneAction } from './types'
|
||||
import type { ActionResult, ExecuteContext } from './action-executor'
|
||||
import type { TmuxUtilDeps } from './manager'
|
||||
|
||||
const realPaneStateQuerier = await import('./pane-state-querier')
|
||||
const realActionExecutor = await import('./action-executor')
|
||||
const realSharedTmux = await import('../../shared/tmux')
|
||||
|
||||
type ExecuteActionsResult = {
|
||||
success: boolean
|
||||
spawnedPaneId?: string
|
||||
@@ -71,6 +75,12 @@ mock.module('../../shared/tmux', () => {
|
||||
}
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
mock.module('./pane-state-querier', () => ({ ...realPaneStateQuerier }))
|
||||
mock.module('./action-executor', () => ({ ...realActionExecutor }))
|
||||
mock.module('../../shared/tmux', () => ({ ...realSharedTmux }))
|
||||
})
|
||||
|
||||
const trackedSessions = new Set<string>()
|
||||
|
||||
function createMockContext(overrides?: {
|
||||
|
||||
@@ -1,13 +1,21 @@
|
||||
import { describe, test, expect, mock, beforeEach } from "bun:test"
|
||||
import { describe, test, expect, mock, beforeEach, afterAll } from "bun:test"
|
||||
import type { PluginInput } from "@opencode-ai/plugin"
|
||||
import type { ExperimentalConfig } from "../../config"
|
||||
|
||||
const attemptDeduplicationRecoveryMock = mock(async () => {})
|
||||
const realDeduplicationRecovery = await import("./deduplication-recovery")
|
||||
|
||||
const attemptDeduplicationRecoveryMock = mock<(sessionID: string) => Promise<void>>(
|
||||
async () => {}
|
||||
)
|
||||
|
||||
mock.module("./deduplication-recovery", () => ({
|
||||
attemptDeduplicationRecovery: attemptDeduplicationRecoveryMock,
|
||||
}))
|
||||
|
||||
afterAll(() => {
|
||||
mock.module("./deduplication-recovery", () => ({ ...realDeduplicationRecovery }))
|
||||
})
|
||||
|
||||
function createImmediateTimeouts(): () => void {
|
||||
const originalSetTimeout = globalThis.setTimeout
|
||||
const originalClearTimeout = globalThis.clearTimeout
|
||||
@@ -37,13 +45,15 @@ describe("createAnthropicContextWindowLimitRecoveryHook", () => {
|
||||
const experimental = {
|
||||
dynamic_context_pruning: {
|
||||
enabled: true,
|
||||
notification: "off",
|
||||
protected_tools: [],
|
||||
strategies: {
|
||||
deduplication: { enabled: true },
|
||||
},
|
||||
},
|
||||
} satisfies ExperimentalConfig
|
||||
|
||||
let resolveSummarize: (() => void) | null = null
|
||||
let resolveSummarize: ((value?: void) => void) | null = null
|
||||
const summarizePromise = new Promise<void>((resolve) => {
|
||||
resolveSummarize = resolve
|
||||
})
|
||||
@@ -62,7 +72,7 @@ describe("createAnthropicContextWindowLimitRecoveryHook", () => {
|
||||
|
||||
try {
|
||||
const { createAnthropicContextWindowLimitRecoveryHook } = await import("./recovery-hook")
|
||||
const ctx = { client: mockClient, directory: "/tmp" } as PluginInput
|
||||
const ctx = { client: mockClient, directory: "/tmp" } as unknown as PluginInput
|
||||
const hook = createAnthropicContextWindowLimitRecoveryHook(ctx, { experimental })
|
||||
|
||||
// first error triggers compaction (setTimeout runs immediately due to mock)
|
||||
@@ -105,7 +115,7 @@ describe("createAnthropicContextWindowLimitRecoveryHook", () => {
|
||||
}
|
||||
|
||||
const { createAnthropicContextWindowLimitRecoveryHook } = await import("./recovery-hook")
|
||||
const ctx = { client: mockClient, directory: "/tmp" } as PluginInput
|
||||
const ctx = { client: mockClient, directory: "/tmp" } as unknown as PluginInput
|
||||
const hook = createAnthropicContextWindowLimitRecoveryHook(ctx)
|
||||
|
||||
//#when - single error (no compaction in progress)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { describe, test, expect, mock, beforeEach } from "bun:test"
|
||||
import { describe, test, expect, mock, beforeEach, afterAll } from "bun:test"
|
||||
import { truncateUntilTargetTokens } from "./storage"
|
||||
import * as storage from "./storage"
|
||||
|
||||
@@ -11,6 +11,10 @@ mock.module("./storage", () => {
|
||||
}
|
||||
})
|
||||
|
||||
afterAll(() => {
|
||||
mock.module("./storage", () => ({ ...storage }))
|
||||
})
|
||||
|
||||
describe("truncateUntilTargetTokens", () => {
|
||||
const sessionID = "test-session"
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { describe, expect, test, beforeEach, afterEach, mock } from "bun:test"
|
||||
import { describe, expect, test, beforeEach, afterEach, afterAll, mock } from "bun:test"
|
||||
import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs"
|
||||
import { join } from "node:path"
|
||||
import { tmpdir } from "node:os"
|
||||
@@ -9,20 +9,19 @@ import {
|
||||
readBoulderState,
|
||||
} from "../../features/boulder-state"
|
||||
import type { BoulderState } from "../../features/boulder-state"
|
||||
|
||||
const TEST_STORAGE_ROOT = join(tmpdir(), `atlas-message-storage-${randomUUID()}`)
|
||||
const TEST_MESSAGE_STORAGE = join(TEST_STORAGE_ROOT, "message")
|
||||
const TEST_PART_STORAGE = join(TEST_STORAGE_ROOT, "part")
|
||||
|
||||
mock.module("../../features/hook-message-injector/constants", () => ({
|
||||
OPENCODE_STORAGE: TEST_STORAGE_ROOT,
|
||||
MESSAGE_STORAGE: TEST_MESSAGE_STORAGE,
|
||||
PART_STORAGE: TEST_PART_STORAGE,
|
||||
}))
|
||||
const realClaudeCodeSessionState = await import(
|
||||
"../../features/claude-code-session-state"
|
||||
)
|
||||
|
||||
const { createAtlasHook } = await import("./index")
|
||||
const { MESSAGE_STORAGE } = await import("../../features/hook-message-injector")
|
||||
|
||||
afterAll(() => {
|
||||
mock.module("../../features/claude-code-session-state", () => ({
|
||||
...realClaudeCodeSessionState,
|
||||
}))
|
||||
})
|
||||
|
||||
describe("atlas hook", () => {
|
||||
let TEST_DIR: string
|
||||
let SISYPHUS_DIR: string
|
||||
@@ -77,7 +76,6 @@ describe("atlas hook", () => {
|
||||
if (existsSync(TEST_DIR)) {
|
||||
rmSync(TEST_DIR, { recursive: true, force: true })
|
||||
}
|
||||
rmSync(TEST_STORAGE_ROOT, { recursive: true, force: true })
|
||||
})
|
||||
|
||||
describe("tool.execute.after handler", () => {
|
||||
|
||||
@@ -1,4 +1,11 @@
|
||||
import { describe, it, expect, mock, beforeEach } from "bun:test"
|
||||
import { describe, it, expect, mock, beforeEach, afterAll } from "bun:test"
|
||||
|
||||
const realChecker = await import("../checker")
|
||||
const realVersionChannel = await import("../version-channel")
|
||||
const realCache = await import("../cache")
|
||||
const realConfigManager = await import("../../../cli/config-manager")
|
||||
const realUpdateToasts = await import("./update-toasts")
|
||||
const realLogger = await import("../../../shared/logger")
|
||||
|
||||
// Mock modules before importing
|
||||
const mockFindPluginEntry = mock(() => null as any)
|
||||
@@ -39,6 +46,15 @@ mock.module("../../../shared/logger", () => ({
|
||||
log: () => {},
|
||||
}))
|
||||
|
||||
afterAll(() => {
|
||||
mock.module("../checker", () => ({ ...realChecker }))
|
||||
mock.module("../version-channel", () => ({ ...realVersionChannel }))
|
||||
mock.module("../cache", () => ({ ...realCache }))
|
||||
mock.module("../../../cli/config-manager", () => ({ ...realConfigManager }))
|
||||
mock.module("./update-toasts", () => ({ ...realUpdateToasts }))
|
||||
mock.module("../../../shared/logger", () => ({ ...realLogger }))
|
||||
})
|
||||
|
||||
const { runBackgroundUpdateCheck } = await import("./background-update-check")
|
||||
|
||||
describe("runBackgroundUpdateCheck", () => {
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { describe, it, expect, mock, beforeEach } from "bun:test"
|
||||
import { describe, it, expect, mock, beforeEach, afterAll } from "bun:test"
|
||||
import type { ClaudeHooksConfig } from "./types"
|
||||
import type { StopContext } from "./stop"
|
||||
|
||||
const realCommandExecutor = await import("../../shared/command-executor")
|
||||
const realLogger = await import("../../shared/logger")
|
||||
|
||||
const mockExecuteHookCommand = mock(() =>
|
||||
Promise.resolve({ exitCode: 0, stdout: "", stderr: "" })
|
||||
)
|
||||
@@ -17,6 +20,11 @@ mock.module("../../shared/logger", () => ({
|
||||
getLogFilePath: () => "/tmp/test.log",
|
||||
}))
|
||||
|
||||
afterAll(() => {
|
||||
mock.module("../../shared/command-executor", () => ({ ...realCommandExecutor }))
|
||||
mock.module("../../shared/logger", () => ({ ...realLogger }))
|
||||
})
|
||||
|
||||
const { executeStopHooks } = await import("./stop")
|
||||
|
||||
function createStopContext(overrides?: Partial<StopContext>): StopContext {
|
||||
|
||||
@@ -1,10 +1,19 @@
|
||||
import { describe, test, expect, mock } from "bun:test"
|
||||
import { describe, test, expect, mock, afterAll } from "bun:test"
|
||||
import { chmodSync, mkdtempSync, writeFileSync } from "node:fs"
|
||||
import { join } from "node:path"
|
||||
import { tmpdir } from "node:os"
|
||||
|
||||
import type { PendingCall } from "./types"
|
||||
|
||||
const realCli = await import("./cli")
|
||||
const cliTsHref = new URL("./cli.ts", import.meta.url).href
|
||||
|
||||
afterAll(() => {
|
||||
mock.module("./cli", () => ({ ...realCli }))
|
||||
mock.module("./cli.ts", () => ({ ...realCli }))
|
||||
mock.module(cliTsHref, () => ({ ...realCli }))
|
||||
})
|
||||
|
||||
function createMockInput() {
|
||||
return {
|
||||
session_id: "test",
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { describe, it, expect, mock, beforeEach } from "bun:test"
|
||||
import { describe, it, expect, mock, beforeEach, afterAll } from "bun:test"
|
||||
|
||||
const realCliRunner = await import("./cli-runner")
|
||||
|
||||
const processApplyPatchEditsWithCli = mock(async () => {})
|
||||
|
||||
@@ -10,6 +12,10 @@ mock.module("./cli-runner", () => ({
|
||||
processApplyPatchEditsWithCli,
|
||||
}))
|
||||
|
||||
afterAll(() => {
|
||||
mock.module("./cli-runner", () => ({ ...realCliRunner }))
|
||||
})
|
||||
|
||||
const { createCommentCheckerHooks } = await import("./hook")
|
||||
|
||||
describe("comment-checker apply_patch integration", () => {
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { describe, expect, it, mock } from "bun:test"
|
||||
import { describe, expect, it, mock, afterAll } from "bun:test"
|
||||
|
||||
const realSystemDirective = await import("../../shared/system-directive")
|
||||
|
||||
mock.module("../../shared/system-directive", () => ({
|
||||
createSystemDirective: (type: string) => `[DIRECTIVE:${type}]`,
|
||||
@@ -14,6 +16,10 @@ mock.module("../../shared/system-directive", () => ({
|
||||
},
|
||||
}))
|
||||
|
||||
afterAll(() => {
|
||||
mock.module("../../shared/system-directive", () => ({ ...realSystemDirective }))
|
||||
})
|
||||
|
||||
import { createCompactionContextInjector } from "./index"
|
||||
import { TaskHistory } from "../../features/background-agent/task-history"
|
||||
|
||||
|
||||
@@ -30,7 +30,7 @@ function createMockContext(todoResponses: TodoSnapshot[][]): PluginInput {
|
||||
},
|
||||
},
|
||||
directory: "/tmp/test",
|
||||
} as PluginInput
|
||||
} as unknown as PluginInput
|
||||
}
|
||||
|
||||
describe("compaction-todo-preserver", () => {
|
||||
@@ -38,7 +38,7 @@ describe("compaction-todo-preserver", () => {
|
||||
//#given
|
||||
updateMock.mockClear()
|
||||
const sessionID = "session-compaction-missing"
|
||||
const todos = [
|
||||
const todos: TodoSnapshot[] = [
|
||||
{ id: "1", content: "Task 1", status: "pending", priority: "high" },
|
||||
{ id: "2", content: "Task 2", status: "in_progress", priority: "medium" },
|
||||
]
|
||||
@@ -58,7 +58,7 @@ describe("compaction-todo-preserver", () => {
|
||||
//#given
|
||||
updateMock.mockClear()
|
||||
const sessionID = "session-compaction-present"
|
||||
const todos = [
|
||||
const todos: TodoSnapshot[] = [
|
||||
{ id: "1", content: "Task 1", status: "pending", priority: "high" },
|
||||
]
|
||||
const ctx = createMockContext([todos, todos])
|
||||
|
||||
@@ -1,34 +1,59 @@
|
||||
import { beforeEach, describe, expect, it, mock } from "bun:test"
|
||||
import { beforeEach, afterEach, describe, expect, it, mock, afterAll } from "bun:test"
|
||||
|
||||
const readFileSyncMock = mock((_: string, __: string) => "# AGENTS")
|
||||
const realNodeFs = await import("node:fs")
|
||||
const realFinder = await import("./finder")
|
||||
const realStorage = await import("./storage")
|
||||
|
||||
const originalReadFileSync = realNodeFs.readFileSync
|
||||
const readFileSyncMock = mock((filePath: string, encoding?: string) => {
|
||||
if (String(filePath).endsWith("AGENTS.md")) {
|
||||
return "# AGENTS"
|
||||
}
|
||||
return originalReadFileSync(filePath as never, encoding as never)
|
||||
})
|
||||
const findAgentsMdUpMock = mock((_: { startDir: string; rootDir: string }) => [] as string[])
|
||||
const resolveFilePathMock = mock((_: string, path: string) => path)
|
||||
const loadInjectedPathsMock = mock((_: string) => new Set<string>())
|
||||
const saveInjectedPathsMock = mock((_: string, __: Set<string>) => {})
|
||||
|
||||
mock.module("node:fs", () => ({
|
||||
readFileSync: readFileSyncMock,
|
||||
}))
|
||||
afterAll(() => {
|
||||
mock.module("node:fs", () => ({ ...realNodeFs }))
|
||||
mock.module("./finder", () => ({ ...realFinder }))
|
||||
mock.module("./storage", () => ({ ...realStorage }))
|
||||
})
|
||||
|
||||
mock.module("./finder", () => ({
|
||||
findAgentsMdUp: findAgentsMdUpMock,
|
||||
resolveFilePath: resolveFilePathMock,
|
||||
}))
|
||||
|
||||
mock.module("./storage", () => ({
|
||||
loadInjectedPaths: loadInjectedPathsMock,
|
||||
saveInjectedPaths: saveInjectedPathsMock,
|
||||
}))
|
||||
|
||||
const { processFilePathForAgentsInjection } = await import("./injector")
|
||||
let processFilePathForAgentsInjection: typeof import("./injector").processFilePathForAgentsInjection
|
||||
|
||||
describe("processFilePathForAgentsInjection", () => {
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
readFileSyncMock.mockClear()
|
||||
findAgentsMdUpMock.mockClear()
|
||||
resolveFilePathMock.mockClear()
|
||||
loadInjectedPathsMock.mockClear()
|
||||
saveInjectedPathsMock.mockClear()
|
||||
|
||||
mock.module("node:fs", () => ({
|
||||
...realNodeFs,
|
||||
readFileSync: readFileSyncMock,
|
||||
}))
|
||||
|
||||
mock.module("./finder", () => ({
|
||||
findAgentsMdUp: findAgentsMdUpMock,
|
||||
resolveFilePath: resolveFilePathMock,
|
||||
}))
|
||||
|
||||
mock.module("./storage", () => ({
|
||||
loadInjectedPaths: loadInjectedPathsMock,
|
||||
saveInjectedPaths: saveInjectedPathsMock,
|
||||
}))
|
||||
|
||||
;({ processFilePathForAgentsInjection } = await import(`./injector?${Date.now()}`))
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
mock.module("node:fs", () => ({ ...realNodeFs }))
|
||||
mock.module("./finder", () => ({ ...realFinder }))
|
||||
mock.module("./storage", () => ({ ...realStorage }))
|
||||
})
|
||||
|
||||
it("does not save when all discovered paths are already cached", async () => {
|
||||
|
||||
@@ -1,34 +1,59 @@
|
||||
import { beforeEach, describe, expect, it, mock } from "bun:test"
|
||||
import { beforeEach, afterEach, describe, expect, it, mock, afterAll } from "bun:test"
|
||||
|
||||
const readFileSyncMock = mock((_: string, __: string) => "# README")
|
||||
const realNodeFs = await import("node:fs")
|
||||
const realFinder = await import("./finder")
|
||||
const realStorage = await import("./storage")
|
||||
|
||||
const originalReadFileSync = realNodeFs.readFileSync
|
||||
const readFileSyncMock = mock((filePath: string, encoding?: string) => {
|
||||
if (String(filePath).endsWith("README.md")) {
|
||||
return "# README"
|
||||
}
|
||||
return originalReadFileSync(filePath as never, encoding as never)
|
||||
})
|
||||
const findReadmeMdUpMock = mock((_: { startDir: string; rootDir: string }) => [] as string[])
|
||||
const resolveFilePathMock = mock((_: string, path: string) => path)
|
||||
const loadInjectedPathsMock = mock((_: string) => new Set<string>())
|
||||
const saveInjectedPathsMock = mock((_: string, __: Set<string>) => {})
|
||||
|
||||
mock.module("node:fs", () => ({
|
||||
readFileSync: readFileSyncMock,
|
||||
}))
|
||||
afterAll(() => {
|
||||
mock.module("node:fs", () => ({ ...realNodeFs }))
|
||||
mock.module("./finder", () => ({ ...realFinder }))
|
||||
mock.module("./storage", () => ({ ...realStorage }))
|
||||
})
|
||||
|
||||
mock.module("./finder", () => ({
|
||||
findReadmeMdUp: findReadmeMdUpMock,
|
||||
resolveFilePath: resolveFilePathMock,
|
||||
}))
|
||||
|
||||
mock.module("./storage", () => ({
|
||||
loadInjectedPaths: loadInjectedPathsMock,
|
||||
saveInjectedPaths: saveInjectedPathsMock,
|
||||
}))
|
||||
|
||||
const { processFilePathForReadmeInjection } = await import("./injector")
|
||||
let processFilePathForReadmeInjection: typeof import("./injector").processFilePathForReadmeInjection
|
||||
|
||||
describe("processFilePathForReadmeInjection", () => {
|
||||
beforeEach(() => {
|
||||
beforeEach(async () => {
|
||||
readFileSyncMock.mockClear()
|
||||
findReadmeMdUpMock.mockClear()
|
||||
resolveFilePathMock.mockClear()
|
||||
loadInjectedPathsMock.mockClear()
|
||||
saveInjectedPathsMock.mockClear()
|
||||
|
||||
mock.module("node:fs", () => ({
|
||||
...realNodeFs,
|
||||
readFileSync: readFileSyncMock,
|
||||
}))
|
||||
|
||||
mock.module("./finder", () => ({
|
||||
findReadmeMdUp: findReadmeMdUpMock,
|
||||
resolveFilePath: resolveFilePathMock,
|
||||
}))
|
||||
|
||||
mock.module("./storage", () => ({
|
||||
loadInjectedPaths: loadInjectedPathsMock,
|
||||
saveInjectedPaths: saveInjectedPathsMock,
|
||||
}))
|
||||
|
||||
;({ processFilePathForReadmeInjection } = await import(`./injector?${Date.now()}`))
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
mock.module("node:fs", () => ({ ...realNodeFs }))
|
||||
mock.module("./finder", () => ({ ...realFinder }))
|
||||
mock.module("./storage", () => ({ ...realStorage }))
|
||||
})
|
||||
|
||||
it("does not save when all discovered paths are already cached", async () => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { describe, expect, test, beforeEach, afterEach, mock } from "bun:test"
|
||||
import { describe, expect, test, beforeEach, afterEach } from "bun:test"
|
||||
import { mkdirSync, rmSync, writeFileSync } from "node:fs"
|
||||
import { join } from "node:path"
|
||||
import { tmpdir } from "node:os"
|
||||
@@ -6,16 +6,6 @@ import { randomUUID } from "node:crypto"
|
||||
import { SYSTEM_DIRECTIVE_PREFIX } from "../../shared/system-directive"
|
||||
import { clearSessionAgent } from "../../features/claude-code-session-state"
|
||||
|
||||
const TEST_STORAGE_ROOT = join(tmpdir(), `prometheus-md-only-${randomUUID()}`)
|
||||
const TEST_MESSAGE_STORAGE = join(TEST_STORAGE_ROOT, "message")
|
||||
const TEST_PART_STORAGE = join(TEST_STORAGE_ROOT, "part")
|
||||
|
||||
mock.module("../../features/hook-message-injector/constants", () => ({
|
||||
OPENCODE_STORAGE: TEST_STORAGE_ROOT,
|
||||
MESSAGE_STORAGE: TEST_MESSAGE_STORAGE,
|
||||
PART_STORAGE: TEST_PART_STORAGE,
|
||||
}))
|
||||
|
||||
const { createPrometheusMdOnlyHook } = await import("./index")
|
||||
const { MESSAGE_STORAGE } = await import("../../features/hook-message-injector")
|
||||
|
||||
@@ -52,7 +42,6 @@ describe("prometheus-md-only", () => {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
rmSync(TEST_STORAGE_ROOT, { recursive: true, force: true })
|
||||
})
|
||||
|
||||
describe("agent name matching", () => {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, mock } from "bun:test";
|
||||
import { afterEach, beforeEach, describe, expect, it, mock, afterAll } from "bun:test";
|
||||
import * as fs from "node:fs";
|
||||
import { mkdirSync, rmSync, writeFileSync } from "node:fs";
|
||||
import * as os from "node:os";
|
||||
@@ -6,6 +6,10 @@ import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { RULES_INJECTOR_STORAGE } from "./constants";
|
||||
|
||||
const realNodeFs = await import("node:fs");
|
||||
const realNodeOs = await import("node:os");
|
||||
const realMatcher = await import("./matcher");
|
||||
|
||||
type StatSnapshot = { mtimeMs: number; size: number };
|
||||
|
||||
let trackedRulePath = "";
|
||||
@@ -56,6 +60,12 @@ mock.module("./matcher", () => ({
|
||||
isDuplicateByContentHash: (hash: string, cache: Set<string>) => cache.has(hash),
|
||||
}));
|
||||
|
||||
afterAll(() => {
|
||||
mock.module("node:fs", () => ({ ...realNodeFs }));
|
||||
mock.module("node:os", () => ({ ...realNodeOs }));
|
||||
mock.module("./matcher", () => ({ ...realMatcher }));
|
||||
});
|
||||
|
||||
function createOutput(): { title: string; output: string; metadata: unknown } {
|
||||
return { title: "tool", output: "", metadata: {} };
|
||||
}
|
||||
|
||||
@@ -518,7 +518,7 @@ describe("todo-continuation-enforcer", () => {
|
||||
|
||||
//#then
|
||||
expect(promptCalls).toHaveLength(2)
|
||||
})
|
||||
}, 20000)
|
||||
|
||||
test("should keep injecting even when todos remain unchanged across cycles", async () => {
|
||||
//#given
|
||||
@@ -553,7 +553,7 @@ describe("todo-continuation-enforcer", () => {
|
||||
|
||||
//#then — all 5 injections should fire (no stagnation cap)
|
||||
expect(promptCalls).toHaveLength(5)
|
||||
})
|
||||
}, 30000)
|
||||
|
||||
test("should skip idle handling while injection is in flight", async () => {
|
||||
//#given
|
||||
@@ -613,7 +613,7 @@ describe("todo-continuation-enforcer", () => {
|
||||
|
||||
//#then
|
||||
expect(promptCalls).toHaveLength(2)
|
||||
})
|
||||
}, 20000)
|
||||
|
||||
test("should accept skipAgents option without error", async () => {
|
||||
// given - session with skipAgents configured for Prometheus
|
||||
|
||||
14
src/shared/agents-config-dir.test.ts
Normal file
14
src/shared/agents-config-dir.test.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { describe, test, expect } from "bun:test"
|
||||
import { getAgentsConfigDir } from "./agents-config-dir"
|
||||
|
||||
describe("getAgentsConfigDir", () => {
|
||||
test("returns path ending with .agents", () => {
|
||||
// given agents config dir is requested
|
||||
|
||||
// when getAgentsConfigDir is called
|
||||
const result = getAgentsConfigDir()
|
||||
|
||||
// then returns path ending with .agents
|
||||
expect(result.endsWith(".agents")).toBe(true)
|
||||
})
|
||||
})
|
||||
6
src/shared/agents-config-dir.ts
Normal file
6
src/shared/agents-config-dir.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { homedir } from "node:os"
|
||||
import { join } from "node:path"
|
||||
|
||||
export function getAgentsConfigDir(): string {
|
||||
return join(homedir(), ".agents")
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
import { describe, it, expect, beforeAll, afterAll } from "bun:test"
|
||||
import { mkdirSync, writeFileSync, symlinkSync, rmSync } from "fs"
|
||||
import { mkdirSync, writeFileSync, symlinkSync, rmSync, realpathSync } from "fs"
|
||||
import { join } from "path"
|
||||
import { tmpdir } from "os"
|
||||
import { resolveSymlink, resolveSymlinkAsync, isSymbolicLink } from "./file-utils"
|
||||
|
||||
const testDir = join(tmpdir(), "file-utils-test-" + Date.now())
|
||||
const testDir = join(realpathSync(tmpdir()), "file-utils-test-" + Date.now())
|
||||
|
||||
// Create a directory structure that mimics the real-world scenario:
|
||||
//
|
||||
|
||||
@@ -1,16 +1,28 @@
|
||||
const { describe, test, expect, mock } = require("bun:test")
|
||||
const { describe, test, expect, mock, beforeEach, afterEach } = require("bun:test")
|
||||
|
||||
mock.module("./session-creator", () => ({
|
||||
createOrGetSession: mock(async () => ({ sessionID: "ses-test-123" })),
|
||||
}))
|
||||
const realCompletionPoller = require("./completion-poller")
|
||||
const realMessageProcessor = require("./message-processor")
|
||||
|
||||
mock.module("./completion-poller", () => ({
|
||||
waitForCompletion: mock(async () => {}),
|
||||
}))
|
||||
const waitForCompletionMock = mock(async () => {})
|
||||
const processMessagesMock = mock(async () => "agent response")
|
||||
|
||||
mock.module("./message-processor", () => ({
|
||||
processMessages: mock(async () => "agent response"),
|
||||
}))
|
||||
beforeEach(() => {
|
||||
waitForCompletionMock.mockClear()
|
||||
processMessagesMock.mockClear()
|
||||
|
||||
mock.module("./completion-poller", () => ({
|
||||
waitForCompletion: waitForCompletionMock,
|
||||
}))
|
||||
|
||||
mock.module("./message-processor", () => ({
|
||||
processMessages: processMessagesMock,
|
||||
}))
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
mock.module("./completion-poller", () => ({ ...realCompletionPoller }))
|
||||
mock.module("./message-processor", () => ({ ...realMessageProcessor }))
|
||||
})
|
||||
|
||||
describe("executeSync", () => {
|
||||
test("passes question=false via tools parameter to block question tool", async () => {
|
||||
@@ -27,6 +39,7 @@ describe("executeSync", () => {
|
||||
subagent_type: "explore",
|
||||
description: "test task",
|
||||
prompt: "find something",
|
||||
session_id: "ses-test-123",
|
||||
}
|
||||
|
||||
const toolContext = {
|
||||
@@ -39,7 +52,10 @@ describe("executeSync", () => {
|
||||
|
||||
const ctx = {
|
||||
client: {
|
||||
session: { promptAsync },
|
||||
session: {
|
||||
promptAsync,
|
||||
get: mock(async () => ({ data: { id: "ses-test-123" } })),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@@ -65,6 +81,7 @@ describe("executeSync", () => {
|
||||
subagent_type: "librarian",
|
||||
description: "search docs",
|
||||
prompt: "find docs",
|
||||
session_id: "ses-test-123",
|
||||
}
|
||||
|
||||
const toolContext = {
|
||||
@@ -77,7 +94,10 @@ describe("executeSync", () => {
|
||||
|
||||
const ctx = {
|
||||
client: {
|
||||
session: { promptAsync },
|
||||
session: {
|
||||
promptAsync,
|
||||
get: mock(async () => ({ data: { id: "ses-test-123" } })),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,9 @@ import { mkdtempSync, rmSync, writeFileSync } from "node:fs"
|
||||
import { join } from "node:path"
|
||||
import { tmpdir } from "node:os"
|
||||
|
||||
import { describe, it, expect, spyOn, mock, beforeEach, afterEach } from "bun:test"
|
||||
import { describe, it, expect, spyOn, mock, beforeEach, afterEach, afterAll } from "bun:test"
|
||||
|
||||
const realJsonRpcNode = await import("vscode-jsonrpc/node")
|
||||
|
||||
mock.module("vscode-jsonrpc/node", () => ({
|
||||
createMessageConnection: () => {
|
||||
@@ -12,6 +14,10 @@ mock.module("vscode-jsonrpc/node", () => ({
|
||||
StreamMessageWriter: function StreamMessageWriter() {},
|
||||
}))
|
||||
|
||||
afterAll(() => {
|
||||
mock.module("vscode-jsonrpc/node", () => ({ ...realJsonRpcNode }))
|
||||
})
|
||||
|
||||
import { LSPClient, lspManager, validateCwd } from "./client"
|
||||
import type { ResolvedServer } from "./types"
|
||||
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import { describe, test, expect, beforeEach, afterEach, mock } from "bun:test"
|
||||
import { describe, test, expect, beforeEach, afterEach, afterAll, mock } from "bun:test"
|
||||
import { mkdirSync, writeFileSync, rmSync, existsSync } from "node:fs"
|
||||
import { join } from "node:path"
|
||||
import { tmpdir } from "node:os"
|
||||
import { randomUUID } from "node:crypto"
|
||||
|
||||
const realConstants = await import("./constants")
|
||||
|
||||
const TEST_DIR = join(tmpdir(), `omo-test-session-manager-${randomUUID()}`)
|
||||
const TEST_MESSAGE_STORAGE = join(TEST_DIR, "message")
|
||||
const TEST_PART_STORAGE = join(TEST_DIR, "part")
|
||||
@@ -26,6 +28,10 @@ mock.module("./constants", () => ({
|
||||
TOOL_NAME_PREFIX: "session_",
|
||||
}))
|
||||
|
||||
afterAll(() => {
|
||||
mock.module("./constants", () => ({ ...realConstants }))
|
||||
})
|
||||
|
||||
const { getAllSessions, getMessageDir, sessionExists, readSessionMessages, readSessionTodos, getSessionInfo } =
|
||||
await import("./storage")
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { describe, it, expect, beforeEach, mock, spyOn } from "bun:test"
|
||||
import { describe, it, expect, beforeEach, mock, spyOn, afterAll } from "bun:test"
|
||||
import type { ToolContext } from "@opencode-ai/plugin/tool"
|
||||
import * as fs from "node:fs"
|
||||
import { createSkillTool } from "./tools"
|
||||
@@ -8,6 +8,8 @@ import type { Tool as McpTool } from "@modelcontextprotocol/sdk/types.js"
|
||||
|
||||
const originalReadFileSync = fs.readFileSync.bind(fs)
|
||||
|
||||
const realNodeFs = await import("node:fs")
|
||||
|
||||
mock.module("node:fs", () => ({
|
||||
...fs,
|
||||
readFileSync: (path: string, encoding?: string) => {
|
||||
@@ -21,6 +23,10 @@ Test skill body content`
|
||||
},
|
||||
}))
|
||||
|
||||
afterAll(() => {
|
||||
mock.module("node:fs", () => ({ ...realNodeFs }))
|
||||
})
|
||||
|
||||
function createMockSkill(name: string, options: { agent?: string } = {}): LoadedSkill {
|
||||
return {
|
||||
name,
|
||||
|
||||
Reference in New Issue
Block a user