import { describe, expect, test } from 'bun:test' import { readFileSync } from 'node:fs' describe('Windows installer recovery prerequisites', () => { test('does not compile native path helpers at install time', () => { const recoveryHelper = readFileSync( 'desktop/build/recover-legacy-install-data.ps1', 'utf8', ) expect(recoveryHelper).not.toContain('Add-Type') expect(recoveryHelper).toContain('function Assert-NoReparsePointInPath') expect(recoveryHelper).toContain('$rootAttributes = [IO.File]::GetAttributes($current)') expect(recoveryHelper).toContain( 'contains a reparse point and cannot be recovered safely', ) expect(recoveryHelper).toContain('function Get-CanonicalPathIdentity') expect(recoveryHelper).toContain('System32\\mountvol.exe') expect(recoveryHelper).toContain('SUBST aliases cannot be recovered safely') expect(recoveryHelper).toContain('Possible 8.3 path alias cannot be proven safe') expect(recoveryHelper).toContain('Alternate path alias cannot be recovered safely') expect(recoveryHelper).toContain('Get-ChildItem -LiteralPath $current -Force') expect(recoveryHelper).toContain("([string]$_.Name).Equals($segment") expect(recoveryHelper).toContain("Join-Path $testRoot 'project~notes'") expect(recoveryHelper).toContain("Join-Path $testRoot 'MISSIN~1'") expect(recoveryHelper).not.toContain("'PROGRA~1'") expect(recoveryHelper).toContain('possible 8.3 alias did not fail closed') expect(recoveryHelper).toContain('legal long directory name containing a tilde') expect(recoveryHelper).toContain('extended volume alias did not fail closed') expect(recoveryHelper).toContain('SUBST alias did not fail closed') }) test('skips PowerShell only for a proven default per-user installation', () => { const installerHook = readFileSync('desktop/build/installer.nsh', 'utf8') const fastPathStart = installerHook.indexOf( 'Function CcHahaCanSkipLegacyRecovery', ) const recoveryCall = installerHook.indexOf( 'UAC_AsUser_Call Function CcHahaRecoverLegacy', ) expect(fastPathStart).toBeGreaterThan(-1) expect(fastPathStart).toBeLessThan(recoveryCall) expect(installerHook).toMatch( /Function CcHahaCanSkipLegacyRecovery[\s\S]*\$8 != "trusted-user"/, ) expect(installerHook).toMatch( /StrCpy \$8 "trusted-user"[\s\S]*UAC_IsAdmin[\s\S]*StrCpy \$8 "untrusted-elevated"[\s\S]*UAC_IsInnerInstance[\s\S]*StrCpy \$8 "trusted-uac-outer"[\s\S]*Call CcHahaCanSkipLegacyRecovery/, ) expect(installerHook).toContain( '$ccHahaPerUserInstallLocation == ""', ) expect(installerHook).toContain( '$ccHahaPerMachineInstallLocation != ""', ) expect(installerHook).toContain( '$ccHahaPerMachineUninstallString != ""', ) expect(installerHook).toContain( 'StrCmp $ccHahaPerUserInstallLocation $INSTDIR', ) expect(installerHook).toContain('ReadEnvStr $R0 CLAUDE_CONFIG_DIR') expect(installerHook).toContain( 'IfFileExists "$ccHahaPerUserInstallLocation\\CLAUDE_CONFIG_DIR\\*.*"', ) expect(installerHook).toContain( 'FileOpen $R2 "$R1\\Claude Code Haha\\app-mode.json" r', ) expect(installerHook).toContain('StrCmp $R3 \' "mode": "default",$\\n\'') expect(installerHook).toContain('StrCmp $R3 \' "portable_dir": null$\\n\'') expect(installerHook).toContain('FileClose $R2') expect(installerHook).toMatch( /Call CcHahaCanSkipLegacyRecovery[\s\S]*No legacy data candidates found for the registered per-user installation[\s\S]*UAC_AsUser_Call Function CcHahaRecoverLegacy/, ) }) test('keeps no-CLR default and portable upgrade cases in Windows smoke', () => { const installerSmoke = readFileSync( 'desktop/scripts/windows-installer-smoke.ps1', 'utf8', ) expect(installerSmoke).toContain("$env:COMPLUS_Version = 'v0.0.0-test-invalid-clr'") expect(installerSmoke).toContain('Test-IsProcessElevated') expect(installerSmoke).toContain('Elevated default-mode reinstall without CLR') expect(installerSmoke).toMatch( /Elevated default-mode reinstall without CLR' -ExpectedExitCode 20/, ) expect(installerSmoke).toContain('Trusted-user default-mode reinstall without CLR') expect(installerSmoke).toContain('Portable reinstall without CLR') expect(installerSmoke).toMatch( /Portable reinstall without CLR' -ExpectedExitCode 20/, ) expect(installerSmoke).toContain('Invoke-ProcessExpectFailure') expect(installerSmoke).toContain('must-survive-failed-upgrade') }) })