mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-11 03:38:07 +00:00
Resolves PR #1693 review B2 (env-var leak in long-running hosts): - --workers is now threaded through AnalyzeOptions -> runFullAnalysis -> PipelineOptions.workerPoolSize -> createWorkerPool's explicit poolSize arg, bypassing the GITNEXUS_WORKER_POOL_SIZE env channel. The env var remains as a back-compat fallback inside resolveAutoPoolSize for operators who set it directly. - analyzeCommand and wikiCommand snapshot the GITNEXUS_* env vars they mutate at function entry and restore them in finally. Inner *Impl extraction keeps the diff surgical (no body re-indent). process.exit(0) on the CLI success path still terminates the process; restoration matters for programmatic callers (tests, long-running hosts) reaching early-return paths or the alreadyUpToDate fast path. - Tests updated to assert the new behavior: analyze-worker-pool-size.test.ts: workerPoolSize flows through runFullAnalysis options; env is not mutated; back-to-back calls see their own values, not the previous call's leak. analyze-worker-timeout.test.ts: env IS set during the runFullAnalysis call (captured via mockImplementation) and restored after, proving the timeout reaches downstream while the leak fix holds. - Also addresses L4: afterEach NODE_OPTIONS restore so back-to-back test runs don't accumulate --max-old-space-size=8192 tokens. Addresses PR #1693 review B2 (blocker) and L4 (test polish).
97 lines
3.7 KiB
TypeScript
97 lines
3.7 KiB
TypeScript
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
const runFullAnalysisMock = vi.fn();
|
|
|
|
vi.mock('../../src/core/run-analyze.js', () => ({
|
|
runFullAnalysis: runFullAnalysisMock,
|
|
}));
|
|
|
|
vi.mock('../../src/core/lbug/lbug-adapter.js', () => ({
|
|
closeLbug: vi.fn(async () => undefined),
|
|
}));
|
|
|
|
vi.mock('../../src/storage/repo-manager.js', () => ({
|
|
getStoragePaths: vi.fn(() => ({ storagePath: '.gitnexus', lbugPath: '.gitnexus/lbug' })),
|
|
getGlobalRegistryPath: vi.fn(() => 'registry.json'),
|
|
RegistryNameCollisionError: class RegistryNameCollisionError extends Error {},
|
|
AnalysisNotFinalizedError: class AnalysisNotFinalizedError extends Error {},
|
|
assertAnalysisFinalized: vi.fn(async () => undefined),
|
|
}));
|
|
|
|
vi.mock('../../src/storage/git.js', () => ({
|
|
getGitRoot: vi.fn(() => '/repo'),
|
|
hasGitDir: vi.fn(() => true),
|
|
}));
|
|
|
|
vi.mock('../../src/core/ingestion/utils/max-file-size.js', () => ({
|
|
getMaxFileSizeBannerMessage: vi.fn(() => null),
|
|
}));
|
|
|
|
describe('analyzeCommand worker timeout validation', () => {
|
|
// analyzeCommand now snapshot/restores GITNEXUS_* env vars, so the value
|
|
// observed *after* the call is the pre-call baseline — not what the CLI
|
|
// wrote. Tests that need to verify "the env was set for the downstream
|
|
// call" must capture it inside the runFullAnalysisMock implementation.
|
|
const ORIGINAL_TIMEOUT = process.env.GITNEXUS_WORKER_SUB_BATCH_TIMEOUT_MS;
|
|
const ORIGINAL_NODE_OPTIONS = process.env.NODE_OPTIONS;
|
|
|
|
beforeEach(() => {
|
|
vi.resetModules();
|
|
runFullAnalysisMock.mockReset();
|
|
process.exitCode = undefined;
|
|
process.env.NODE_OPTIONS = `${process.env.NODE_OPTIONS ?? ''} --max-old-space-size=8192`.trim();
|
|
});
|
|
|
|
afterEach(() => {
|
|
if (ORIGINAL_NODE_OPTIONS === undefined) {
|
|
delete process.env.NODE_OPTIONS;
|
|
} else {
|
|
process.env.NODE_OPTIONS = ORIGINAL_NODE_OPTIONS;
|
|
}
|
|
});
|
|
|
|
it.each(['0', 'abc', '-5', 'Infinity'])(
|
|
'rejects invalid --worker-timeout value %s before analysis starts',
|
|
async (workerTimeout) => {
|
|
// Import _captureLogger from the SAME module instance analyze.js will
|
|
// see — vi.resetModules() in beforeEach invalidates the singleton.
|
|
const { _captureLogger } = await import('../../src/core/logger.js');
|
|
const cap = _captureLogger();
|
|
const { analyzeCommand } = await import('../../src/cli/analyze.js');
|
|
|
|
await analyzeCommand(undefined, { workerTimeout });
|
|
|
|
expect(process.exitCode).toBe(1);
|
|
expect(
|
|
cap.records().some((r) => r.msg === ' --worker-timeout must be at least 1 second.\n'),
|
|
).toBe(true);
|
|
expect(runFullAnalysisMock).not.toHaveBeenCalled();
|
|
cap.restore();
|
|
},
|
|
);
|
|
|
|
it('sets the worker timeout env var during the runFullAnalysis call and restores it after', async () => {
|
|
const { analyzeCommand } = await import('../../src/cli/analyze.js');
|
|
let envAtCallTime: string | undefined;
|
|
runFullAnalysisMock.mockImplementation(async () => {
|
|
envAtCallTime = process.env.GITNEXUS_WORKER_SUB_BATCH_TIMEOUT_MS;
|
|
return {
|
|
repoName: 'repo',
|
|
repoPath: '/repo',
|
|
stats: {},
|
|
alreadyUpToDate: true,
|
|
};
|
|
});
|
|
|
|
await analyzeCommand(undefined, { workerTimeout: '2' });
|
|
|
|
// Downstream sees the parsed milliseconds value during the call.
|
|
expect(envAtCallTime).toBe('2000');
|
|
expect(runFullAnalysisMock).toHaveBeenCalled();
|
|
// After the call, the snapshot/restore wrapper has reset the env so a
|
|
// subsequent analyzeCommand invocation in the same host (or test
|
|
// process) doesn't inherit the previous call's worker timeout. This
|
|
// is the env-leak fix from PR #1693 review (B2).
|
|
expect(process.env.GITNEXUS_WORKER_SUB_BATCH_TIMEOUT_MS).toBe(ORIGINAL_TIMEOUT);
|
|
});
|
|
});
|