Consolidate MockChatService

We have two `MockChatService` classes. This makes updating the interface slow since you have to update both locations. The two also behave slightly differently
This commit is contained in:
Matt Bierner
2026-03-10 09:02:25 -07:00
parent eed7ce60dc
commit 3ef1baf097
2 changed files with 150 additions and 324 deletions

View File

@@ -6,21 +6,21 @@
import assert from 'assert';
import { CancellationToken } from '../../../../../../base/common/cancellation.js';
import { Codicon } from '../../../../../../base/common/codicons.js';
import { Emitter, Event } from '../../../../../../base/common/event.js';
import { DisposableStore, IDisposable } from '../../../../../../base/common/lifecycle.js';
import { ISettableObservable, observableValue } from '../../../../../../base/common/observable.js';
import { Emitter } from '../../../../../../base/common/event.js';
import { DisposableStore } from '../../../../../../base/common/lifecycle.js';
import { observableValue } from '../../../../../../base/common/observable.js';
import { URI } from '../../../../../../base/common/uri.js';
import { runWithFakedTimers } from '../../../../../../base/test/common/timeTravelScheduler.js';
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../base/test/common/utils.js';
import { TestInstantiationService } from '../../../../../../platform/instantiation/test/common/instantiationServiceMock.js';
import { workbenchInstantiationService } from '../../../../../test/browser/workbenchTestServices.js';
import { LocalAgentsSessionsController } from '../../../browser/agentSessions/localAgentSessionsController.js';
import { IChatService, ResponseModelState } from '../../../common/chatService/chatService.js';
import { ChatSessionStatus, IChatSessionItem, IChatSessionsService, localChatSessionType } from '../../../common/chatSessionsService.js';
import { ModifiedFileEntryState } from '../../../common/editing/chatEditingService.js';
import { IChatModel, IChatRequestModel, IChatResponseModel } from '../../../common/model/chatModel.js';
import { ChatRequestQueueKind, IChatDetail, IChatQuestionAnswers, IChatService, IChatSessionStartOptions, ResponseModelState } from '../../../common/chatService/chatService.js';
import { ChatSessionStatus, IChatSessionItem, IChatSessionsService, localChatSessionType } from '../../../common/chatSessionsService.js';
import { LocalChatSessionUri } from '../../../common/model/chatUri.js';
import { ChatAgentLocation } from '../../../common/constants.js';
import { MockChatService } from '../../common/chatService/mockChatService.js';
import { MockChatSessionsService } from '../../common/mockChatSessionsService.js';
function createTestTiming(options?: {
@@ -36,204 +36,6 @@ function createTestTiming(options?: {
};
}
class MockChatService implements IChatService {
private readonly _chatModels: ISettableObservable<Iterable<IChatModel>> = observableValue('chatModels', []);
readonly chatModels = this._chatModels;
requestInProgressObs = observableValue('name', false);
_serviceBrand: undefined;
editingSessions = [];
transferredSessionResource = undefined;
readonly onDidSubmitRequest = Event.None;
readonly onDidCreateModel = Event.None;
private sessions = new Map<string, IChatModel>();
private liveSessionItems: IChatDetail[] = [];
private historySessionItems: IChatDetail[] = [];
private readonly _onDidDisposeSession = new Emitter<{ sessionResource: URI[]; reason: 'cleared' }>();
readonly onDidDisposeSession = this._onDidDisposeSession.event;
fireDidDisposeSession(sessionResource: URI[]): void {
this._onDidDisposeSession.fire({ sessionResource, reason: 'cleared' });
}
setSaveModelsEnabled(enabled: boolean): void {
}
processPendingRequests(sessionResource: URI): void {
}
setLiveSessionItems(items: IChatDetail[]): void {
this.liveSessionItems = items;
}
setHistorySessionItems(items: IChatDetail[]): void {
this.historySessionItems = items;
}
addSession(sessionResource: URI, session: IChatModel): void {
this.sessions.set(sessionResource.toString(), session);
// Update the chatModels observable
this._chatModels.set([...this.sessions.values()], undefined);
}
removeSession(sessionResource: URI): void {
this.sessions.delete(sessionResource.toString());
// Update the chatModels observable
this._chatModels.set([...this.sessions.values()], undefined);
}
isEnabled(_location: ChatAgentLocation): boolean {
return true;
}
hasSessions(): boolean {
return this.sessions.size > 0;
}
getProviderInfos() {
return [];
}
startNewLocalSession(_location: ChatAgentLocation, _options?: IChatSessionStartOptions): any {
throw new Error('Method not implemented.');
}
getSession(sessionResource: URI): IChatModel | undefined {
return this.sessions.get(sessionResource.toString());
}
getLatestRequest(): IChatRequestModel | undefined {
return undefined;
}
acquireOrRestoreSession(_sessionResource: URI): Promise<any> {
throw new Error('Method not implemented.');
}
getSessionTitle(_sessionResource: URI): string | undefined {
return undefined;
}
loadSessionFromData(_data: any): any {
throw new Error('Method not implemented.');
}
acquireOrLoadSession(_resource: URI, _position: ChatAgentLocation, _token: CancellationToken): Promise<any> {
throw new Error('Method not implemented.');
}
acquireExistingSession(_sessionResource: URI): any {
return undefined;
}
setSessionTitle(_sessionResource: URI, _title: string): void { }
appendProgress(_request: IChatRequestModel, _progress: any): void { }
sendRequest(_sessionResource: URI, _message: string): Promise<any> {
throw new Error('Method not implemented.');
}
resendRequest(_request: IChatRequestModel, _options?: any): Promise<void> {
throw new Error('Method not implemented.');
}
adoptRequest(_sessionResource: URI, _request: IChatRequestModel): Promise<void> {
throw new Error('Method not implemented.');
}
removeRequest(_sessionResource: URI, _requestId: string): Promise<void> {
throw new Error('Method not implemented.');
}
async cancelCurrentRequestForSession(_sessionResource: URI, _source?: string): Promise<void> { }
setYieldRequested(_sessionResource: URI): void { }
removePendingRequest(_sessionResource: URI, _requestId: string): void { }
setPendingRequests(_sessionResource: URI, _requests: readonly { requestId: string; kind: ChatRequestQueueKind }[]): void { }
addCompleteRequest(): void { }
async getLocalSessionHistory(): Promise<IChatDetail[]> {
return this.historySessionItems;
}
async clearAllHistoryEntries(): Promise<void> { }
async removeHistoryEntry(_resource: URI): Promise<void> { }
readonly onDidPerformUserAction = Event.None;
notifyUserAction(_event: any): void { }
readonly onDidReceiveQuestionCarouselAnswer = Event.None;
notifyQuestionCarouselAnswer(_requestId: string, _resolveId: string, _answers: IChatQuestionAnswers | undefined): void { }
async transferChatSession(): Promise<void> { }
setChatSessionTitle(): void { }
isEditingLocation(_location: ChatAgentLocation): boolean {
return false;
}
getChatStorageFolder(): URI {
return URI.file('/tmp');
}
logChatIndex(): void { }
activateDefaultAgent(_location: ChatAgentLocation): Promise<void> {
return Promise.resolve();
}
getChatSessionFromInternalUri(_sessionResource: URI): any {
return undefined;
}
async getLiveSessionItems(): Promise<IChatDetail[]> {
return this.liveSessionItems;
}
async getHistorySessionItems(): Promise<IChatDetail[]> {
return this.historySessionItems;
}
waitForModelDisposals(): Promise<void> {
return Promise.resolve();
}
getMetadataForSession(sessionResource: URI): Promise<IChatDetail | undefined> {
throw new Error('Method not implemented.');
}
private onChange?: () => void;
registerChatModelChangeListeners(chatSessionType: string, onChange: () => void): IDisposable {
// Store the emitter so tests can trigger it
this.onChange = onChange;
return {
dispose: () => {
this.onChange = undefined;
}
};
}
// Helper method for tests to trigger progress events
triggerProgressEvent(): void {
if (this.onChange) {
this.onChange();
}
}
}
function createMockChatModel(options: {
sessionResource: URI;
hasRequests?: boolean;
@@ -364,7 +166,7 @@ suite('LocalAgentsSessionsController', () => {
timestamp: Date.now()
});
mockChatService.addSession(sessionResource, mockModel);
mockChatService.addSession(mockModel);
mockChatService.setLiveSessionItems([{
sessionResource,
title: 'Test Session',
@@ -415,7 +217,7 @@ suite('LocalAgentsSessionsController', () => {
hasRequests: true
});
mockChatService.addSession(sessionResource, mockModel);
mockChatService.addSession(mockModel);
mockChatService.setLiveSessionItems([{
sessionResource,
title: 'Live Session',
@@ -452,7 +254,7 @@ suite('LocalAgentsSessionsController', () => {
requestInProgress: true
});
mockChatService.addSession(sessionResource, mockModel);
mockChatService.addSession(mockModel);
mockChatService.setLiveSessionItems([{
sessionResource,
title: 'In Progress Session',
@@ -483,7 +285,7 @@ suite('LocalAgentsSessionsController', () => {
lastResponseHasError: false
});
mockChatService.addSession(sessionResource, mockModel);
mockChatService.addSession(mockModel);
mockChatService.setLiveSessionItems([{
sessionResource,
title: 'Completed Session',
@@ -513,7 +315,7 @@ suite('LocalAgentsSessionsController', () => {
lastResponseCanceled: true
});
mockChatService.addSession(sessionResource, mockModel);
mockChatService.addSession(mockModel);
mockChatService.setLiveSessionItems([{
sessionResource,
title: 'Canceled Session',
@@ -543,7 +345,7 @@ suite('LocalAgentsSessionsController', () => {
lastResponseHasError: true
});
mockChatService.addSession(sessionResource, mockModel);
mockChatService.addSession(mockModel);
mockChatService.setLiveSessionItems([{
sessionResource,
title: 'Error Session',
@@ -588,7 +390,7 @@ suite('LocalAgentsSessionsController', () => {
}
});
mockChatService.addSession(sessionResource, mockModel);
mockChatService.addSession(mockModel);
mockChatService.setLiveSessionItems([{
sessionResource,
title: 'Stats Session',
@@ -634,7 +436,7 @@ suite('LocalAgentsSessionsController', () => {
}
});
mockChatService.addSession(sessionResource, mockModel);
mockChatService.addSession(mockModel);
mockChatService.setLiveSessionItems([{
sessionResource,
title: 'No Stats Session',
@@ -665,7 +467,7 @@ suite('LocalAgentsSessionsController', () => {
timestamp: modelTimestamp
});
mockChatService.addSession(sessionResource, mockModel);
mockChatService.addSession(mockModel);
mockChatService.setLiveSessionItems([{
sessionResource,
title: 'Timing Session',
@@ -719,7 +521,7 @@ suite('LocalAgentsSessionsController', () => {
lastResponseCompletedAt: completedAt
});
mockChatService.addSession(sessionResource, mockModel);
mockChatService.addSession(mockModel);
mockChatService.setLiveSessionItems([{
sessionResource,
title: 'EndTime Session',
@@ -748,7 +550,7 @@ suite('LocalAgentsSessionsController', () => {
hasRequests: true
});
mockChatService.addSession(sessionResource, mockModel);
mockChatService.addSession(mockModel);
mockChatService.setLiveSessionItems([{
sessionResource,
title: 'Icon Session',
@@ -779,7 +581,7 @@ suite('LocalAgentsSessionsController', () => {
});
// Add the session first
mockChatService.addSession(sessionResource, mockModel);
mockChatService.addSession(mockModel);
let changeEventCount = 0;
disposables.add(controller.onDidChangeChatSessionItems(() => {
@@ -805,7 +607,7 @@ suite('LocalAgentsSessionsController', () => {
});
// Add the session first
mockChatService.addSession(sessionResource, mockModel);
mockChatService.addSession(mockModel);
let changeEventCount = 0;
disposables.add(controller.onDidChangeChatSessionItems(() => {
@@ -830,7 +632,7 @@ suite('LocalAgentsSessionsController', () => {
});
// Add the session first
mockChatService.addSession(sessionResource, mockModel);
mockChatService.addSession(mockModel);
// Now remove the session - the observable should trigger cleanup
mockChatService.removeSession(sessionResource);

View File

@@ -4,168 +4,192 @@
*--------------------------------------------------------------------------------------------*/
import { CancellationToken } from '../../../../../../base/common/cancellation.js';
import { Event } from '../../../../../../base/common/event.js';
import { ResourceMap } from '../../../../../../base/common/map.js';
import { IObservable, observableValue } from '../../../../../../base/common/observable.js';
import { URI } from '../../../../../../base/common/uri.js';
import { IChatModel, IChatRequestModel, IChatRequestVariableData, ISerializableChatData } from '../../../common/model/chatModel.js';
import { IParsedChatRequest } from '../../../common/requestParser/chatParserTypes.js';
import { ChatRequestQueueKind, ChatSendResult, IChatCompleteResponse, IChatDetail, IChatModelReference, IChatProgress, IChatProviderInfo, IChatQuestionAnswers, IChatSendRequestOptions, IChatService, IChatSessionContext, IChatSessionStartOptions, IChatUserActionEvent } from '../../../common/chatService/chatService.js';
import { ChatAgentLocation } from '../../../common/constants.js';
import { Emitter, Event } from '../../../../../../base/common/event.js';
import { IDisposable } from '../../../../../../base/common/lifecycle.js';
import { ISettableObservable, observableValue } from '../../../../../../base/common/observable.js';
import { URI } from '../../../../../../base/common/uri.js';
import { ChatRequestQueueKind, ChatSendResult, IChatDetail, IChatModelReference, IChatProgress, IChatSendRequestOptions, IChatService, IChatSessionContext, IChatSessionStartOptions, IChatUserActionEvent } from '../../../common/chatService/chatService.js';
import { ChatAgentLocation } from '../../../common/constants.js';
import { IChatModel, IChatRequestModel, IExportableChatData, ISerializableChatData } from '../../../common/model/chatModel.js';
export class MockChatService implements IChatService {
chatModels: IObservable<Iterable<IChatModel>> = observableValue('chatModels', []);
private readonly _chatModels: ISettableObservable<Iterable<IChatModel>> = observableValue('chatModels', []);
readonly chatModels = this._chatModels;
requestInProgressObs = observableValue('name', false);
_serviceBrand: undefined;
editingSessions = [];
transferredSessionResource: URI | undefined;
readonly onDidSubmitRequest: Event<{ readonly chatSessionResource: URI; readonly message?: IParsedChatRequest }> = Event.None;
readonly onDidCreateModel: Event<IChatModel> = Event.None;
transferredSessionResource = undefined;
readonly onDidSubmitRequest = Event.None;
readonly onDidCreateModel = Event.None;
private sessions = new ResourceMap<IChatModel>();
private sessions = new Map<string, IChatModel>();
private liveSessionItems: IChatDetail[] = [];
private historySessionItems: IChatDetail[] = [];
private readonly _onDidDisposeSession = new Emitter<{ sessionResource: URI[]; reason: 'cleared' }>();
readonly onDidDisposeSession = this._onDidDisposeSession.event;
fireDidDisposeSession(sessionResource: URI[]): void {
this._onDidDisposeSession.fire({ sessionResource, reason: 'cleared' });
}
setSaveModelsEnabled(enabled: boolean): void {
}
isEnabled(location: ChatAgentLocation): boolean {
throw new Error('Method not implemented.');
}
hasSessions(): boolean {
throw new Error('Method not implemented.');
}
getProviderInfos(): IChatProviderInfo[] {
throw new Error('Method not implemented.');
}
startNewLocalSession(location: ChatAgentLocation, options?: IChatSessionStartOptions): IChatModelReference {
throw new Error('Method not implemented.');
}
addSession(session: IChatModel): void {
this.sessions.set(session.sessionResource, session);
}
getSession(sessionResource: URI): IChatModel | undefined {
// eslint-disable-next-line local/code-no-dangerous-type-assertions
return this.sessions.get(sessionResource) ?? {} as IChatModel;
}
getLatestRequest(): IChatRequestModel | undefined {
return undefined;
}
async acquireOrRestoreSession(sessionResource: URI): Promise<IChatModelReference | undefined> {
throw new Error('Method not implemented.');
}
getSessionTitle(sessionResource: URI): string | undefined {
throw new Error('Method not implemented.');
}
loadSessionFromData(data: ISerializableChatData): IChatModelReference {
throw new Error('Method not implemented.');
}
acquireOrLoadSession(resource: URI, position: ChatAgentLocation, token: CancellationToken): Promise<IChatModelReference | undefined> {
throw new Error('Method not implemented.');
}
acquireExistingSession(sessionResource: URI): IChatModelReference | undefined {
return undefined;
}
setSessionTitle(sessionResource: URI, title: string): void {
throw new Error('Method not implemented.');
}
appendProgress(request: IChatRequestModel, progress: IChatProgress): void {
}
processPendingRequests(sessionResource: URI): void {
}
/**
* Returns whether the request was accepted.
*/
sendRequest(sessionResource: URI, message: string): Promise<ChatSendResult> {
setLiveSessionItems(items: IChatDetail[]): void {
this.liveSessionItems = items;
}
setHistorySessionItems(items: IChatDetail[]): void {
this.historySessionItems = items;
}
addSession(session: IChatModel): void {
this.sessions.set(session.sessionResource.toString(), session);
// Update the chatModels observable
this._chatModels.set([...this.sessions.values()], undefined);
}
removeSession(sessionResource: URI): void {
this.sessions.delete(sessionResource.toString());
// Update the chatModels observable
this._chatModels.set([...this.sessions.values()], undefined);
}
isEnabled(_location: ChatAgentLocation): boolean {
return true;
}
hasSessions(): boolean {
return this.sessions.size > 0;
}
getProviderInfos() {
return [];
}
startNewLocalSession(_location: ChatAgentLocation, _options?: IChatSessionStartOptions): IChatModelReference {
throw new Error('Method not implemented.');
}
resendRequest(request: IChatRequestModel, options?: IChatSendRequestOptions | undefined): Promise<void> {
getSession(sessionResource: URI): IChatModel | undefined {
return this.sessions.get(sessionResource.toString());
}
getLatestRequest(): IChatRequestModel | undefined {
return undefined;
}
acquireOrRestoreSession(_sessionResource: URI): Promise<IChatModelReference | undefined> {
throw new Error('Method not implemented.');
}
adoptRequest(sessionResource: URI, request: IChatRequestModel): Promise<void> {
getSessionTitle(_sessionResource: URI): string | undefined {
return undefined;
}
loadSessionFromData(data: IExportableChatData | ISerializableChatData): IChatModelReference {
throw new Error('Method not implemented.');
}
removeRequest(sessionResource: URI, requestId: string): Promise<void> {
acquireOrLoadSession(_resource: URI, _position: ChatAgentLocation, _token: CancellationToken): Promise<IChatModelReference | undefined> {
throw new Error('Method not implemented.');
}
async cancelCurrentRequestForSession(sessionResource: URI, source?: string): Promise<void> {
acquireExistingSession(_sessionResource: URI): IChatModelReference | undefined {
return undefined;
}
setSessionTitle(_sessionResource: URI, _title: string): void { }
appendProgress(_request: IChatRequestModel, _progress: IChatProgress): void { }
sendRequest(_sessionResource: URI, _message: string): Promise<ChatSendResult> {
throw new Error('Method not implemented.');
}
setYieldRequested(sessionResource: URI): void {
resendRequest(_request: IChatRequestModel, _options?: IChatSendRequestOptions): Promise<void> {
throw new Error('Method not implemented.');
}
removePendingRequest(sessionResource: URI, requestId: string): void {
adoptRequest(_sessionResource: URI, _request: IChatRequestModel): Promise<void> {
throw new Error('Method not implemented.');
}
setPendingRequests(sessionResource: URI, requests: readonly { requestId: string; kind: ChatRequestQueueKind }[]): void {
throw new Error('Method not implemented.');
}
addCompleteRequest(sessionResource: URI, message: IParsedChatRequest | string, variableData: IChatRequestVariableData | undefined, attempt: number | undefined, response: IChatCompleteResponse): void {
removeRequest(_sessionResource: URI, _requestId: string): Promise<void> {
throw new Error('Method not implemented.');
}
async cancelCurrentRequestForSession(_sessionResource: URI, _source?: string): Promise<void> { }
setYieldRequested(_sessionResource: URI): void { }
removePendingRequest(_sessionResource: URI, _requestId: string): void { }
setPendingRequests(_sessionResource: URI, _requests: readonly { requestId: string; kind: ChatRequestQueueKind }[]): void { }
addCompleteRequest(): void { }
async getLocalSessionHistory(): Promise<IChatDetail[]> {
throw new Error('Method not implemented.');
}
async clearAllHistoryEntries() {
throw new Error('Method not implemented.');
}
async removeHistoryEntry(resource: URI) {
throw new Error('Method not implemented.');
return this.historySessionItems;
}
readonly onDidPerformUserAction: Event<IChatUserActionEvent> = undefined!;
notifyUserAction(event: IChatUserActionEvent): void {
throw new Error('Method not implemented.');
}
readonly onDidReceiveQuestionCarouselAnswer: Event<{ requestId: string; resolveId: string; answers: IChatQuestionAnswers | undefined }> = undefined!;
notifyQuestionCarouselAnswer(requestId: string, resolveId: string, answers: IChatQuestionAnswers | undefined): void {
throw new Error('Method not implemented.');
}
readonly onDidDisposeSession: Event<{ sessionResource: URI[]; reason: 'cleared' }> = undefined!;
async clearAllHistoryEntries(): Promise<void> { }
async transferChatSession(transferredSessionResource: URI, toWorkspace: URI): Promise<void> {
throw new Error('Method not implemented.');
}
async removeHistoryEntry(_resource: URI): Promise<void> { }
setChatSessionTitle(sessionResource: URI, title: string): void {
throw new Error('Method not implemented.');
}
readonly onDidPerformUserAction = Event.None;
isEditingLocation(location: ChatAgentLocation): boolean {
throw new Error('Method not implemented.');
notifyUserAction(_event: IChatUserActionEvent): void { }
readonly onDidReceiveQuestionCarouselAnswer = Event.None;
notifyQuestionCarouselAnswer(_requestId: string, _resolveId: string, _answers: Record<string, unknown> | undefined): void { }
async transferChatSession(): Promise<void> { }
setChatSessionTitle(): void { }
isEditingLocation(_location: ChatAgentLocation): boolean {
return false;
}
getChatStorageFolder(): URI {
throw new Error('Method not implemented.');
return URI.file('/tmp');
}
logChatIndex(): void {
throw new Error('Method not implemented.');
logChatIndex(): void { }
activateDefaultAgent(_location: ChatAgentLocation): Promise<void> {
return Promise.resolve();
}
activateDefaultAgent(location: ChatAgentLocation): Promise<void> {
throw new Error('Method not implemented.');
}
getChatSessionFromInternalUri(sessionResource: URI): IChatSessionContext | undefined {
throw new Error('Method not implemented.');
getChatSessionFromInternalUri(_sessionResource: URI): IChatSessionContext | undefined {
return undefined;
}
async getLiveSessionItems(): Promise<IChatDetail[]> {
throw new Error('Method not implemented.');
return this.liveSessionItems;
}
getHistorySessionItems(): Promise<IChatDetail[]> {
throw new Error('Method not implemented.');
async getHistorySessionItems(): Promise<IChatDetail[]> {
return this.historySessionItems;
}
waitForModelDisposals(): Promise<void> {
throw new Error('Method not implemented.');
return Promise.resolve();
}
getMetadataForSession(sessionResource: URI): Promise<IChatDetail | undefined> {
throw new Error('Method not implemented.');
}
private onChange?: () => void;
registerChatModelChangeListeners(chatSessionType: string, onChange: () => void): IDisposable {