Skip to content

Commit

Permalink
test: improve test coverage for core components
Browse files Browse the repository at this point in the history
  • Loading branch information
yamadashy committed Nov 16, 2024
1 parent 44dab54 commit 2a52f62
Show file tree
Hide file tree
Showing 12 changed files with 786 additions and 82 deletions.
1 change: 1 addition & 0 deletions biome.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"$schema": "https://biomejs.dev/schemas/1.8.3/schema.json",
"files": {
"include": [
"./bin/**",
"./src/**",
"./tests/**",
"package.json",
Expand Down
8 changes: 4 additions & 4 deletions src/cli/actions/initAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ export const runInitAction = async (rootDir: string, isGlobal: boolean): Promise
}
};

export async function createConfigFile(rootDir: string, isGlobal: boolean): Promise<boolean> {
export const createConfigFile = async (rootDir: string, isGlobal: boolean): Promise<boolean> => {
const configPath = path.resolve(isGlobal ? getGlobalDirectory() : rootDir, 'repomix.config.json');

const isCreateConfig = await prompts.confirm({
Expand Down Expand Up @@ -121,9 +121,9 @@ export async function createConfigFile(rootDir: string, isGlobal: boolean): Prom
);

return true;
}
};

export async function createIgnoreFile(rootDir: string, isGlobal: boolean): Promise<boolean> {
export const createIgnoreFile = async (rootDir: string, isGlobal: boolean): Promise<boolean> => {
if (isGlobal) {
prompts.log.info(`Skipping ${pc.green('.repomixignore')} file creation for global configuration.`);
return false;
Expand Down Expand Up @@ -173,4 +173,4 @@ export async function createIgnoreFile(rootDir: string, isGlobal: boolean): Prom
);

return true;
}
};
10 changes: 5 additions & 5 deletions src/cli/actions/remoteAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,13 @@ export const formatGitUrl = (url: string): string => {
return url;
};

const createTempDirectory = async (): Promise<string> => {
export const createTempDirectory = async (): Promise<string> => {
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'repomix-'));
logger.trace(`Created temporary directory. (path: ${pc.dim(tempDir)})`);
return tempDir;
};

const cloneRepository = async (url: string, directory: string): Promise<void> => {
export const cloneRepository = async (url: string, directory: string): Promise<void> => {
logger.log(`Clone repository: ${url} to temporary directory. ${pc.dim(`path: ${directory}`)}`);
logger.log('');

Expand All @@ -69,12 +69,12 @@ const cloneRepository = async (url: string, directory: string): Promise<void> =>
}
};

const cleanupTempDirectory = async (directory: string): Promise<void> => {
export const cleanupTempDirectory = async (directory: string): Promise<void> => {
logger.trace(`Cleaning up temporary directory: ${directory}`);
await fs.rm(directory, { recursive: true, force: true });
};

const checkGitInstallation = async (): Promise<boolean> => {
export const checkGitInstallation = async (): Promise<boolean> => {
try {
const result = await execAsync('git --version');
return !result.stderr;
Expand All @@ -84,7 +84,7 @@ const checkGitInstallation = async (): Promise<boolean> => {
}
};

const copyOutputToCurrentDirectory = async (
export const copyOutputToCurrentDirectory = async (
sourceDir: string,
targetDir: string,
outputFileName: string,
Expand Down
6 changes: 3 additions & 3 deletions src/cli/cliRun.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export interface CliOptions extends OptionValues {
remote?: string;
}

export async function run() {
export const run = async () => {
try {
program
.description('Repomix - Pack your repository into a single AI-friendly file')
Expand All @@ -50,9 +50,9 @@ export async function run() {
} catch (error) {
handleError(error);
}
}
};

const executeAction = async (directory: string, cwd: string, options: CliOptions) => {
export const executeAction = async (directory: string, cwd: string, options: CliOptions) => {
logger.setVerbose(options.verbose || false);

if (options.version) {
Expand Down
8 changes: 4 additions & 4 deletions src/core/file/permissionCheck.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export class PermissionError extends Error {
}
}

export async function checkDirectoryPermissions(dirPath: string): Promise<PermissionCheckResult> {
export const checkDirectoryPermissions = async (dirPath: string): Promise<PermissionCheckResult> => {
try {
// First try to read directory contents
await fs.readdir(dirPath);
Expand Down Expand Up @@ -87,9 +87,9 @@ export async function checkDirectoryPermissions(dirPath: string): Promise<Permis
error: error instanceof Error ? error : new Error(String(error)),
};
}
}
};

function getMacOSPermissionMessage(dirPath: string, errorCode?: string): string {
const getMacOSPermissionMessage = (dirPath: string, errorCode?: string): string => {
if (platform() === 'darwin') {
return `Permission denied: Cannot access '${dirPath}', error code: ${errorCode}.
Expand All @@ -109,4 +109,4 @@ If your terminal app is not listed:
}

return `Permission denied: Cannot access '${dirPath}'`;
}
};
85 changes: 65 additions & 20 deletions tests/cli/actions/remoteAction.test.ts
Original file line number Diff line number Diff line change
@@ -1,43 +1,88 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { formatGitUrl } from '../../../src/cli/actions/remoteAction.js';
import * as fs from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import { beforeEach, describe, expect, test, vi } from 'vitest';
import {
cleanupTempDirectory,
copyOutputToCurrentDirectory,
createTempDirectory,
formatGitUrl,
} from '../../../src/cli/actions/remoteAction.js';

vi.mock('node:fs/promises');
vi.mock('node:child_process');
vi.mock('../../../src/cli/actions/defaultAction.js');
vi.mock('../../../src/shared/logger.js');
vi.mock('node:fs/promises');
vi.mock('node:os');
vi.mock('../../../src/shared/logger');

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

afterEach(() => {
vi.restoreAllMocks();
});

describe('formatGitUrl', () => {
it('should format GitHub shorthand correctly', () => {
test('should convert GitHub shorthand to full URL', () => {
expect(formatGitUrl('user/repo')).toBe('https://github.com/user/repo.git');
expect(formatGitUrl('user-name/repo-name')).toBe('https://github.com/user-name/repo-name.git');
expect(formatGitUrl('user_name/repo_name')).toBe('https://github.com/user_name/repo_name.git');
});

it('should add .git to HTTPS URLs if missing', () => {
test('should handle HTTPS URLs', () => {
expect(formatGitUrl('https://github.com/user/repo')).toBe('https://github.com/user/repo.git');
expect(formatGitUrl('https://github.com/user/repo.git')).toBe('https://github.com/user/repo.git');
});

it('should not modify URLs that are already correctly formatted', () => {
expect(formatGitUrl('https://github.com/user/repo.git')).toBe('https://github.com/user/repo.git');
expect(formatGitUrl('[email protected]:user/repo.git')).toBe('[email protected]:user/repo.git');
test('should not modify SSH URLs', () => {
const sshUrl = 'git@github.com:user/repo.git';
expect(formatGitUrl(sshUrl)).toBe(sshUrl);
});
});

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);

it('should not modify SSH URLs', () => {
expect(formatGitUrl('[email protected]:user/repo.git')).toBe('[email protected]:user/repo.git');
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';
const targetDir = '/target/dir';
const fileName = 'output.txt';

vi.mocked(fs.copyFile).mockResolvedValue();

await copyOutputToCurrentDirectory(sourceDir, targetDir, fileName);

expect(fs.copyFile).toHaveBeenCalledWith(path.join(sourceDir, fileName), path.join(targetDir, fileName));
});

test('should throw error when copy fails', async () => {
const sourceDir = '/source/dir';
const targetDir = '/target/dir';
const fileName = 'output.txt';

vi.mocked(fs.copyFile).mockRejectedValue(new Error('Permission denied'));

it('should not modify URLs from other Git hosting services', () => {
expect(formatGitUrl('https://gitlab.com/user/repo.git')).toBe('https://gitlab.com/user/repo.git');
expect(formatGitUrl('https://bitbucket.org/user/repo.git')).toBe('https://bitbucket.org/user/repo.git');
await expect(copyOutputToCurrentDirectory(sourceDir, targetDir, fileName)).rejects.toThrow(
'Failed to copy output file',
);
});
});
});
118 changes: 118 additions & 0 deletions tests/cli/cliRun.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
import { beforeEach, describe, expect, test, vi } from 'vitest';
import * as defaultAction from '../../src/cli/actions/defaultAction.js';
import * as initAction from '../../src/cli/actions/initAction.js';
import * as remoteAction from '../../src/cli/actions/remoteAction.js';
import * as versionAction from '../../src/cli/actions/versionAction.js';
import { executeAction, run } from '../../src/cli/cliRun.js';
import type { RepomixConfigMerged } from '../../src/config/configSchema.js';
import type { PackResult } from '../../src/core/packager.js';
import { logger } from '../../src/shared/logger.js';

vi.mock('../../src/shared/logger', () => ({
logger: {
log: vi.fn(),
trace: vi.fn(),
debug: vi.fn(),
info: vi.fn(),
warn: vi.fn(),
error: vi.fn(),
success: vi.fn(),
note: vi.fn(),
setVerbose: vi.fn(),
},
}));

vi.mock('commander', () => ({
program: {
description: vi.fn().mockReturnThis(),
arguments: vi.fn().mockReturnThis(),
option: vi.fn().mockReturnThis(),
action: vi.fn().mockReturnThis(),
parseAsync: vi.fn().mockResolvedValue(undefined),
},
}));

vi.mock('../../src/cli/actions/defaultAction');
vi.mock('../../src/cli/actions/initAction');
vi.mock('../../src/cli/actions/remoteAction');
vi.mock('../../src/cli/actions/versionAction');

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

vi.mocked(defaultAction.runDefaultAction).mockResolvedValue({
config: {
cwd: process.cwd(),
output: {
filePath: 'repomix-output.txt',
style: 'plain',
topFilesLength: 5,
showLineNumbers: false,
removeComments: false,
removeEmptyLines: false,
copyToClipboard: false,
},
include: [],
ignore: {
useGitignore: true,
useDefaultPatterns: true,
customPatterns: [],
},
security: {
enableSecurityCheck: true,
},
} satisfies RepomixConfigMerged,
packResult: {
totalFiles: 0,
totalCharacters: 0,
totalTokens: 0,
fileCharCounts: {},
fileTokenCounts: {},
suspiciousFilesResults: [],
} satisfies PackResult,
});
vi.mocked(initAction.runInitAction).mockResolvedValue();
vi.mocked(remoteAction.runRemoteAction).mockResolvedValue();
vi.mocked(versionAction.runVersionAction).mockResolvedValue();
});

test('should run without arguments', async () => {
await expect(run()).resolves.not.toThrow();
});

describe('executeAction', () => {
test('should execute default action when no special options provided', async () => {
await executeAction('.', process.cwd(), {});

expect(defaultAction.runDefaultAction).toHaveBeenCalledWith('.', process.cwd(), expect.any(Object));
});

test('should enable verbose logging when verbose option is true', async () => {
await executeAction('.', process.cwd(), { verbose: true });

expect(logger.setVerbose).toHaveBeenCalledWith(true);
});

test('should execute version action when version option is true', async () => {
await executeAction('.', process.cwd(), { version: true });

expect(versionAction.runVersionAction).toHaveBeenCalled();
expect(defaultAction.runDefaultAction).not.toHaveBeenCalled();
});

test('should execute init action when init option is true', async () => {
await executeAction('.', process.cwd(), { init: true });

expect(initAction.runInitAction).toHaveBeenCalledWith(process.cwd(), false);
expect(defaultAction.runDefaultAction).not.toHaveBeenCalled();
});

test('should execute remote action when remote option is provided', async () => {
await executeAction('.', process.cwd(), { remote: 'yamadashy/repomix' });

expect(remoteAction.runRemoteAction).toHaveBeenCalledWith('yamadashy/repomix', expect.any(Object));
expect(defaultAction.runDefaultAction).not.toHaveBeenCalled();
});
});
});
File renamed without changes.
Loading

0 comments on commit 2a52f62

Please sign in to comment.