owned-directory.spec.ts 2.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253
  1. import { existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, rmSync, symlinkSync, writeFileSync } from 'node:fs'
  2. import { tmpdir } from 'node:os'
  3. import { join } from 'node:path'
  4. import { afterEach, expect, it } from 'vitest'
  5. import { removeOwnedDirectory } from '../src/owned-directory.ts'
  6. const roots: string[] = []
  7. function fixture(): string {
  8. const root = mkdtempSync(join(tmpdir(), 'desktop-owned-directory-'))
  9. roots.push(root)
  10. return root
  11. }
  12. afterEach(() => { for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true }) })
  13. it('removes nested files and directories while preserving unknown directory-link targets', () => {
  14. const root = fixture()
  15. const target = join(root, 'target')
  16. mkdirSync(target)
  17. writeFileSync(join(target, 'sentinel'), 'preserved')
  18. const owned = join(root, 'owned')
  19. const nested = join(owned, 'node_modules', '@unknown', 'private')
  20. mkdirSync(nested, { recursive: true })
  21. writeFileSync(join(nested, 'file'), 'remove')
  22. symlinkSync(target, join(nested, 'link'), process.platform === 'win32' ? 'junction' : 'dir')
  23. symlinkSync(join(root, 'missing-target'), join(owned, 'broken'), process.platform === 'win32' ? 'junction' : 'dir')
  24. removeOwnedDirectory(owned)
  25. expect(existsSync(owned)).toBe(false)
  26. expect(readFileSync(join(target, 'sentinel'), 'utf8')).toBe('preserved')
  27. })
  28. it.each([false, true])('unlinks a root directory link with missing target: %s', (missing) => {
  29. const root = fixture()
  30. const target = join(root, 'target')
  31. if (!missing) {
  32. mkdirSync(target)
  33. writeFileSync(join(target, 'sentinel'), 'preserved')
  34. }
  35. const link = join(root, 'link')
  36. symlinkSync(target, link, process.platform === 'win32' ? 'junction' : 'dir')
  37. removeOwnedDirectory(link)
  38. expect(lstatSync(link, { throwIfNoEntry: false })).toBeUndefined()
  39. if (!missing) expect(readFileSync(join(target, 'sentinel'), 'utf8')).toBe('preserved')
  40. })
  41. it('ignores a missing root and refuses to delete a regular-file root', () => {
  42. const root = fixture()
  43. expect(() => { removeOwnedDirectory(join(root, 'missing')) }).not.toThrow()
  44. const file = join(root, 'file')
  45. writeFileSync(file, 'preserved')
  46. expect(() => { removeOwnedDirectory(file) }).toThrow(/not a directory/u)
  47. expect(readFileSync(file, 'utf8')).toBe('preserved')
  48. })