Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/sessions/contrib/chat/test/browser/agentHost/agentHostPermissionPickerDelegate.test.ts
13406 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import assert from 'assert';
7
import { Emitter, Event } from '../../../../../../base/common/event.js';
8
import { DisposableStore } from '../../../../../../base/common/lifecycle.js';
9
import { observableValue } from '../../../../../../base/common/observable.js';
10
import { mock } from '../../../../../../base/test/common/mock.js';
11
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../base/test/common/utils.js';
12
import { TestInstantiationService } from '../../../../../../platform/instantiation/test/common/instantiationServiceMock.js';
13
import { ResolveSessionConfigResult, SessionConfigPropertySchema } from '../../../../../../platform/agentHost/common/state/protocol/commands.js';
14
import { ChatPermissionLevel } from '../../../../../../workbench/contrib/chat/common/constants.js';
15
import { AgentHostPermissionPickerDelegate, isWellKnownAutoApproveSchema, isWellKnownModeSchema } from '../../../browser/agentHost/agentHostPermissionPickerDelegate.js';
16
import { IAgentHostSessionsProvider } from '../../../../../common/agentHostSessionsProvider.js';
17
import { ISessionsProvidersChangeEvent, ISessionsProvidersService } from '../../../../../services/sessions/browser/sessionsProvidersService.js';
18
import { ISessionsProvider } from '../../../../../services/sessions/common/sessionsProvider.js';
19
import { IActiveSession, ISessionsManagementService } from '../../../../../services/sessions/common/sessionsManagement.js';
20
21
const PROVIDER_ID = 'local-agent-host';
22
const SESSION_ID = 'local-agent-host:s1';
23
24
function makeWellKnownConfig(value: string | undefined): ResolveSessionConfigResult {
25
return {
26
schema: {
27
type: 'object',
28
properties: {
29
autoApprove: {
30
title: 'Auto Approve',
31
description: '',
32
type: 'string',
33
enum: ['default', 'autoApprove', 'autopilot'],
34
sessionMutable: true,
35
},
36
},
37
},
38
values: value === undefined ? {} : { autoApprove: value },
39
} as ResolveSessionConfigResult;
40
}
41
42
class FakeProvider implements Pick<IAgentHostSessionsProvider, 'id' | 'onDidChangeSessionConfig' | 'getSessionConfig' | 'setSessionConfigValue'> {
43
readonly id: string = PROVIDER_ID;
44
private readonly _onDidChange = new Emitter<string>();
45
readonly onDidChangeSessionConfig: Event<string> = this._onDidChange.event;
46
47
config: ResolveSessionConfigResult | undefined;
48
readonly setCalls: Array<[string, string, string]> = [];
49
50
getSessionConfig(_sessionId: string): ResolveSessionConfigResult | undefined {
51
return this.config;
52
}
53
async setSessionConfigValue(sessionId: string, property: string, value: string): Promise<void> {
54
this.setCalls.push([sessionId, property, value]);
55
}
56
fireChange(sessionId: string = SESSION_ID): void {
57
this._onDidChange.fire(sessionId);
58
}
59
dispose(): void {
60
this._onDidChange.dispose();
61
}
62
}
63
64
interface ITestRig {
65
readonly delegate: AgentHostPermissionPickerDelegate;
66
readonly provider: FakeProvider;
67
readonly activeSessionObs: ReturnType<typeof observableValue<IActiveSession | undefined>>;
68
}
69
70
function setup(store: Pick<DisposableStore, 'add'>, activeSession: IActiveSession | undefined, configValue?: string): ITestRig {
71
const provider = new FakeProvider();
72
store.add({ dispose: () => provider.dispose() });
73
if (configValue !== undefined) {
74
provider.config = makeWellKnownConfig(configValue);
75
}
76
const onDidChangeProviders = store.add(new Emitter<ISessionsProvidersChangeEvent>());
77
const sessionsProvidersService = new (class extends mock<ISessionsProvidersService>() {
78
override readonly onDidChangeProviders = onDidChangeProviders.event;
79
override getProviders(): ISessionsProvider[] { return [provider as unknown as ISessionsProvider]; }
80
override getProvider<T extends ISessionsProvider>(id: string): T | undefined {
81
return id === provider.id ? (provider as unknown as T) : undefined;
82
}
83
})();
84
const activeSessionObs = observableValue<IActiveSession | undefined>('activeSession', activeSession);
85
const sessionsManagementService = new (class extends mock<ISessionsManagementService>() {
86
override readonly activeSession = activeSessionObs;
87
})();
88
89
const insta = store.add(new TestInstantiationService());
90
insta.set(ISessionsManagementService, sessionsManagementService);
91
insta.set(ISessionsProvidersService, sessionsProvidersService);
92
93
const delegate = store.add(insta.createInstance(AgentHostPermissionPickerDelegate));
94
return { delegate, provider, activeSessionObs };
95
}
96
97
function makeActiveSession(): IActiveSession {
98
return { providerId: PROVIDER_ID, sessionId: SESSION_ID } as IActiveSession;
99
}
100
101
suite('AgentHostPermissionPickerDelegate', () => {
102
const store = ensureNoDisposablesAreLeakedInTestSuite();
103
104
test('returns Default when there is no active session', () => {
105
const { delegate } = setup(store, undefined);
106
107
assert.strictEqual(delegate.currentPermissionLevel.get(), ChatPermissionLevel.Default);
108
});
109
110
test('returns Default when the active session has no config seeded yet', () => {
111
const { delegate } = setup(store, makeActiveSession());
112
113
assert.strictEqual(delegate.currentPermissionLevel.get(), ChatPermissionLevel.Default);
114
});
115
116
test('reflects the active session\'s autoApprove value and updates on provider change', () => {
117
const { delegate, provider } = setup(store, makeActiveSession(), 'autoApprove');
118
119
assert.strictEqual(delegate.currentPermissionLevel.get(), ChatPermissionLevel.AutoApprove);
120
121
provider.config = makeWellKnownConfig('autopilot');
122
provider.fireChange();
123
assert.strictEqual(delegate.currentPermissionLevel.get(), ChatPermissionLevel.Autopilot);
124
125
provider.config = makeWellKnownConfig('default');
126
provider.fireChange();
127
assert.strictEqual(delegate.currentPermissionLevel.get(), ChatPermissionLevel.Default);
128
});
129
130
test('falls back to Default when the stored value is unrecognized', () => {
131
const { delegate } = setup(store, makeActiveSession(), 'something-else');
132
133
assert.strictEqual(delegate.currentPermissionLevel.get(), ChatPermissionLevel.Default);
134
});
135
136
test('setPermissionLevel writes through to the active session\'s provider', () => {
137
const { delegate, provider } = setup(store, makeActiveSession(), 'default');
138
139
delegate.setPermissionLevel(ChatPermissionLevel.AutoApprove);
140
delegate.setPermissionLevel(ChatPermissionLevel.Autopilot);
141
142
assert.deepStrictEqual(provider.setCalls, [
143
[SESSION_ID, 'autoApprove', 'autoApprove'],
144
[SESSION_ID, 'autoApprove', 'autopilot'],
145
]);
146
});
147
148
test('setPermissionLevel is a no-op when there is no active session', () => {
149
const { delegate, provider } = setup(store, undefined);
150
151
delegate.setPermissionLevel(ChatPermissionLevel.AutoApprove);
152
153
assert.deepStrictEqual(provider.setCalls, []);
154
});
155
156
test('isApplicable reacts to active session and config changes', () => {
157
const { delegate, provider, activeSessionObs } = setup(store, undefined);
158
159
// No active session → false
160
assert.strictEqual(delegate.isApplicable.get(), false);
161
162
// Active session, no config seeded → false
163
activeSessionObs.set(makeActiveSession(), undefined);
164
assert.strictEqual(delegate.isApplicable.get(), false);
165
166
// Active session with well-known schema → true
167
provider.config = makeWellKnownConfig('default');
168
provider.fireChange();
169
assert.strictEqual(delegate.isApplicable.get(), true);
170
171
// Active session cleared → false (covers the 'back to new chat view' regression)
172
activeSessionObs.set(undefined, undefined);
173
assert.strictEqual(delegate.isApplicable.get(), false);
174
});
175
});
176
177
suite('isWellKnownAutoApproveSchema', () => {
178
ensureNoDisposablesAreLeakedInTestSuite();
179
180
function schema(overrides: Partial<SessionConfigPropertySchema> = {}): SessionConfigPropertySchema {
181
return {
182
title: 'Auto Approve',
183
description: 'desc',
184
type: 'string',
185
enum: ['default', 'autoApprove', 'autopilot'],
186
...overrides,
187
} as SessionConfigPropertySchema;
188
}
189
190
test('matches the canonical three-value enum', () => {
191
assert.strictEqual(isWellKnownAutoApproveSchema(schema()), true);
192
});
193
194
test('matches a subset that still contains "default"', () => {
195
assert.strictEqual(isWellKnownAutoApproveSchema(schema({ enum: ['default', 'autoApprove'] })), true);
196
assert.strictEqual(isWellKnownAutoApproveSchema(schema({ enum: ['default'] })), true);
197
});
198
199
test('rejects schemas missing the required "default" value', () => {
200
assert.strictEqual(isWellKnownAutoApproveSchema(schema({ enum: ['autoApprove', 'autopilot'] })), false);
201
});
202
203
test('rejects schemas with unknown enum values', () => {
204
assert.strictEqual(isWellKnownAutoApproveSchema(schema({ enum: ['default', 'custom'] })), false);
205
});
206
207
test('rejects non-string types and missing/empty enums', () => {
208
assert.strictEqual(isWellKnownAutoApproveSchema(schema({ type: 'number' as 'string' })), false);
209
assert.strictEqual(isWellKnownAutoApproveSchema(schema({ enum: undefined })), false);
210
assert.strictEqual(isWellKnownAutoApproveSchema(schema({ enum: [] })), false);
211
});
212
});
213
214
suite('isWellKnownModeSchema', () => {
215
ensureNoDisposablesAreLeakedInTestSuite();
216
217
function schema(overrides: Partial<SessionConfigPropertySchema> = {}): SessionConfigPropertySchema {
218
return {
219
title: 'Agent Mode',
220
description: 'desc',
221
type: 'string',
222
enum: ['interactive', 'plan'],
223
...overrides,
224
} as SessionConfigPropertySchema;
225
}
226
227
test('matches the canonical two-value enum', () => {
228
assert.strictEqual(isWellKnownModeSchema(schema()), true);
229
});
230
231
test('matches a subset that still contains "interactive"', () => {
232
assert.strictEqual(isWellKnownModeSchema(schema({ enum: ['interactive'] })), true);
233
});
234
235
test('rejects schemas missing the required "interactive" value', () => {
236
assert.strictEqual(isWellKnownModeSchema(schema({ enum: ['plan'] })), false);
237
});
238
239
test('rejects non-string types and missing/empty enums', () => {
240
assert.strictEqual(isWellKnownModeSchema(schema({ type: 'number' as 'string' })), false);
241
assert.strictEqual(isWellKnownModeSchema(schema({ enum: undefined })), false);
242
assert.strictEqual(isWellKnownModeSchema(schema({ enum: [] })), false);
243
});
244
});
245
246