@@ -11,6 +11,14 @@ describe('PowerShellInstaller', () => {
1111 let originalPlatform : NodeJS . Platform ;
1212 let originalEnv : NodeJS . ProcessEnv ;
1313
14+ const restoreEnvValue = ( key : string , value : string | undefined ) : void => {
15+ if ( value === undefined ) {
16+ delete process . env [ key ] ;
17+ } else {
18+ process . env [ key ] = value ;
19+ }
20+ } ;
21+
1422 beforeEach ( async ( ) => {
1523 testHomeDir = path . join ( os . tmpdir ( ) , `openspec-powershell-test-${ randomUUID ( ) } ` ) ;
1624 await fs . mkdir ( testHomeDir , { recursive : true } ) ;
@@ -257,6 +265,28 @@ describe('PowerShellInstaller', () => {
257265
258266 expect ( result ) . toBe ( false ) ;
259267 } ) ;
268+
269+ it . skipIf ( process . platform === 'win32' ) ( 'should not create profile directory when parent is not writable' , async ( ) => {
270+ const originalNoAutoConfig = process . env . OPENSPEC_NO_AUTO_CONFIG ;
271+ const restrictedHome = path . join ( testHomeDir , 'restricted-home' ) ;
272+ await fs . mkdir ( restrictedHome ) ;
273+ await fs . chmod ( restrictedHome , 0o555 ) ;
274+ const restrictedInstaller = new PowerShellInstaller ( restrictedHome ) ;
275+ const profileDir = path . dirname ( restrictedInstaller . getProfilePath ( ) ) ;
276+
277+ let result = true ;
278+ try {
279+ delete process . env . OPENSPEC_NO_AUTO_CONFIG ;
280+ result = await restrictedInstaller . configureProfile ( mockScriptPath ) ;
281+ } finally {
282+ restoreEnvValue ( 'OPENSPEC_NO_AUTO_CONFIG' , originalNoAutoConfig ) ;
283+ await fs . chmod ( restrictedHome , 0o755 ) ;
284+ }
285+
286+ const profileDirExists = await fs . access ( profileDir ) . then ( ( ) => true ) . catch ( ( ) => false ) ;
287+ expect ( result ) . toBe ( false ) ;
288+ expect ( profileDirExists ) . toBe ( false ) ;
289+ } ) ;
260290 } ) ;
261291
262292 describe ( 'removeProfileConfig' , ( ) => {
@@ -767,6 +797,26 @@ Register-ArgumentCompleter -CommandName openspec -ScriptBlock $openspecCompleter
767797 expect ( result . message ) . toBe ( 'Completion script uninstalled successfully' ) ;
768798 } ) ;
769799
800+ it . skipIf ( process . platform === 'win32' ) ( 'should uninstall read-only completion script when parent directory is writable' , async ( ) => {
801+ const originalNoAutoConfig = process . env . OPENSPEC_NO_AUTO_CONFIG ;
802+ const targetPath = installer . getInstallationPath ( ) ;
803+ let result : Awaited < ReturnType < PowerShellInstaller [ 'uninstall' ] > > | undefined ;
804+
805+ try {
806+ delete process . env . OPENSPEC_NO_AUTO_CONFIG ;
807+ await installer . install ( mockCompletionScript ) ;
808+ await fs . chmod ( targetPath , 0o444 ) ;
809+ result = await installer . uninstall ( ) ;
810+ } finally {
811+ restoreEnvValue ( 'OPENSPEC_NO_AUTO_CONFIG' , originalNoAutoConfig ) ;
812+ await fs . chmod ( targetPath , 0o644 ) . catch ( ( ) => undefined ) ;
813+ }
814+
815+ const scriptExists = await fs . access ( targetPath ) . then ( ( ) => true ) . catch ( ( ) => false ) ;
816+ expect ( result ?. success ) . toBe ( true ) ;
817+ expect ( scriptExists ) . toBe ( false ) ;
818+ } ) ;
819+
770820 it ( 'should handle both script and config removal' , async ( ) => {
771821 delete process . env . OPENSPEC_NO_AUTO_CONFIG ;
772822 await installer . install ( mockCompletionScript ) ;
0 commit comments