Skip to content

Commit a1b2abe

Browse files
committed
test(dlx): add 18 edge case tests to increase coverage
Add comprehensive edge case testing for DLX modules (cache, paths, dir, packages): generateCacheKey edge cases (7 new tests): - Empty strings - Special characters (@#$%^&*...) - Unicode characters (测试-тест-テスト) - Very long inputs (10,000 chars) - URL specs - Hash collision testing for similar strings Path function edge cases (8 new tests): - getDlxPackageDir: scoped packages, special chars, empty names - getDlxPackageNodeModulesDir: scoped packages, empty names - getDlxPackageJsonPath: scoped packages, empty names - isInSocketDlx: exact directory path, nested paths, similar paths, special chars Package detection edge cases (3 new tests): - isDlxPackageInstalled: scoped packages, empty names - listDlxPackages: special chars, scoped packages, filtering non-directories - listDlxPackagesAsync: sorted list with 3 packages Test count: 48 → 66 tests (+18 tests, +37.5%) All tests passing - validates robust edge case handling across DLX utilities.
1 parent 9203a71 commit a1b2abe

File tree

1 file changed

+178
-0
lines changed

1 file changed

+178
-0
lines changed

test/unit/dlx/main.test.ts

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,43 @@ describe.sequential('dlx', () => {
9898
expect(key).toHaveLength(16)
9999
expect(key).toMatch(/^[0-9a-f]{16}$/)
100100
})
101+
102+
it('should handle empty strings', () => {
103+
const key = generateCacheKey('')
104+
expect(key).toHaveLength(16)
105+
expect(key).toMatch(/^[0-9a-f]{16}$/)
106+
})
107+
108+
it('should handle special characters', () => {
109+
const key = generateCacheKey('test@#$%^&*()_+-=[]{}|;:,.<>?')
110+
expect(key).toHaveLength(16)
111+
expect(key).toMatch(/^[0-9a-f]{16}$/)
112+
})
113+
114+
it('should handle unicode characters', () => {
115+
const key = generateCacheKey('测试-тест-テスト')
116+
expect(key).toHaveLength(16)
117+
expect(key).toMatch(/^[0-9a-f]{16}$/)
118+
})
119+
120+
it('should handle very long inputs', () => {
121+
const longSpec = 'a'.repeat(10_000)
122+
const key = generateCacheKey(longSpec)
123+
expect(key).toHaveLength(16)
124+
expect(key).toMatch(/^[0-9a-f]{16}$/)
125+
})
126+
127+
it('should handle URLs as specs', () => {
128+
const key = generateCacheKey('https://example.com/binary.tar.gz:binary')
129+
expect(key).toHaveLength(16)
130+
expect(key).toMatch(/^[0-9a-f]{16}$/)
131+
})
132+
133+
it('should produce different hashes for similar strings', () => {
134+
const key1 = generateCacheKey('npm:test@1.0.0')
135+
const key2 = generateCacheKey('npm:test@1.0.1')
136+
expect(key1).not.toBe(key2)
137+
})
101138
})
102139

103140
describe('dlxDirExists / dlxDirExistsAsync', () => {
@@ -175,6 +212,25 @@ describe.sequential('dlx', () => {
175212
// Path should not contain backslashes on any platform after normalization
176213
expect(packageDir).not.toContain('\\')
177214
})
215+
216+
it('should handle scoped package names', () => {
217+
const scopedPackage = '@socket/cli'
218+
const packageDir = getDlxPackageDir(scopedPackage)
219+
expect(packageDir).toContain(getSocketDlxDir())
220+
expect(packageDir).toContain('@socket/cli')
221+
})
222+
223+
it('should handle package names with special characters', () => {
224+
const specialPackage = 'package-name_with.chars'
225+
const packageDir = getDlxPackageDir(specialPackage)
226+
expect(packageDir).toContain(getSocketDlxDir())
227+
expect(packageDir).toContain(specialPackage)
228+
})
229+
230+
it('should handle empty package name', () => {
231+
const packageDir = getDlxPackageDir('')
232+
expect(packageDir).toContain(getSocketDlxDir())
233+
})
178234
})
179235

180236
describe('getDlxPackageNodeModulesDir', () => {
@@ -184,6 +240,19 @@ describe.sequential('dlx', () => {
184240
expect(nodeModulesDir).toContain(testPackageName)
185241
expect(nodeModulesDir).toContain('node_modules')
186242
})
243+
244+
it('should handle scoped packages', () => {
245+
const scopedPackage = '@socket/test'
246+
const nodeModulesDir = getDlxPackageNodeModulesDir(scopedPackage)
247+
expect(nodeModulesDir).toContain(getSocketDlxDir())
248+
expect(nodeModulesDir).toContain('node_modules')
249+
})
250+
251+
it('should handle empty package name', () => {
252+
const nodeModulesDir = getDlxPackageNodeModulesDir('')
253+
expect(nodeModulesDir).toContain(getSocketDlxDir())
254+
expect(nodeModulesDir).toContain('node_modules')
255+
})
187256
})
188257

189258
describe('getDlxInstalledPackageDir', () => {
@@ -209,6 +278,19 @@ describe.sequential('dlx', () => {
209278
expect(packageJsonPath).toContain(testPackageName)
210279
expect(packageJsonPath).toContain('package.json')
211280
})
281+
282+
it('should handle scoped packages', () => {
283+
const scopedPackage = '@socket/test'
284+
const packageJsonPath = getDlxPackageJsonPath(scopedPackage)
285+
expect(packageJsonPath).toContain(getSocketDlxDir())
286+
expect(packageJsonPath).toContain('package.json')
287+
})
288+
289+
it('should handle empty package name', () => {
290+
const packageJsonPath = getDlxPackageJsonPath('')
291+
expect(packageJsonPath).toContain(getSocketDlxDir())
292+
expect(packageJsonPath).toContain('package.json')
293+
})
212294
})
213295

214296
describe('isInSocketDlx', () => {
@@ -240,6 +322,39 @@ describe.sequential('dlx', () => {
240322
const dlxPath = path.join(getSocketDlxDir(), 'package', '')
241323
expect(isInSocketDlx(dlxPath)).toBe(true)
242324
})
325+
326+
it('should return false for exact DLX directory path', () => {
327+
// The DLX directory itself is not "inside" the DLX directory
328+
expect(isInSocketDlx(getSocketDlxDir())).toBe(false)
329+
})
330+
331+
it('should return true for paths inside DLX with trailing slash', () => {
332+
// A path with trailing slash after DLX dir is considered inside
333+
const insidePath = path.join(getSocketDlxDir(), 'anything')
334+
expect(isInSocketDlx(insidePath)).toBe(true)
335+
})
336+
337+
it('should handle nested paths', () => {
338+
const nestedPath = path.join(
339+
getSocketDlxDir(),
340+
'pkg',
341+
'node_modules',
342+
'dep',
343+
'lib',
344+
'file.js',
345+
)
346+
expect(isInSocketDlx(nestedPath)).toBe(true)
347+
})
348+
349+
it('should handle similar but different paths', () => {
350+
const similarPath = `${getSocketDlxDir()}-other/package`
351+
expect(isInSocketDlx(similarPath)).toBe(false)
352+
})
353+
354+
it('should handle paths with special characters', () => {
355+
const specialPath = path.join(getSocketDlxDir(), '@scope/package', 'bin')
356+
expect(isInSocketDlx(specialPath)).toBe(true)
357+
})
243358
})
244359

245360
describe('isDlxPackageInstalled / isDlxPackageInstalledAsync', () => {
@@ -264,6 +379,18 @@ describe.sequential('dlx', () => {
264379
await fs.promises.mkdir(installedDir, { recursive: true })
265380
expect(await isDlxPackageInstalledAsync(testPackageName)).toBe(true)
266381
})
382+
383+
it('should handle scoped packages', async () => {
384+
const scopedPackage = '@socket/test'
385+
expect(isDlxPackageInstalled(scopedPackage)).toBe(false)
386+
const installedDir = getDlxInstalledPackageDir(scopedPackage)
387+
await fs.promises.mkdir(installedDir, { recursive: true })
388+
expect(isDlxPackageInstalled(scopedPackage)).toBe(true)
389+
})
390+
391+
it('should handle empty package name', () => {
392+
expect(isDlxPackageInstalled('')).toBe(false)
393+
})
267394
})
268395

269396
describe('listDlxPackages / listDlxPackagesAsync', () => {
@@ -298,6 +425,44 @@ describe.sequential('dlx', () => {
298425
expect(packages).toEqual(['a-package', 'z-package'])
299426
})
300427

428+
it('should handle packages with special characters', async () => {
429+
await ensureDlxDir()
430+
const pkg1Dir = getDlxPackageDir('package-name_with.chars')
431+
const pkg2Dir = getDlxPackageDir('other-package_123')
432+
await fs.promises.mkdir(pkg1Dir, { recursive: true })
433+
await fs.promises.mkdir(pkg2Dir, { recursive: true })
434+
435+
const packages = listDlxPackages()
436+
expect(packages).toContain('other-package_123')
437+
expect(packages).toContain('package-name_with.chars')
438+
expect(packages).toHaveLength(2)
439+
})
440+
441+
it('should list scoped packages by scope directory', async () => {
442+
// When creating @scope/package, filesystem creates @scope dir and @scope/package subdir
443+
// listDlxPackages returns only top-level dirs, so it returns '@scope' not '@scope/package'
444+
await ensureDlxDir()
445+
const scopedPkgDir = getDlxPackageDir('@scope/package')
446+
await fs.promises.mkdir(scopedPkgDir, { recursive: true })
447+
448+
const packages = listDlxPackages()
449+
expect(packages).toContain('@scope')
450+
expect(packages).toHaveLength(1)
451+
})
452+
453+
it('should filter out non-directory entries', async () => {
454+
await ensureDlxDir()
455+
const pkgDir = getDlxPackageDir('real-package')
456+
await fs.promises.mkdir(pkgDir, { recursive: true })
457+
// Create a file in the DLX directory (should be ignored)
458+
const filePath = path.join(getSocketDlxDir(), 'some-file.txt')
459+
await fs.promises.writeFile(filePath, 'content')
460+
461+
const packages = listDlxPackages()
462+
expect(packages).toEqual(['real-package'])
463+
expect(packages).toHaveLength(1)
464+
})
465+
301466
it('async version should return empty array when no packages are installed', async () => {
302467
const packages = await listDlxPackagesAsync()
303468
expect(packages).toEqual([])
@@ -316,6 +481,19 @@ describe.sequential('dlx', () => {
316481
expect(packages).toContain('package-2')
317482
expect(packages).toHaveLength(2)
318483
})
484+
485+
it('async version should return sorted list', async () => {
486+
await ensureDlxDir()
487+
const pkgZDir = getDlxPackageDir('z-package')
488+
const pkgADir = getDlxPackageDir('a-package')
489+
const pkgMDir = getDlxPackageDir('m-package')
490+
await fs.promises.mkdir(pkgZDir, { recursive: true })
491+
await fs.promises.mkdir(pkgADir, { recursive: true })
492+
await fs.promises.mkdir(pkgMDir, { recursive: true })
493+
494+
const packages = await listDlxPackagesAsync()
495+
expect(packages).toEqual(['a-package', 'm-package', 'z-package'])
496+
})
319497
})
320498

321499
describe('removeDlxPackage / removeDlxPackageSync', () => {

0 commit comments

Comments
 (0)