basant307/AI_Governance_Project
048
1/**2 * @license3 * Copyright 2025 Google LLC4 * SPDX-License-Identifier: Apache-2.05 */6 7import { vi } from 'vitest';8import type { CommandContext } from '../ui/commands/types.js';9import type { LoadedSettings } from '../config/settings.js';10import type { SessionStatsState } from '../ui/contexts/SessionContext.js';11import { ToolCallDecision } from '../ui/contexts/SessionContext.js';12 13// A utility type to make all properties of an object, and its nested objects, partial.14type DeepPartial<T> = T extends object15 ? {16 [P in keyof T]?: DeepPartial<T[P]>;17 }18 : T;19 20/**21 * Creates a deep, fully-typed mock of the CommandContext for use in tests.22 * All functions are pre-mocked with `vi.fn()`.23 *24 * @param overrides - A deep partial object to override any default mock values.25 * @returns A complete, mocked CommandContext object.26 */27export const createMockCommandContext = (28 overrides: DeepPartial<CommandContext> = {},29): CommandContext => {30 const defaultMocks: CommandContext = {31 executionMode: 'interactive',32 invocation: {33 raw: '',34 name: '',35 args: '',36 },37 services: {38 config: null,39 settings: {40 merged: {},41 setValue: vi.fn(),42 isTrusted: true,43 } as unknown as LoadedSettings,44 logger: {45 log: vi.fn(),46 logMessage: vi.fn(),47 saveCheckpoint: vi.fn(),48 loadCheckpoint: vi.fn().mockResolvedValue([]),49 // eslint-disable-next-line @typescript-eslint/no-explicit-any50 } as any, // Cast because Logger is a class.51 },52 ui: {53 history: [],54 addItem: vi.fn(),55 clear: vi.fn(),56 setDebugMessage: vi.fn(),57 pendingItem: null,58 setPendingItem: vi.fn(),59 btwItem: null,60 setBtwItem: vi.fn(),61 cancelBtw: vi.fn(),62 btwAbortControllerRef: { current: null },63 isIdleRef: { current: true },64 loadHistory: vi.fn(),65 refreshStatic: vi.fn(),66 toggleVimEnabled: vi.fn(),67 extensionsUpdateState: new Map(),68 setExtensionsUpdateState: vi.fn(),69 reloadCommands: vi.fn(),70 setSessionName: vi.fn(),71 // eslint-disable-next-line @typescript-eslint/no-explicit-any72 } as any,73 session: {74 sessionShellAllowlist: new Set<string>(),75 startNewSession: vi.fn(),76 stats: {77 sessionId: '',78 sessionStartTime: new Date(),79 lastPromptTokenCount: 0,80 metrics: {81 models: {},82 tools: {83 totalCalls: 0,84 totalSuccess: 0,85 totalFail: 0,86 totalDurationMs: 0,87 totalDecisions: {88 [ToolCallDecision.ACCEPT]: 0,89 [ToolCallDecision.REJECT]: 0,90 [ToolCallDecision.MODIFY]: 0,91 [ToolCallDecision.AUTO_ACCEPT]: 0,92 },93 byName: {},94 },95 files: { totalLinesAdded: 0, totalLinesRemoved: 0 },96 skills: {97 totalCalls: 0,98 totalSuccess: 0,99 totalFail: 0,100 byName: {},101 },102 },103 promptCount: 0,104 } as SessionStatsState,105 },106 };107 108 // eslint-disable-next-line @typescript-eslint/no-explicit-any109 const merge = (target: any, source: any): any => {110 const output = { ...target };111 112 for (const key in source) {113 if (Object.prototype.hasOwnProperty.call(source, key)) {114 const sourceValue = source[key];115 const targetValue = output[key];116 117 if (118 // We only want to recursivlty merge plain objects119 Object.prototype.toString.call(sourceValue) === '[object Object]' &&120 Object.prototype.toString.call(targetValue) === '[object Object]'121 ) {122 output[key] = merge(targetValue, sourceValue);123 } else {124 // If not, we do a direct assignment. This preserves Date objects and others.125 output[key] = sourceValue;126 }127 }128 }129 return output;130 };131 132 return merge(defaultMocks, overrides);133};134 