mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-08-09 16:23:27 +08:00
🐛 fix(update): 同步 Windows Portable 更新版本
- 版本化 Portable 更新后切换到新包文件名并清理旧文件 - 下载完成进度采用后端实际返回的版本与资产 - 补充前端竞态和 Windows PowerShell 集成回归测试
This commit is contained in:
@@ -167,6 +167,63 @@ describe('useAppUpdateManager', () => {
|
||||
}));
|
||||
});
|
||||
|
||||
it('uses the backend download result version for progress and cached update metadata', async () => {
|
||||
backendApp.CheckForUpdates.mockResolvedValue({
|
||||
success: true,
|
||||
data: {
|
||||
hasUpdate: true,
|
||||
channel: 'dev',
|
||||
currentVersion: 'dev-current',
|
||||
latestVersion: 'dev-old',
|
||||
assetName: 'GoNavi-dev-old-Windows-Amd64-Portable.exe',
|
||||
packageType: 'portable',
|
||||
installMode: 'portable',
|
||||
autoRelaunch: true,
|
||||
downloaded: false,
|
||||
assetSize: 4096,
|
||||
},
|
||||
});
|
||||
backendApp.DownloadUpdate.mockResolvedValue({
|
||||
success: true,
|
||||
data: {
|
||||
info: {
|
||||
hasUpdate: true,
|
||||
channel: 'dev',
|
||||
currentVersion: 'dev-current',
|
||||
latestVersion: 'dev-new',
|
||||
assetName: 'GoNavi-dev-new-Windows-Amd64-Portable.exe',
|
||||
packageType: 'portable',
|
||||
installMode: 'portable',
|
||||
autoRelaunch: true,
|
||||
},
|
||||
downloadPath: 'C:\\GoNavi\\GoNavi-dev-new-Windows-Amd64-Portable.exe',
|
||||
packageType: 'portable',
|
||||
installMode: 'portable',
|
||||
autoRelaunch: true,
|
||||
},
|
||||
});
|
||||
|
||||
renderHook();
|
||||
await act(async () => {
|
||||
await hook?.checkForUpdates(false);
|
||||
});
|
||||
await act(async () => {
|
||||
await hook?.downloadUpdate(hook?.lastUpdateInfo!, false);
|
||||
});
|
||||
|
||||
expect(hook?.lastUpdateInfo).toMatchObject({
|
||||
latestVersion: 'dev-new',
|
||||
assetName: 'GoNavi-dev-new-Windows-Amd64-Portable.exe',
|
||||
downloaded: true,
|
||||
downloadPath: 'C:\\GoNavi\\GoNavi-dev-new-Windows-Amd64-Portable.exe',
|
||||
});
|
||||
expect(hook?.updateDownloadProgress).toMatchObject({
|
||||
version: 'dev-new',
|
||||
key: 'dev:dev-new:portable:gonavi-dev-new-windows-amd64-portable.exe',
|
||||
status: 'done',
|
||||
});
|
||||
});
|
||||
|
||||
it('keeps same-version Portable and MSI downloads in separate cache identities', async () => {
|
||||
const portableInfo = {
|
||||
hasUpdate: true,
|
||||
|
||||
@@ -314,6 +314,7 @@ export const useAppUpdateManager = ({
|
||||
const total = prev.total > 0 ? prev.total : (info.assetSize || 0);
|
||||
return {
|
||||
...prev,
|
||||
version: downloadedInfo.latestVersion,
|
||||
key: downloadedKey,
|
||||
status: 'done',
|
||||
percent: 100,
|
||||
@@ -328,6 +329,7 @@ export const useAppUpdateManager = ({
|
||||
// 下载到 100% 后停留在就绪态,由用户确认当前安装方式对应的更新动作。
|
||||
setUpdateDownloadProgress((prev) => ({
|
||||
...prev,
|
||||
version: downloadedInfo.latestVersion,
|
||||
open: prev.open || !updateUserDismissedRef.current,
|
||||
status: 'done',
|
||||
percent: 100,
|
||||
|
||||
@@ -138,8 +138,25 @@ func launchWindowsUpdateWithCleanup(staged *stagedUpdate, targetExe string, pid
|
||||
return nil
|
||||
}
|
||||
|
||||
func resolveWindowsUpdateFinalTargetPath(currentTarget string, _ string) string {
|
||||
return strings.TrimSpace(currentTarget)
|
||||
func resolveWindowsUpdateFinalTargetPath(currentTarget string, sourcePath string) string {
|
||||
currentTarget = strings.TrimSpace(currentTarget)
|
||||
if currentTarget == "" {
|
||||
return currentTarget
|
||||
}
|
||||
currentName := filepath.Base(currentTarget)
|
||||
sourceName := filepath.Base(strings.TrimSpace(sourcePath))
|
||||
if isVersionedWindowsUpdatePackageName(currentName) && isVersionedWindowsUpdatePackageName(sourceName) {
|
||||
return filepath.Join(filepath.Dir(currentTarget), sourceName)
|
||||
}
|
||||
return currentTarget
|
||||
}
|
||||
|
||||
func isVersionedWindowsUpdatePackageName(name string) bool {
|
||||
trimmed := strings.TrimSpace(name)
|
||||
lower := strings.ToLower(trimmed)
|
||||
return strings.HasPrefix(trimmed, "GoNavi-") &&
|
||||
strings.Contains(trimmed, "-Windows-") &&
|
||||
strings.HasSuffix(lower, ".exe")
|
||||
}
|
||||
|
||||
func prepareWindowsStagedUpdateAsset(sourcePath string, stagedDir string) (string, error) {
|
||||
|
||||
@@ -120,12 +120,22 @@ func TestPrepareWindowsStagedUpdateAssetMovesPackageIntoStagedDir(t *testing.T)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveWindowsUpdateFinalTargetPathPreservesExecutablePath(t *testing.T) {
|
||||
func TestResolveWindowsUpdateFinalTargetPathUsesDownloadedVersionedPortableName(t *testing.T) {
|
||||
currentTarget := filepath.Join("D:", "软件", "数据库管理工具", "GoNavi", "GoNavi-dev-f930ffe-Windows-Amd64.exe")
|
||||
stagedSource := filepath.Join("C:", "Temp", "gonavi-updates", "GoNavi-0.8.5-Windows-Amd64.exe")
|
||||
stagedSource := filepath.Join("C:", "Temp", "gonavi-updates", "GoNavi-dev-2d5f246-Windows-Amd64-Portable.exe")
|
||||
want := filepath.Join(filepath.Dir(currentTarget), filepath.Base(stagedSource))
|
||||
|
||||
if got := resolveWindowsUpdateFinalTargetPath(currentTarget, stagedSource); got != want {
|
||||
t.Fatalf("Windows update target = %q, want downloaded versioned path %q", got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveWindowsUpdateFinalTargetPathKeepsFixedExecutablePath(t *testing.T) {
|
||||
currentTarget := filepath.Join("D:", "软件", "数据库管理工具", "GoNavi", "GoNavi.exe")
|
||||
stagedSource := filepath.Join("C:", "Temp", "gonavi-updates", "GoNavi-dev-2d5f246-Windows-Amd64-Portable.exe")
|
||||
|
||||
if got := resolveWindowsUpdateFinalTargetPath(currentTarget, stagedSource); got != currentTarget {
|
||||
t.Fatalf("Windows update target = %q, want current executable path %q", got, currentTarget)
|
||||
t.Fatalf("Windows update target = %q, want fixed executable path %q", got, currentTarget)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -88,6 +88,65 @@ func TestWindowsPowerShellUpdaterHandlesUnicodeAndShellMetacharacters(t *testing
|
||||
t.Logf("updated target at %s (%s)", targetPath, fmt.Sprintf("%x", wantHash[:8]))
|
||||
}
|
||||
|
||||
func TestWindowsPowerShellUpdaterRenamesVersionedPortableExecutable(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
installDir := filepath.Join(root, "install")
|
||||
stagedDir := filepath.Join(root, "stage")
|
||||
for _, dir := range []string{installDir, stagedDir} {
|
||||
if err := os.MkdirAll(dir, 0o755); err != nil {
|
||||
t.Fatalf("MkdirAll %q: %v", dir, err)
|
||||
}
|
||||
}
|
||||
|
||||
sourcePath := filepath.Join(stagedDir, "GoNavi-dev-new-Windows-Amd64-Portable.exe")
|
||||
build := exec.Command("go", "build", "-ldflags=-H=windowsgui", "-o", sourcePath, "./testdata/windows_update_helper")
|
||||
if output, err := build.CombinedOutput(); err != nil {
|
||||
t.Fatalf("build update helper: %v\n%s", err, output)
|
||||
}
|
||||
sourceData, err := os.ReadFile(sourcePath)
|
||||
if err != nil {
|
||||
t.Fatalf("ReadFile source: %v", err)
|
||||
}
|
||||
wantHash := sha256.Sum256(sourceData)
|
||||
|
||||
currentTargetPath := filepath.Join(installDir, "GoNavi-dev-old-Windows-Amd64.exe")
|
||||
targetPath := filepath.Join(installDir, "GoNavi-dev-new-Windows-Amd64-Portable.exe")
|
||||
if err := os.WriteFile(currentTargetPath, []byte("old executable"), 0o755); err != nil {
|
||||
t.Fatalf("WriteFile old target: %v", err)
|
||||
}
|
||||
logPath := filepath.Join(stagedDir, "gonavi-update-windows-versioned.log")
|
||||
scriptPath := filepath.Join(stagedDir, "update.ps1")
|
||||
if err := os.WriteFile(scriptPath, []byte(buildWindowsPowerShellScript()), 0o644); err != nil {
|
||||
t.Fatalf("WriteFile updater: %v", err)
|
||||
}
|
||||
|
||||
cmd := buildWindowsLaunchCommand(scriptPath, windowsUpdateLaunchContext{
|
||||
SourcePath: sourcePath,
|
||||
TargetPath: targetPath,
|
||||
CurrentTargetPath: currentTargetPath,
|
||||
StagedDir: stagedDir,
|
||||
LogPath: logPath,
|
||||
PID: 2147483647,
|
||||
})
|
||||
if output, err := cmd.CombinedOutput(); err != nil {
|
||||
logData, _ := os.ReadFile(logPath)
|
||||
t.Fatalf("run versioned updater: %v\nstdout/stderr:\n%s\nlog:\n%s", err, output, logData)
|
||||
}
|
||||
|
||||
// The helper remains alive through the updater's launch health check.
|
||||
time.Sleep(7 * time.Second)
|
||||
targetData, err := os.ReadFile(targetPath)
|
||||
if err != nil {
|
||||
t.Fatalf("ReadFile renamed target: %v", err)
|
||||
}
|
||||
if gotHash := sha256.Sum256(targetData); gotHash != wantHash {
|
||||
t.Fatalf("renamed target hash = %x, want %x", gotHash, wantHash)
|
||||
}
|
||||
if _, err := os.Stat(currentTargetPath); !os.IsNotExist(err) {
|
||||
t.Fatalf("expected old versioned executable to be removed, stat err=%v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestWindowsPowerShellUpdaterSelectsExactTargetFilenameRecursivelyFromZip(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
installDir := filepath.Join(root, "install")
|
||||
|
||||
Reference in New Issue
Block a user