Skip to content

Commit d558fc8

Browse files
Thomas StrombergThomas Stromberg
authored andcommitted
ctx, lint
1 parent 04bf3e9 commit d558fc8

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

73 files changed

+1094
-1107
lines changed

.golangci.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ linters:
7070
- testableexamples # checks if examples are testable (have an expected output)
7171
- testpackage # makes you use a separate _test package
7272
- paralleltest # not every test should be in parallel
73+
- tparallel # table-driven tests share mock servers for performance
7374
- wrapcheck # not required
7475
- wsl # [too strict and mostly code is not more readable] whitespace linter forces you to use empty lines
7576
- wsl_v5 # [too strict and mostly code is not more readable] add or remove empty lines

cmd/server/main.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -249,8 +249,8 @@ func run(ctx context.Context, cancel context.CancelFunc, cfg *config.ServerConfi
249249
// Get GitHub token from one of the installations
250250
var githubToken string
251251
for _, org := range githubManager.AllOrgs() {
252-
if client, ok := githubManager.ClientForOrg(org); ok {
253-
githubToken = client.InstallationToken(ctx)
252+
if ghClient, ok := githubManager.ClientForOrg(org); ok {
253+
githubToken = ghClient.InstallationToken(ctx)
254254
break
255255
}
256256
}
@@ -731,7 +731,7 @@ func runBotCoordinators(
731731

732732
// Run cleanup once on startup
733733
go func() {
734-
if err := stateStore.Cleanup(); err != nil {
734+
if err := stateStore.Cleanup(context.Background()); err != nil {
735735
slog.Warn("initial state cleanup failed", "error", err)
736736
}
737737
}()
@@ -765,7 +765,7 @@ func runBotCoordinators(
765765
case <-cleanupTicker.C:
766766
// Periodic cleanup of old state data
767767
go func() {
768-
if err := stateStore.Cleanup(); err != nil {
768+
if err := stateStore.Cleanup(context.Background()); err != nil {
769769
slog.Warn("state cleanup failed", "error", err)
770770
} else {
771771
slog.Debug("state cleanup completed successfully")

pkg/bot/bot.go

Lines changed: 19 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -57,15 +57,15 @@ type Coordinator struct {
5757

5858
// StateStore interface for persistent state - allows dependency injection for testing.
5959
type StateStore interface {
60-
Thread(owner, repo string, number int, channelID string) (cache.ThreadInfo, bool)
61-
SaveThread(owner, repo string, number int, channelID string, info cache.ThreadInfo) error
62-
LastDM(userID, prURL string) (time.Time, bool)
63-
RecordDM(userID, prURL string, sentAt time.Time) error
64-
ListDMUsers(prURL string) []string
65-
WasProcessed(eventKey string) bool
66-
MarkProcessed(eventKey string, ttl time.Duration) error
67-
LastNotification(prURL string) time.Time
68-
RecordNotification(prURL string, notifiedAt time.Time) error
60+
Thread(ctx context.Context, owner, repo string, number int, channelID string) (cache.ThreadInfo, bool)
61+
SaveThread(ctx context.Context, owner, repo string, number int, channelID string, info cache.ThreadInfo) error
62+
LastDM(ctx context.Context, userID, prURL string) (time.Time, bool)
63+
RecordDM(ctx context.Context, userID, prURL string, sentAt time.Time) error
64+
ListDMUsers(ctx context.Context, prURL string) []string
65+
WasProcessed(ctx context.Context, eventKey string) bool
66+
MarkProcessed(ctx context.Context, eventKey string, ttl time.Duration) error
67+
LastNotification(ctx context.Context, prURL string) time.Time
68+
RecordNotification(ctx context.Context, prURL string, notifiedAt time.Time) error
6969
Close() error
7070
}
7171

@@ -118,13 +118,13 @@ func New(
118118

119119
// saveThread persists thread info to both cache and persistent storage.
120120
// This ensures threads survive restarts and are available for closed PR updates.
121-
func (c *Coordinator) saveThread(owner, repo string, number int, channelID string, info cache.ThreadInfo) {
121+
func (c *Coordinator) saveThread(ctx context.Context, owner, repo string, number int, channelID string, info cache.ThreadInfo) {
122122
// Save to in-memory cache for fast lookups
123123
key := fmt.Sprintf("%s/%s#%d:%s", owner, repo, number, channelID)
124124
c.threadCache.Set(key, info)
125125

126126
// Persist to state store for cross-instance sharing and restart recovery
127-
if err := c.stateStore.SaveThread(owner, repo, number, channelID, info); err != nil {
127+
if err := c.stateStore.SaveThread(ctx, owner, repo, number, channelID, info); err != nil {
128128
slog.Warn("failed to persist thread to state store",
129129
"pr", fmt.Sprintf("%s/%s#%d", owner, repo, number),
130130
"channel_id", channelID,
@@ -196,7 +196,7 @@ func (c *Coordinator) findOrCreatePRThread(ctx context.Context, channelID, owner
196196
"current_message_preview", initialSearchText[:min(100, len(initialSearchText))])
197197

198198
// Save the found thread (cache + persist)
199-
c.saveThread(owner, repo, prNumber, channelID, cache.ThreadInfo{
199+
c.saveThread(ctx, owner, repo, prNumber, channelID, cache.ThreadInfo{
200200
ThreadTS: initialSearchTS,
201201
ChannelID: channelID,
202202
LastState: prState,
@@ -272,7 +272,7 @@ func (c *Coordinator) findOrCreatePRThread(ctx context.Context, channelID, owner
272272
"note", "this prevented duplicate thread creation during rolling deployment")
273273

274274
// Save it and return (cache + persist)
275-
c.saveThread(owner, repo, prNumber, channelID, cache.ThreadInfo{
275+
c.saveThread(ctx, owner, repo, prNumber, channelID, cache.ThreadInfo{
276276
ThreadTS: crossInstanceCheckTS,
277277
ChannelID: channelID,
278278
LastState: prState,
@@ -295,7 +295,7 @@ func (c *Coordinator) findOrCreatePRThread(ctx context.Context, channelID, owner
295295
}
296296

297297
// Save the new thread (cache + persist)
298-
c.saveThread(owner, repo, prNumber, channelID, cache.ThreadInfo{
298+
c.saveThread(ctx, owner, repo, prNumber, channelID, cache.ThreadInfo{
299299
ThreadTS: newThreadTS,
300300
ChannelID: channelID,
301301
LastState: prState,
@@ -925,7 +925,7 @@ func (c *Coordinator) updateDMMessagesForPR(ctx context.Context, pr prUpdateInfo
925925

926926
// For terminal states (merged/closed), update all users who received DMs
927927
if prState == "merged" || prState == "closed" {
928-
slackUserIDs = c.stateStore.ListDMUsers(prURL)
928+
slackUserIDs = c.stateStore.ListDMUsers(ctx, prURL)
929929
if len(slackUserIDs) == 0 {
930930
slog.Debug("no DM recipients found for merged/closed PR",
931931
"pr", fmt.Sprintf("%s/%s#%d", owner, repo, prNumber),
@@ -983,8 +983,7 @@ func (c *Coordinator) updateDMMessagesForPR(ctx context.Context, pr prUpdateInfo
983983
slog.Info("no analysis available - using state-based emoji fallback",
984984
"pr", fmt.Sprintf("%s/%s#%d", owner, repo, prNumber),
985985
"pr_state", prState)
986-
//nolint:staticcheck // deprecated method kept for backward compatibility
987-
prefix = notify.PrefixForState(prState)
986+
prefix = notify.PrefixForAnalysis("", nil)
988987
}
989988
var action string
990989
switch prState {
@@ -1216,6 +1215,8 @@ func (c *Coordinator) processChannelsInParallel(
12161215

12171216
// processPRForChannel handles PR processing for a single channel (extracted from the main loop).
12181217
// Returns a map of Slack user IDs that were successfully tagged in this channel.
1218+
//
1219+
//nolint:maintidx // Core PR processing logic with necessary complexity for handling notifications
12191220
func (c *Coordinator) processPRForChannel(
12201221
ctx context.Context, prCtx prContext, channelName, workspaceID string,
12211222
) map[string]bool {
@@ -1385,7 +1386,7 @@ func (c *Coordinator) processPRForChannel(
13851386
"next_poll_in", "5m")
13861387
} else {
13871388
// Save updated thread info (cache + persist)
1388-
c.saveThread(owner, repo, prNumber, channelID, cache.ThreadInfo{
1389+
c.saveThread(ctx, owner, repo, prNumber, channelID, cache.ThreadInfo{
13891390
ThreadTS: threadTS,
13901391
ChannelID: channelID,
13911392
LastState: prState,

pkg/bot/bot_sprinkler.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ func (c *Coordinator) lookupPRsForCheckEvent(ctx context.Context, event client.E
118118
// If yes, fetch it via turnclient to see if it contains this commit
119119
// This is cheaper than searching all PRs via GitHub API
120120
mostRecentPR := c.commitPRCache.MostRecentPR(owner, repo)
121+
//nolint:nestif // Complex but necessary cache population logic with early returns
121122
if mostRecentPR > 0 {
122123
slog.Debug("attempting turnclient lookup on most recent PR for repo",
123124
"organization", organization,
@@ -156,14 +157,14 @@ func (c *Coordinator) lookupPRsForCheckEvent(ctx context.Context, event client.E
156157

157158
// Populate cache with all commits from this PR
158159
for _, commit := range checkResult.PullRequest.Commits {
160+
//nolint:revive // Nesting depth acceptable for cache population logic
159161
if commit != "" {
160162
c.commitPRCache.RecordPR(owner, repo, mostRecentPR, commit)
161163
}
162164
}
163165

164166
// Process the PR update since we have fresh data
165-
//nolint:contextcheck // Background context intentional - goroutine must outlive parent timeout
166-
go c.handlePullRequestEventWithData(context.Background(), owner, repo, struct {
167+
c.handlePullRequestEventWithData(ctx, owner, repo, struct {
167168
Action string `json:"action"`
168169
PullRequest struct {
169170
HTMLURL string `json:"html_url"`
@@ -270,7 +271,7 @@ func (c *Coordinator) handleSprinklerEvent(ctx context.Context, event client.Eve
270271

271272
// Try to claim this event atomically using persistent store (Datastore transaction).
272273
// This is the single source of truth for cross-instance deduplication.
273-
if err := c.stateStore.MarkProcessed(eventKey, 24*time.Hour); err != nil {
274+
if err := c.stateStore.MarkProcessed(ctx, eventKey, 24*time.Hour); err != nil {
274275
// Check if this is a race condition vs a database error
275276
if errors.Is(err, state.ErrAlreadyProcessed) {
276277
slog.Info("skipping duplicate event - claimed by this or another instance",

pkg/bot/bot_test.go

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,7 @@ func TestNew_WithGitHubClient(t *testing.T) {
167167
}
168168

169169
func TestSaveThread(t *testing.T) {
170+
ctx := context.Background()
170171
mockSlack := &mockSlackClient{}
171172
configMgr := NewMockConfig().Build()
172173

@@ -190,7 +191,7 @@ func TestSaveThread(t *testing.T) {
190191
ThreadTS: "1234567890.123456",
191192
}
192193

193-
c.saveThread("testorg", "testrepo", 42, "C123456", threadInfo)
194+
c.saveThread(ctx, "testorg", "testrepo", 42, "C123456", threadInfo)
194195

195196
// Check cache
196197
key := "testorg/testrepo#42:C123456"
@@ -220,6 +221,7 @@ func TestSaveThread(t *testing.T) {
220221
}
221222

222223
func TestSaveThread_PersistenceError(t *testing.T) {
224+
ctx := context.Background()
223225
mockSlack := &mockSlackClient{}
224226
configMgr := NewMockConfig().Build()
225227

@@ -245,7 +247,7 @@ func TestSaveThread_PersistenceError(t *testing.T) {
245247
}
246248

247249
// Should still save to cache even if persistence fails
248-
c.saveThread("testorg", "testrepo", 42, "C123456", threadInfo)
250+
c.saveThread(ctx, "testorg", "testrepo", 42, "C123456", threadInfo)
249251

250252
// Check cache (should succeed)
251253
key := "testorg/testrepo#42:C123456"

pkg/bot/cache/commit_pr.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,17 @@ import (
77

88
// CommitPREntry caches recent commit→PR mappings for fast lookup.
99
type CommitPREntry struct {
10-
PRNumber int
11-
HeadSHA string
1210
UpdatedAt time.Time
11+
HeadSHA string
12+
PRNumber int
1313
}
1414

1515
// CommitPRCache provides in-memory caching of commit SHA → PR mappings.
1616
// This allows quick lookup when check events arrive with just a commit SHA,
1717
// avoiding expensive GitHub API calls for recently-seen PRs.
1818
type CommitPRCache struct {
19+
entries map[string][]CommitPREntry
1920
mu sync.RWMutex
20-
entries map[string][]CommitPREntry // "owner/repo" -> recent PRs with commits
2121
}
2222

2323
// NewCommitPRCache creates a new CommitPRCache with initialized maps.

pkg/bot/cache/commit_pr_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -266,20 +266,20 @@ func TestCommitPRCache_Concurrency(t *testing.T) {
266266

267267
// Concurrent writes
268268
done := make(chan bool)
269-
for i := 0; i < 10; i++ {
269+
for i := range 10 {
270270
go func(prNum int) {
271271
cache.RecordPR("owner", "repo", prNum, "commit"+string(rune(prNum)))
272272
done <- true
273273
}(i)
274274
}
275275

276276
// Wait for all writes
277-
for i := 0; i < 10; i++ {
277+
for range 10 {
278278
<-done
279279
}
280280

281281
// Concurrent reads
282-
for i := 0; i < 10; i++ {
282+
for i := range 10 {
283283
go func(prNum int) {
284284
_ = cache.FindPRsForCommit("owner", "repo", "commit"+string(rune(prNum)))
285285
_ = cache.MostRecentPR("owner", "repo")
@@ -288,7 +288,7 @@ func TestCommitPRCache_Concurrency(t *testing.T) {
288288
}
289289

290290
// Wait for all reads
291-
for i := 0; i < 10; i++ {
291+
for range 10 {
292292
<-done
293293
}
294294

pkg/bot/cache/thread.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
// Package cache provides thread information caching for bot operations.
12
package cache
23

34
import (

pkg/bot/cache/thread_test.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@ func TestThreadCache_Concurrency(t *testing.T) {
318318

319319
// Concurrent operations on different keys
320320
done := make(chan bool)
321-
for i := 0; i < 10; i++ {
321+
for i := range 10 {
322322
go func(n int) {
323323
key := "key" + string(rune(n))
324324
info := ThreadInfo{ThreadTS: "123.456", ChannelID: "C123"}
@@ -334,13 +334,13 @@ func TestThreadCache_Concurrency(t *testing.T) {
334334
}
335335

336336
// Wait for all goroutines
337-
for i := 0; i < 10; i++ {
337+
for range 10 {
338338
<-done
339339
}
340340

341341
// Concurrent operations on same key
342342
key := "shared"
343-
for i := 0; i < 10; i++ {
343+
for range 10 {
344344
go func() {
345345
info := ThreadInfo{ThreadTS: "123.456", ChannelID: "C123"}
346346
cache.Set(key, info)
@@ -349,21 +349,21 @@ func TestThreadCache_Concurrency(t *testing.T) {
349349
}()
350350
}
351351

352-
for i := 0; i < 10; i++ {
352+
for range 10 {
353353
<-done
354354
}
355355

356356
// Concurrent MarkCreating on same key (only first should succeed)
357357
successCount := 0
358358
resultChan := make(chan bool, 10)
359359

360-
for i := 0; i < 10; i++ {
360+
for range 10 {
361361
go func() {
362362
resultChan <- cache.MarkCreating("concurrent-test")
363363
}()
364364
}
365365

366-
for i := 0; i < 10; i++ {
366+
for range 10 {
367367
if <-resultChan {
368368
successCount++
369369
}
@@ -374,14 +374,14 @@ func TestThreadCache_Concurrency(t *testing.T) {
374374
}
375375

376376
// Cleanup concurrency test
377-
for i := 0; i < 5; i++ {
377+
for range 5 {
378378
go func() {
379379
cache.Cleanup(1 * time.Hour)
380380
done <- true
381381
}()
382382
}
383383

384-
for i := 0; i < 5; i++ {
384+
for range 5 {
385385
<-done
386386
}
387387

pkg/bot/coordinator_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
package bot
22

33
import (
4+
"context"
45
"testing"
56
"time"
67

78
"github.com/codeGROOVE-dev/slacker/pkg/bot/cache"
89
)
910

1011
func TestCoordinator_saveThread(t *testing.T) {
12+
ctx := context.Background()
1113
// Create mock state store
1214
mockStore := &mockStateStore{
1315
threads: make(map[string]cache.ThreadInfo),
@@ -33,7 +35,7 @@ func TestCoordinator_saveThread(t *testing.T) {
3335
LastEventTime: time.Now(),
3436
}
3537

36-
c.saveThread(owner, repo, number, channelID, info)
38+
c.saveThread(ctx, owner, repo, number, channelID, info)
3739

3840
// Verify thread was saved to cache
3941
cacheKey := owner + "/" + repo + "#123:" + channelID

0 commit comments

Comments
 (0)