Path: blob/main/src/vs/platform/agentHost/test/node/protocol/sessionDiffs.integrationTest.ts
13405 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 assert from 'assert';6import * as cp from 'child_process';7import { mkdtempSync, rmSync, writeFileSync } from 'fs';8import { tmpdir } from 'os';9import { join } from '../../../../../base/common/path.js';10import { URI } from '../../../../../base/common/uri.js';11import { SubscribeResult } from '../../../common/state/protocol/commands.js';12import type { SessionAddedNotification, SessionDiffsChangedAction } from '../../../common/state/sessionActions.js';13import { PROTOCOL_VERSION } from '../../../common/state/sessionCapabilities.js';14import type { INotificationBroadcastParams } from '../../../common/state/sessionProtocol.js';15import {16dispatchTurnStarted,17getActionEnvelope,18IServerHandle,19isActionNotification,20nextSessionUri,21startServer,22TestProtocolClient,23} from './testHelpers.js';2425const hasGit = (() => {26try { cp.execFileSync('git', ['--version'], { stdio: 'ignore' }); return true; } catch { return false; }27})();2829(hasGit ? suite : suite.skip)('Protocol WebSocket — Git-driven session diffs', function () {3031let server: IServerHandle;32let client: TestProtocolClient;33let tmpRoot: string;3435suiteSetup(async function () {36this.timeout(15_000);37server = await startServer();38});3940suiteTeardown(function () {41server.process.kill();42});4344setup(async function () {45this.timeout(10_000);46// Initialize a tmp git repo as the session's working directory.47tmpRoot = mkdtempSync(join(tmpdir(), 'agent-host-proto-diff-'));48const env = { ...process.env, GIT_AUTHOR_NAME: 't', GIT_AUTHOR_EMAIL: 't@t', GIT_COMMITTER_NAME: 't', GIT_COMMITTER_EMAIL: 't@t' };49const run = (...args: string[]) => cp.execFileSync('git', args, { cwd: tmpRoot, env, stdio: 'pipe' });50run('init', '-q', '-b', 'main');51writeFileSync(join(tmpRoot, 'seed.txt'), 'seed\n');52run('add', '.');53run('commit', '-q', '-m', 'init');5455client = new TestProtocolClient(server.port);56await client.connect();57});5859teardown(function () {60client.close();61if (tmpRoot) {62rmSync(tmpRoot, { recursive: true, force: true });63}64});6566test('terminal-driven file edit (no ToolResultFileEditContent) is reported via summary.diffs', async function () {67this.timeout(15_000);6869// Create a session whose working directory is the tmp git repo.70await client.call('initialize', { protocolVersion: PROTOCOL_VERSION, clientId: 'test-git-diffs' });7172const workingDirectory = URI.file(tmpRoot).toString();73await client.call('createSession', { session: nextSessionUri(), provider: 'mock', workingDirectory });7475const addedNotif = await client.waitForNotification(n =>76n.method === 'notification' && (n.params as INotificationBroadcastParams).notification.type === 'notify/sessionAdded'77);78const sessionUri = ((addedNotif.params as INotificationBroadcastParams).notification as SessionAddedNotification).summary.resource;7980await client.call<SubscribeResult>('subscribe', { resource: sessionUri });81client.clearReceived();8283// Fire a turn that runs the `terminal-edit:<path>` mock prompt. The mock84// agent writes the file via fs.writeFile (no ToolResultFileEditContent),85// so the diff must come from the git-driven path.86const editedFile = join(tmpRoot, 'from-terminal.txt');87dispatchTurnStarted(client, sessionUri, 'turn-1', `terminal-edit:${editedFile}`, 1);8889// Wait for the diff broadcast that comes after the idle event.90const diffNotif = await client.waitForNotification(n => isActionNotification(n, 'session/diffsChanged'), 10_000);91const action = getActionEnvelope(diffNotif).action as SessionDiffsChangedAction;9293// On macOS, git's `--show-toplevel` resolves symlinks (/var → /private/var)94// so the diff URI may differ in prefix; match by basename instead.95const matching = action.diffs.find(d => {96const u = d.after?.uri ?? d.before?.uri;97return typeof u === 'string' && u.endsWith('/from-terminal.txt');98});99assert.ok(matching, `expected diff for from-terminal.txt; got ${JSON.stringify(action.diffs.map(d => d.after?.uri ?? d.before?.uri))}`);100assert.ok(matching!.after, 'expected after-side for newly added file');101assert.ok(!matching!.before, 'newly added file should have no before-side');102});103});104105106