🎯 Areas for Improvement
1. Table-Driven Consolidation for checkReleaseCoolDown variants
TestCheckReleaseCoolDown_OldRelease, TestCheckReleaseCoolDown_RecentRelease, TestCheckReleaseCoolDown_OldReleaseReturnsPublishedAt, and TestCheckReleaseCoolDown_FetchError are four separate test functions with nearly identical setup (stub getReleasePublishedAtFn, defer restore, call the function). They would benefit from being a single table-driven test.
Current (separate functions):
func TestCheckReleaseCoolDown_OldRelease(t *testing.T) {
orig := getReleasePublishedAtFn
defer func() { getReleasePublishedAtFn = orig }()
getReleasePublishedAtFn = func(_ context.Context, _, _ string) (time.Time, error) {
return time.Now().Add(-10 * 24 * time.Hour), nil
}
result := checkReleaseCoolDown(context.Background(), "owner/repo", "v1.2.0", 7*24*time.Hour)
assert.False(t, result.InCoolDown, "release older than cooldown period should not be blocked")
}
func TestCheckReleaseCoolDown_RecentRelease(t *testing.T) {
// ... same boilerplate again ...
}
Recommended (table-driven):
func TestCheckReleaseCoolDown(t *testing.T) {
tests := []struct {
name string
publishedAt func() time.Time
fetchErr error
coolDown time.Duration
wantInCoolDown bool
wantPublished bool // PublishedAt should be non-zero
wantMsgContain string
}{
{
name: "disabled cooldown",
publishedAt: func() time.Time { return time.Now() },
coolDown: 0,
wantInCoolDown: false,
},
{
name: "old release not blocked",
publishedAt: func() time.Time { return time.Now().Add(-10 * 24 * time.Hour) },
coolDown: 7 * 24 * time.Hour,
wantInCoolDown: false,
wantPublished: true,
},
{
name: "recent release blocked",
publishedAt: func() time.Time { return time.Now().Add(-2 * 24 * time.Hour) },
coolDown: 7 * 24 * time.Hour,
wantInCoolDown: true,
wantPublished: true,
wantMsgContain: "v1.2.0",
},
{
name: "fetch error allows update (fail-open)",
fetchErr: errors.New("network error"),
coolDown: 7 * 24 * time.Hour,
wantInCoolDown: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
orig := getReleasePublishedAtFn
defer func() { getReleasePublishedAtFn = orig }()
getReleasePublishedAtFn = func(_ context.Context, _, _ string) (time.Time, error) {
if tt.fetchErr != nil {
return time.Time{}, tt.fetchErr
}
return tt.publishedAt(), nil
}
result := checkReleaseCoolDown(context.Background(), "owner/repo", "v1.2.0", tt.coolDown)
assert.Equal(t, tt.wantInCoolDown, result.InCoolDown, "InCoolDown mismatch for %q", tt.name)
if tt.wantPublished {
assert.False(t, result.PublishedAt.IsZero(), "PublishedAt should be non-zero for %q", tt.name)
}
if tt.wantMsgContain != "" {
assert.Contains(t, result.Message, tt.wantMsgContain, "message should contain tag for %q", tt.name)
}
})
}
}
Why this matters: Consolidating to a table-driven test eliminates ~60 lines of boilerplate, makes adding new scenarios trivial, and makes the test intent clearer at a glance.
2. Named Field in TestIsExemptFromCoolDown table
The TestIsExemptFromCoolDown struct only has repo and want fields. Using tt.repo as the subtest name works, but adding a name field makes the intent of each case explicit and helps distinguish between semantically similar cases.
Current:
tests := []struct {
repo string
want bool
}{
{"actions/checkout", true},
{"owner/repo", false},
// ...
}
for _, tt := range tests {
t.Run(tt.repo, func(t *testing.T) {
got := isExemptFromCoolDown(tt.repo)
assert.Equal(t, tt.want, got, ...)
})
}
Recommended:
tests := []struct {
name string
repo string
want bool
}{
{name: "actions org is exempt", repo: "actions/checkout", want: true},
{name: "github org is exempt", repo: "github/gh-aw", want: true},
{name: "actions sub-path exempt", repo: "actions/cache/restore", want: true},
{name: "non-exempt org blocked", repo: "owner/repo", want: false},
{name: "prefix-not-actions blocked", repo: "notactions/checkout", want: false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { ... })
}
3. Missing message-content assertions for checkReleaseCoolDownWithDate
TestCheckReleaseCoolDownWithDate_InCoolDown only checks that Message contains the tag (v1.2.0), but the source generates a richer message including "cool down", "remaining", and "cooldown period". Adding assertions for these key phrases protects against message regressions.
Recommended additions:
func TestCheckReleaseCoolDownWithDate_InCoolDown(t *testing.T) {
publishedAt := time.Now().Add(-2 * 24 * time.Hour)
result := checkReleaseCoolDownWithDate("owner/repo", "v1.2.0", publishedAt, 7*24*time.Hour)
assert.True(t, result.InCoolDown, "release 2d old should be in cooldown with 7d window")
assert.Contains(t, result.Message, "v1.2.0", "message should mention the tag")
assert.Contains(t, result.Message, "cool down", "message should mention cooldown")
assert.Contains(t, result.Message, "remaining", "message should mention remaining time")
assert.Contains(t, result.Message, "owner/repo","message should mention the repository")
}
4. Missing negative-duration edge case for formatCoolDownDuration
The source code explicitly handles d < 0 by clamping to 0, but the test table doesn't include a negative-duration case. This leaves the clamping logic untested.
Recommended addition to TestFormatCoolDownDuration table:
{-1 * time.Hour, "< 1h"}, // negative duration clamped to 0
5. Missing test for checkReleaseCoolDownWithDate — PublishedAt field
The function signature returns a coolDownCheckResult, but when InCoolDown is false the test never verifies whether PublishedAt is zero (it should be, since checkReleaseCoolDownWithDate never sets it — only checkReleaseCoolDown does). Adding this assertion makes the contract explicit.
Recommended:
func TestCheckReleaseCoolDownWithDate_NotInCoolDown(t *testing.T) {
publishedAt := time.Now().Add(-10 * 24 * time.Hour)
result := checkReleaseCoolDownWithDate("owner/repo", "v1.2.0", publishedAt, 7*24*time.Hour)
assert.False(t, result.InCoolDown, "release 10d old should not be in cooldown with 7d window")
assert.True(t, result.PublishedAt.IsZero(), "checkReleaseCoolDownWithDate should not populate PublishedAt")
assert.Empty(t, result.Message, "no message expected when not in cooldown")
}
Overview
The test file
pkg/cli/update_cooldown_test.gowas selected for quality improvement by the Daily Testify Uber Super Expert. This file tests the release cooldown functionality and is already well-written with good testify usage. This issue provides specific, actionable refinements to elevate it further.Current State
pkg/cli/update_cooldown_test.gopkg/cli/update_cooldown.goparseCoolDownFlag,isExemptFromCoolDown,checkReleaseCoolDown,checkReleaseCoolDownWithDate,formatCoolDownDurationTest Quality Analysis
Strengths ✅
//go:build !integrationis correctly placed at the top.if err != nil { t.Fatal(...) }anti-patterns;require/assertused throughout.TestParseCoolDownFlagandTestFormatCoolDownDurationcorrectly use table-driven tests.require.NoErrorused on the critical path,assert.*for non-fatal checks.🎯 Areas for Improvement
1. Table-Driven Consolidation for
checkReleaseCoolDownvariantsTestCheckReleaseCoolDown_OldRelease,TestCheckReleaseCoolDown_RecentRelease,TestCheckReleaseCoolDown_OldReleaseReturnsPublishedAt, andTestCheckReleaseCoolDown_FetchErrorare four separate test functions with nearly identical setup (stubgetReleasePublishedAtFn, defer restore, call the function). They would benefit from being a single table-driven test.Current (separate functions):
Recommended (table-driven):
Why this matters: Consolidating to a table-driven test eliminates ~60 lines of boilerplate, makes adding new scenarios trivial, and makes the test intent clearer at a glance.
2. Named Field in
TestIsExemptFromCoolDowntableThe
TestIsExemptFromCoolDownstruct only hasrepoandwantfields. Usingtt.repoas the subtest name works, but adding anamefield makes the intent of each case explicit and helps distinguish between semantically similar cases.Current:
Recommended:
3. Missing message-content assertions for
checkReleaseCoolDownWithDateTestCheckReleaseCoolDownWithDate_InCoolDownonly checks thatMessagecontains the tag (v1.2.0), but the source generates a richer message including "cool down", "remaining", and "cooldown period". Adding assertions for these key phrases protects against message regressions.Recommended additions:
4. Missing negative-duration edge case for
formatCoolDownDurationThe source code explicitly handles
d < 0by clamping to 0, but the test table doesn't include a negative-duration case. This leaves the clamping logic untested.Recommended addition to
TestFormatCoolDownDurationtable:{-1 * time.Hour, "< 1h"}, // negative duration clamped to 05. Missing test for
checkReleaseCoolDownWithDate—PublishedAtfieldThe function signature returns a
coolDownCheckResult, but whenInCoolDownisfalsethe test never verifies whetherPublishedAtis zero (it should be, sincecheckReleaseCoolDownWithDatenever sets it — onlycheckReleaseCoolDowndoes). Adding this assertion makes the contract explicit.Recommended:
📋 Implementation Guidelines
Priority Order
TestCheckReleaseCoolDown_*functions into a single table-driven test (eliminates boilerplate, improves extensibility)namefield toTestIsExemptFromCoolDowntableTestCheckReleaseCoolDownWithDate_InCoolDownTestFormatCoolDownDurationPublishedAtzero-value assertion toTestCheckReleaseCoolDownWithDate_NotInCoolDownBest Practices from
scratchpad/testing.mdrequire.*for critical setup (stops test on failure)assert.*for test validations (continues checking)t.Run()and descriptive namesTesting Commands
Acceptance Criteria
TestCheckReleaseCoolDown_*functions consolidated into a single table-drivenTestCheckReleaseCoolDownTestIsExemptFromCoolDownstruct includes anamefield; subtests uset.Run(tt.name, ...)TestCheckReleaseCoolDownWithDate_InCoolDownasserts on"cool down","remaining", and"owner/repo"in the messageTestFormatCoolDownDurationtable includes a negative-duration case (-1 * time.Hour → "< 1h")TestCheckReleaseCoolDownWithDate_NotInCoolDownassertsPublishedAt.IsZero()andMessage == ""make test-unitscratchpad/testing.mdAdditional Context
scratchpad/testing.mdfor comprehensive testing patternspkg/cli/update_cooldown_test.goitself — it is a good baseline alreadyPriority: Low
Effort: Small (~30-40 lines net reduction after refactoring)
Expected Impact: Cleaner tests, better regression protection for message content, easier addition of new cooldown scenarios
Files Involved:
pkg/cli/update_cooldown_test.gopkg/cli/update_cooldown.go