Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions cmd/goose/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@
logPath := filepath.Join(logDir, fmt.Sprintf("goose-%s.log", time.Now().Format("2006-01-02")))
logFile, err := os.OpenFile(logPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0o600)
if err != nil {
slog.Error("Failed to open log file", "error", err)

Check failure on line 222 in cmd/goose/main.go

View workflow job for this annotation

GitHub Actions / golangci-lint

add-constant: string literal "error" appears, at least, 6 times, create a named constant for it (revive)
} else {
// Update logger to write to both stderr and file
multiHandler := &MultiHandler{
Expand Down Expand Up @@ -455,11 +455,19 @@

// Update health status on success
app.mu.Lock()
previousFailures := app.consecutiveFailures
app.lastSuccessfulFetch = time.Now()
app.consecutiveFailures = 0
app.lastFetchError = ""
app.mu.Unlock()

// Restore normal tray icon after successful fetch
if previousFailures > 0 {
slog.Info("[RECOVERY] Network recovered, restoring tray icon",
"previousFailures", previousFailures)
}
app.setTrayTitle()

// Update state atomically
app.mu.Lock()
// Log PRs that were removed (likely merged/closed)
Expand Down Expand Up @@ -584,11 +592,19 @@

// Update health status on success
app.mu.Lock()
previousFailures := app.consecutiveFailures
app.lastSuccessfulFetch = time.Now()
app.consecutiveFailures = 0
app.lastFetchError = ""
app.mu.Unlock()

// Restore normal tray icon after successful fetch
if previousFailures > 0 {
slog.Info("[RECOVERY] Network recovered, restoring tray icon",
"previousFailures", previousFailures)
}
app.setTrayTitle()

// Update state
app.mu.Lock()
app.incoming = incoming
Expand Down Expand Up @@ -660,7 +676,7 @@
return
}
if err := openURL(ctx, pr.URL); err != nil {
slog.Error("[BROWSER] Failed to auto-open PR", "url", pr.URL, "error", err)

Check failure on line 679 in cmd/goose/main.go

View workflow job for this annotation

GitHub Actions / golangci-lint

add-constant: string literal "url" appears, at least, 6 times, create a named constant for it (revive)
} else {
app.browserRateLimiter.RecordOpen(pr.URL)
slog.Info("[BROWSER] Successfully opened PR in browser",
Expand Down
44 changes: 44 additions & 0 deletions cmd/goose/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,50 @@ func TestMenuItemTitleTransition(t *testing.T) {
_ = ctx // Unused in this test but would be used for real menu operations
}

// TestTrayIconRestoredAfterNetworkRecovery tests that the tray icon is restored
// to normal after network failures are resolved.
func TestTrayIconRestoredAfterNetworkRecovery(t *testing.T) {
ctx := context.Background()
mock := &MockSystray{}
app := &App{
mu: sync.RWMutex{},
stateManager: NewPRStateManager(time.Now().Add(-35 * time.Second)), // Past grace period
blockedPRTimes: make(map[string]time.Time),
hiddenOrgs: make(map[string]bool),
seenOrgs: make(map[string]bool),
browserRateLimiter: NewBrowserRateLimiter(30*time.Second, 5, defaultMaxBrowserOpensDay),
systrayInterface: mock,
menuInitialized: true,
}

// Initial state - successful fetch with some PRs
app.incoming = []PR{
{Repository: "test/repo", Number: 1, NeedsReview: true, UpdatedAt: time.Now()},
}
app.setTrayTitle()
initialTitle := mock.title
if initialTitle != "🪿 1" {
t.Errorf("Expected initial tray title '🪿 1', got %q", initialTitle)
}

// Simulate network failure - updatePRs would set warning icon and return early
app.consecutiveFailures = 3
app.lastFetchError = "network timeout"
// In the old code, rebuildMenu would be called but return early, never calling setTrayTitle()
app.rebuildMenu(ctx)
// The mock systray won't have the warning icon because rebuildMenu doesn't set it directly

// Simulate network recovery - this should restore the normal icon
app.consecutiveFailures = 0
app.lastFetchError = ""
// With our fix, setTrayTitle() is now called after successful fetch
app.setTrayTitle()
recoveredTitle := mock.title
if recoveredTitle != "🪿 1" {
t.Errorf("Expected tray title to be restored to '🪿 1' after recovery, got %q", recoveredTitle)
}
}

// TestTrayTitleUpdates tests that the tray title updates correctly based on PR counts.
func TestTrayTitleUpdates(t *testing.T) {
app := &App{
Expand Down
Loading