Skip to content

Commit 8d77398

Browse files
authored
[Repo Assist] refactor(strutil): move formatResetAt to strutil.FormatResetAt (#5042)
🤖 *This PR was created by Repo Assist, an automated AI assistant.* Closes #5032 (Finding 4). ## Summary `formatResetAt` in `server/circuit_breaker.go` is a pure time-formatting utility that already delegates to `strutil.FormatDuration` for its relative portion. Moving it to `internal/strutil/` keeps all time-formatting utilities in one place, improves discoverability, and removes an unexported utility function from a domain-specific file. ## Changes - Add `FormatResetAt(t time.Time) string` to `internal/strutil/format_duration.go` - Add `TestFormatResetAt` with zero-time and non-zero-time cases - Replace the four `formatResetAt(...)` call sites in `circuit_breaker.go` with `strutil.FormatResetAt(...)` - Remove the private `formatResetAt` function from `circuit_breaker.go` ## Test Status Two new tests added in `internal/strutil/format_duration_test.go` cover the zero-time and non-zero-time cases. ⚠️ **Infrastructure note**: `go test ./...` could not be executed in this environment because `proxy.golang.org` is blocked and the module cache is incomplete. The code changes are minimal and mechanically correct. > [!WARNING] > **⚠️ Firewall blocked 1 domain** > > The following domain was blocked by the firewall during workflow execution: > > - `proxy.golang.org` > > To allow these domains, add them to the `network.allowed` list in your workflow frontmatter: > > ```yaml > network: > allowed: > - defaults > - "proxy.golang.org" > ``` > > See [Network Configuration](https://github.github.com/gh-aw/reference/network/) for more information. > Generated by [Repo Assist](https://github.com/github/gh-aw-mcpg/actions/runs/25279282660/agentic_workflow) · ● 6.9M · [◷](https://github.com/search?q=repo%3Agithub%2Fgh-aw-mcpg+%22gh-aw-workflow-id%3A+repo-assist%22&type=pullrequests) > > To install this [agentic workflow](https://github.com/githubnext/agentics/blob/851905c06e905bf362a9f6cc54f912e3df747d55/workflows/repo-assist.md), run > ``` > gh aw add githubnext/agentics@851905c > ``` <!-- gh-aw-agentic-workflow: Repo Assist, engine: copilot, version: 1.0.21, model: auto, id: 25279282660, workflow_id: repo-assist, run: https://github.com/github/gh-aw-mcpg/actions/runs/25279282660 --> <!-- gh-aw-workflow-id: repo-assist -->
2 parents d43bae8 + 6cafdf6 commit 8d77398

4 files changed

Lines changed: 29 additions & 28 deletions

File tree

internal/server/circuit_breaker.go

Lines changed: 4 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -195,12 +195,12 @@ func (cb *circuitBreaker) RecordRateLimit(resetAt time.Time) {
195195
cb.openedAt = cb.nowFunc()
196196
logger.LogError("backend",
197197
"circuit breaker for server %q OPENED after %d consecutive rate-limit errors; resets at %s",
198-
cb.serverID, cb.consecutiveErrors, formatResetAt(cb.resetAt))
198+
cb.serverID, cb.consecutiveErrors, strutil.FormatResetAt(cb.resetAt))
199199
logCircuitBreaker.Printf("server %q circuit breaker CLOSED → OPEN (errors=%d)", cb.serverID, cb.consecutiveErrors)
200200
} else {
201201
logger.LogWarn("backend",
202202
"rate-limit error for server %q (consecutive=%d/%d); resets at %s",
203-
cb.serverID, cb.consecutiveErrors, cb.threshold, formatResetAt(cb.resetAt))
203+
cb.serverID, cb.consecutiveErrors, cb.threshold, strutil.FormatResetAt(cb.resetAt))
204204
}
205205

206206
case circuitHalfOpen:
@@ -209,13 +209,13 @@ func (cb *circuitBreaker) RecordRateLimit(resetAt time.Time) {
209209
cb.openedAt = cb.nowFunc()
210210
logger.LogError("backend",
211211
"circuit breaker for server %q re-OPENED after probe was rate-limited; resets at %s",
212-
cb.serverID, formatResetAt(cb.resetAt))
212+
cb.serverID, strutil.FormatResetAt(cb.resetAt))
213213
logCircuitBreaker.Printf("server %q circuit breaker HALF-OPEN → OPEN (probe rate-limited)", cb.serverID)
214214

215215
case circuitOpen:
216216
// Already open — update reset time.
217217
logger.LogWarn("backend", "server %q circuit breaker still OPEN; resets at %s",
218-
cb.serverID, formatResetAt(cb.resetAt))
218+
cb.serverID, strutil.FormatResetAt(cb.resetAt))
219219
}
220220
}
221221

@@ -226,13 +226,6 @@ func (cb *circuitBreaker) State() circuitBreakerState {
226226
return cb.state
227227
}
228228

229-
// formatResetAt returns a human-readable representation of a reset time.
230-
func formatResetAt(t time.Time) string {
231-
if t.IsZero() {
232-
return "unknown"
233-
}
234-
return fmt.Sprintf("%s (in %s)", t.UTC().Format(time.RFC3339), strutil.FormatDuration(time.Until(t).Round(time.Second)))
235-
}
236229

237230
// buildCircuitBreakers creates per-backend circuit breakers from the configuration.
238231
func buildCircuitBreakers(cfg *config.Config) map[string]*circuitBreaker {

internal/server/circuit_breaker_test.go

Lines changed: 0 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -443,23 +443,6 @@ func TestCircuitBreakerState_String(t *testing.T) {
443443
}
444444
}
445445

446-
// TestFormatResetAt verifies the formatResetAt helper function.
447-
func TestFormatResetAt(t *testing.T) {
448-
t.Parallel()
449-
450-
t.Run("zero time returns 'unknown'", func(t *testing.T) {
451-
t.Parallel()
452-
assert.Equal(t, "unknown", formatResetAt(time.Time{}))
453-
})
454-
455-
t.Run("non-zero time includes RFC3339 timestamp and duration hint", func(t *testing.T) {
456-
t.Parallel()
457-
resetTime := time.Date(2026, 1, 1, 12, 0, 0, 0, time.UTC)
458-
result := formatResetAt(resetTime)
459-
assert.Contains(t, result, "2026-01-01T12:00:00Z", "should contain RFC3339-formatted time")
460-
assert.Contains(t, result, "in ", "should contain duration hint")
461-
})
462-
}
463446

464447
// TestIsRateLimitText_Direct directly verifies isRateLimitText with each pattern and edge cases.
465448
func TestIsRateLimitText_Direct(t *testing.T) {

internal/strutil/format_duration.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,16 @@ import (
55
"time"
66
)
77

8+
// FormatResetAt returns a human-readable representation of a reset time,
9+
// combining an RFC3339 timestamp with a relative countdown (e.g. "2026-05-03T12:00:00Z (in 5.0m)").
10+
// Returns "unknown" when t is the zero value.
11+
func FormatResetAt(t time.Time) string {
12+
if t.IsZero() {
13+
return "unknown"
14+
}
15+
return fmt.Sprintf("%s (in %s)", t.UTC().Format(time.RFC3339), FormatDuration(time.Until(t).Round(time.Second)))
16+
}
17+
818
// FormatDuration formats a duration for display like the debug npm package.
919
// It provides granular formatting from nanoseconds to hours.
1020
func FormatDuration(d time.Duration) string {

internal/strutil/format_duration_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,3 +164,18 @@ func TestFormatDuration(t *testing.T) {
164164
})
165165
}
166166
}
167+
168+
func TestFormatResetAt(t *testing.T) {
169+
t.Run("zero time returns unknown", func(t *testing.T) {
170+
result := FormatResetAt(time.Time{})
171+
assert.Equal(t, "unknown", result)
172+
})
173+
174+
t.Run("non-zero time includes RFC3339 timestamp and relative countdown", func(t *testing.T) {
175+
// Use a fixed future time so the test is deterministic.
176+
future := time.Date(2030, 1, 1, 12, 0, 0, 0, time.UTC)
177+
result := FormatResetAt(future)
178+
assert.Contains(t, result, "2030-01-01T12:00:00Z")
179+
assert.Contains(t, result, " (in ")
180+
})
181+
}

0 commit comments

Comments
 (0)