Path: blob/main/src/vs/platform/agentHost/test/common/sessionTestHelpers.ts
13399 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import type { IReference } from '../../../../base/common/lifecycle.js';6import { Schemas } from '../../../../base/common/network.js';7import { URI } from '../../../../base/common/uri.js';8import type { IDiffComputeService, IDiffCountResult } from '../../common/diffComputeService.js';9import type { IFileEditContent, IFileEditRecord, ISessionDatabase, ISessionDataService } from '../../common/sessionDataService.js';1011export class TestSessionDatabase implements ISessionDatabase {12private readonly _edits: (IFileEditRecord & IFileEditContent)[] = [];13private readonly _metadata = new Map<string, string>();1415getAllFileEditsCalls = 0;16getFileEditsByTurnCalls = 0;1718addEdit(edit: IFileEditRecord & IFileEditContent): void {19this._edits.push(edit);20}2122async createTurn(): Promise<void> { }2324async deleteTurn(turnId: string): Promise<void> {25for (let i = this._edits.length - 1; i >= 0; i--) {26if (this._edits[i].turnId === turnId) {27this._edits.splice(i, 1);28}29}30}3132async storeFileEdit(edit: IFileEditRecord & IFileEditContent): Promise<void> {33const existingIndex = this._edits.findIndex(e => e.toolCallId === edit.toolCallId && e.filePath === edit.filePath);34if (existingIndex >= 0) {35this._edits[existingIndex] = edit;36} else {37this._edits.push(edit);38}39}4041async getFileEdits(toolCallIds: string[]): Promise<IFileEditRecord[]> {42const toolCallIdsSet = new Set(toolCallIds);43return this._toEditRecords(this._edits.filter(e => toolCallIdsSet.has(e.toolCallId)));44}4546async getAllFileEdits(): Promise<IFileEditRecord[]> {47this.getAllFileEditsCalls++;48return this._toEditRecords(this._edits);49}5051async getFileEditsByTurn(turnId: string): Promise<IFileEditRecord[]> {52this.getFileEditsByTurnCalls++;53return this._toEditRecords(this._edits.filter(e => e.turnId === turnId));54}5556async readFileEditContent(toolCallId: string, filePath: string): Promise<IFileEditContent | undefined> {57return this._edits.find(e => e.toolCallId === toolCallId && e.filePath === filePath);58}5960async getMetadata(key: string): Promise<string | undefined> {61return this._metadata.get(key);62}6364async getMetadataObject<T extends Record<string, unknown>>(obj: T): Promise<{ [K in keyof T]: string | undefined }> {65return Object.fromEntries(Object.keys(obj).map(key => [key, this._metadata.get(key)])) as { [K in keyof T]: string | undefined };66}6768async setMetadata(key: string, value: string): Promise<void> {69this._metadata.set(key, value);70}7172async close(): Promise<void> { }7374async vacuumInto(_targetPath: string): Promise<void> { }7576dispose(): void { }7778async setTurnEventId(_turnId: string, _eventId: string): Promise<void> { }7980async getTurnEventId(_turnId: string): Promise<string | undefined> { return undefined; }8182async getNextTurnEventId(_turnId: string): Promise<string | undefined> { return undefined; }8384async getFirstTurnEventId(): Promise<string | undefined> { return undefined; }8586async truncateFromTurn(_turnId: string): Promise<void> { }8788async deleteTurnsAfter(_turnId: string): Promise<void> { }8990async deleteAllTurns(): Promise<void> { }9192async remapTurnIds(_mapping: ReadonlyMap<string, string>): Promise<void> { }9394async whenIdle(): Promise<void> { }9596private _toEditRecords(edits: (IFileEditRecord & IFileEditContent)[]): IFileEditRecord[] {97return edits.map(({ beforeContent: _, afterContent: _2, ...metadata }) => metadata);98}99}100101export class TestDiffComputeService implements IDiffComputeService {102declare readonly _serviceBrand: undefined;103104callCount = 0;105106constructor(private readonly _result?: IDiffCountResult) { }107108async computeDiffCounts(original: string, modified: string): Promise<IDiffCountResult> {109this.callCount++;110if (this._result) {111return this._result;112}113114const originalLines = original ? original.split('\n') : [];115const modifiedLines = modified ? modified.split('\n') : [];116return {117added: Math.max(0, modifiedLines.length - originalLines.length),118removed: Math.max(0, originalLines.length - modifiedLines.length),119};120}121}122123export function createZeroDiffComputeService(): IDiffComputeService {124return new TestDiffComputeService({ added: 0, removed: 0 });125}126127export function createSessionDataService(database: ISessionDatabase = new TestSessionDatabase()): ISessionDataService {128return {129_serviceBrand: undefined,130getSessionDataDir: session => URI.from({ scheme: Schemas.inMemory, path: `/session-data${session.path}` }),131getSessionDataDirById: sessionId => URI.from({ scheme: Schemas.inMemory, path: `/session-data/${sessionId}` }),132openDatabase: () => createReference(database),133tryOpenDatabase: async () => createReference(database),134deleteSessionData: async () => { },135cleanupOrphanedData: async () => { },136whenIdle: async () => { },137};138}139140export function createNullSessionDataService(): ISessionDataService {141return {142_serviceBrand: undefined,143getSessionDataDir: session => URI.from({ scheme: Schemas.inMemory, path: `/session-data${session.path}` }),144getSessionDataDirById: sessionId => URI.from({ scheme: Schemas.inMemory, path: `/session-data/${sessionId}` }),145openDatabase: () => { throw new Error('not implemented'); },146tryOpenDatabase: async () => undefined,147deleteSessionData: async () => { },148cleanupOrphanedData: async () => { },149whenIdle: async () => { },150};151}152153export function encodeString(text: string): Uint8Array {154return new TextEncoder().encode(text);155}156157/**158* Returns a no-op {@link IAgentHostGitService} suitable for tests that159* exercise the {@link AgentService} but don't care about git state.160* Tests that DO care about git state should pass their own implementation.161*/162export function createNoopGitService(): import('../../node/agentHostGitService.js').IAgentHostGitService {163return {164_serviceBrand: undefined,165isInsideWorkTree: async () => false,166getCurrentBranch: async () => undefined,167getDefaultBranch: async () => undefined,168getBranches: async () => [],169getRepositoryRoot: async () => undefined,170getWorktreeRoots: async () => [],171addWorktree: async () => { },172addExistingWorktree: async () => { },173removeWorktree: async () => { },174branchExists: async () => false,175hasUncommittedChanges: async () => false,176getSessionGitState: async () => undefined,177computeSessionFileDiffs: async () => undefined,178showBlob: async () => undefined,179};180}181182function createReference<T>(object: T): IReference<T> {183return {184object,185dispose: () => { },186};187}188189190