Path: blob/main/extensions/copilot/src/extension/chatSessions/vscode-node/test/claudeWorkspaceFolderService.spec.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 { beforeEach, describe, expect, it, vi } from 'vitest';6import * as vscode from 'vscode';7import { IVSCodeExtensionContext } from '../../../../platform/extContext/common/extensionContext';8import { MockFileSystemService } from '../../../../platform/filesystem/node/test/mockFileSystemService';9import { RepoContext } from '../../../../platform/git/common/gitService';10import { MockGitService } from '../../../../platform/ignore/node/test/mockGitService';11import { ILogService } from '../../../../platform/log/common/logService';12import { mock } from '../../../../util/common/test/simpleMock';13import { constObservable } from '../../../../util/vs/base/common/observableInternal';14import { URI } from '../../../../util/vs/base/common/uri';15import { ClaudeWorkspaceFolderService } from '../claudeWorkspaceFolderServiceImpl';1617class MockLogService extends mock<ILogService>() {18override trace = vi.fn();19override info = vi.fn();20override warn = vi.fn();21override error = vi.fn();22override debug = vi.fn();23}2425class MockExtensionContext extends mock<IVSCodeExtensionContext>() {26override globalStorageUri = vscode.Uri.file('/mock/global/storage');27}2829function createMockRepoContext(overrides?: Partial<RepoContext>): RepoContext {30return {31rootUri: URI.file('/mock/repo'),32kind: 0 as any,33isUsingVirtualFileSystem: false,34headIncomingChanges: undefined,35headOutgoingChanges: undefined,36headBranchName: 'feature-branch',37headCommitHash: 'abc123',38upstreamBranchName: undefined,39upstreamRemote: undefined,40isRebasing: false,41remotes: [],42worktrees: [],43changes: {44mergeChanges: [],45indexChanges: [],46workingTree: [],47untrackedChanges: [],48},49headBranchNameObs: constObservable('feature-branch'),50headCommitHashObs: constObservable('abc123'),51upstreamBranchNameObs: constObservable(undefined),52upstreamRemoteObs: constObservable(undefined),53isRebasingObs: constObservable(false),54isIgnored: vi.fn().mockResolvedValue(false),55...overrides,56};57}5859describe('ClaudeWorkspaceFolderService', () => {60let gitService: MockGitService;61let logService: MockLogService;62let extensionContext: MockExtensionContext;63let fileSystemService: MockFileSystemService;64let service: ClaudeWorkspaceFolderService;6566beforeEach(() => {67gitService = new MockGitService();68logService = new MockLogService();69extensionContext = new MockExtensionContext();70fileSystemService = new MockFileSystemService();71service = new ClaudeWorkspaceFolderService(gitService, logService, extensionContext, fileSystemService);72});7374describe('getWorkspaceChanges', () => {75it('returns empty array when repository is not found', async () => {76gitService.getRepository = vi.fn().mockResolvedValue(undefined);7778const result = await service.getWorkspaceChanges('/nonexistent', 'main', undefined);7980expect(result).toEqual([]);81expect(logService.warn).toHaveBeenCalled();82});8384it('returns empty array when repository has no changes object', async () => {85gitService.getRepository = vi.fn().mockResolvedValue(86createMockRepoContext({ changes: undefined }),87);8889const result = await service.getWorkspaceChanges('/mock/repo', 'main', undefined);9091expect(result).toEqual([]);92});9394it('returns cached result on second call with same inputs', async () => {95const repo = createMockRepoContext();96gitService.getRepository = vi.fn().mockResolvedValue(repo);97gitService.exec = vi.fn().mockResolvedValue('');9899const result1 = await service.getWorkspaceChanges('/mock/repo', 'feature-branch', undefined);100const result2 = await service.getWorkspaceChanges('/mock/repo', 'feature-branch', undefined);101102expect(result1).toBe(result2);103expect(gitService.exec).toHaveBeenCalledTimes(1);104});105106it('bypasses cache when forceRefresh is true', async () => {107const repo = createMockRepoContext();108gitService.getRepository = vi.fn().mockResolvedValue(repo);109gitService.exec = vi.fn().mockResolvedValue('');110111await service.getWorkspaceChanges('/mock/repo', 'feature-branch', undefined);112await service.getWorkspaceChanges('/mock/repo', 'feature-branch', undefined, true);113114expect(gitService.exec).toHaveBeenCalledTimes(2);115});116117it('returns empty array on git exec error', async () => {118const repo = createMockRepoContext();119gitService.getRepository = vi.fn().mockResolvedValue(repo);120gitService.exec = vi.fn().mockRejectedValue(new Error('git failed'));121122const result = await service.getWorkspaceChanges('/mock/repo', 'feature-branch', undefined);123124expect(result).toEqual([]);125expect(logService.error).toHaveBeenCalled();126});127});128129describe('base branch auto-resolution', () => {130it('calls getBranchBase when gitBaseBranch is undefined and gitBranch is provided', async () => {131const repo = createMockRepoContext();132gitService.getRepository = vi.fn().mockResolvedValue(repo);133gitService.getBranchBase = vi.fn().mockResolvedValue({ name: 'main', commit: 'def456', type: 0 });134gitService.exec = vi.fn().mockResolvedValue('');135gitService.getMergeBase = vi.fn().mockResolvedValue('def456');136137await service.getWorkspaceChanges('/mock/repo', 'feature-branch', undefined);138139expect(gitService.getBranchBase).toHaveBeenCalledWith(repo.rootUri, 'feature-branch');140expect(gitService.exec).toHaveBeenCalledWith(141repo.rootUri,142expect.arrayContaining(['--merge-base', 'main']),143);144});145146it('does not call getBranchBase when gitBaseBranch is explicitly provided', async () => {147const repo = createMockRepoContext();148gitService.getRepository = vi.fn().mockResolvedValue(repo);149gitService.getBranchBase = vi.fn();150gitService.exec = vi.fn().mockResolvedValue('');151152await service.getWorkspaceChanges('/mock/repo', 'feature-branch', 'develop');153154expect(gitService.getBranchBase).not.toHaveBeenCalled();155expect(gitService.exec).toHaveBeenCalledWith(156repo.rootUri,157expect.arrayContaining(['--merge-base', 'develop']),158);159});160161it('handles getBranchBase returning undefined gracefully', async () => {162const repo = createMockRepoContext();163gitService.getRepository = vi.fn().mockResolvedValue(repo);164gitService.getBranchBase = vi.fn().mockResolvedValue(undefined);165gitService.exec = vi.fn().mockResolvedValue('');166167const result = await service.getWorkspaceChanges('/mock/repo', 'feature-branch', undefined);168169expect(result).toEqual([]);170expect(gitService.exec).toHaveBeenCalledWith(171repo.rootUri,172expect.not.arrayContaining(['--merge-base']),173);174});175176it('handles getBranchBase throwing an error gracefully', async () => {177const repo = createMockRepoContext();178gitService.getRepository = vi.fn().mockResolvedValue(repo);179gitService.getBranchBase = vi.fn().mockRejectedValue(new Error('branch not found'));180gitService.exec = vi.fn().mockResolvedValue('');181182const result = await service.getWorkspaceChanges('/mock/repo', 'feature-branch', undefined);183184expect(result).toEqual([]);185expect(logService.warn).toHaveBeenCalledWith(186expect.stringContaining('Failed to resolve base branch'),187);188});189190it('does not call getBranchBase when headCommitHash is undefined', async () => {191const repo = createMockRepoContext({ headCommitHash: undefined });192gitService.getRepository = vi.fn().mockResolvedValue(repo);193gitService.getBranchBase = vi.fn();194gitService.exec = vi.fn().mockResolvedValue('');195196await service.getWorkspaceChanges('/mock/repo', 'feature-branch', undefined);197198expect(gitService.getBranchBase).not.toHaveBeenCalled();199});200});201202describe('dispose', () => {203it('clears the cache on dispose', async () => {204const repo = createMockRepoContext();205gitService.getRepository = vi.fn().mockResolvedValue(repo);206gitService.exec = vi.fn().mockResolvedValue('');207208await service.getWorkspaceChanges('/mock/repo', 'feature-branch', undefined);209210service.dispose();211212gitService.exec = vi.fn().mockResolvedValue('');213await service.getWorkspaceChanges('/mock/repo', 'feature-branch', undefined);214215expect(gitService.exec).toHaveBeenCalledTimes(1);216});217});218});219220221