UNPKG

meld

Version:

Meld: A template language for LLM prompts

293 lines (263 loc) 10.2 kB
import { describe, it, expect, beforeEach, afterEach } from 'vitest'; import { TestContext } from '@tests/utils/TestContext.js'; import { PathService } from './PathService.js'; import { PathValidationError, PathErrorCode } from './errors/PathValidationError.js'; import type { Location } from '@core/types/index.js'; import type { StructuredPath } from 'meld-spec'; import { PathErrorMessages } from '@core/errors/messages/paths.js'; describe('PathService Temporary Path Rules', () => { let context: TestContext; let service: PathService; beforeEach(async () => { // Initialize test context context = new TestContext(); await context.initialize(); // Get PathService from context service = context.services.path; // Set known paths for testing service.setHomePath('/home/user'); service.setProjectPath('/project/root'); }); afterEach(async () => { await context.cleanup(); }); it('should allow simple filenames in current directory', () => { const result = service.resolvePath('file.meld', '/current/dir'); expect(result).toBe('/current/dir/file.meld'); // Test with structured path const structuredPath: StructuredPath = { raw: 'file.meld', structured: { segments: ['file.meld'], cwd: true // This is important - indicates it's relative to current directory } }; const structuredResult = service.resolvePath(structuredPath, '/current/dir'); expect(structuredResult).toBe('/current/dir/file.meld'); }); describe('Special path variables', () => { it('should resolve $HOMEPATH paths', () => { const result = service.resolvePath('$~/path/to/file.meld'); expect(result).toBe('/home/user/path/to/file.meld'); // Test with structured path const structuredPath: StructuredPath = { raw: '$HOMEPATH/path/to/file.meld', structured: { segments: ['path', 'to', 'file.meld'], variables: { special: ['HOMEPATH'] } } }; const structuredResult = service.resolvePath(structuredPath); expect(structuredResult).toBe('/home/user/path/to/file.meld'); }); it('should resolve $~ paths (alias for $HOMEPATH)', () => { const result = service.resolvePath('$~/path/to/file.meld'); expect(result).toBe('/home/user/path/to/file.meld'); // Test with structured path const structuredPath: StructuredPath = { raw: '$~/path/to/file.meld', structured: { segments: ['path', 'to', 'file.meld'], variables: { special: ['HOMEPATH'] } } }; const structuredResult = service.resolvePath(structuredPath); expect(structuredResult).toBe('/home/user/path/to/file.meld'); }); it('should resolve $PROJECTPATH paths', () => { const result = service.resolvePath('$./path/to/file.meld'); expect(result).toBe('/project/root/path/to/file.meld'); // Test with structured path const structuredPath: StructuredPath = { raw: '$PROJECTPATH/path/to/file.meld', structured: { segments: ['path', 'to', 'file.meld'], variables: { special: ['PROJECTPATH'] } } }; const structuredResult = service.resolvePath(structuredPath); expect(structuredResult).toBe('/project/root/path/to/file.meld'); }); it('should resolve $. paths (alias for $PROJECTPATH)', () => { const result = service.resolvePath('$./path/to/file.meld'); expect(result).toBe('/project/root/path/to/file.meld'); // Test with structured path const structuredPath: StructuredPath = { raw: '$./path/to/file.meld', structured: { segments: ['path', 'to', 'file.meld'], variables: { special: ['PROJECTPATH'] } } }; const structuredResult = service.resolvePath(structuredPath); expect(structuredResult).toBe('/project/root/path/to/file.meld'); }); }); it('should reject simple paths containing dots', () => { expect(() => service.resolvePath('./file.meld')).toThrow(PathValidationError); expect(() => service.resolvePath('../file.meld')).toThrow(PathValidationError); // Test with structured paths const dotPath: StructuredPath = { raw: './file.meld', structured: { segments: ['.', 'file.meld'], cwd: true } }; const dotDotPath: StructuredPath = { raw: '../file.meld', structured: { segments: ['..', 'file.meld'], cwd: true } }; expect(() => service.resolvePath(dotPath)).toThrow(PathValidationError); expect(() => service.resolvePath(dotDotPath)).toThrow(PathValidationError); }); describe('Path validation rules', () => { it('should reject paths with .. segments', () => { expect(() => service.resolvePath('$./path/../file.meld')) .toThrow(new PathValidationError( PathErrorMessages.validation.dotSegments.message, PathErrorCode.CONTAINS_DOT_SEGMENTS )); // Test with structured path const structuredPath: StructuredPath = { raw: '$./path/../file.meld', structured: { segments: ['path', '..', 'file.meld'], variables: { special: ['PROJECTPATH'] } } }; expect(() => service.resolvePath(structuredPath)) .toThrow(new PathValidationError( PathErrorMessages.validation.dotSegments.message, PathErrorCode.CONTAINS_DOT_SEGMENTS )); }); it('should reject paths with . segments', () => { expect(() => service.resolvePath('$./path/./file.meld')) .toThrow(new PathValidationError( PathErrorMessages.validation.dotSegments.message, PathErrorCode.CONTAINS_DOT_SEGMENTS )); // Test with structured path const structuredPath: StructuredPath = { raw: '$./path/./file.meld', structured: { segments: ['path', '.', 'file.meld'], variables: { special: ['PROJECTPATH'] } } }; expect(() => service.resolvePath(structuredPath)) .toThrow(new PathValidationError( PathErrorMessages.validation.dotSegments.message, PathErrorCode.CONTAINS_DOT_SEGMENTS )); }); it('should reject raw absolute paths', () => { // Note: The current implementation checks for path variables first, // so the error code is INVALID_PATH_FORMAT instead of RAW_ABSOLUTE_PATH expect(() => service.resolvePath('/absolute/path/file.meld')) .toThrow(new PathValidationError( PathErrorMessages.validation.rawAbsolutePath.message, PathErrorCode.INVALID_PATH_FORMAT )); // Test with structured path const structuredPath: StructuredPath = { raw: '/absolute/path/file.meld', structured: { segments: ['absolute', 'path', 'file.meld'] // No special variables or cwd flag } }; expect(() => service.resolvePath(structuredPath)) .toThrow(new PathValidationError( PathErrorMessages.validation.rawAbsolutePath.message, PathErrorCode.INVALID_PATH_FORMAT )); }); it('should reject paths with slashes but no path variable', () => { expect(() => service.resolvePath('path/to/file.meld')) .toThrow(new PathValidationError( PathErrorMessages.validation.slashesWithoutPathVariable.message, PathErrorCode.INVALID_PATH_FORMAT )); // Test with structured path const structuredPath: StructuredPath = { raw: 'path/to/file.meld', structured: { segments: ['path', 'to', 'file.meld'] // No special variables or cwd flag } }; expect(() => service.resolvePath(structuredPath)) .toThrow(new PathValidationError( PathErrorMessages.validation.slashesWithoutPathVariable.message, PathErrorCode.INVALID_PATH_FORMAT )); }); }); describe('Error messages and codes', () => { it('should provide helpful error messages for dot segments', () => { try { service.resolvePath('$./path/../file.meld'); fail('Should have thrown error'); } catch (e) { const err = e as PathValidationError; expect(err.code).toBe(PathErrorCode.CONTAINS_DOT_SEGMENTS); expect(err.message).toBe(PathErrorMessages.validation.dotSegments.message); } }); it('should provide helpful error messages for raw absolute paths', () => { try { service.resolvePath('/absolute/path.meld'); fail('Should have thrown error'); } catch (e) { const err = e as PathValidationError; // Note: The current implementation checks for path variables first, // so the error code is INVALID_PATH_FORMAT instead of RAW_ABSOLUTE_PATH expect(err.code).toBe(PathErrorCode.INVALID_PATH_FORMAT); expect(err.message).toBe(PathErrorMessages.validation.rawAbsolutePath.message); } }); it('should provide helpful error messages for invalid path formats', () => { try { service.resolvePath('path/to/file.meld'); fail('Should have thrown error'); } catch (e) { const err = e as PathValidationError; expect(err.code).toBe(PathErrorCode.INVALID_PATH_FORMAT); expect(err.message).toBe(PathErrorMessages.validation.slashesWithoutPathVariable.message); } }); }); describe('Location information in errors', () => { const testLocation: Location = { start: { line: 1, column: 1 }, end: { line: 1, column: 20 }, filePath: 'test.meld' }; it('should include location information in errors when provided', () => { try { service.validateMeldPath('../invalid.meld', testLocation); fail('Should have thrown error'); } catch (e) { const err = e as PathValidationError; expect(err.location).toBe(testLocation); } }); }); });