agent-ecosystem/test/main/utils/pathDecoder.test.ts
matt d07485bf27 feat(pathDecoder): add support for legacy Windows-style encoded paths
- Enhanced the decodePath and isValidEncodedPath functions to handle legacy Windows format, allowing paths like "C--Users-name-project" to be correctly decoded and validated.
- Updated tests to cover the new legacy format, ensuring proper functionality and validation.

This commit improves compatibility with legacy path formats, enhancing the utility of the path decoder.
2026-02-13 02:05:26 +09:00

223 lines
7.6 KiB
TypeScript

import * as path from 'path';
import { describe, expect, it } from 'vitest';
import {
buildSessionPath,
buildSubagentsPath,
buildTodoPath,
decodePath,
encodePath,
extractProjectName,
extractSessionId,
getProjectsBasePath,
getTodosBasePath,
isValidEncodedPath,
} from '../../../src/main/utils/pathDecoder';
describe('pathDecoder', () => {
describe('encodePath', () => {
it('should encode a macOS-style absolute path', () => {
expect(encodePath('/Users/username/projectname')).toBe('-Users-username-projectname');
});
it('should encode a Windows-style absolute path', () => {
expect(encodePath('C:\\Users\\username\\projectname')).toBe('-C:-Users-username-projectname');
});
it('should handle empty string', () => {
expect(encodePath('')).toBe('');
});
it('should round-trip with decodePath for POSIX paths', () => {
const original = '/Users/username/projectname';
expect(decodePath(encodePath(original))).toBe(original);
});
it('should round-trip with decodePath for Windows paths', () => {
const original = 'C:/Users/username/projectname';
expect(decodePath(encodePath(original))).toBe(original);
});
it('should encode a Linux-style path', () => {
expect(encodePath('/home/user/projects/myapp')).toBe('-home-user-projects-myapp');
});
});
describe('decodePath', () => {
it('should decode a simple encoded path', () => {
expect(decodePath('-Users-username-projectname')).toBe('/Users/username/projectname');
});
it('should handle empty string', () => {
expect(decodePath('')).toBe('');
});
it('should ensure leading slash for absolute paths', () => {
expect(decodePath('Users-username-projectname')).toBe('/Users/username/projectname');
});
it('should decode path with multiple segments', () => {
expect(decodePath('-home-user-projects-myapp-src')).toBe('/home/user/projects/myapp/src');
});
it('should handle single segment path', () => {
expect(decodePath('-project')).toBe('/project');
});
it('should handle path with underscores', () => {
expect(decodePath('-Users-username-my_projectname')).toBe('/Users/username/my_projectname');
});
it('should handle path with dots', () => {
expect(decodePath('-Users-username-.config')).toBe('/Users/username/.config');
});
it('should decode Windows-style encoded path without adding leading slash', () => {
expect(decodePath('-C:-Users-username-projectname')).toBe('C:/Users/username/projectname');
});
it('should decode legacy Windows-style encoded path without leading dash', () => {
expect(decodePath('C--Users-username-projectname')).toBe('C:/Users/username/projectname');
});
});
describe('extractProjectName', () => {
it('should extract project name from encoded path', () => {
expect(extractProjectName('-Users-username-projectname')).toBe('projectname');
});
it('should handle deeply nested paths', () => {
expect(extractProjectName('-home-user-dev-projects-appname')).toBe('appname');
});
it('should return encoded name if decoding fails', () => {
expect(extractProjectName('')).toBe('');
});
it('should handle single segment', () => {
expect(extractProjectName('-projectname')).toBe('projectname');
});
it('should handle path with underscore in project name', () => {
expect(extractProjectName('-Users-username-my_cool_projectname')).toBe('my_cool_projectname');
});
it('should prefer cwdHint over lossy decode for dashed project names', () => {
// Without cwdHint, dashes are decoded as slashes (lossy)
expect(extractProjectName('-Users-name-claude-devtools')).toBe('devtools');
// With cwdHint, the actual project name is preserved
expect(
extractProjectName('-Users-name-claude-devtools', '/Users/name/claude-devtools')
).toBe('claude-devtools');
});
it('should fall back to decoded name when cwdHint is undefined', () => {
expect(extractProjectName('-Users-username-projectname')).toBe('projectname');
});
});
describe('isValidEncodedPath', () => {
it('should return true for valid encoded path', () => {
expect(isValidEncodedPath('-Users-username-projectname')).toBe(true);
});
it('should return false for empty string', () => {
expect(isValidEncodedPath('')).toBe(false);
});
it('should return false for path without leading dash', () => {
expect(isValidEncodedPath('Users-username-projectname')).toBe(false);
});
it('should return true for path with underscores', () => {
expect(isValidEncodedPath('-Users-username-my_projectname')).toBe(true);
});
it('should return true for path with dots', () => {
expect(isValidEncodedPath('-Users-username-.config')).toBe(true);
});
it('should return true for path with numbers', () => {
expect(isValidEncodedPath('-Users-username-projectname123')).toBe(true);
});
it('should return true for path with spaces', () => {
expect(isValidEncodedPath('-Users-username-My Projectname')).toBe(true);
});
it('should return true for valid Windows-style encoded path', () => {
expect(isValidEncodedPath('-C:-Users-username-projectname')).toBe(true);
});
it('should return true for legacy Windows-style encoded path', () => {
expect(isValidEncodedPath('C--Users-username-projectname')).toBe(true);
});
it('should return false for misplaced colons', () => {
expect(isValidEncodedPath('-Users-username:project')).toBe(false);
expect(isValidEncodedPath('-C:-Users-name-project:extra')).toBe(false);
});
});
describe('extractSessionId', () => {
it('should extract session ID from JSONL filename', () => {
expect(extractSessionId('abc123.jsonl')).toBe('abc123');
});
it('should handle UUID-style session IDs', () => {
expect(extractSessionId('550e8400-e29b-41d4-a716-446655440000.jsonl')).toBe(
'550e8400-e29b-41d4-a716-446655440000'
);
});
it('should handle filename without extension', () => {
expect(extractSessionId('session123')).toBe('session123');
});
it('should handle empty string', () => {
expect(extractSessionId('')).toBe('');
});
});
describe('buildSessionPath', () => {
it('should construct correct session path', () => {
expect(buildSessionPath('/base', 'project-id', 'session-123')).toBe(
path.join('/base', 'project-id', 'session-123.jsonl')
);
});
it('should handle paths with special characters', () => {
expect(buildSessionPath('/home/user/.claude/projects', '-Users-name', 'abc123')).toBe(
path.join('/home/user/.claude/projects', '-Users-name', 'abc123.jsonl')
);
});
});
describe('buildSubagentsPath', () => {
it('should construct correct subagents path', () => {
expect(buildSubagentsPath('/base', 'project-id', 'session-123')).toBe(
path.join('/base', 'project-id', 'session-123', 'subagents')
);
});
});
describe('buildTodoPath', () => {
it('should construct correct todo path', () => {
expect(buildTodoPath('/home/user/.claude', 'session-123')).toBe(
path.join('/home/user/.claude', 'todos', 'session-123.json')
);
});
});
describe('getProjectsBasePath', () => {
it('should return projects base path', () => {
expect(getProjectsBasePath()).toBe(path.join('/home/testuser', '.claude', 'projects'));
});
});
describe('getTodosBasePath', () => {
it('should return todos base path', () => {
expect(getTodosBasePath()).toBe(path.join('/home/testuser', '.claude', 'todos'));
});
});
});