From 0933e16983c153085686ed6d6a2418d22f938e28 Mon Sep 17 00:00:00 2001 From: Dustin Loring Date: Thu, 25 Sep 2025 17:54:17 -0400 Subject: [PATCH] tests: fixed faild symlink tests on windows due to permission error (fixed by skipping if on windows) --- package-lock.json | 68 +++--- .../core/src/utils/workspaceContext.test.ts | 207 +++++++++++------- 2 files changed, 169 insertions(+), 106 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8ab31fdea..0e92613ea 100644 --- a/package-lock.json +++ b/package-lock.json @@ -967,13 +967,13 @@ } }, "node_modules/@eslint/plugin-kit": { - "version": "0.3.3", - "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.3.tgz", - "integrity": "sha512-1+WqvgNMhmlAambTvT3KPtCl/Ibr68VldY2XY40SL1CE0ZXiakFR/cbTspaF5HsnpDMvcYYoJHfl4980NBjGag==", + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.3.5.tgz", + "integrity": "sha512-Z5kJ+wU3oA7MMIqVR9tyZRtjYPr4OC004Q4Rw7pgOKUOKkJfZ3O24nz3WYfGRpMDNmcOi3TwQOmgm7B7Tpii0w==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@eslint/core": "^0.15.1", + "@eslint/core": "^0.15.2", "levn": "^0.4.1" }, "engines": { @@ -981,9 +981,9 @@ } }, "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": { - "version": "0.15.1", - "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.1.tgz", - "integrity": "sha512-bkOp+iumZCCbt1K1CmWf0R9pM5yKpDv+ZXtvSyQpudrI9kuFLp+bM2WOPXImuD/ceQuaa8f5pj93Y7zyECIGNA==", + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.15.2.tgz", + "integrity": "sha512-78Md3/Rrxh83gCxoUc0EiciuOHsIITzLy53m3d9UyiW8y9Dj2D29FeETqyKA+BRK76tnTp6RXWb3pCay8Oyomg==", "dev": true, "license": "Apache-2.0", "dependencies": { @@ -11995,14 +11995,14 @@ "license": "MIT" }, "node_modules/tinyglobby": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", - "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", "dev": true, "license": "MIT", "dependencies": { - "fdir": "^6.4.4", - "picomatch": "^4.0.2" + "fdir": "^6.5.0", + "picomatch": "^4.0.3" }, "engines": { "node": ">=12.0.0" @@ -12012,11 +12012,14 @@ } }, "node_modules/tinyglobby/node_modules/fdir": { - "version": "6.4.6", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", - "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, "peerDependencies": { "picomatch": "^3 || ^4" }, @@ -12027,9 +12030,9 @@ } }, "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", "engines": { @@ -12708,18 +12711,18 @@ } }, "node_modules/vite": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/vite/-/vite-7.0.0.tgz", - "integrity": "sha512-ixXJB1YRgDIw2OszKQS9WxGHKwLdCsbQNkpJN171udl6szi/rIySHL6/Os3s2+oE4P/FLD4dxg4mD7Wust+u5g==", + "version": "7.1.7", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.7.tgz", + "integrity": "sha512-VbA8ScMvAISJNJVbRDTJdCwqQoAareR/wutevKanhR2/1EkoXVZVkkORaYm/tNVCjP/UDTKtcw3bAkwOUdedmA==", "dev": true, "license": "MIT", "dependencies": { "esbuild": "^0.25.0", - "fdir": "^6.4.6", - "picomatch": "^4.0.2", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", "postcss": "^8.5.6", - "rollup": "^4.40.0", - "tinyglobby": "^0.2.14" + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" }, "bin": { "vite": "bin/vite.js" @@ -12806,11 +12809,14 @@ } }, "node_modules/vite/node_modules/fdir": { - "version": "6.4.6", - "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.6.tgz", - "integrity": "sha512-hiFoqpyZcfNm1yc4u8oWCf9A2c4D3QjCrks3zmoVKVxpQRzmPNar1hUJcBG2RQHvEVGDN+Jm81ZheVLAQMK6+w==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, "peerDependencies": { "picomatch": "^3 || ^4" }, @@ -12821,9 +12827,9 @@ } }, "node_modules/vite/node_modules/picomatch": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", - "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", "engines": { diff --git a/packages/core/src/utils/workspaceContext.test.ts b/packages/core/src/utils/workspaceContext.test.ts index c93dffe47..5c32979cf 100644 --- a/packages/core/src/utils/workspaceContext.test.ts +++ b/packages/core/src/utils/workspaceContext.test.ts @@ -10,6 +10,12 @@ import * as os from 'node:os'; import * as path from 'node:path'; import { WorkspaceContext } from './workspaceContext.js'; +// Symlink creation on Windows requires special permissions that are not +// available in the standard CI environment. Therefore, we skip these tests +// on Windows to prevent CI failures. The core functionality is still +// validated on Linux and macOS. +const itif = (condition: boolean) => (condition ? it : it.skip); + describe('WorkspaceContext with real filesystem', () => { let tempDir: string; let cwd: string; @@ -83,18 +89,27 @@ describe('WorkspaceContext with real filesystem', () => { expect(directories).toHaveLength(2); }); - it('should handle symbolic links correctly', () => { - const realDir = path.join(tempDir, 'real'); - fs.mkdirSync(realDir, { recursive: true }); - const symlinkDir = path.join(tempDir, 'symlink-to-real'); - fs.symlinkSync(realDir, symlinkDir, 'dir'); - const workspaceContext = new WorkspaceContext(cwd); - workspaceContext.addDirectory(symlinkDir); + // Symlink creation on Windows requires special permissions that are not + // available in the standard CI environment. Therefore, we skip these tests + // on Windows to prevent CI failures. The core functionality is still + // validated on Linux and macOS. + const itif = (condition: boolean) => (condition ? it : it.skip); + + itif(process.platform !== 'win32')( + 'should handle symbolic links correctly', + () => { + const realDir = path.join(tempDir, 'real'); + fs.mkdirSync(realDir, { recursive: true }); + const symlinkDir = path.join(tempDir, 'symlink-to-real'); + fs.symlinkSync(realDir, symlinkDir, 'dir'); + const workspaceContext = new WorkspaceContext(cwd); + workspaceContext.addDirectory(symlinkDir); - const directories = workspaceContext.getDirectories(); + const directories = workspaceContext.getDirectories(); - expect(directories).toEqual([cwd, realDir]); - }); + expect(directories).toEqual([cwd, realDir]); + }, + ); }); describe('path validation', () => { @@ -170,27 +185,37 @@ describe('WorkspaceContext with real filesystem', () => { fs.symlinkSync(realDir, symlinkDir, 'dir'); }); - it('should accept dir paths', () => { + itif(process.platform !== 'win32')('should accept dir paths', () => { const workspaceContext = new WorkspaceContext(cwd); expect(workspaceContext.isPathWithinWorkspace(symlinkDir)).toBe(true); }); - it('should accept non-existent paths', () => { - const filePath = path.join(symlinkDir, 'does-not-exist.txt'); + itif(process.platform !== 'win32')( + 'should accept non-existent paths', + () => { + const filePath = path.join(symlinkDir, 'does-not-exist.txt'); - const workspaceContext = new WorkspaceContext(cwd); + const workspaceContext = new WorkspaceContext(cwd); - expect(workspaceContext.isPathWithinWorkspace(filePath)).toBe(true); - }); + expect(workspaceContext.isPathWithinWorkspace(filePath)).toBe(true); + }, + ); - it('should accept non-existent deep paths', () => { - const filePath = path.join(symlinkDir, 'deep', 'does-not-exist.txt'); + itif(process.platform !== 'win32')( + 'should accept non-existent deep paths', + () => { + const filePath = path.join( + symlinkDir, + 'deep', + 'does-not-exist.txt', + ); - const workspaceContext = new WorkspaceContext(cwd); + const workspaceContext = new WorkspaceContext(cwd); - expect(workspaceContext.isPathWithinWorkspace(filePath)).toBe(true); - }); + expect(workspaceContext.isPathWithinWorkspace(filePath)).toBe(true); + }, + ); }); describe('outside the workspace', () => { @@ -204,7 +229,7 @@ describe('WorkspaceContext with real filesystem', () => { fs.symlinkSync(realDir, symlinkDir, 'dir'); }); - it('should reject dir paths', () => { + itif(process.platform !== 'win32')('should reject dir paths', () => { const workspaceContext = new WorkspaceContext(cwd); expect(workspaceContext.isPathWithinWorkspace(symlinkDir)).toBe( @@ -212,69 +237,101 @@ describe('WorkspaceContext with real filesystem', () => { ); }); - it('should reject non-existent paths', () => { - const filePath = path.join(symlinkDir, 'does-not-exist.txt'); - - const workspaceContext = new WorkspaceContext(cwd); - - expect(workspaceContext.isPathWithinWorkspace(filePath)).toBe(false); - }); - - it('should reject non-existent deep paths', () => { - const filePath = path.join(symlinkDir, 'deep', 'does-not-exist.txt'); - - const workspaceContext = new WorkspaceContext(cwd); - - expect(workspaceContext.isPathWithinWorkspace(filePath)).toBe(false); - }); - - it('should reject partially non-existent deep paths', () => { - const deepDir = path.join(symlinkDir, 'deep'); - fs.mkdirSync(deepDir, { recursive: true }); - const filePath = path.join(deepDir, 'does-not-exist.txt'); - - const workspaceContext = new WorkspaceContext(cwd); - - expect(workspaceContext.isPathWithinWorkspace(filePath)).toBe(false); - }); + itif(process.platform !== 'win32')( + 'should reject non-existent paths', + () => { + const filePath = path.join(symlinkDir, 'does-not-exist.txt'); + + const workspaceContext = new WorkspaceContext(cwd); + + expect(workspaceContext.isPathWithinWorkspace(filePath)).toBe( + false, + ); + }, + ); + + itif(process.platform !== 'win32')( + 'should reject non-existent deep paths', + () => { + const filePath = path.join( + symlinkDir, + 'deep', + 'does-not-exist.txt', + ); + + const workspaceContext = new WorkspaceContext(cwd); + + expect(workspaceContext.isPathWithinWorkspace(filePath)).toBe( + false, + ); + }, + ); + + itif(process.platform !== 'win32')( + 'should reject partially non-existent deep paths', + () => { + const deepDir = path.join(symlinkDir, 'deep'); + fs.mkdirSync(deepDir, { recursive: true }); + const filePath = path.join(deepDir, 'does-not-exist.txt'); + + const workspaceContext = new WorkspaceContext(cwd); + + expect(workspaceContext.isPathWithinWorkspace(filePath)).toBe( + false, + ); + }, + ); }); - it('should reject symbolic file links outside the workspace', () => { - const realFile = path.join(tempDir, 'real-file.txt'); - fs.writeFileSync(realFile, 'content'); + itif(process.platform !== 'win32')( + 'should reject symbolic file links outside the workspace', + () => { + const realFile = path.join(tempDir, 'real-file.txt'); + fs.writeFileSync(realFile, 'content'); - const symlinkFile = path.join(cwd, 'symlink-to-real-file'); - fs.symlinkSync(realFile, symlinkFile, 'file'); + const symlinkFile = path.join(cwd, 'symlink-to-real-file'); + fs.symlinkSync(realFile, symlinkFile, 'file'); - const workspaceContext = new WorkspaceContext(cwd); + const workspaceContext = new WorkspaceContext(cwd); - expect(workspaceContext.isPathWithinWorkspace(symlinkFile)).toBe(false); - }); + expect(workspaceContext.isPathWithinWorkspace(symlinkFile)).toBe( + false, + ); + }, + ); - it('should reject non-existent symbolic file links outside the workspace', () => { - const realFile = path.join(tempDir, 'real-file.txt'); + itif(process.platform !== 'win32')( + 'should reject non-existent symbolic file links outside the workspace', + () => { + const realFile = path.join(tempDir, 'real-file.txt'); - const symlinkFile = path.join(cwd, 'symlink-to-real-file'); - fs.symlinkSync(realFile, symlinkFile, 'file'); + const symlinkFile = path.join(cwd, 'symlink-to-real-file'); + fs.symlinkSync(realFile, symlinkFile, 'file'); - const workspaceContext = new WorkspaceContext(cwd); + const workspaceContext = new WorkspaceContext(cwd); - expect(workspaceContext.isPathWithinWorkspace(symlinkFile)).toBe(false); - }); + expect(workspaceContext.isPathWithinWorkspace(symlinkFile)).toBe( + false, + ); + }, + ); - it('should handle circular symlinks gracefully', () => { - const workspaceContext = new WorkspaceContext(cwd); - const linkA = path.join(cwd, 'link-a'); - const linkB = path.join(cwd, 'link-b'); - // Create a circular dependency: linkA -> linkB -> linkA - fs.symlinkSync(linkB, linkA, 'dir'); - fs.symlinkSync(linkA, linkB, 'dir'); - - // fs.realpathSync should throw ELOOP, and isPathWithinWorkspace should - // handle it gracefully and return false. - expect(workspaceContext.isPathWithinWorkspace(linkA)).toBe(false); - expect(workspaceContext.isPathWithinWorkspace(linkB)).toBe(false); - }); + itif(process.platform !== 'win32')( + 'should handle circular symlinks gracefully', + () => { + const workspaceContext = new WorkspaceContext(cwd); + const linkA = path.join(cwd, 'link-a'); + const linkB = path.join(cwd, 'link-b'); + // Create a circular dependency: linkA -> linkB -> linkA + fs.symlinkSync(linkB, linkA, 'dir'); + fs.symlinkSync(linkA, linkB, 'dir'); + + // fs.realpathSync should throw ELOOP, and isPathWithinWorkspace should + // handle it gracefully and return false. + expect(workspaceContext.isPathWithinWorkspace(linkA)).toBe(false); + expect(workspaceContext.isPathWithinWorkspace(linkB)).toBe(false); + }, + ); }); });