@@ -10,7 +10,7 @@ import { existsSync, mkdirSync, mkdtempSync, rmSync } from 'node:fs'
1010import { tmpdir } from 'node:os'
1111import path from 'node:path'
1212
13- import { afterEach , beforeEach , describe , expect , it } from 'vitest'
13+ import { afterEach , beforeEach , describe , expect , it , vi } from 'vitest'
1414
1515import {
1616 isDlxPackageInstalled ,
@@ -127,5 +127,87 @@ describe.sequential('dlx/packages', () => {
127127 it ( 'does not throw when removing a non-existent package' , ( ) => {
128128 expect ( ( ) => removeDlxPackageSync ( 'does-not-exist' ) ) . not . toThrow ( )
129129 } )
130+
131+ it ( 'throws a permission-denied error when safeDeleteSync fails with EACCES' , async ( ) => {
132+ // Spy on safeDeleteSync to throw a synthetic EACCES.
133+ const fsModule = await import ( '@socketsecurity/lib/fs' )
134+ const originalSafeDelete = fsModule . safeDeleteSync
135+ const err = new Error (
136+ 'EACCES: permission denied' ,
137+ ) as NodeJS . ErrnoException
138+ err . code = 'EACCES'
139+ const spy = vi
140+ . spyOn ( fsModule , 'safeDeleteSync' )
141+ . mockImplementation ( ( ) => {
142+ throw err
143+ } )
144+ try {
145+ expect ( ( ) => removeDlxPackageSync ( 'locked-pkg' ) ) . toThrow (
146+ / P e r m i s s i o n d e n i e d r e m o v i n g D L X p a c k a g e / ,
147+ )
148+ } finally {
149+ spy . mockRestore ( )
150+ // Sanity: restore is in place.
151+ expect ( fsModule . safeDeleteSync ) . toBe ( originalSafeDelete )
152+ }
153+ } )
154+
155+ it ( 'throws a permission-denied error when safeDeleteSync fails with EPERM' , async ( ) => {
156+ const fsModule = await import ( '@socketsecurity/lib/fs' )
157+ const err = new Error (
158+ 'EPERM: operation not permitted' ,
159+ ) as NodeJS . ErrnoException
160+ err . code = 'EPERM'
161+ const spy = vi
162+ . spyOn ( fsModule , 'safeDeleteSync' )
163+ . mockImplementation ( ( ) => {
164+ throw err
165+ } )
166+ try {
167+ expect ( ( ) => removeDlxPackageSync ( 'eperm-pkg' ) ) . toThrow (
168+ / P e r m i s s i o n d e n i e d r e m o v i n g D L X p a c k a g e / ,
169+ )
170+ } finally {
171+ spy . mockRestore ( )
172+ }
173+ } )
174+
175+ it ( 'throws a read-only-filesystem error when safeDeleteSync fails with EROFS' , async ( ) => {
176+ const fsModule = await import ( '@socketsecurity/lib/fs' )
177+ const err = new Error (
178+ 'EROFS: read-only file system' ,
179+ ) as NodeJS . ErrnoException
180+ err . code = 'EROFS'
181+ const spy = vi
182+ . spyOn ( fsModule , 'safeDeleteSync' )
183+ . mockImplementation ( ( ) => {
184+ throw err
185+ } )
186+ try {
187+ expect ( ( ) => removeDlxPackageSync ( 'rofs-pkg' ) ) . toThrow (
188+ / r e a d - o n l y f i l e s y s t e m / ,
189+ )
190+ } finally {
191+ spy . mockRestore ( )
192+ }
193+ } )
194+
195+ it ( 'throws a generic failure error for unrecognized errno codes' , async ( ) => {
196+ const fsModule = await import ( '@socketsecurity/lib/fs' )
197+ const err = new Error ( 'EBUSY: resource busy' ) as NodeJS . ErrnoException
198+ err . code = 'EBUSY'
199+ const spy = vi
200+ . spyOn ( fsModule , 'safeDeleteSync' )
201+ . mockImplementation ( ( ) => {
202+ throw err
203+ } )
204+ try {
205+ expect ( ( ) => removeDlxPackageSync ( 'busy-pkg' ) ) . toThrow (
206+ / F a i l e d t o r e m o v e D L X p a c k a g e / ,
207+ )
208+ } finally {
209+ spy . mockRestore ( )
210+ }
211+ } )
130212 } )
131213} )
0 commit comments