Files
MyGoNavi/internal/app/application_quit_test.go
Syngnat e1fb5b12d4 🐛 fix(update): 修复 Windows 在线更新中文路径失效
- 将 Windows 更新脚本统一为 PowerShell,并通过 Unicode 环境变量传递中文与特殊字符路径
- 保留当前可执行文件路径,补充替换重试、启动健康检查、失败回滚和严格 ZIP 选择
- 更新前复用未保存 SQL 确认流程,并延长退出兜底时间
- 新增 Windows 原生集成测试并接入 dev/release 构建
2026-07-10 13:31:34 +08:00

162 lines
4.9 KiB
Go

package app
import (
"context"
"testing"
"time"
)
func TestApplicationBeforeCloseEmitsPromptOnceUntilCancelled(t *testing.T) {
originalEmit := emitApplicationBeforeCloseRequest
originalQuit := quitApplicationRuntime
t.Cleanup(func() {
emitApplicationBeforeCloseRequest = originalEmit
quitApplicationRuntime = originalQuit
})
var emitted []string
emitApplicationBeforeCloseRequest = func(_ context.Context, eventName string, _ ...interface{}) {
emitted = append(emitted, eventName)
}
quitApplicationRuntime = func(context.Context) {}
app := NewAppWithSecretStore(nil)
handler := NewBeforeCloseHandler(app)
if prevent := handler(context.Background()); !prevent {
t.Fatal("expected first close request to be prevented")
}
if len(emitted) != 1 || emitted[0] != applicationBeforeCloseRequestEvent {
t.Fatalf("expected one before-close event, got %#v", emitted)
}
if prevent := handler(context.Background()); !prevent {
t.Fatal("expected repeated close request to stay prevented while prompt is open")
}
if len(emitted) != 1 {
t.Fatalf("expected no duplicate prompt event, got %#v", emitted)
}
result := app.CancelApplicationQuit()
if !result.Success {
t.Fatalf("expected cancel quit success, got %#v", result)
}
if prevent := handler(context.Background()); !prevent {
t.Fatal("expected close request after cancellation to be prevented again")
}
if len(emitted) != 2 {
t.Fatalf("expected prompt event after cancellation, got %#v", emitted)
}
}
func TestForceQuitApplicationAllowsNextCloseRequest(t *testing.T) {
originalEmit := emitApplicationBeforeCloseRequest
originalQuit := quitApplicationRuntime
t.Cleanup(func() {
emitApplicationBeforeCloseRequest = originalEmit
quitApplicationRuntime = originalQuit
})
emitApplicationBeforeCloseRequest = func(context.Context, string, ...interface{}) {}
quitCalls := 0
quitApplicationRuntime = func(context.Context) {
quitCalls++
}
app := NewAppWithSecretStore(nil)
app.ctx = context.Background()
if result := app.ForceQuitApplication(); !result.Success {
t.Fatalf("expected force quit success, got %#v", result)
}
if quitCalls != 1 {
t.Fatalf("expected runtime Quit to be called once, got %d", quitCalls)
}
if prevent := NewBeforeCloseHandler(app)(context.Background()); prevent {
t.Fatal("expected next close request to be allowed after force quit")
}
}
func TestInstallUpdateAndRestartAllowsGuardedCloseBeforeFallbackExit(t *testing.T) {
originalEmit := emitApplicationBeforeCloseRequest
originalQuit := quitApplicationRuntime
originalResolveInstallTarget := updateResolveInstallTarget
originalLaunchInstallScript := updateLaunchInstallScript
originalSleep := updateQuitSleep
originalExit := updateExitProcess
t.Cleanup(func() {
emitApplicationBeforeCloseRequest = originalEmit
quitApplicationRuntime = originalQuit
updateResolveInstallTarget = originalResolveInstallTarget
updateLaunchInstallScript = originalLaunchInstallScript
updateQuitSleep = originalSleep
updateExitProcess = originalExit
})
app := NewAppWithSecretStore(nil)
app.ctx = context.Background()
stagedDir := t.TempDir()
app.updateState.staged = &stagedUpdate{
FilePath: stagedDir + "/GoNavi-update.exe",
StagedDir: stagedDir,
}
events := make(chan string, 3)
promptEvents := make(chan struct{}, 1)
guardResults := make(chan bool, 1)
sleepDurations := make(chan time.Duration, 2)
exitCodes := make(chan int, 1)
emitApplicationBeforeCloseRequest = func(context.Context, string, ...interface{}) {
promptEvents <- struct{}{}
}
quitApplicationRuntime = func(ctx context.Context) {
events <- "quit"
guardResults <- app.beforeClose(ctx)
}
updateResolveInstallTarget = func() string {
return stagedDir + "/GoNavi.exe"
}
updateLaunchInstallScript = func(*stagedUpdate) error {
events <- "installer"
return nil
}
updateQuitSleep = func(duration time.Duration) {
sleepDurations <- duration
}
updateExitProcess = func(code int) {
events <- "exit"
exitCodes <- code
}
result := app.InstallUpdateAndRestart()
if !result.Success {
t.Fatalf("expected update installation to start, got %#v", result)
}
for index, want := range []string{"installer", "quit", "exit"} {
select {
case got := <-events:
if got != want {
t.Fatalf("event %d = %q, want %q", index, got, want)
}
case <-time.After(time.Second):
t.Fatalf("timed out waiting for %q event", want)
}
}
if prevented := <-guardResults; prevented {
t.Fatal("expected updater-controlled close to bypass the normal close prompt")
}
select {
case <-promptEvents:
t.Fatal("expected no before-close prompt during update restart")
default:
}
if exitCode := <-exitCodes; exitCode != 0 {
t.Fatalf("expected fallback exit code 0, got %d", exitCode)
}
for index, want := range []time.Duration{300 * time.Millisecond, 35 * time.Second} {
if got := <-sleepDurations; got != want {
t.Fatalf("quit sleep %d = %s, want %s", index, got, want)
}
}
}