mirror of
https://github.com/google-gemini/gemini-cli.git
synced 2026-07-10 10:00:53 -07:00
refactoring continued
This commit is contained in:
@@ -1,13 +1,13 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import * as fs from 'node:fs';
|
||||
import { describe, it, expect, beforeEach } from 'vitest';
|
||||
import { SidecarLoader } from './SidecarLoader.js';
|
||||
import { defaultSidecarProfile } from './profiles.js';
|
||||
import { InMemoryFileSystem } from '../system/InMemoryFileSystem.js';
|
||||
|
||||
vi.mock('node:fs');
|
||||
describe('SidecarLoader (Fake FS)', () => {
|
||||
let fileSystem: InMemoryFileSystem;
|
||||
|
||||
describe('SidecarLoader', () => {
|
||||
beforeEach(() => {
|
||||
vi.resetAllMocks();
|
||||
fileSystem = new InMemoryFileSystem();
|
||||
});
|
||||
|
||||
const mockConfig = {
|
||||
@@ -15,48 +15,38 @@ describe('SidecarLoader', () => {
|
||||
} as any;
|
||||
|
||||
it('returns default profile if file does not exist', () => {
|
||||
vi.mocked(fs.existsSync).mockReturnValue(false);
|
||||
const result = SidecarLoader.fromConfig(mockConfig);
|
||||
const result = SidecarLoader.fromConfig(mockConfig, fileSystem);
|
||||
expect(result).toBe(defaultSidecarProfile);
|
||||
});
|
||||
|
||||
it('returns default profile if file exists but is 0 bytes', () => {
|
||||
vi.mocked(fs.existsSync).mockReturnValue(true);
|
||||
vi.mocked(fs.statSync).mockReturnValue({ size: 0 } as any);
|
||||
const result = SidecarLoader.fromConfig(mockConfig);
|
||||
fileSystem.setFile('/path/to/sidecar.json', '');
|
||||
const result = SidecarLoader.fromConfig(mockConfig, fileSystem);
|
||||
expect(result).toBe(defaultSidecarProfile);
|
||||
});
|
||||
|
||||
it('throws an error if file is empty whitespace', () => {
|
||||
vi.mocked(fs.existsSync).mockReturnValue(true);
|
||||
vi.mocked(fs.statSync).mockReturnValue({ size: 5 } as any);
|
||||
vi.mocked(fs.readFileSync).mockReturnValue(' \n ');
|
||||
|
||||
expect(() => SidecarLoader.fromConfig(mockConfig)).toThrow('is empty');
|
||||
fileSystem.setFile('/path/to/sidecar.json', ' \n ');
|
||||
expect(() => SidecarLoader.fromConfig(mockConfig, fileSystem)).toThrow('is empty');
|
||||
});
|
||||
|
||||
it('returns parsed config if file is valid', () => {
|
||||
vi.mocked(fs.existsSync).mockReturnValue(true);
|
||||
vi.mocked(fs.statSync).mockReturnValue({ size: 100 } as any);
|
||||
const validConfig = {
|
||||
budget: { retainedTokens: 1000, maxTokens: 2000 },
|
||||
gcBackstop: { strategy: 'truncate', target: 'max' },
|
||||
pipelines: []
|
||||
};
|
||||
vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify(validConfig));
|
||||
const result = SidecarLoader.fromConfig(mockConfig);
|
||||
fileSystem.setFile('/path/to/sidecar.json', JSON.stringify(validConfig));
|
||||
const result = SidecarLoader.fromConfig(mockConfig, fileSystem);
|
||||
expect(result).toEqual(validConfig);
|
||||
});
|
||||
|
||||
it('throws an error if schema validation fails', () => {
|
||||
vi.mocked(fs.existsSync).mockReturnValue(true);
|
||||
vi.mocked(fs.statSync).mockReturnValue({ size: 100 } as any);
|
||||
const invalidConfig = {
|
||||
budget: { retainedTokens: "invalid string" }, // Invalid type
|
||||
pipelines: []
|
||||
};
|
||||
vi.mocked(fs.readFileSync).mockReturnValue(JSON.stringify(invalidConfig));
|
||||
|
||||
expect(() => SidecarLoader.fromConfig(mockConfig)).toThrow('Validation error:');
|
||||
fileSystem.setFile('/path/to/sidecar.json', JSON.stringify(invalidConfig));
|
||||
expect(() => SidecarLoader.fromConfig(mockConfig, fileSystem)).toThrow('Validation error:');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -4,19 +4,21 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import * as fs from 'node:fs';
|
||||
import type { Config } from '../../config/config.js';
|
||||
import type { SidecarConfig } from './types.js';
|
||||
import { defaultSidecarProfile } from './profiles.js';
|
||||
import { SchemaValidator } from '../../utils/schemaValidator.js';
|
||||
import { sidecarConfigSchema } from './schema.js';
|
||||
import type { IFileSystem } from '../system/IFileSystem.js';
|
||||
import { NodeFileSystem } from '../system/NodeFileSystem.js';
|
||||
|
||||
export class SidecarLoader {
|
||||
/**
|
||||
* Loads and validates a sidecar config from a specific file path.
|
||||
* Throws an error if the file cannot be read, parsed, or fails schema validation.
|
||||
*/
|
||||
static loadFromFile(sidecarPath: string): SidecarConfig {
|
||||
const fileContent = fs.readFileSync(sidecarPath, 'utf8');
|
||||
static loadFromFile(sidecarPath: string, fileSystem: IFileSystem = new NodeFileSystem()): SidecarConfig {
|
||||
const fileContent = fileSystem.readFileSync(sidecarPath, 'utf8');
|
||||
|
||||
if (!fileContent.trim()) {
|
||||
throw new Error(`Sidecar configuration file at ${sidecarPath} is empty.`);
|
||||
@@ -49,18 +51,18 @@ export class SidecarLoader {
|
||||
* Generates a Sidecar JSON graph from the experimental config file path or defaults.
|
||||
* If a config file is present but invalid, this will THROW to prevent silent misconfiguration.
|
||||
*/
|
||||
static fromConfig(config: Config): SidecarConfig {
|
||||
static fromConfig(config: Config, fileSystem: IFileSystem = new NodeFileSystem()): SidecarConfig {
|
||||
const sidecarPath = config.getExperimentalContextSidecarConfig();
|
||||
|
||||
if (sidecarPath && fs.existsSync(sidecarPath)) {
|
||||
const stat = fs.statSync(sidecarPath);
|
||||
if (sidecarPath && fileSystem.existsSync(sidecarPath)) {
|
||||
const size = fileSystem.statSyncSize(sidecarPath);
|
||||
// If the file exists but is completely empty (0 bytes), it's safe to fallback.
|
||||
if (stat.size === 0) {
|
||||
if (size === 0) {
|
||||
return defaultSidecarProfile;
|
||||
}
|
||||
|
||||
// If the file has content, enforce strict validation and throw on failure.
|
||||
return this.loadFromFile(sidecarPath);
|
||||
return this.loadFromFile(sidecarPath, fileSystem);
|
||||
}
|
||||
|
||||
return defaultSidecarProfile;
|
||||
|
||||
@@ -4,20 +4,25 @@
|
||||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
import type { BaseLlmClient } from '../../core/baseLlmClient.js';
|
||||
import type { ContextTracer } from '../tracer.js';
|
||||
import type { ContextEventBus } from '../eventBus.js';
|
||||
import type { ContextEventBus } from '../eventBus.js';
|
||||
import type { ContextTokenCalculator } from '../utils/contextTokenCalculator.js';
|
||||
export type { ContextTracer, ContextEventBus };
|
||||
import type { ContextTracer } from '../tracer.js';
|
||||
import type { IFileSystem } from '../system/IFileSystem.js';
|
||||
import type { IIdGenerator } from '../system/IIdGenerator.js';
|
||||
|
||||
export interface ContextEnvironment {
|
||||
readonly llmClient: BaseLlmClient;
|
||||
readonly promptId: string;
|
||||
readonly sessionId: string;
|
||||
readonly traceDir: string;
|
||||
readonly projectTempDir: string;
|
||||
readonly tracer: ContextTracer;
|
||||
readonly charsPerToken: number;
|
||||
readonly tokenCalculator: ContextTokenCalculator;
|
||||
|
||||
eventBus: ContextEventBus;
|
||||
export type { ContextTracer, ContextEventBus };
|
||||
|
||||
export interface ContextEnvironment {
|
||||
readonly llmClient: BaseLlmClient;
|
||||
readonly promptId: string;
|
||||
readonly sessionId: string;
|
||||
readonly traceDir: string;
|
||||
readonly projectTempDir: string;
|
||||
readonly tracer: ContextTracer;
|
||||
readonly charsPerToken: number;
|
||||
readonly tokenCalculator: ContextTokenCalculator;
|
||||
readonly fileSystem: IFileSystem;
|
||||
readonly idGenerator: IIdGenerator;
|
||||
|
||||
eventBus: ContextEventBus;
|
||||
}
|
||||
|
||||
@@ -11,9 +11,15 @@ import type { ContextEnvironment } from './environment.js';
|
||||
import type { ContextEventBus } from '../eventBus.js';
|
||||
|
||||
import { ContextTokenCalculator } from '../utils/contextTokenCalculator.js';
|
||||
import type { IFileSystem } from '../system/IFileSystem.js';
|
||||
import { NodeFileSystem } from '../system/NodeFileSystem.js';
|
||||
import type { IIdGenerator } from '../system/IIdGenerator.js';
|
||||
import { NodeIdGenerator } from '../system/NodeIdGenerator.js';
|
||||
|
||||
export class ContextEnvironmentImpl implements ContextEnvironment {
|
||||
public readonly tokenCalculator: ContextTokenCalculator;
|
||||
public readonly fileSystem: IFileSystem;
|
||||
public readonly idGenerator: IIdGenerator;
|
||||
|
||||
constructor(
|
||||
public readonly llmClient: BaseLlmClient,
|
||||
@@ -24,7 +30,11 @@ export class ContextEnvironmentImpl implements ContextEnvironment {
|
||||
public readonly tracer: ContextTracer,
|
||||
public readonly charsPerToken: number,
|
||||
public readonly eventBus: ContextEventBus,
|
||||
fileSystem?: IFileSystem,
|
||||
idGenerator?: IIdGenerator,
|
||||
) {
|
||||
this.tokenCalculator = new ContextTokenCalculator(this.charsPerToken);
|
||||
this.fileSystem = fileSystem || new NodeFileSystem();
|
||||
this.idGenerator = idGenerator || new NodeIdGenerator();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user