sourcesailor
Version:
A CLI tool for analyzing and documenting codebases
259 lines (258 loc) • 12.6 kB
JavaScript
/* eslint-disable @typescript-eslint/no-explicit-any */
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
import { handler } from '../commands/prepareReport.mjs';
import * as utils from '../utils.mjs';
import ModelUtils from '../modelUtils.mjs';
import ora from 'ora';
import * as inquirer from '@inquirer/prompts';
import * as setExpertise from '../commands/setExpertise.mjs';
vi.mock('../utils.mjs');
vi.mock('../modelUtils.mjs', () => {
return {
default: {
getInstance: vi.fn().mockReturnValue({
initializeModels: vi.fn().mockResolvedValue(undefined),
getLlmInterface: vi.fn().mockReturnValue({
getName: vi.fn().mockReturnValue('Mocked model'),
generateReadme: vi.fn().mockResolvedValue('Generated README content'),
}),
}),
},
};
});
vi.mock('ora');
vi.mock('@inquirer/prompts');
vi.mock('../commands/setExpertise.mjs');
describe('prepareReport command', () => {
beforeEach(() => {
vi.resetAllMocks();
vi.mocked(inquirer.confirm).mockResolvedValue(false);
vi.mocked(setExpertise.handler).mockResolvedValue(undefined);
// Add mock for process.cwd
const mockCwd = vi.spyOn(process, 'cwd');
mockCwd.mockReturnValue('/test/current-project');
});
afterEach(() => {
vi.resetAllMocks();
vi.restoreAllMocks(); // Add this to cleanup process.cwd mock
});
it('should fail if no analysis is found', async () => {
vi.mocked(utils.readConfig).mockReturnValue({ ANALYSIS_DIR: 'test-dir', DEFAULT_OPENAI_MODEL: 'test-model' });
vi.mocked(utils.getAnalysis).mockReturnValue({});
const mockSpinner = {
start: vi.fn().mockReturnThis(),
fail: vi.fn(),
};
vi.mocked(ora).mockReturnValue(mockSpinner);
const mockModelUtils = {
initializeModels: vi.fn().mockResolvedValue(undefined),
getLlmInterface: vi.fn().mockReturnValue({
getName: vi.fn().mockReturnValue('Mocked model'),
generateReadme: vi.fn().mockResolvedValue(null),
}),
};
vi.mocked(ModelUtils.getInstance).mockReturnValue(mockModelUtils);
await handler({ path: 'test-project', verbose: false, streaming: false });
expect(mockSpinner.fail).toHaveBeenCalledWith('No analysis found, Please run analyse command first');
});
it('should generate a report successfully', async () => {
const mockAnalysis = {
directoryStructure: 'mock directory structure',
dependencyInference: 'mock dependency inference',
codeInferrence: 'mock code inference',
};
const mockReport = 'Generated README content';
vi.mocked(utils.readConfig).mockReturnValue({ ANALYSIS_DIR: 'test-dir' });
vi.mocked(utils.getAnalysis).mockReturnValue(mockAnalysis);
const mockModelUtils = {
initializeModels: vi.fn().mockResolvedValue(undefined),
getLlmInterface: vi.fn().mockReturnValue({
getName: vi.fn().mockReturnValue('Mocked model'),
generateReadme: vi.fn().mockResolvedValue(mockReport),
}),
};
vi.mocked(ModelUtils.getInstance).mockReturnValue(mockModelUtils);
const mockSpinner = {
start: vi.fn().mockReturnThis(),
stopAndPersist: vi.fn(),
};
vi.mocked(ora).mockReturnValue(mockSpinner);
await handler({ path: 'test-project', verbose: false, streaming: false });
expect(mockModelUtils.getLlmInterface().generateReadme).toHaveBeenCalledWith('mock directory structure', 'mock dependency inference', 'mock code inference', false, false, undefined, undefined);
expect(mockSpinner.stopAndPersist).toHaveBeenCalledWith({ symbol: '✔️', text: mockReport });
});
it('should handle streaming output', async () => {
const mockAnalysis = {
directoryStructure: 'mock directory structure',
dependencyInference: 'mock dependency inference',
codeInferrence: 'mock code inference',
};
const mockStreamChunks = [
'Chunk 1',
'Chunk 2',
];
vi.mocked(utils.readConfig).mockReturnValue({ ANALYSIS_DIR: 'test-dir' });
vi.mocked(utils.getAnalysis).mockReturnValue(mockAnalysis);
const mockModelUtils = {
initializeModels: vi.fn().mockResolvedValue(undefined),
getLlmInterface: vi.fn().mockReturnValue({
getName: vi.fn().mockReturnValue('Mocked model'),
generateReadme: vi.fn().mockResolvedValue(mockStreamChunks),
}),
};
vi.mocked(ModelUtils.getInstance).mockReturnValue(mockModelUtils);
const mockSpinner = {
start: vi.fn().mockReturnThis(),
stop: vi.fn().mockReturnThis(),
clear: vi.fn(),
};
vi.mocked(ora).mockReturnValue(mockSpinner);
const mockStdout = {
write: vi.fn(),
};
vi.spyOn(process.stdout, 'write').mockImplementation(mockStdout.write);
await handler({ path: 'test-project', verbose: false, streaming: true });
expect(mockModelUtils.getLlmInterface().generateReadme).toHaveBeenCalledWith('mock directory structure', 'mock dependency inference', 'mock code inference', true, false, undefined, undefined);
expect(mockSpinner.stop).toHaveBeenCalled();
expect(mockSpinner.clear).toHaveBeenCalled();
expect(mockStdout.write).toHaveBeenCalledWith('Chunk 1');
expect(mockStdout.write).toHaveBeenCalledWith('Chunk 2');
expect(mockStdout.write).toHaveBeenCalledWith('\n');
});
it('should handle verbose output', async () => {
const mockAnalysis = {
directoryStructure: 'mock directory structure',
dependencyInference: 'mock dependency inference',
codeInferrence: 'mock code inference',
};
const mockReport = 'Generated README content';
vi.mocked(utils.readConfig).mockReturnValue({ ANALYSIS_DIR: 'test-dir' });
vi.mocked(utils.getAnalysis).mockReturnValue(mockAnalysis);
const mockModelUtils = {
initializeModels: vi.fn().mockResolvedValue(undefined),
getLlmInterface: vi.fn().mockReturnValue({
getName: vi.fn().mockReturnValue('Mocked model'),
generateReadme: vi.fn().mockResolvedValue(mockReport),
}),
};
vi.mocked(ModelUtils.getInstance).mockReturnValue(mockModelUtils);
const mockSpinner = {
start: vi.fn().mockReturnThis(),
stopAndPersist: vi.fn(),
};
vi.mocked(ora).mockReturnValue(mockSpinner);
const consoleSpy = vi.spyOn(console, 'log');
await handler({ path: 'test-project', verbose: true, streaming: false });
expect(consoleSpy).toHaveBeenCalledWith(expect.objectContaining({
dirPath: expect.any(String),
isProjectRoot: expect.any(Boolean),
}));
expect(consoleSpy).toHaveBeenCalledWith({
analysis: ['directoryStructure', 'dependencyInference', 'codeInferrence'],
});
expect(consoleSpy).toHaveBeenCalledWith({
directoryStructure: 'mock directory structure',
dependencyInference: 'mock dependency inference',
codeInference: 'mock code inference',
});
});
it('should prompt for user expertise if not set', async () => {
const mockAnalysis = {
directoryStructure: 'mock directory structure',
dependencyInference: 'mock dependency inference',
codeInferrence: 'mock code inference',
};
const mockReport = 'Generated README content';
vi.mocked(utils.readConfig).mockReturnValue({ ANALYSIS_DIR: 'test-dir' });
vi.mocked(utils.getAnalysis).mockReturnValue(mockAnalysis);
const mockModelUtils = {
initializeModels: vi.fn().mockResolvedValue(undefined),
getLlmInterface: vi.fn().mockReturnValue({
getName: vi.fn().mockReturnValue('Mocked model'),
generateReadme: vi.fn().mockResolvedValue(mockReport),
}),
};
vi.mocked(ModelUtils.getInstance).mockReturnValue(mockModelUtils);
vi.mocked(ora).mockReturnValue({
start: vi.fn().mockReturnThis(),
stopAndPersist: vi.fn(),
});
vi.mocked(inquirer.confirm).mockResolvedValue(true);
await handler({ path: 'test-project', verbose: false, streaming: false });
expect(inquirer.confirm).toHaveBeenCalledWith({
message: "Would you like to set your expertise now?",
default: true
});
expect(setExpertise.handler).toHaveBeenCalled();
});
it('should not prompt for user expertise if already set', async () => {
const mockAnalysis = {
directoryStructure: 'mock directory structure',
dependencyInference: 'mock dependency inference',
codeInferrence: 'mock code inference',
};
const mockReport = 'Generated README content';
vi.mocked(utils.readConfig).mockReturnValue({ ANALYSIS_DIR: 'test-dir', userExpertise: 'intermediate' });
vi.mocked(utils.getAnalysis).mockReturnValue(mockAnalysis);
const mockModelUtils = {
initializeModels: vi.fn().mockResolvedValue(undefined),
getLlmInterface: vi.fn().mockReturnValue({
getName: vi.fn().mockReturnValue('Mocked model'),
generateReadme: vi.fn().mockResolvedValue(mockReport),
}),
};
vi.mocked(ModelUtils.getInstance).mockReturnValue(mockModelUtils);
vi.mocked(ora).mockReturnValue({
start: vi.fn().mockReturnThis(),
stopAndPersist: vi.fn(),
});
await handler({ path: 'test-project', verbose: false, streaming: false });
expect(inquirer.confirm).not.toHaveBeenCalled();
expect(setExpertise.handler).not.toHaveBeenCalled();
});
it('should handle current directory analysis correctly', async () => {
const mockAnalysis = {
directoryStructure: 'mock directory structure',
dependencyInference: 'mock dependency inference',
codeInferrence: 'mock code inference',
};
const mockReport = 'Generated README content';
// Mock config with a specific ANALYSIS_DIR
vi.mocked(utils.readConfig).mockReturnValue({
ANALYSIS_DIR: '/test/analysis',
DEFAULT_OPENAI_MODEL: 'test-model'
});
vi.mocked(utils.getAnalysis).mockReturnValue(mockAnalysis);
const mockModelUtils = {
initializeModels: vi.fn().mockResolvedValue(undefined),
getLlmInterface: vi.fn().mockReturnValue({
getName: vi.fn().mockReturnValue('Mocked model'),
generateReadme: vi.fn().mockResolvedValue(mockReport),
}),
};
vi.mocked(ModelUtils.getInstance).mockReturnValue(mockModelUtils);
const mockSpinner = {
start: vi.fn().mockReturnThis(),
stopAndPersist: vi.fn(),
};
vi.mocked(ora).mockReturnValue(mockSpinner);
const consoleSpy = vi.spyOn(console, 'log');
await handler({ path: '.', verbose: true });
// Verify the analysis path is constructed correctly
// When processing current directory, it should be rootDir/.SourceSailor/current-project
expect(utils.getAnalysis).toHaveBeenCalledWith('/test/analysis/.SourceSailor/current-project', false // isProjectRoot should be false for current directory
);
// Verify that if verbose is enabled, we log the correct path
expect(consoleSpy).toHaveBeenCalledWith({
dirPath: '/test/analysis/.SourceSailor/current-project',
isProjectRoot: false,
projectDir: '.'
});
// Verify the report generation
expect(mockModelUtils.getLlmInterface().generateReadme).toHaveBeenCalledWith('mock directory structure', 'mock dependency inference', 'mock code inference', false, true, undefined, "test-model");
// Verify writing the analysis uses the correct path
expect(utils.writeAnalysis).toHaveBeenCalledWith('/test/analysis/.SourceSailor/current-project', 'inferredReadme', mockReport, false // isProjectRoot should be false
);
consoleSpy.mockRestore();
});
});