mirror of
https://github.com/NanmiCoder/claude-code-haha.git
synced 2026-10-10 20:03:13 +08:00
@@ -543,7 +543,8 @@ describe('release desktop workflow', () => {
|
||||
expect(recoveryHelper).toContain('function Assert-TreeManifestsEqual')
|
||||
expect(recoveryHelper).toContain('function Write-AppModeAtomically')
|
||||
expect(recoveryHelper).toContain('[IO.File]::Replace')
|
||||
expect(recoveryHelper).toContain('GetFinalPathNameByHandle')
|
||||
expect(recoveryHelper).not.toContain('Add-Type')
|
||||
expect(recoveryHelper).toContain('function Assert-NoReparsePointInPath')
|
||||
expect(recoveryHelper).toContain('robocopy.exe')
|
||||
expect(recoveryHelper).not.toMatch(/\/XC|\/XN|\/XO/)
|
||||
expect(recoveryHelper).toContain('Multiple distinct legacy data sources')
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
import { describe, expect, test } from 'bun:test'
|
||||
import { readFileSync } from 'node:fs'
|
||||
|
||||
describe('Windows installer process matching', () => {
|
||||
test('uses a directory-boundary-aware process helper', () => {
|
||||
const installerHook = readFileSync('desktop/build/installer.nsh', 'utf8')
|
||||
const processHelper = readFileSync(
|
||||
'desktop/build/check-install-processes.ps1',
|
||||
'utf8',
|
||||
)
|
||||
|
||||
expect(installerHook).not.toContain('!insertmacro _CHECK_APP_RUNNING')
|
||||
expect(installerHook).toContain('check-install-processes.ps1')
|
||||
expect(installerHook).toContain('!macro CcHahaFindInstallProcess')
|
||||
expect(installerHook).toContain('!macro CcHahaKillInstallProcess')
|
||||
expect(installerHook).toContain('-InstallerPid "$pid"')
|
||||
expect(installerHook).toContain('-InstallerParentPid "$1"')
|
||||
expect(installerHook).toContain('tasklist /FI "USERNAME eq %USERNAME%" /FO CSV /NH >')
|
||||
expect(installerHook).toContain('tasklist process enumeration failed')
|
||||
expect(installerHook).toContain('fallback process filtering failed')
|
||||
expect(installerHook).toMatch(
|
||||
/tasklist process enumeration failed[\s\S]*StrCpy \$\{_RETURN\} 0/,
|
||||
)
|
||||
expect(installerHook).toMatch(
|
||||
/fallback process filtering failed[\s\S]*StrCpy \$\{_RETURN\} 0/,
|
||||
)
|
||||
expect(installerHook).toContain('claude-sidecar-x86_64-pc-windows-msvc.exe')
|
||||
expect(installerHook).toContain('claude-sidecar-aarch64-pc-windows-msvc.exe')
|
||||
expect(installerHook).toContain('/C:"OpenConsole.exe"')
|
||||
expect(installerHook).toContain('/C:"winpty-agent.exe"')
|
||||
expect(installerHook).toContain('/C:"rg.exe"')
|
||||
expect(installerHook).toContain('bundled terminal/search helper')
|
||||
expect(installerHook).toContain('Differently named child processes cannot be attributed')
|
||||
expect(installerHook).not.toContain('| "$FindPath"')
|
||||
|
||||
expect(processHelper).toContain('function Test-PathInsideInstallDirectory')
|
||||
expect(processHelper).toContain(
|
||||
'$rootWithSeparator = $resolvedRoot + [IO.Path]::DirectorySeparatorChar',
|
||||
)
|
||||
expect(processHelper).toContain('[StringComparison]::OrdinalIgnoreCase')
|
||||
expect(processHelper).toContain('$process.ProcessId -eq $InstallerPid')
|
||||
expect(processHelper).not.toContain('$process.ProcessId -eq $InstallerParentPid')
|
||||
expect(processHelper).toContain('Matched protected install process:')
|
||||
expect(processHelper).toContain('Blocked unknown-path application process:')
|
||||
expect(processHelper).toContain('$unknownPathMatches.Add($process)')
|
||||
expect(installerHook).not.toContain('taskkill')
|
||||
expect(installerHook).toContain('refusing to terminate by image name')
|
||||
expect(installerHook.match(/SetErrorLevel 22/g)).toHaveLength(2)
|
||||
expect(installerHook).toMatch(
|
||||
/MessageBox MB_OKCANCEL[\s\S]*SetErrorLevel 22\s+Quit/,
|
||||
)
|
||||
expect(installerHook).toMatch(
|
||||
/MessageBox MB_RETRYCANCEL[\s\S]*SetErrorLevel 22\s+Quit/,
|
||||
)
|
||||
})
|
||||
|
||||
test('keeps sibling-prefix and real install process cases in Windows smoke', () => {
|
||||
const installerSmoke = readFileSync(
|
||||
'desktop/scripts/windows-installer-smoke.ps1',
|
||||
'utf8',
|
||||
)
|
||||
|
||||
expect(installerSmoke).toContain("$siblingDir = \"$installDir Tools\"")
|
||||
expect(installerSmoke).toContain("$siblingProbe = Join-Path $siblingDir 'Claude Code Haha.exe'")
|
||||
expect(installerSmoke).toContain('Sibling-prefix process remains running')
|
||||
expect(installerSmoke).toContain('Install-directory parent process detection')
|
||||
expect(installerSmoke).toContain('Install-directory process was not terminated')
|
||||
expect(installerSmoke).toContain("$bundledHelperProbe = Join-Path $siblingDir 'OpenConsole.exe'")
|
||||
expect(installerSmoke).toContain('No-CLR external bundled-helper process reinstall')
|
||||
expect(installerSmoke).toMatch(
|
||||
/No-CLR external bundled-helper process reinstall' -ExpectedExitCode 22/,
|
||||
)
|
||||
expect(installerSmoke).toContain('No-CLR exact-image fallback terminated an external bundled-helper')
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,94 @@
|
||||
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('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')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user