Files

263 lines
8.8 KiB
JavaScript
Raw Permalink Normal View History

const { describe, it, beforeEach, afterEach } = require('node:test');
const assert = require('node:assert/strict');
const fs = require('fs');
const os = require('os');
const path = require('path');
function freshRequire(modulePath) {
const resolved = require.resolve(modulePath);
delete require.cache[resolved];
return require(resolved);
}
describe('getRepoRoot', () => {
let tmpDir;
const savedEnv = {};
const envKeys = [
'EVOLVER_REPO_ROOT', 'EVOLVER_USE_PARENT_GIT',
'OPENCLAW_WORKSPACE', 'MEMORY_DIR', 'EVOLUTION_DIR', 'GEP_ASSETS_DIR',
];
beforeEach(() => {
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'paths-test-'));
for (const k of envKeys) {
savedEnv[k] = process.env[k];
delete process.env[k];
}
});
afterEach(() => {
for (const k of envKeys) {
if (savedEnv[k] === undefined) delete process.env[k];
else process.env[k] = savedEnv[k];
}
fs.rmSync(tmpDir, { recursive: true, force: true });
});
it('returns EVOLVER_REPO_ROOT when set', () => {
process.env.EVOLVER_REPO_ROOT = tmpDir;
const { getRepoRoot } = freshRequire('../src/gep/paths');
assert.equal(getRepoRoot(), tmpDir);
});
it('returns own directory when it has .git', () => {
const ownDir = path.resolve(__dirname, '..');
const { getRepoRoot } = freshRequire('../src/gep/paths');
delete process.env.EVOLVER_REPO_ROOT;
const result = getRepoRoot();
assert.ok(typeof result === 'string' && result.length > 0);
});
it('EVOLVER_REPO_ROOT takes precedence over .git detection', () => {
process.env.EVOLVER_REPO_ROOT = tmpDir;
const { getRepoRoot } = freshRequire('../src/gep/paths');
assert.equal(getRepoRoot(), tmpDir);
});
});
describe('getSessionScope', () => {
let saved;
beforeEach(() => {
saved = process.env.EVOLVER_SESSION_SCOPE;
delete process.env.EVOLVER_SESSION_SCOPE;
});
afterEach(() => {
if (saved === undefined) delete process.env.EVOLVER_SESSION_SCOPE;
else process.env.EVOLVER_SESSION_SCOPE = saved;
});
it('returns null when not set', () => {
const { getSessionScope } = freshRequire('../src/gep/paths');
assert.equal(getSessionScope(), null);
});
it('returns null for empty string', () => {
process.env.EVOLVER_SESSION_SCOPE = '';
const { getSessionScope } = freshRequire('../src/gep/paths');
assert.equal(getSessionScope(), null);
});
it('returns null for whitespace-only', () => {
process.env.EVOLVER_SESSION_SCOPE = ' ';
const { getSessionScope } = freshRequire('../src/gep/paths');
assert.equal(getSessionScope(), null);
});
it('returns sanitized value for valid scope', () => {
process.env.EVOLVER_SESSION_SCOPE = 'channel-123';
const { getSessionScope } = freshRequire('../src/gep/paths');
assert.equal(getSessionScope(), 'channel-123');
});
it('sanitizes special characters', () => {
process.env.EVOLVER_SESSION_SCOPE = 'my/scope\\with:bad*chars';
const { getSessionScope } = freshRequire('../src/gep/paths');
const result = getSessionScope();
assert.ok(result);
assert.ok(!/[\/\\:*]/.test(result), 'should not contain path-unsafe characters');
});
it('rejects path traversal attempts', () => {
process.env.EVOLVER_SESSION_SCOPE = '..';
const { getSessionScope } = freshRequire('../src/gep/paths');
assert.equal(getSessionScope(), null);
});
it('rejects embedded path traversal', () => {
process.env.EVOLVER_SESSION_SCOPE = 'foo..bar';
const { getSessionScope } = freshRequire('../src/gep/paths');
assert.equal(getSessionScope(), null);
});
it('truncates to 128 characters', () => {
process.env.EVOLVER_SESSION_SCOPE = 'a'.repeat(200);
const { getSessionScope } = freshRequire('../src/gep/paths');
const result = getSessionScope();
assert.ok(result);
assert.ok(result.length <= 128);
});
});
describe('getEvolutionDir', () => {
let saved = {};
const envKeys = ['EVOLUTION_DIR', 'EVOLVER_SESSION_SCOPE', 'MEMORY_DIR', 'OPENCLAW_WORKSPACE', 'EVOLVER_REPO_ROOT'];
beforeEach(() => {
for (const k of envKeys) { saved[k] = process.env[k]; delete process.env[k]; }
});
afterEach(() => {
for (const k of envKeys) {
if (saved[k] === undefined) delete process.env[k];
else process.env[k] = saved[k];
}
});
it('returns EVOLUTION_DIR when set', () => {
process.env.EVOLUTION_DIR = '/custom/evo';
const { getEvolutionDir } = freshRequire('../src/gep/paths');
assert.equal(getEvolutionDir(), '/custom/evo');
});
it('appends scope subdirectory when session scope is set', () => {
process.env.EVOLUTION_DIR = '/custom/evo';
process.env.EVOLVER_SESSION_SCOPE = 'test-scope';
const { getEvolutionDir } = freshRequire('../src/gep/paths');
const result = getEvolutionDir();
assert.ok(result.includes('scopes'));
assert.ok(result.includes('test-scope'));
});
it('returns base dir when no scope set', () => {
process.env.EVOLUTION_DIR = '/custom/evo';
const { getEvolutionDir } = freshRequire('../src/gep/paths');
assert.equal(getEvolutionDir(), '/custom/evo');
assert.ok(!getEvolutionDir().includes('scopes'));
});
});
describe('getGepAssetsDir', () => {
let saved = {};
const envKeys = ['GEP_ASSETS_DIR', 'EVOLVER_SESSION_SCOPE', 'EVOLVER_REPO_ROOT'];
beforeEach(() => {
for (const k of envKeys) { saved[k] = process.env[k]; delete process.env[k]; }
});
afterEach(() => {
for (const k of envKeys) {
if (saved[k] === undefined) delete process.env[k];
else process.env[k] = saved[k];
}
});
it('returns GEP_ASSETS_DIR when set', () => {
process.env.GEP_ASSETS_DIR = '/custom/assets';
const { getGepAssetsDir } = freshRequire('../src/gep/paths');
assert.equal(getGepAssetsDir(), '/custom/assets');
});
it('appends scope subdirectory when session scope is set', () => {
process.env.GEP_ASSETS_DIR = '/custom/assets';
process.env.EVOLVER_SESSION_SCOPE = 'my-project';
const { getGepAssetsDir } = freshRequire('../src/gep/paths');
const result = getGepAssetsDir();
assert.ok(result.includes('scopes'));
assert.ok(result.includes('my-project'));
});
});
describe('getWorkspaceRoot', () => {
let saved = {};
let tmpDir;
const envKeys = ['OPENCLAW_WORKSPACE', 'EVOLVER_REPO_ROOT'];
beforeEach(() => {
tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ws-test-'));
for (const k of envKeys) { saved[k] = process.env[k]; delete process.env[k]; }
});
afterEach(() => {
for (const k of envKeys) {
if (saved[k] === undefined) delete process.env[k];
else process.env[k] = saved[k];
}
fs.rmSync(tmpDir, { recursive: true, force: true });
});
it('returns OPENCLAW_WORKSPACE when set', () => {
process.env.OPENCLAW_WORKSPACE = '/my/workspace';
const { getWorkspaceRoot } = freshRequire('../src/gep/paths');
assert.equal(getWorkspaceRoot(), '/my/workspace');
});
it('returns a string when no env vars set', () => {
const { getWorkspaceRoot } = freshRequire('../src/gep/paths');
const result = getWorkspaceRoot();
assert.ok(typeof result === 'string' && result.length > 0);
});
it('returns repoRoot when no workspace/ dir exists (standalone/Cursor fix)', () => {
process.env.EVOLVER_REPO_ROOT = tmpDir;
const { getWorkspaceRoot, getRepoRoot } = freshRequire('../src/gep/paths');
assert.equal(getWorkspaceRoot(), getRepoRoot());
});
it('does NOT resolve to a directory above repoRoot', () => {
process.env.EVOLVER_REPO_ROOT = tmpDir;
const { getWorkspaceRoot } = freshRequire('../src/gep/paths');
const wsRoot = getWorkspaceRoot();
assert.ok(
wsRoot.startsWith(tmpDir),
'workspaceRoot should be at or below repoRoot, got: ' + wsRoot
);
});
it('returns workspace/ subdirectory when it exists inside repoRoot', () => {
process.env.EVOLVER_REPO_ROOT = tmpDir;
const wsDir = path.join(tmpDir, 'workspace');
fs.mkdirSync(wsDir);
const { getWorkspaceRoot } = freshRequire('../src/gep/paths');
assert.equal(getWorkspaceRoot(), wsDir);
});
it('OPENCLAW_WORKSPACE takes precedence over workspace/ dir', () => {
process.env.EVOLVER_REPO_ROOT = tmpDir;
process.env.OPENCLAW_WORKSPACE = '/override/path';
fs.mkdirSync(path.join(tmpDir, 'workspace'));
const { getWorkspaceRoot } = freshRequire('../src/gep/paths');
assert.equal(getWorkspaceRoot(), '/override/path');
});
it('derived paths (memoryDir, logsDir, skillsDir) resolve under workspaceRoot', () => {
process.env.EVOLVER_REPO_ROOT = tmpDir;
const { getWorkspaceRoot, getMemoryDir, getLogsDir, getSkillsDir } = freshRequire('../src/gep/paths');
const ws = getWorkspaceRoot();
assert.ok(getMemoryDir().startsWith(ws), 'memoryDir should be under workspaceRoot');
assert.ok(getLogsDir().startsWith(ws), 'logsDir should be under workspaceRoot');
assert.ok(getSkillsDir().startsWith(ws), 'skillsDir should be under workspaceRoot');
});
});