Path: blob/main/src/vs/platform/agentHost/test/node/sessionDataService.test.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 assert from 'assert';6import { VSBuffer } from '../../../../base/common/buffer.js';7import { DisposableStore } from '../../../../base/common/lifecycle.js';8import { Schemas } from '../../../../base/common/network.js';9import { URI } from '../../../../base/common/uri.js';10import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../base/test/common/utils.js';11import { FileService } from '../../../files/common/fileService.js';12import { InMemoryFileSystemProvider } from '../../../files/common/inMemoryFilesystemProvider.js';13import { NullLogService } from '../../../log/common/log.js';14import { AgentSession } from '../../common/agentService.js';15import { SessionDataService } from '../../node/sessionDataService.js';1617suite('SessionDataService', () => {1819const disposables = new DisposableStore();20let fileService: FileService;21let service: SessionDataService;22const basePath = URI.from({ scheme: Schemas.inMemory, path: '/userData' });2324setup(() => {25fileService = disposables.add(new FileService(new NullLogService()));26disposables.add(fileService.registerProvider(Schemas.inMemory, disposables.add(new InMemoryFileSystemProvider())));27service = new SessionDataService(basePath, fileService, new NullLogService());28});2930teardown(() => disposables.clear());31ensureNoDisposablesAreLeakedInTestSuite();3233test('getSessionDataDir returns correct URI', () => {34const session = AgentSession.uri('copilot', 'abc-123');35const dir = service.getSessionDataDir(session);36assert.strictEqual(dir.toString(), URI.joinPath(basePath, 'agentSessionData', 'abc-123').toString());37});3839test('getSessionDataDir sanitizes unsafe characters', () => {40const session = AgentSession.uri('copilot', 'foo/bar:baz\\qux');41const dir = service.getSessionDataDir(session);42assert.strictEqual(dir.toString(), URI.joinPath(basePath, 'agentSessionData', 'foo-bar-baz-qux').toString());43});4445test('deleteSessionData removes directory', async () => {46const session = AgentSession.uri('copilot', 'session-1');47const dir = service.getSessionDataDir(session);48await fileService.createFolder(dir);49await fileService.writeFile(URI.joinPath(dir, 'snapshot.json'), VSBuffer.fromString('{}'));5051assert.ok(await fileService.exists(dir));52await service.deleteSessionData(session);53assert.ok(!(await fileService.exists(dir)));54});5556test('deleteSessionData is a no-op when directory does not exist', async () => {57const session = AgentSession.uri('copilot', 'nonexistent');58// Should not throw59await service.deleteSessionData(session);60});6162test('cleanupOrphanedData deletes orphans but keeps known sessions', async () => {63const baseDir = URI.joinPath(basePath, 'agentSessionData');64await fileService.createFolder(URI.joinPath(baseDir, 'keep-1'));65await fileService.createFolder(URI.joinPath(baseDir, 'keep-2'));66await fileService.createFolder(URI.joinPath(baseDir, 'orphan-1'));67await fileService.createFolder(URI.joinPath(baseDir, 'orphan-2'));6869await service.cleanupOrphanedData(new Set(['keep-1', 'keep-2']));7071assert.ok(await fileService.exists(URI.joinPath(baseDir, 'keep-1')));72assert.ok(await fileService.exists(URI.joinPath(baseDir, 'keep-2')));73assert.ok(!(await fileService.exists(URI.joinPath(baseDir, 'orphan-1'))));74assert.ok(!(await fileService.exists(URI.joinPath(baseDir, 'orphan-2'))));75});7677test('cleanupOrphanedData is a no-op when base directory does not exist', async () => {78// Should not throw79await service.cleanupOrphanedData(new Set());80});81});8283suite('SessionDataService — openDatabase ref-counting', () => {8485const disposables = new DisposableStore();86const basePath = URI.from({ scheme: Schemas.inMemory, path: '/userData' });87let service: SessionDataService;8889setup(() => {90const fileService = disposables.add(new FileService(new NullLogService()));91disposables.add(fileService.registerProvider(Schemas.inMemory, disposables.add(new InMemoryFileSystemProvider())));92service = new SessionDataService(basePath, fileService, new NullLogService(), () => ':memory:');93});9495teardown(() => {96disposables.clear();97});98ensureNoDisposablesAreLeakedInTestSuite();99100test('returns a functional database reference', async () => {101const session = AgentSession.uri('copilot', 'ref-test');102const ref = service.openDatabase(session);103disposables.add(ref);104105await ref.object.createTurn('turn-1');106const edits = await ref.object.getFileEdits([]);107assert.deepStrictEqual(edits, []);108await ref.object.close();109});110111test('multiple references share the same database', async () => {112const session = AgentSession.uri('copilot', 'shared-test');113const ref1 = service.openDatabase(session);114const ref2 = service.openDatabase(session);115116assert.strictEqual(ref1.object, ref2.object);117118ref1.dispose();119ref2.dispose();120await ref1.object.close();121});122123test('database remains usable until last reference is disposed', async () => {124const session = AgentSession.uri('copilot', 'refcount-test');125const ref1 = service.openDatabase(session);126const ref2 = service.openDatabase(session);127128ref1.dispose();129130// ref2 still works131await ref2.object.createTurn('turn-1');132133ref2.dispose();134135await ref1.object.close();136});137138test('new reference after all disposed gets a fresh database', async () => {139const session = AgentSession.uri('copilot', 'reopen-test');140const ref1 = service.openDatabase(session);141const db1 = ref1.object;142ref1.dispose();143144const ref2 = service.openDatabase(session);145disposables.add(ref2);146// New reference — may or may not be the same object, but must be functional147await ref2.object.createTurn('turn-1');148assert.notStrictEqual(ref2.object, db1);149150await ref2.object.close();151});152});153154155