From 37e550200c34cefee96304ec5377844f4278db04 Mon Sep 17 00:00:00 2001 From: Jiayuan Zhang Date: Fri, 13 Feb 2026 22:17:41 +0800 Subject: [PATCH] refactor(core): add baseDir DI and test helpers for mock-free testing Add AuthStoreOptions with baseDir to auth-profiles/store.ts functions, add baseDir option to announce.ts readLatestAssistantReply, and add seedSubagentRunForTests helper to registry.ts. These enable tests to use real implementations with temp directories instead of mocking internal modules. Co-Authored-By: Claude Opus 4.6 --- .../core/src/agent/auth-profiles/store.ts | 32 +++++++++++-------- packages/core/src/agent/subagent/announce.ts | 7 ++-- packages/core/src/agent/subagent/registry.ts | 5 +++ 3 files changed, 29 insertions(+), 15 deletions(-) diff --git a/packages/core/src/agent/auth-profiles/store.ts b/packages/core/src/agent/auth-profiles/store.ts index 7b090064..bfebc1bc 100644 --- a/packages/core/src/agent/auth-profiles/store.ts +++ b/packages/core/src/agent/auth-profiles/store.ts @@ -115,9 +115,14 @@ function acquireLockSync(filePath: string): () => void { // Paths // ============================================================ +export interface AuthStoreOptions { + /** Override the base directory (defaults to DATA_DIR). Useful for testing. */ + baseDir?: string; +} + /** Resolve the auth profile store file path */ -export function resolveAuthStorePath(): string { - return join(DATA_DIR, AUTH_PROFILE_STORE_FILENAME); +export function resolveAuthStorePath(options?: AuthStoreOptions): string { + return join(options?.baseDir ?? DATA_DIR, AUTH_PROFILE_STORE_FILENAME); } // ============================================================ @@ -148,8 +153,8 @@ export function coerceStore(raw: unknown): AuthProfileStore { } /** Ensure the store file exists on disk (creates it if missing) */ -export function ensureAuthStoreFile(): string { - const storePath = resolveAuthStorePath(); +export function ensureAuthStoreFile(options?: AuthStoreOptions): string { + const storePath = resolveAuthStorePath(options); const dir = dirname(storePath); if (!existsSync(dir)) { mkdirSync(dir, { recursive: true }); @@ -161,8 +166,8 @@ export function ensureAuthStoreFile(): string { } /** Load auth profile store from disk. Returns empty store if file doesn't exist. */ -export function loadAuthProfileStore(): AuthProfileStore { - const storePath = resolveAuthStorePath(); +export function loadAuthProfileStore(options?: AuthStoreOptions): AuthProfileStore { + const storePath = resolveAuthStorePath(options); if (!existsSync(storePath)) return createEmptyStore(); try { @@ -174,8 +179,8 @@ export function loadAuthProfileStore(): AuthProfileStore { } /** Save auth profile store to disk */ -export function saveAuthProfileStore(store: AuthProfileStore): void { - const storePath = resolveAuthStorePath(); +export function saveAuthProfileStore(store: AuthProfileStore, options?: AuthStoreOptions): void { + const storePath = resolveAuthStorePath(options); const dir = dirname(storePath); if (!existsSync(dir)) { mkdirSync(dir, { recursive: true }); @@ -191,24 +196,25 @@ export function saveAuthProfileStore(store: AuthProfileStore): void { */ export function updateAuthProfileStore( updater: (store: AuthProfileStore) => void, + options?: AuthStoreOptions, ): AuthProfileStore { - const storePath = ensureAuthStoreFile(); + const storePath = ensureAuthStoreFile(options); try { const release = acquireLockSync(storePath); try { - const store = loadAuthProfileStore(); + const store = loadAuthProfileStore(options); updater(store); - saveAuthProfileStore(store); + saveAuthProfileStore(store, options); return store; } finally { release(); } } catch { // Fallback: unlocked update (better than losing the write entirely) - const store = loadAuthProfileStore(); + const store = loadAuthProfileStore(options); updater(store); - saveAuthProfileStore(store); + saveAuthProfileStore(store, options); return store; } } diff --git a/packages/core/src/agent/subagent/announce.ts b/packages/core/src/agent/subagent/announce.ts index de6091f3..3ab8eeeb 100644 --- a/packages/core/src/agent/subagent/announce.ts +++ b/packages/core/src/agent/subagent/announce.ts @@ -38,8 +38,11 @@ export function buildSubagentSystemPrompt(params: SubagentSystemPromptParams): s /** * Read the latest assistant reply from a session's JSONL file. */ -export function readLatestAssistantReply(sessionId: string): string | undefined { - const entries = readEntries(sessionId); +export function readLatestAssistantReply( + sessionId: string, + options?: { baseDir?: string }, +): string | undefined { + const entries = readEntries(sessionId, options); let latestToolResultText: string | undefined; // Walk backwards to find the last non-empty assistant reply. diff --git a/packages/core/src/agent/subagent/registry.ts b/packages/core/src/agent/subagent/registry.ts index eaff0158..ca26f5dc 100644 --- a/packages/core/src/agent/subagent/registry.ts +++ b/packages/core/src/agent/subagent/registry.ts @@ -244,6 +244,11 @@ export function resetSubagentRegistryForTests(): void { stopSweeper(); } +/** Seed a run record directly (for testing). Bypasses persistence and side effects. */ +export function seedSubagentRunForTests(record: SubagentRunRecord): void { + subagentRuns.set(record.runId, record); +} + // ============================================================================ // Lifecycle watching // ============================================================================