mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-03-13 15:40:57 -07:00
287 lines
8.6 KiB
TypeScript
287 lines
8.6 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2025 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
import * as path from 'node:path';
|
|
import { loadCliConfig, type CliArgs } from './config.js';
|
|
import { createTestMergedSettings } from './settings.js';
|
|
import * as ServerConfig from '@google/gemini-cli-core';
|
|
import { isWorkspaceTrusted } from './trustedFolders.js';
|
|
import * as Policy from './policy.js';
|
|
|
|
// Mock dependencies
|
|
vi.mock('./trustedFolders.js', () => ({
|
|
isWorkspaceTrusted: vi.fn(),
|
|
}));
|
|
|
|
const mockCheckIntegrity = vi.fn();
|
|
const mockAcceptIntegrity = vi.fn();
|
|
|
|
vi.mock('@google/gemini-cli-core', async () => {
|
|
const actual = await vi.importActual<typeof ServerConfig>(
|
|
'@google/gemini-cli-core',
|
|
);
|
|
return {
|
|
...actual,
|
|
loadServerHierarchicalMemory: vi.fn().mockResolvedValue({
|
|
memoryContent: '',
|
|
fileCount: 0,
|
|
filePaths: [],
|
|
}),
|
|
createPolicyEngineConfig: vi.fn().mockResolvedValue({
|
|
rules: [],
|
|
checkers: [],
|
|
}),
|
|
getVersion: vi.fn().mockResolvedValue('test-version'),
|
|
PolicyIntegrityManager: vi.fn().mockImplementation(() => ({
|
|
checkIntegrity: mockCheckIntegrity,
|
|
acceptIntegrity: mockAcceptIntegrity,
|
|
})),
|
|
IntegrityStatus: { MATCH: 'match', NEW: 'new', MISMATCH: 'mismatch' },
|
|
debugLogger: {
|
|
warn: vi.fn(),
|
|
error: vi.fn(),
|
|
},
|
|
isHeadlessMode: vi.fn().mockReturnValue(false), // Default to interactive
|
|
};
|
|
});
|
|
|
|
describe('Workspace-Level Policy CLI Integration', () => {
|
|
const MOCK_CWD = process.cwd();
|
|
|
|
beforeEach(() => {
|
|
vi.clearAllMocks();
|
|
// Default to MATCH for existing tests
|
|
mockCheckIntegrity.mockResolvedValue({
|
|
status: 'match',
|
|
hash: 'test-hash',
|
|
fileCount: 1,
|
|
});
|
|
vi.mocked(ServerConfig.isHeadlessMode).mockReturnValue(false);
|
|
});
|
|
|
|
it('should have getWorkspacePoliciesDir on Storage class', () => {
|
|
const storage = new ServerConfig.Storage(MOCK_CWD);
|
|
expect(storage.getWorkspacePoliciesDir).toBeDefined();
|
|
expect(typeof storage.getWorkspacePoliciesDir).toBe('function');
|
|
});
|
|
|
|
it('should pass workspacePoliciesDir to createPolicyEngineConfig when folder is trusted', async () => {
|
|
vi.mocked(isWorkspaceTrusted).mockReturnValue({
|
|
isTrusted: true,
|
|
source: 'file',
|
|
});
|
|
|
|
const settings = createTestMergedSettings();
|
|
const argv = { query: 'test' } as unknown as CliArgs;
|
|
|
|
await loadCliConfig(settings, 'test-session', argv, { cwd: MOCK_CWD });
|
|
|
|
expect(ServerConfig.createPolicyEngineConfig).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
workspacePoliciesDir: expect.stringContaining(
|
|
path.join('.gemini', 'policies'),
|
|
),
|
|
}),
|
|
expect.anything(),
|
|
);
|
|
});
|
|
|
|
it('should NOT pass workspacePoliciesDir to createPolicyEngineConfig when folder is NOT trusted', async () => {
|
|
vi.mocked(isWorkspaceTrusted).mockReturnValue({
|
|
isTrusted: false,
|
|
source: 'file',
|
|
});
|
|
|
|
const settings = createTestMergedSettings();
|
|
const argv = { query: 'test' } as unknown as CliArgs;
|
|
|
|
await loadCliConfig(settings, 'test-session', argv, { cwd: MOCK_CWD });
|
|
|
|
expect(ServerConfig.createPolicyEngineConfig).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
workspacePoliciesDir: undefined,
|
|
}),
|
|
expect.anything(),
|
|
);
|
|
});
|
|
|
|
it('should NOT pass workspacePoliciesDir if integrity is NEW but fileCount is 0', async () => {
|
|
vi.mocked(isWorkspaceTrusted).mockReturnValue({
|
|
isTrusted: true,
|
|
source: 'file',
|
|
});
|
|
mockCheckIntegrity.mockResolvedValue({
|
|
status: 'new',
|
|
hash: 'hash',
|
|
fileCount: 0,
|
|
});
|
|
|
|
const settings = createTestMergedSettings();
|
|
const argv = { query: 'test' } as unknown as CliArgs;
|
|
|
|
await loadCliConfig(settings, 'test-session', argv, { cwd: MOCK_CWD });
|
|
|
|
expect(ServerConfig.createPolicyEngineConfig).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
workspacePoliciesDir: undefined,
|
|
}),
|
|
expect.anything(),
|
|
);
|
|
});
|
|
|
|
it('should automatically accept and load workspacePoliciesDir if integrity MISMATCH in non-interactive mode', async () => {
|
|
vi.mocked(isWorkspaceTrusted).mockReturnValue({
|
|
isTrusted: true,
|
|
source: 'file',
|
|
});
|
|
mockCheckIntegrity.mockResolvedValue({
|
|
status: 'mismatch',
|
|
hash: 'new-hash',
|
|
fileCount: 1,
|
|
});
|
|
vi.mocked(ServerConfig.isHeadlessMode).mockReturnValue(true); // Non-interactive
|
|
|
|
const settings = createTestMergedSettings();
|
|
const argv = { prompt: 'do something' } as unknown as CliArgs;
|
|
|
|
await loadCliConfig(settings, 'test-session', argv, { cwd: MOCK_CWD });
|
|
|
|
expect(mockAcceptIntegrity).toHaveBeenCalledWith(
|
|
'workspace',
|
|
MOCK_CWD,
|
|
'new-hash',
|
|
);
|
|
expect(ServerConfig.createPolicyEngineConfig).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
workspacePoliciesDir: expect.stringContaining(
|
|
path.join('.gemini', 'policies'),
|
|
),
|
|
}),
|
|
expect.anything(),
|
|
);
|
|
});
|
|
|
|
it('should automatically accept and load workspacePoliciesDir if integrity MISMATCH in interactive mode when AUTO_ACCEPT is true', async () => {
|
|
vi.mocked(isWorkspaceTrusted).mockReturnValue({
|
|
isTrusted: true,
|
|
source: 'file',
|
|
});
|
|
mockCheckIntegrity.mockResolvedValue({
|
|
status: 'mismatch',
|
|
hash: 'new-hash',
|
|
fileCount: 1,
|
|
});
|
|
vi.mocked(ServerConfig.isHeadlessMode).mockReturnValue(false); // Interactive
|
|
|
|
const settings = createTestMergedSettings();
|
|
const argv = {
|
|
query: 'test',
|
|
promptInteractive: 'test',
|
|
} as unknown as CliArgs;
|
|
|
|
const config = await loadCliConfig(settings, 'test-session', argv, {
|
|
cwd: MOCK_CWD,
|
|
});
|
|
|
|
expect(config.getPolicyUpdateConfirmationRequest()).toBeUndefined();
|
|
expect(mockAcceptIntegrity).toHaveBeenCalledWith(
|
|
'workspace',
|
|
MOCK_CWD,
|
|
'new-hash',
|
|
);
|
|
expect(ServerConfig.createPolicyEngineConfig).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
workspacePoliciesDir: expect.stringContaining(
|
|
path.join('.gemini', 'policies'),
|
|
),
|
|
}),
|
|
expect.anything(),
|
|
);
|
|
});
|
|
|
|
it('should automatically accept and load workspacePoliciesDir if integrity is NEW in interactive mode when AUTO_ACCEPT is true', async () => {
|
|
vi.mocked(isWorkspaceTrusted).mockReturnValue({
|
|
isTrusted: true,
|
|
source: 'file',
|
|
});
|
|
mockCheckIntegrity.mockResolvedValue({
|
|
status: 'new',
|
|
hash: 'new-hash',
|
|
fileCount: 5,
|
|
});
|
|
vi.mocked(ServerConfig.isHeadlessMode).mockReturnValue(false); // Interactive
|
|
|
|
const settings = createTestMergedSettings();
|
|
const argv = { query: 'test' } as unknown as CliArgs;
|
|
|
|
const config = await loadCliConfig(settings, 'test-session', argv, {
|
|
cwd: MOCK_CWD,
|
|
});
|
|
|
|
expect(config.getPolicyUpdateConfirmationRequest()).toBeUndefined();
|
|
expect(mockAcceptIntegrity).toHaveBeenCalledWith(
|
|
'workspace',
|
|
MOCK_CWD,
|
|
'new-hash',
|
|
);
|
|
|
|
expect(ServerConfig.createPolicyEngineConfig).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
workspacePoliciesDir: expect.stringContaining(
|
|
path.join('.gemini', 'policies'),
|
|
),
|
|
}),
|
|
expect.anything(),
|
|
);
|
|
});
|
|
|
|
it('should set policyUpdateConfirmationRequest if integrity MISMATCH in interactive mode when AUTO_ACCEPT is false', async () => {
|
|
// Monkey patch autoAcceptWorkspacePolicies using setter
|
|
const originalValue = Policy.autoAcceptWorkspacePolicies;
|
|
Policy.setAutoAcceptWorkspacePolicies(false);
|
|
|
|
try {
|
|
vi.mocked(isWorkspaceTrusted).mockReturnValue({
|
|
isTrusted: true,
|
|
source: 'file',
|
|
});
|
|
mockCheckIntegrity.mockResolvedValue({
|
|
status: 'mismatch',
|
|
hash: 'new-hash',
|
|
fileCount: 1,
|
|
});
|
|
vi.mocked(ServerConfig.isHeadlessMode).mockReturnValue(false); // Interactive
|
|
|
|
const settings = createTestMergedSettings();
|
|
const argv = {
|
|
query: 'test',
|
|
promptInteractive: 'test',
|
|
} as unknown as CliArgs;
|
|
|
|
const config = await loadCliConfig(settings, 'test-session', argv, {
|
|
cwd: MOCK_CWD,
|
|
});
|
|
|
|
expect(config.getPolicyUpdateConfirmationRequest()).toEqual({
|
|
scope: 'workspace',
|
|
identifier: MOCK_CWD,
|
|
policyDir: expect.stringContaining(path.join('.gemini', 'policies')),
|
|
newHash: 'new-hash',
|
|
});
|
|
expect(ServerConfig.createPolicyEngineConfig).toHaveBeenCalledWith(
|
|
expect.objectContaining({
|
|
workspacePoliciesDir: undefined,
|
|
}),
|
|
expect.anything(),
|
|
);
|
|
} finally {
|
|
// Restore for other tests
|
|
Policy.setAutoAcceptWorkspacePolicies(originalValue);
|
|
}
|
|
});
|
|
});
|