Download packages/core/src/services/gitService.test.ts from Jimmi42/openCLI: direct link, hf CLI and curl.
- Browser
- Download file 9.74 kB
-
https://huggingface.co/Jimmi42/openCLI/resolve/main/packages/core/src/services/gitService.test.ts
- Command line
-
hf download hf://Jimmi42/openCLI/packages/core/src/services/gitService.test.ts
-
curl -L -o gitService.test.ts https://huggingface.co/Jimmi42/openCLI/resolve/main/packages/core/src/services/gitService.test.ts
9.74 kB
| /** | |
| * @license | |
| * Copyright 2025 Google LLC | |
| * SPDX-License-Identifier: Apache-2.0 | |
| */ | |
| import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'; | |
| import { GitService } from './gitService.js'; | |
| import * as path from 'path'; | |
| import type * as FsPromisesModule from 'fs/promises'; | |
| import type { ChildProcess } from 'node:child_process'; | |
| const hoistedMockExec = vi.hoisted(() => vi.fn()); | |
| vi.mock('node:child_process', () => ({ | |
| exec: hoistedMockExec, | |
| })); | |
| const hoistedMockMkdir = vi.hoisted(() => vi.fn()); | |
| const hoistedMockReadFile = vi.hoisted(() => vi.fn()); | |
| const hoistedMockWriteFile = vi.hoisted(() => vi.fn()); | |
| vi.mock('fs/promises', async (importOriginal) => { | |
| const actual = (await importOriginal()) as typeof FsPromisesModule; | |
| return { | |
| ...actual, | |
| mkdir: hoistedMockMkdir, | |
| readFile: hoistedMockReadFile, | |
| writeFile: hoistedMockWriteFile, | |
| }; | |
| }); | |
| const hoistedMockEnv = vi.hoisted(() => vi.fn()); | |
| const hoistedMockSimpleGit = vi.hoisted(() => vi.fn()); | |
| const hoistedMockCheckIsRepo = vi.hoisted(() => vi.fn()); | |
| const hoistedMockInit = vi.hoisted(() => vi.fn()); | |
| const hoistedMockRaw = vi.hoisted(() => vi.fn()); | |
| const hoistedMockAdd = vi.hoisted(() => vi.fn()); | |
| const hoistedMockCommit = vi.hoisted(() => vi.fn()); | |
| vi.mock('simple-git', () => ({ | |
| simpleGit: hoistedMockSimpleGit.mockImplementation(() => ({ | |
| checkIsRepo: hoistedMockCheckIsRepo, | |
| init: hoistedMockInit, | |
| raw: hoistedMockRaw, | |
| add: hoistedMockAdd, | |
| commit: hoistedMockCommit, | |
| env: hoistedMockEnv, | |
| })), | |
| CheckRepoActions: { IS_REPO_ROOT: 'is-repo-root' }, | |
| })); | |
| const hoistedIsGitRepositoryMock = vi.hoisted(() => vi.fn()); | |
| vi.mock('../utils/gitUtils.js', () => ({ | |
| isGitRepository: hoistedIsGitRepositoryMock, | |
| })); | |
| const hoistedMockIsNodeError = vi.hoisted(() => vi.fn()); | |
| vi.mock('../utils/errors.js', () => ({ | |
| isNodeError: hoistedMockIsNodeError, | |
| })); | |
| const hoistedMockHomedir = vi.hoisted(() => vi.fn()); | |
| vi.mock('os', () => ({ | |
| homedir: hoistedMockHomedir, | |
| })); | |
| const hoistedMockCreateHash = vi.hoisted(() => { | |
| const mockUpdate = vi.fn().mockReturnThis(); | |
| const mockDigest = vi.fn(); | |
| return { | |
| createHash: vi.fn(() => ({ | |
| update: mockUpdate, | |
| digest: mockDigest, | |
| })), | |
| mockUpdate, | |
| mockDigest, | |
| }; | |
| }); | |
| vi.mock('crypto', () => ({ | |
| createHash: hoistedMockCreateHash.createHash, | |
| })); | |
| describe('GitService', () => { | |
| const mockProjectRoot = '/test/project'; | |
| const mockHomedir = '/mock/home'; | |
| const mockHash = 'mock-hash'; | |
| beforeEach(() => { | |
| vi.clearAllMocks(); | |
| hoistedIsGitRepositoryMock.mockReturnValue(true); | |
| hoistedMockExec.mockImplementation((command, callback) => { | |
| if (command === 'git --version') { | |
| callback(null, 'git version 2.0.0'); | |
| } else { | |
| callback(new Error('Command not mocked')); | |
| } | |
| return {}; | |
| }); | |
| hoistedMockMkdir.mockResolvedValue(undefined); | |
| hoistedMockReadFile.mockResolvedValue(''); | |
| hoistedMockWriteFile.mockResolvedValue(undefined); | |
| hoistedMockIsNodeError.mockImplementation((e) => e instanceof Error); | |
| hoistedMockHomedir.mockReturnValue(mockHomedir); | |
| hoistedMockCreateHash.mockUpdate.mockReturnThis(); | |
| hoistedMockCreateHash.mockDigest.mockReturnValue(mockHash); | |
| hoistedMockEnv.mockImplementation(() => ({ | |
| checkIsRepo: hoistedMockCheckIsRepo, | |
| init: hoistedMockInit, | |
| raw: hoistedMockRaw, | |
| add: hoistedMockAdd, | |
| commit: hoistedMockCommit, | |
| })); | |
| hoistedMockSimpleGit.mockImplementation(() => ({ | |
| checkIsRepo: hoistedMockCheckIsRepo, | |
| init: hoistedMockInit, | |
| raw: hoistedMockRaw, | |
| add: hoistedMockAdd, | |
| commit: hoistedMockCommit, | |
| env: hoistedMockEnv, | |
| })); | |
| hoistedMockCheckIsRepo.mockResolvedValue(false); | |
| hoistedMockInit.mockResolvedValue(undefined); | |
| hoistedMockRaw.mockResolvedValue(''); | |
| hoistedMockAdd.mockResolvedValue(undefined); | |
| hoistedMockCommit.mockResolvedValue({ | |
| commit: 'initial', | |
| }); | |
| }); | |
| afterEach(() => { | |
| vi.restoreAllMocks(); | |
| }); | |
| describe('constructor', () => { | |
| it('should successfully create an instance if projectRoot is a Git repository', () => { | |
| expect(() => new GitService(mockProjectRoot)).not.toThrow(); | |
| }); | |
| }); | |
| describe('verifyGitAvailability', () => { | |
| it('should resolve true if git --version command succeeds', async () => { | |
| const service = new GitService(mockProjectRoot); | |
| await expect(service.verifyGitAvailability()).resolves.toBe(true); | |
| }); | |
| it('should resolve false if git --version command fails', async () => { | |
| hoistedMockExec.mockImplementation((command, callback) => { | |
| callback(new Error('git not found')); | |
| return {} as ChildProcess; | |
| }); | |
| const service = new GitService(mockProjectRoot); | |
| await expect(service.verifyGitAvailability()).resolves.toBe(false); | |
| }); | |
| }); | |
| describe('initialize', () => { | |
| it('should throw an error if projectRoot is not a Git repository', async () => { | |
| hoistedIsGitRepositoryMock.mockReturnValue(false); | |
| const service = new GitService(mockProjectRoot); | |
| await expect(service.initialize()).rejects.toThrow( | |
| 'GitService requires a Git repository', | |
| ); | |
| }); | |
| it('should throw an error if Git is not available', async () => { | |
| hoistedMockExec.mockImplementation((command, callback) => { | |
| callback(new Error('git not found')); | |
| return {} as ChildProcess; | |
| }); | |
| const service = new GitService(mockProjectRoot); | |
| await expect(service.initialize()).rejects.toThrow( | |
| 'GitService requires Git to be installed', | |
| ); | |
| }); | |
| it('should call setupShadowGitRepository if Git is available', async () => { | |
| const service = new GitService(mockProjectRoot); | |
| const setupSpy = vi | |
| .spyOn(service, 'setupShadowGitRepository') | |
| .mockResolvedValue(undefined); | |
| await service.initialize(); | |
| expect(setupSpy).toHaveBeenCalled(); | |
| }); | |
| }); | |
| describe('setupShadowGitRepository', () => { | |
| const repoDir = path.join(mockHomedir, '.gemini', 'history', mockHash); | |
| const hiddenGitIgnorePath = path.join(repoDir, '.gitignore'); | |
| const visibleGitIgnorePath = path.join(mockProjectRoot, '.gitignore'); | |
| const gitConfigPath = path.join(repoDir, '.gitconfig'); | |
| it('should create a .gitconfig file with the correct content', async () => { | |
| const service = new GitService(mockProjectRoot); | |
| await service.setupShadowGitRepository(); | |
| const expectedConfigContent = | |
| '[user]\n name = Gemini CLI\n email = gemini-cli@google.com\n[commit]\n gpgsign = false\n'; | |
| expect(hoistedMockWriteFile).toHaveBeenCalledWith( | |
| gitConfigPath, | |
| expectedConfigContent, | |
| ); | |
| }); | |
| it('should create history and repository directories', async () => { | |
| const service = new GitService(mockProjectRoot); | |
| await service.setupShadowGitRepository(); | |
| expect(hoistedMockMkdir).toHaveBeenCalledWith(repoDir, { | |
| recursive: true, | |
| }); | |
| }); | |
| it('should initialize git repo in historyDir if not already initialized', async () => { | |
| hoistedMockCheckIsRepo.mockResolvedValue(false); | |
| const service = new GitService(mockProjectRoot); | |
| await service.setupShadowGitRepository(); | |
| expect(hoistedMockSimpleGit).toHaveBeenCalledWith(repoDir); | |
| expect(hoistedMockInit).toHaveBeenCalled(); | |
| }); | |
| it('should not initialize git repo if already initialized', async () => { | |
| hoistedMockCheckIsRepo.mockResolvedValue(true); | |
| const service = new GitService(mockProjectRoot); | |
| await service.setupShadowGitRepository(); | |
| expect(hoistedMockInit).not.toHaveBeenCalled(); | |
| }); | |
| it('should copy .gitignore from projectRoot if it exists', async () => { | |
| const gitignoreContent = `node_modules/\n.env`; | |
| hoistedMockReadFile.mockImplementation(async (filePath) => { | |
| if (filePath === visibleGitIgnorePath) { | |
| return gitignoreContent; | |
| } | |
| return ''; | |
| }); | |
| const service = new GitService(mockProjectRoot); | |
| await service.setupShadowGitRepository(); | |
| expect(hoistedMockReadFile).toHaveBeenCalledWith( | |
| visibleGitIgnorePath, | |
| 'utf-8', | |
| ); | |
| expect(hoistedMockWriteFile).toHaveBeenCalledWith( | |
| hiddenGitIgnorePath, | |
| gitignoreContent, | |
| ); | |
| }); | |
| it('should throw an error if reading projectRoot .gitignore fails with other errors', async () => { | |
| const readError = new Error('Read permission denied'); | |
| hoistedMockReadFile.mockImplementation(async (filePath) => { | |
| if (filePath === visibleGitIgnorePath) { | |
| throw readError; | |
| } | |
| return ''; | |
| }); | |
| hoistedMockIsNodeError.mockImplementation( | |
| (e: unknown): e is NodeJS.ErrnoException => e instanceof Error, | |
| ); | |
| const service = new GitService(mockProjectRoot); | |
| await expect(service.setupShadowGitRepository()).rejects.toThrow( | |
| 'Read permission denied', | |
| ); | |
| }); | |
| it('should make an initial commit if no commits exist in history repo', async () => { | |
| hoistedMockCheckIsRepo.mockResolvedValue(false); | |
| const service = new GitService(mockProjectRoot); | |
| await service.setupShadowGitRepository(); | |
| expect(hoistedMockCommit).toHaveBeenCalledWith('Initial commit', { | |
| '--allow-empty': null, | |
| }); | |
| }); | |
| it('should not make an initial commit if commits already exist', async () => { | |
| hoistedMockCheckIsRepo.mockResolvedValue(true); | |
| const service = new GitService(mockProjectRoot); | |
| await service.setupShadowGitRepository(); | |
| expect(hoistedMockCommit).not.toHaveBeenCalled(); | |
| }); | |
| }); | |
| }); | |