mirror of
https://github.com/Twigpine/zero.git
synced 2026-11-12 08:41:32 -10:00
* fix: address bugs found in a multi-agent codebase audit Five parallel review agents each swept a different area (self-update, TUI keybindings, agent/session handling, sandbox/shell safety, and auth/credential storage) and surfaced nine concrete defects, all fixed here with regression tests: - sandbox: grant_scope path comparisons were case-sensitive, letting a persisted deny grant be bypassed by spelling a Windows path with different case - securefile/oauth: createSecretFile's Windows ERROR_ACCESS_DENIED handling was only applied to the oauth token store, not the shared credstore backend; both also masked the real lock-contention error behind a misleading "file does not exist" timeout - cli: firstUsableProvider ignored OAuth logins when picking a fallback provider, forcing unnecessary re-onboarding for an already-authenticated user - sessions: exec prompt summarization truncated on a raw byte offset, which could split a multi-byte UTF-8 rune and embed invalid UTF-8 into a resumed session's prompt - tui: configurable keybindings had no collision detection, so a remapped chord could silently make a hardcoded shortcut (or another configured binding) permanently unreachable - tools: the Windows shell-syntax pre-flight check wasn't quote-aware and could block harmless commands whose quoted arguments merely contained operator-like text - update: checksum verification didn't cross-check the archive filename it verified against the one being extracted; extractZip didn't reject symlink-mode entries like extractTarGz already did; replaceBinary's restore-on-failure rename had no retry, risking a permanently missing binary on a transient Windows file lock * fix: close remaining PR 481 review findings * Potential fix for pull request finding Co-authored-by: Copilot Autofix powered by AI <[email protected]> * fix(tools): drop stale ; test case after rebasing onto main's MSYS check Rebasing fix/bug-hunt-findings onto main pulled in #476, which replaced this file's regex-based windows_shell_syntax detection with a segment/word-anchored check and deliberately stopped treating ; as a command separator on Windows (cmd.exe doesn't split on ; the way bash does). detectShellCommandIssue itself came out of the rebase unchanged from main (our quote-masking version was fully superseded by #476's more thorough segment-based approach), but the quoted-content test this PR added still asserted the old bash-style semantics for `echo "foo" ; ls -la`, which now correctly returns no issue since it's a single echo invocation with literal arguments under cmd.exe, not a real ls invocation. --------- Co-authored-by: Copilot Autofix powered by AI <[email protected]>
66 lines
1.8 KiB
Go
66 lines
1.8 KiB
Go
//go:build windows
|
|
|
|
package update
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
)
|
|
|
|
func TestReplaceBinaryReplacesRunningBinary(t *testing.T) {
|
|
dir := t.TempDir()
|
|
targetPath := filepath.Join(dir, "zero.exe")
|
|
newPath := filepath.Join(dir, "zero.exe.new")
|
|
|
|
if err := os.WriteFile(targetPath, []byte("old-binary"), 0o755); err != nil {
|
|
t.Fatalf("WriteFile target: %v", err)
|
|
}
|
|
if err := os.WriteFile(newPath, []byte("new-binary"), 0o755); err != nil {
|
|
t.Fatalf("WriteFile new: %v", err)
|
|
}
|
|
|
|
if err := replaceBinary(targetPath, newPath); err != nil {
|
|
t.Fatalf("replaceBinary: %v", err)
|
|
}
|
|
|
|
data, err := os.ReadFile(targetPath)
|
|
if err != nil {
|
|
t.Fatalf("ReadFile target: %v", err)
|
|
}
|
|
if string(data) != "new-binary" {
|
|
t.Fatalf("target content = %q, want %q", data, "new-binary")
|
|
}
|
|
if _, err := os.Stat(targetPath + ".old"); err != nil {
|
|
t.Fatalf("expected the original binary to be preserved at %s.old: %v", targetPath, err)
|
|
}
|
|
}
|
|
|
|
func TestRenameWithRetrySucceedsImmediately(t *testing.T) {
|
|
dir := t.TempDir()
|
|
src := filepath.Join(dir, "src")
|
|
dst := filepath.Join(dir, "dst")
|
|
if err := os.WriteFile(src, []byte("data"), 0o644); err != nil {
|
|
t.Fatalf("WriteFile src: %v", err)
|
|
}
|
|
|
|
if err := renameWithRetry(src, dst); err != nil {
|
|
t.Fatalf("renameWithRetry: %v", err)
|
|
}
|
|
if _, err := os.Stat(dst); err != nil {
|
|
t.Fatalf("expected dst to exist after rename: %v", err)
|
|
}
|
|
}
|
|
|
|
// A permanently-failing rename (source never appears) must exhaust its
|
|
// retries and surface the underlying error, rather than retrying forever.
|
|
func TestRenameWithRetryFailsAfterExhaustingAttempts(t *testing.T) {
|
|
dir := t.TempDir()
|
|
missing := filepath.Join(dir, "does-not-exist")
|
|
dst := filepath.Join(dir, "dst")
|
|
|
|
if err := renameWithRetry(missing, dst); err == nil {
|
|
t.Fatal("expected renameWithRetry to fail for a source that never appears")
|
|
}
|
|
}
|