import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import { PathResolver } from '../src/utils/path-resolver.js';
import { resolve, join } from 'path';
import { mkdir, writeFile, rm } from 'fs/promises';
describe('PathResolver', () => {
let pathResolver: PathResolver;
let testWorkspace: string;
beforeEach(() => {
testWorkspace = resolve(process.cwd(), 'tests', 'fixtures');
pathResolver = new PathResolver(testWorkspace);
});
describe('resolveWorkspacePath', () => {
it('should resolve relative paths to absolute paths', () => {
const result = pathResolver.resolveWorkspacePath('test.mdx');
expect(result).toBe(resolve(testWorkspace, 'test.mdx'));
});
it('should handle nested paths correctly', () => {
const result = pathResolver.resolveWorkspacePath('docs/guide.mdx');
expect(result).toBe(resolve(testWorkspace, 'docs', 'guide.mdx'));
});
it('should normalize mixed path separators', () => {
const result = pathResolver.resolveWorkspacePath('docs\\guide.mdx');
// On non-Windows, backslashes are treated as part of the filename
// On Windows, they are treated as path separators
// Just verify it returns an absolute path
expect(result).toContain(testWorkspace);
expect(result).toContain('guide.mdx');
});
it('should handle paths with .. correctly', () => {
const result = pathResolver.resolveWorkspacePath('./docs/../test.mdx');
expect(result).toBe(resolve(testWorkspace, 'test.mdx'));
});
});
describe('validateFilePath', () => {
it('should return true for existing files', () => {
const result = pathResolver.validateFilePath('basic-with-frontmatter.mdx');
expect(result).toBe(true);
});
it('should return false for non-existent files', () => {
const result = pathResolver.validateFilePath('nonexistent.mdx');
expect(result).toBe(false);
});
it('should work with nested paths', () => {
const result = pathResolver.validateFilePath('basic-with-frontmatter.mdx');
expect(result).toBe(true);
});
});
describe('isPathSafe', () => {
it('should allow paths within workspace', () => {
const result = pathResolver.isPathSafe('test.mdx');
expect(result).toBe(true);
});
it('should allow nested paths within workspace', () => {
const result = pathResolver.isPathSafe('docs/guide.mdx');
expect(result).toBe(true);
});
it('should prevent directory traversal attacks', () => {
const result = pathResolver.isPathSafe('../../etc/passwd');
expect(result).toBe(false);
});
it('should prevent absolute paths outside workspace', () => {
const result = pathResolver.isPathSafe('/etc/passwd');
expect(result).toBe(false);
});
it('should handle sneaky traversal attempts', () => {
const result = pathResolver.isPathSafe('docs/../../etc/passwd');
expect(result).toBe(false);
});
});
describe('ensureDirectory', () => {
let tempDir: string;
beforeEach(() => {
tempDir = resolve(testWorkspace, 'temp-test-dir');
});
afterEach(async () => {
try {
await rm(tempDir, { recursive: true, force: true });
} catch (error) {
// Ignore cleanup errors
}
});
it('should create parent directories for a file path', async () => {
const filePath = join('temp-test-dir', 'nested', 'deep', 'file.txt');
await pathResolver.ensureDirectory(filePath);
// Verify the directory structure was created
const dirPath = resolve(testWorkspace, 'temp-test-dir', 'nested', 'deep');
const fs = await import('fs');
expect(fs.existsSync(dirPath)).toBe(true);
});
it('should not error if directory already exists', async () => {
const filePath = join('temp-test-dir', 'file.txt');
// Create it once
await pathResolver.ensureDirectory(filePath);
// Create it again - should not throw
await expect(pathResolver.ensureDirectory(filePath)).resolves.not.toThrow();
});
});
describe('getWorkspaceRoot', () => {
it('should return the workspace root path', () => {
const result = pathResolver.getWorkspaceRoot();
expect(result).toBe(testWorkspace);
});
});
describe('constructor', () => {
it('should use provided workspace root', () => {
const customRoot = '/custom/path';
const resolver = new PathResolver(customRoot);
expect(resolver.getWorkspaceRoot()).toBe(resolve(customRoot));
});
it('should use process.cwd() when no workspace is provided', () => {
const resolver = new PathResolver();
expect(resolver.getWorkspaceRoot()).toBe(process.cwd());
});
});
});