Files
2026-07-13 12:47:58 +08:00

169 lines
5.4 KiB
TypeScript

import { describe, expect, test } from 'vitest'
import { validateRelativePath } from '../../../src/template/utils'
import { TemplateError } from '../../../src/errors'
const isWindows = process.platform === 'win32'
describe('validateRelativePath', () => {
describe('valid paths', () => {
test('accepts simple relative path', () => {
expect(() => validateRelativePath('foo', undefined)).not.toThrow()
})
test('accepts nested relative path', () => {
expect(() => validateRelativePath('foo/bar', undefined)).not.toThrow()
})
test('accepts path with ./ prefix', () => {
expect(() => validateRelativePath('./foo', undefined)).not.toThrow()
})
test('accepts nested path with ./ prefix', () => {
expect(() => validateRelativePath('./foo/bar', undefined)).not.toThrow()
})
test('accepts path with internal parent ref that stays within context', () => {
expect(() => validateRelativePath('foo/../bar', undefined)).not.toThrow()
})
test('accepts current directory', () => {
expect(() => validateRelativePath('.', undefined)).not.toThrow()
})
test('accepts glob patterns', () => {
expect(() => validateRelativePath('*.txt', undefined)).not.toThrow()
expect(() => validateRelativePath('**/*.ts', undefined)).not.toThrow()
expect(() => validateRelativePath('src/**/*', undefined)).not.toThrow()
})
test('accepts hidden files and directories', () => {
expect(() => validateRelativePath('.hidden', undefined)).not.toThrow()
expect(() =>
validateRelativePath('.config/settings', undefined)
).not.toThrow()
})
test('accepts filenames starting with double dots', () => {
expect(() => validateRelativePath('..myconfig', undefined)).not.toThrow()
expect(() => validateRelativePath('..cache', undefined)).not.toThrow()
expect(() =>
validateRelativePath('...something', undefined)
).not.toThrow()
expect(() =>
validateRelativePath('foo/..myconfig', undefined)
).not.toThrow()
})
})
describe('invalid paths - absolute', () => {
test('rejects Unix absolute path', () => {
expect(() => validateRelativePath('/absolute/path', undefined)).toThrow(
TemplateError
)
expect(() => validateRelativePath('/absolute/path', undefined)).toThrow(
'absolute paths are not allowed'
)
})
test('rejects root path', () => {
expect(() => validateRelativePath('/', undefined)).toThrow(TemplateError)
})
// Windows path tests - only run on Windows where path.isAbsolute detects them
test.skipIf(!isWindows)('rejects Windows drive letter path', () => {
expect(() =>
validateRelativePath('C:\\Windows\\System32', undefined)
).toThrow(TemplateError)
expect(() =>
validateRelativePath('C:\\Windows\\System32', undefined)
).toThrow('absolute paths are not allowed')
})
test.skipIf(!isWindows)('rejects Windows UNC path', () => {
expect(() =>
validateRelativePath('\\\\server\\share', undefined)
).toThrow(TemplateError)
})
})
describe('invalid paths - parent directory escape', () => {
test('rejects simple parent directory escape', () => {
expect(() => validateRelativePath('../foo', undefined)).toThrow(
TemplateError
)
expect(() => validateRelativePath('../foo', undefined)).toThrow(
'path escapes the context directory'
)
})
test('rejects parent directory escape with forward slash', () => {
expect(() => validateRelativePath('../file.txt', undefined)).toThrow(
TemplateError
)
})
test.skipIf(!isWindows)(
'rejects parent directory escape with backslash',
() => {
expect(() => validateRelativePath('..\\file.txt', undefined)).toThrow(
TemplateError
)
}
)
test('rejects double parent directory escape', () => {
expect(() => validateRelativePath('../../foo', undefined)).toThrow(
TemplateError
)
})
test('rejects path that escapes via nested parent refs', () => {
expect(() => validateRelativePath('foo/../../bar', undefined)).toThrow(
TemplateError
)
})
test('rejects path with ./ prefix that escapes', () => {
expect(() =>
validateRelativePath('./foo/../../../bar', undefined)
).toThrow(TemplateError)
})
test('rejects just parent directory', () => {
expect(() => validateRelativePath('..', undefined)).toThrow(TemplateError)
})
test('rejects current directory followed by parent', () => {
expect(() => validateRelativePath('./..', undefined)).toThrow(
TemplateError
)
})
test('rejects deeply nested escape', () => {
expect(() =>
validateRelativePath('a/b/c/../../../../escape', undefined)
).toThrow(TemplateError)
})
})
describe('error messages include path', () => {
test('absolute path error includes the path', () => {
try {
validateRelativePath('/etc/passwd', undefined)
expect.fail('Should have thrown')
} catch (e) {
expect(e.message).toContain('/etc/passwd')
}
})
test('escape path error includes the path', () => {
try {
validateRelativePath('../secret', undefined)
expect.fail('Should have thrown')
} catch (e) {
expect(e.message).toContain('../secret')
}
})
})
})