Skip to content

Commit

Permalink
test(core): improve test coverage
Browse files Browse the repository at this point in the history
  • Loading branch information
yamadashy committed Nov 21, 2024
1 parent e34a8da commit 93f1601
Show file tree
Hide file tree
Showing 5 changed files with 238 additions and 62 deletions.
2 changes: 2 additions & 0 deletions bin/repomix.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,7 @@ function setupErrorHandlers() {
} else {
console.error('Fatal Error:', error);
}

process.exit(EXIT_CODES.ERROR);
}
})();
21 changes: 14 additions & 7 deletions src/cli/actions/remoteAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,28 @@ export const runRemoteAction = async (repoUrl: string, options: CliOptions): Pro
throw new RepomixError('Git is not installed or not in the system PATH.');
}

const formattedUrl = formatGitUrl(repoUrl);
const tempDir = await createTempDirectory();
const spinner = new Spinner('Cloning repository...');

const tempDirPath = await createTempDirectory();

try {
spinner.start();
await cloneRepository(formattedUrl, tempDir);

// Clone the repository
await cloneRepository(formatGitUrl(repoUrl), tempDirPath);

spinner.succeed('Repository cloned successfully!');
logger.log('');

const result = await runDefaultAction(tempDir, tempDir, options);
await copyOutputToCurrentDirectory(tempDir, process.cwd(), result.config.output.filePath);
// Run the default action on the cloned repository
const result = await runDefaultAction(tempDirPath, tempDirPath, options);
await copyOutputToCurrentDirectory(tempDirPath, process.cwd(), result.config.output.filePath);
} catch (error) {
spinner.fail('Error during repository cloning. cleanup...');
throw error;
} finally {
// Clean up the temporary directory
await cleanupTempDirectory(tempDir);
// Cleanup the temporary directory
await cleanupTempDirectory(tempDirPath);
}
};

Expand Down
52 changes: 25 additions & 27 deletions tests/cli/actions/remoteAction.test.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,44 @@
import * as fs from 'node:fs/promises';
import os from 'node:os';
import * as os from 'node:os';
import path from 'node:path';
import { beforeEach, describe, expect, test, vi } from 'vitest';
import {
checkGitInstallation,
cleanupTempDirectory,
copyOutputToCurrentDirectory,
createTempDirectory,
formatGitUrl,
runRemoteAction,
} from '../../../src/cli/actions/remoteAction.js';

vi.mock('node:child_process');
vi.mock('node:fs/promises');
vi.mock('node:os');
vi.mock('node:fs/promises', async (importOriginal) => {
const actual = await importOriginal<typeof import('node:fs/promises')>();
return {
...actual,
copyFile: vi.fn(),
};
});
vi.mock('../../../src/shared/logger');

describe('remoteAction functions', () => {
beforeEach(() => {
vi.resetAllMocks();
});

describe('runRemoteAction', () => {
test('should clone the repository', async () => {
vi.mocked(fs.copyFile).mockResolvedValue(undefined);
await runRemoteAction('yamadashy/repomix', {});
});
});

describe('checkGitInstallation Integration', () => {
test('should detect git installation in real environment', async () => {
const result = await checkGitInstallation();
expect(result).toBe(true);
});
});

describe('formatGitUrl', () => {
test('should convert GitHub shorthand to full URL', () => {
expect(formatGitUrl('user/repo')).toBe('https://github.com/user/repo.git');
Expand All @@ -37,29 +57,6 @@ describe('remoteAction functions', () => {
});
});

describe('createTempDirectory', () => {
test('should create temporary directory', async () => {
const mockTempDir = '/mock/temp/dir';
vi.mocked(os.tmpdir).mockReturnValue('/mock/temp');
vi.mocked(fs.mkdtemp).mockResolvedValue(mockTempDir);

const result = await createTempDirectory();
expect(result).toBe(mockTempDir);
expect(fs.mkdtemp).toHaveBeenCalledWith(path.join('/mock/temp', 'repomix-'));
});
});

describe('cleanupTempDirectory', () => {
test('should cleanup directory', async () => {
const mockDir = '/mock/temp/dir';
vi.mocked(fs.rm).mockResolvedValue();

await cleanupTempDirectory(mockDir);

expect(fs.rm).toHaveBeenCalledWith(mockDir, { recursive: true, force: true });
});
});

describe('copyOutputToCurrentDirectory', () => {
test('should copy output file', async () => {
const sourceDir = '/source/dir';
Expand All @@ -85,4 +82,5 @@ describe('remoteAction functions', () => {
);
});
});

});
131 changes: 131 additions & 0 deletions tests/cli/cliPrint.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import path from 'node:path';
import { beforeEach, describe, expect, test, vi } from 'vitest';
import { printCompletion, printSecurityCheck, printSummary, printTopFiles } from '../../src/cli/cliPrint.js';
import type { SuspiciousFileResult } from '../../src/core/security/securityCheck.js';
import { logger } from '../../src/shared/logger.js';
import { createMockConfig } from '../testing/testUtils.js';

vi.mock('../../src/shared/logger');
vi.mock('picocolors', () => ({
default: {
white: (str: string) => `WHITE:${str}`,
dim: (str: string) => `DIM:${str}`,
green: (str: string) => `GREEN:${str}`,
yellow: (str: string) => `YELLOW:${str}`,
red: (str: string) => `RED:${str}`,
cyan: (str: string) => `CYAN:${str}`,
},
}));

describe('cliPrint', () => {
beforeEach(() => {
vi.resetAllMocks();
});

describe('printSummary', () => {
test('should print summary with suspicious files and security check enabled', () => {
const config = createMockConfig({
security: { enableSecurityCheck: true },
});
const suspiciousFiles: SuspiciousFileResult[] = [
{ filePath: 'suspicious.txt', messages: ['Contains sensitive data'] },
];

printSummary(10, 1000, 200, 'output.txt', suspiciousFiles, config);

expect(logger.log).toHaveBeenCalledWith(expect.stringContaining('1 suspicious file(s) detected and excluded'));
});

test('should print summary with security check disabled', () => {
const config = createMockConfig({
security: { enableSecurityCheck: false },
});

printSummary(10, 1000, 200, 'output.txt', [], config);

expect(logger.log).toHaveBeenCalledWith(expect.stringContaining('Security check disabled'));
});
});

describe('printSecurityCheck', () => {
test('should skip printing when security check is disabled', () => {
const config = createMockConfig({
security: { enableSecurityCheck: false },
});

printSecurityCheck('/root', [], config);
expect(logger.log).not.toHaveBeenCalled();
});

test('should print message when no suspicious files found', () => {
const config = createMockConfig({
security: { enableSecurityCheck: true },
});

printSecurityCheck('/root', [], config);

expect(logger.log).toHaveBeenCalledWith('WHITE:🔎 Security Check:');
expect(logger.log).toHaveBeenCalledWith('DIM:──────────────────');
expect(logger.log).toHaveBeenCalledWith('GREEN:✔ WHITE:No suspicious files detected.');
});

test('should print details of suspicious files when found', () => {
const config = createMockConfig({
security: { enableSecurityCheck: true },
});
const suspiciousFiles: SuspiciousFileResult[] = [
{
filePath: path.join('/root', 'config/secrets.txt'),
messages: ['Contains API key', 'Contains password'],
},
];

printSecurityCheck('/root', suspiciousFiles, config);

expect(logger.log).toHaveBeenCalledWith('YELLOW:1 suspicious file(s) detected and excluded from the output:');
expect(logger.log).toHaveBeenCalledWith('WHITE:1. WHITE:config/secrets.txt');

Check failure on line 86 in tests/cli/cliPrint.test.ts

View workflow job for this annotation

GitHub Actions / Test (windows-latest, 18.x)

tests/cli/cliPrint.test.ts > cliPrint > printSecurityCheck > should print details of suspicious files when found

AssertionError: expected "log" to be called with arguments: [ 'WHITE:1. WHITE:config/secrets.txt' ] Received: 1st log call: Array [ - "WHITE:1. WHITE:config/secrets.txt", + "WHITE:🔎 Security Check:", ] 2nd log call: Array [ - "WHITE:1. WHITE:config/secrets.txt", + "DIM:──────────────────", ] 3rd log call: Array [ - "WHITE:1. WHITE:config/secrets.txt", + "YELLOW:1 suspicious file(s) detected and excluded from the output:", ] 4th log call: Array [ - "WHITE:1. WHITE:config/secrets.txt", + "WHITE:1. WHITE:config\\secrets.txt", ] 5th log call: Array [ - "WHITE:1. WHITE:config/secrets.txt", + "DIM: - Contains API key + - Contains password", ] 6th log call: Array [ - "WHITE:1. WHITE:config/secrets.txt", + "YELLOW: + These files have been excluded from the output for security reasons.", ] 7th log call: Array [ - "WHITE:1. WHITE:config/secrets.txt", + "YELLOW:Please review these files for potential sensitive information.", ] Number of calls: 7 ❯ tests/cli/cliPrint.test.ts:86:26
expect(logger.log).toHaveBeenCalledWith(expect.stringContaining('Contains API key'));
expect(logger.log).toHaveBeenCalledWith(expect.stringContaining('Contains password'));
expect(logger.log).toHaveBeenCalledWith(
expect.stringContaining('Please review these files for potential sensitive information.'),
);
});
});

describe('printTopFiles', () => {
test('should print top files sorted by character count', () => {
const fileCharCounts = {
'src/index.ts': 1000,
'src/utils.ts': 500,
'README.md': 2000,
};
const fileTokenCounts = {
'src/index.ts': 200,
'src/utils.ts': 100,
'README.md': 400,
};

printTopFiles(fileCharCounts, fileTokenCounts, 2);

expect(logger.log).toHaveBeenCalledWith(expect.stringContaining('Top 2 Files'));
expect(logger.log).toHaveBeenCalledWith(expect.stringContaining('README.md'));
expect(logger.log).toHaveBeenCalledWith(expect.stringContaining('src/index.ts'));
expect(logger.log).not.toHaveBeenCalledWith(expect.stringContaining('src/utils.ts'));
});

test('should handle empty file list', () => {
printTopFiles({}, {}, 5);

expect(logger.log).toHaveBeenCalledWith(expect.stringContaining('Top 5 Files'));
});
});

describe('printCompletion', () => {
test('should print completion message', () => {
printCompletion();

expect(logger.log).toHaveBeenCalledWith('GREEN:🎉 All Done!');
expect(logger.log).toHaveBeenCalledWith('WHITE:Your repository has been successfully packed.');
});
});
});
94 changes: 66 additions & 28 deletions tests/config/globalDirectory.test.ts
Original file line number Diff line number Diff line change
@@ -1,49 +1,87 @@
import os from 'node:os';
import path from 'node:path';
import { beforeEach, describe, expect, test, vi } from 'vitest';
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
import { getGlobalDirectory } from '../../src/config/globalDirectory.js';
import { isLinux, isMac, isWindows } from '../testing/testUtils.js';

vi.mock('node:os');

describe('globalDirectory', () => {
describe('getGlobalDirectory', () => {
const originalPlatform = process.platform;
const originalEnv = process.env;

beforeEach(() => {
vi.resetAllMocks();
process.env = {};
process.env = { ...originalEnv };
});

test.runIf(isWindows)('should return correct path for Windows', () => {
vi.mocked(os.platform).mockReturnValue('win32');
vi.mocked(os.homedir).mockReturnValue('C:\\Users\\TestUser');
process.env.LOCALAPPDATA = 'C:\\Users\\TestUser\\AppData\\Local';

const result = getGlobalDirectory();
expect(result).toBe(path.join('C:\\Users\\TestUser\\AppData\\Local', 'Repomix'));
afterEach(() => {
Object.defineProperty(process, 'platform', { value: originalPlatform });
process.env = originalEnv;
});

test.runIf(isWindows)('should use homedir if LOCALAPPDATA is not set on Windows', () => {
vi.mocked(os.platform).mockReturnValue('win32');
vi.mocked(os.homedir).mockReturnValue('C:\\Users\\TestUser');
process.env.LOCALAPPDATA = undefined;
describe('Windows platform', () => {
test('should use LOCALAPPDATA when available', () => {
Object.defineProperty(process, 'platform', { value: 'win32' });
process.env.LOCALAPPDATA = 'C:\\Users\\Test\\AppData\\Local';

const result = getGlobalDirectory();
expect(result).toBe(path.join('C:\\Users\\Test\\AppData\\Local', 'Repomix'));
});

const result = getGlobalDirectory();
expect(result).toBe(path.join('C:\\Users\\TestUser', 'AppData', 'Local', 'Repomix'));
test('should fall back to homedir when LOCALAPPDATA is not available', () => {
Object.defineProperty(process, 'platform', { value: 'win32' });
process.env.LOCALAPPDATA = undefined;
vi.mocked(os.homedir).mockReturnValue('C:\\Users\\Test');

const result = getGlobalDirectory();
expect(result).toBe(path.join('C:\\Users\\Test', 'AppData', 'Local', 'Repomix'));
});
});

test.runIf(isLinux)('should use XDG_CONFIG_HOME on Unix systems if set', () => {
vi.mocked(os.platform).mockReturnValue('linux');
process.env.XDG_CONFIG_HOME = '/custom/config';
describe('Unix platforms', () => {
test('should use XDG_CONFIG_HOME when available', () => {
Object.defineProperty(process, 'platform', { value: 'linux' });
process.env.XDG_CONFIG_HOME = '/custom/config';

const result = getGlobalDirectory();
expect(result).toBe(path.join('/custom/config', 'repomix'));
});

const result = getGlobalDirectory();
expect(result).toBe(path.join('/custom/config', 'repomix'));
test('should fall back to ~/.config on Linux', () => {
Object.defineProperty(process, 'platform', { value: 'linux' });
process.env.XDG_CONFIG_HOME = undefined;
vi.mocked(os.homedir).mockReturnValue('/home/test');

const result = getGlobalDirectory();
expect(result).toBe(path.join('/home/test', '.config', 'repomix'));
});

test('should fall back to ~/.config on macOS', () => {
Object.defineProperty(process, 'platform', { value: 'darwin' });
process.env.XDG_CONFIG_HOME = undefined;
vi.mocked(os.homedir).mockReturnValue('/Users/test');

const result = getGlobalDirectory();
expect(result).toBe(path.join('/Users/test', '.config', 'repomix'));
});
});

test.runIf(isMac)('should use ~/.config on Unix systems if XDG_CONFIG_HOME is not set', () => {
vi.mocked(os.platform).mockReturnValue('darwin');
vi.mocked(os.homedir).mockReturnValue('/Users/TestUser');
process.env.XDG_CONFIG_HOME = undefined;
describe('Edge cases', () => {
test('should handle empty homedir', () => {
Object.defineProperty(process, 'platform', { value: 'linux' });
process.env.XDG_CONFIG_HOME = undefined;
vi.mocked(os.homedir).mockReturnValue('');

const result = getGlobalDirectory();
expect(result).toBe(path.join('', '.config', 'repomix'));
});

test('should handle unusual XDG_CONFIG_HOME paths', () => {
Object.defineProperty(process, 'platform', { value: 'linux' });
process.env.XDG_CONFIG_HOME = '////multiple///slashes///';

const result = getGlobalDirectory();
expect(result).toBe(path.join('/Users/TestUser', '.config', 'repomix'));
const result = getGlobalDirectory();
expect(result).toBe(path.join('////multiple///slashes///', 'repomix'));
});
});
});

0 comments on commit 93f1601

Please sign in to comment.