buroventures-harald-code-core
Version:
Harald Code Core - Core functionality for AI-powered coding assistant
222 lines (214 loc) • 11.5 kB
JavaScript
/**
* @license
* Copyright 2025 Google LLC
* SPDX-License-Identifier: Apache-2.0
*/
import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
import * as fsPromises from 'fs/promises';
import * as os from 'os';
import * as path from 'path';
import { loadServerHierarchicalMemory } from './memoryDiscovery.js';
import { GEMINI_CONFIG_DIR, setGeminiMdFilename, DEFAULT_CONTEXT_FILENAME, } from '../tools/memoryTool.js';
import { FileDiscoveryService } from '../services/fileDiscoveryService.js';
vi.mock('os', async (importOriginal) => {
const actualOs = await importOriginal();
return {
...actualOs,
homedir: vi.fn(),
};
});
describe('loadServerHierarchicalMemory', () => {
let testRootDir;
let cwd;
let projectRoot;
let homedir;
async function createEmptyDir(fullPath) {
await fsPromises.mkdir(fullPath, { recursive: true });
return fullPath;
}
async function createTestFile(fullPath, fileContents) {
await fsPromises.mkdir(path.dirname(fullPath), { recursive: true });
await fsPromises.writeFile(fullPath, fileContents);
return path.resolve(testRootDir, fullPath);
}
beforeEach(async () => {
testRootDir = await fsPromises.mkdtemp(path.join(os.tmpdir(), 'folder-structure-test-'));
vi.resetAllMocks();
// Set environment variables to indicate test environment
process.env.NODE_ENV = 'test';
process.env.VITEST = 'true';
projectRoot = await createEmptyDir(path.join(testRootDir, 'project'));
cwd = await createEmptyDir(path.join(projectRoot, 'src'));
homedir = await createEmptyDir(path.join(testRootDir, 'userhome'));
vi.mocked(os.homedir).mockReturnValue(homedir);
});
afterEach(async () => {
// Some tests set this to a different value.
setGeminiMdFilename(DEFAULT_CONTEXT_FILENAME);
// Clean up the temporary directory to prevent resource leaks.
await fsPromises.rm(testRootDir, { recursive: true, force: true });
});
it('should return empty memory and count if no context files are found', async () => {
const result = await loadServerHierarchicalMemory(cwd, false, new FileDiscoveryService(projectRoot));
expect(result).toEqual({
memoryContent: '',
fileCount: 0,
});
});
it('should load only the global context file if present and others are not (default filename)', async () => {
const defaultContextFile = await createTestFile(path.join(homedir, GEMINI_CONFIG_DIR, DEFAULT_CONTEXT_FILENAME), 'default context content');
const result = await loadServerHierarchicalMemory(cwd, false, new FileDiscoveryService(projectRoot));
expect(result).toEqual({
memoryContent: `--- Context from: ${path.relative(cwd, defaultContextFile)} ---
default context content
--- End of Context from: ${path.relative(cwd, defaultContextFile)} ---`,
fileCount: 1,
});
});
it('should load only the global custom context file if present and filename is changed', async () => {
const customFilename = 'CUSTOM_AGENTS.md';
setGeminiMdFilename(customFilename);
const customContextFile = await createTestFile(path.join(homedir, GEMINI_CONFIG_DIR, customFilename), 'custom context content');
const result = await loadServerHierarchicalMemory(cwd, false, new FileDiscoveryService(projectRoot));
expect(result).toEqual({
memoryContent: `--- Context from: ${path.relative(cwd, customContextFile)} ---
custom context content
--- End of Context from: ${path.relative(cwd, customContextFile)} ---`,
fileCount: 1,
});
});
it('should load context files by upward traversal with custom filename', async () => {
const customFilename = 'PROJECT_CONTEXT.md';
setGeminiMdFilename(customFilename);
const projectContextFile = await createTestFile(path.join(projectRoot, customFilename), 'project context content');
const cwdContextFile = await createTestFile(path.join(cwd, customFilename), 'cwd context content');
const result = await loadServerHierarchicalMemory(cwd, false, new FileDiscoveryService(projectRoot));
expect(result).toEqual({
memoryContent: `--- Context from: ${path.relative(cwd, projectContextFile)} ---
project context content
--- End of Context from: ${path.relative(cwd, projectContextFile)} ---
--- Context from: ${path.relative(cwd, cwdContextFile)} ---
cwd context content
--- End of Context from: ${path.relative(cwd, cwdContextFile)} ---`,
fileCount: 2,
});
});
it('should load context files by downward traversal with custom filename', async () => {
const customFilename = 'LOCAL_CONTEXT.md';
setGeminiMdFilename(customFilename);
await createTestFile(path.join(cwd, 'subdir', customFilename), 'Subdir custom memory');
await createTestFile(path.join(cwd, customFilename), 'CWD custom memory');
const result = await loadServerHierarchicalMemory(cwd, false, new FileDiscoveryService(projectRoot));
expect(result).toEqual({
memoryContent: `--- Context from: ${customFilename} ---
CWD custom memory
--- End of Context from: ${customFilename} ---
--- Context from: ${path.join('subdir', customFilename)} ---
Subdir custom memory
--- End of Context from: ${path.join('subdir', customFilename)} ---`,
fileCount: 2,
});
});
it('should load ORIGINAL_GEMINI_MD_FILENAME files by upward traversal from CWD to project root', async () => {
const projectRootGeminiFile = await createTestFile(path.join(projectRoot, DEFAULT_CONTEXT_FILENAME), 'Project root memory');
const srcGeminiFile = await createTestFile(path.join(cwd, DEFAULT_CONTEXT_FILENAME), 'Src directory memory');
const result = await loadServerHierarchicalMemory(cwd, false, new FileDiscoveryService(projectRoot));
expect(result).toEqual({
memoryContent: `--- Context from: ${path.relative(cwd, projectRootGeminiFile)} ---
Project root memory
--- End of Context from: ${path.relative(cwd, projectRootGeminiFile)} ---
--- Context from: ${path.relative(cwd, srcGeminiFile)} ---
Src directory memory
--- End of Context from: ${path.relative(cwd, srcGeminiFile)} ---`,
fileCount: 2,
});
});
it('should load ORIGINAL_GEMINI_MD_FILENAME files by downward traversal from CWD', async () => {
await createTestFile(path.join(cwd, 'subdir', DEFAULT_CONTEXT_FILENAME), 'Subdir memory');
await createTestFile(path.join(cwd, DEFAULT_CONTEXT_FILENAME), 'CWD memory');
const result = await loadServerHierarchicalMemory(cwd, false, new FileDiscoveryService(projectRoot));
expect(result).toEqual({
memoryContent: `--- Context from: ${DEFAULT_CONTEXT_FILENAME} ---
CWD memory
--- End of Context from: ${DEFAULT_CONTEXT_FILENAME} ---
--- Context from: ${path.join('subdir', DEFAULT_CONTEXT_FILENAME)} ---
Subdir memory
--- End of Context from: ${path.join('subdir', DEFAULT_CONTEXT_FILENAME)} ---`,
fileCount: 2,
});
});
it('should load and correctly order global, upward, and downward ORIGINAL_GEMINI_MD_FILENAME files', async () => {
const defaultContextFile = await createTestFile(path.join(homedir, GEMINI_CONFIG_DIR, DEFAULT_CONTEXT_FILENAME), 'default context content');
const rootGeminiFile = await createTestFile(path.join(testRootDir, DEFAULT_CONTEXT_FILENAME), 'Project parent memory');
const projectRootGeminiFile = await createTestFile(path.join(projectRoot, DEFAULT_CONTEXT_FILENAME), 'Project root memory');
const cwdGeminiFile = await createTestFile(path.join(cwd, DEFAULT_CONTEXT_FILENAME), 'CWD memory');
const subDirGeminiFile = await createTestFile(path.join(cwd, 'sub', DEFAULT_CONTEXT_FILENAME), 'Subdir memory');
const result = await loadServerHierarchicalMemory(cwd, false, new FileDiscoveryService(projectRoot));
expect(result).toEqual({
memoryContent: `--- Context from: ${path.relative(cwd, defaultContextFile)} ---
default context content
--- End of Context from: ${path.relative(cwd, defaultContextFile)} ---
--- Context from: ${path.relative(cwd, rootGeminiFile)} ---
Project parent memory
--- End of Context from: ${path.relative(cwd, rootGeminiFile)} ---
--- Context from: ${path.relative(cwd, projectRootGeminiFile)} ---
Project root memory
--- End of Context from: ${path.relative(cwd, projectRootGeminiFile)} ---
--- Context from: ${path.relative(cwd, cwdGeminiFile)} ---
CWD memory
--- End of Context from: ${path.relative(cwd, cwdGeminiFile)} ---
--- Context from: ${path.relative(cwd, subDirGeminiFile)} ---
Subdir memory
--- End of Context from: ${path.relative(cwd, subDirGeminiFile)} ---`,
fileCount: 5,
});
});
it('should ignore specified directories during downward scan', async () => {
await createEmptyDir(path.join(projectRoot, '.git'));
await createTestFile(path.join(projectRoot, '.gitignore'), 'node_modules');
await createTestFile(path.join(cwd, 'node_modules', DEFAULT_CONTEXT_FILENAME), 'Ignored memory');
const regularSubDirGeminiFile = await createTestFile(path.join(cwd, 'my_code', DEFAULT_CONTEXT_FILENAME), 'My code memory');
const result = await loadServerHierarchicalMemory(cwd, false, new FileDiscoveryService(projectRoot), [], 'tree', {
respectGitIgnore: true,
respectGeminiIgnore: true,
}, 200);
expect(result).toEqual({
memoryContent: `--- Context from: ${path.relative(cwd, regularSubDirGeminiFile)} ---
My code memory
--- End of Context from: ${path.relative(cwd, regularSubDirGeminiFile)} ---`,
fileCount: 1,
});
});
it('should respect the maxDirs parameter during downward scan', async () => {
const consoleDebugSpy = vi
.spyOn(console, 'debug')
.mockImplementation(() => { });
for (let i = 0; i < 100; i++) {
await createEmptyDir(path.join(cwd, `deep_dir_${i}`));
}
// Pass the custom limit directly to the function
await loadServerHierarchicalMemory(cwd, true, new FileDiscoveryService(projectRoot), [], 'tree', // importFormat
{
respectGitIgnore: true,
respectGeminiIgnore: true,
}, 50);
expect(consoleDebugSpy).toHaveBeenCalledWith(expect.stringContaining('[DEBUG] [BfsFileSearch]'), expect.stringContaining('Scanning [50/50]:'));
vi.mocked(console.debug).mockRestore();
const result = await loadServerHierarchicalMemory(cwd, false, new FileDiscoveryService(projectRoot));
expect(result).toEqual({
memoryContent: '',
fileCount: 0,
});
});
it('should load extension context file paths', async () => {
const extensionFilePath = await createTestFile(path.join(testRootDir, 'extensions/ext1/GEMINI.md'), 'Extension memory content');
const result = await loadServerHierarchicalMemory(cwd, false, new FileDiscoveryService(projectRoot), [extensionFilePath]);
expect(result).toEqual({
memoryContent: `--- Context from: ${path.relative(cwd, extensionFilePath)} ---
Extension memory content
--- End of Context from: ${path.relative(cwd, extensionFilePath)} ---`,
fileCount: 1,
});
});
});
//# sourceMappingURL=memoryDiscovery.test.js.map