mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-04-04 02:11:11 -07:00
250 lines
7.2 KiB
TypeScript
250 lines
7.2 KiB
TypeScript
/**
|
|
* @license
|
|
* Copyright 2026 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
import { describe, it, expect, vi, afterEach, beforeEach } from 'vitest';
|
|
import { PolicyIntegrityManager, IntegrityStatus } from './integrity.js';
|
|
import * as fs from 'node:fs/promises';
|
|
import * as path from 'node:path';
|
|
import * as os from 'node:os';
|
|
import { Storage } from '../config/storage.js';
|
|
|
|
describe('PolicyIntegrityManager', () => {
|
|
let integrityManager: PolicyIntegrityManager;
|
|
let tempDir: string;
|
|
let integrityStoragePath: string;
|
|
|
|
beforeEach(async () => {
|
|
tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'gemini-cli-test-'));
|
|
integrityStoragePath = path.join(tempDir, 'policy_integrity.json');
|
|
|
|
vi.spyOn(Storage, 'getPolicyIntegrityStoragePath').mockReturnValue(
|
|
integrityStoragePath,
|
|
);
|
|
|
|
integrityManager = new PolicyIntegrityManager();
|
|
});
|
|
|
|
afterEach(async () => {
|
|
await fs.rm(tempDir, { recursive: true, force: true });
|
|
vi.restoreAllMocks();
|
|
});
|
|
|
|
describe('checkIntegrity', () => {
|
|
it('should return NEW if no stored hash', async () => {
|
|
const policyDir = path.join(tempDir, 'policies');
|
|
await fs.mkdir(policyDir);
|
|
await fs.writeFile(path.join(policyDir, 'a.toml'), 'contentA');
|
|
|
|
const result = await integrityManager.checkIntegrity(
|
|
'workspace',
|
|
'id',
|
|
policyDir,
|
|
);
|
|
expect(result.status).toBe(IntegrityStatus.NEW);
|
|
expect(result.hash).toBeDefined();
|
|
expect(result.hash).toHaveLength(64);
|
|
expect(result.fileCount).toBe(1);
|
|
});
|
|
|
|
it('should return MATCH if stored hash matches', async () => {
|
|
const policyDir = path.join(tempDir, 'policies');
|
|
await fs.mkdir(policyDir);
|
|
await fs.writeFile(path.join(policyDir, 'a.toml'), 'contentA');
|
|
|
|
// First run to get the hash
|
|
const resultNew = await integrityManager.checkIntegrity(
|
|
'workspace',
|
|
'id',
|
|
policyDir,
|
|
);
|
|
const currentHash = resultNew.hash;
|
|
|
|
// Save the hash to mock storage
|
|
await fs.writeFile(
|
|
integrityStoragePath,
|
|
JSON.stringify({ 'workspace:id': currentHash }),
|
|
);
|
|
|
|
const result = await integrityManager.checkIntegrity(
|
|
'workspace',
|
|
'id',
|
|
policyDir,
|
|
);
|
|
expect(result.status).toBe(IntegrityStatus.MATCH);
|
|
expect(result.hash).toBe(currentHash);
|
|
});
|
|
|
|
it('should return MISMATCH if stored hash differs', async () => {
|
|
const policyDir = path.join(tempDir, 'policies');
|
|
await fs.mkdir(policyDir);
|
|
await fs.writeFile(path.join(policyDir, 'a.toml'), 'contentA');
|
|
|
|
const resultNew = await integrityManager.checkIntegrity(
|
|
'workspace',
|
|
'id',
|
|
policyDir,
|
|
);
|
|
const currentHash = resultNew.hash;
|
|
|
|
// Save a different hash
|
|
await fs.writeFile(
|
|
integrityStoragePath,
|
|
JSON.stringify({ 'workspace:id': 'different_hash' }),
|
|
);
|
|
|
|
const result = await integrityManager.checkIntegrity(
|
|
'workspace',
|
|
'id',
|
|
policyDir,
|
|
);
|
|
expect(result.status).toBe(IntegrityStatus.MISMATCH);
|
|
expect(result.hash).toBe(currentHash);
|
|
});
|
|
|
|
it('should result in different hash if filename changes', async () => {
|
|
const policyDir1 = path.join(tempDir, 'policies1');
|
|
await fs.mkdir(policyDir1);
|
|
await fs.writeFile(path.join(policyDir1, 'a.toml'), 'contentA');
|
|
|
|
const result1 = await integrityManager.checkIntegrity(
|
|
'workspace',
|
|
'id',
|
|
policyDir1,
|
|
);
|
|
|
|
const policyDir2 = path.join(tempDir, 'policies2');
|
|
await fs.mkdir(policyDir2);
|
|
await fs.writeFile(path.join(policyDir2, 'b.toml'), 'contentA');
|
|
|
|
const result2 = await integrityManager.checkIntegrity(
|
|
'workspace',
|
|
'id',
|
|
policyDir2,
|
|
);
|
|
|
|
expect(result1.hash).not.toBe(result2.hash);
|
|
});
|
|
|
|
it('should result in different hash if content changes', async () => {
|
|
const policyDir = path.join(tempDir, 'policies');
|
|
await fs.mkdir(policyDir);
|
|
|
|
await fs.writeFile(path.join(policyDir, 'a.toml'), 'contentA');
|
|
const result1 = await integrityManager.checkIntegrity(
|
|
'workspace',
|
|
'id',
|
|
policyDir,
|
|
);
|
|
|
|
await fs.writeFile(path.join(policyDir, 'a.toml'), 'contentB');
|
|
const result2 = await integrityManager.checkIntegrity(
|
|
'workspace',
|
|
'id',
|
|
policyDir,
|
|
);
|
|
|
|
expect(result1.hash).not.toBe(result2.hash);
|
|
});
|
|
|
|
it('should be deterministic (sort order)', async () => {
|
|
const policyDir1 = path.join(tempDir, 'policies1');
|
|
await fs.mkdir(policyDir1);
|
|
await fs.writeFile(path.join(policyDir1, 'a.toml'), 'contentA');
|
|
await fs.writeFile(path.join(policyDir1, 'b.toml'), 'contentB');
|
|
|
|
const result1 = await integrityManager.checkIntegrity(
|
|
'workspace',
|
|
'id',
|
|
policyDir1,
|
|
);
|
|
|
|
// Re-read with same files but they might be in different order in readdir
|
|
// PolicyIntegrityManager should sort them.
|
|
const result2 = await integrityManager.checkIntegrity(
|
|
'workspace',
|
|
'id',
|
|
policyDir1,
|
|
);
|
|
|
|
expect(result1.hash).toBe(result2.hash);
|
|
});
|
|
|
|
it('should handle multiple projects correctly', async () => {
|
|
const dirA = path.join(tempDir, 'dirA');
|
|
await fs.mkdir(dirA);
|
|
await fs.writeFile(path.join(dirA, 'p.toml'), 'contentA');
|
|
|
|
const dirB = path.join(tempDir, 'dirB');
|
|
await fs.mkdir(dirB);
|
|
await fs.writeFile(path.join(dirB, 'p.toml'), 'contentB');
|
|
|
|
const { hash: hashA } = await integrityManager.checkIntegrity(
|
|
'workspace',
|
|
'idA',
|
|
dirA,
|
|
);
|
|
const { hash: hashB } = await integrityManager.checkIntegrity(
|
|
'workspace',
|
|
'idB',
|
|
dirB,
|
|
);
|
|
|
|
// Save to storage
|
|
await fs.writeFile(
|
|
integrityStoragePath,
|
|
JSON.stringify({
|
|
'workspace:idA': hashA,
|
|
'workspace:idB': 'oldHashB',
|
|
}),
|
|
);
|
|
|
|
// Project A should match
|
|
const resultA = await integrityManager.checkIntegrity(
|
|
'workspace',
|
|
'idA',
|
|
dirA,
|
|
);
|
|
expect(resultA.status).toBe(IntegrityStatus.MATCH);
|
|
expect(resultA.hash).toBe(hashA);
|
|
|
|
// Project B should mismatch
|
|
const resultB = await integrityManager.checkIntegrity(
|
|
'workspace',
|
|
'idB',
|
|
dirB,
|
|
);
|
|
expect(resultB.status).toBe(IntegrityStatus.MISMATCH);
|
|
expect(resultB.hash).toBe(hashB);
|
|
});
|
|
});
|
|
|
|
describe('acceptIntegrity', () => {
|
|
it('should save the hash to storage', async () => {
|
|
await integrityManager.acceptIntegrity('workspace', 'id', 'hash123');
|
|
|
|
const stored = JSON.parse(
|
|
await fs.readFile(integrityStoragePath, 'utf-8'),
|
|
);
|
|
expect(stored['workspace:id']).toBe('hash123');
|
|
});
|
|
|
|
it('should update existing hash', async () => {
|
|
await fs.writeFile(
|
|
integrityStoragePath,
|
|
JSON.stringify({ 'other:id': 'otherhash' }),
|
|
);
|
|
|
|
await integrityManager.acceptIntegrity('workspace', 'id', 'hash123');
|
|
|
|
const stored = JSON.parse(
|
|
await fs.readFile(integrityStoragePath, 'utf-8'),
|
|
);
|
|
expect(stored['other:id']).toBe('otherhash');
|
|
expect(stored['workspace:id']).toBe('hash123');
|
|
});
|
|
});
|
|
});
|