Skip to content

Commit 4419d1d

Browse files
Thomas StrombergThomas Stromberg
authored andcommitted
Simplify DM handling
1 parent 64c97be commit 4419d1d

File tree

13 files changed

+1531
-417
lines changed

13 files changed

+1531
-417
lines changed

pkg/bot/bot.go

Lines changed: 31 additions & 330 deletions
Large diffs are not rendered by default.

pkg/bot/bot_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ func TestMain(m *testing.M) {
2525

2626
// Cleanup (before os.Exit to avoid exitAfterDefer lint error)
2727
mockServer.Close()
28-
_ = os.Unsetenv("TURN_TEST_BACKEND") // Best effort cleanup
28+
_ = os.Unsetenv("TURN_TEST_BACKEND") //nolint:errcheck // Best effort cleanup
2929

3030
os.Exit(code)
3131
}

pkg/bot/coordinator_test_helpers.go

Lines changed: 127 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,23 @@ import (
99

1010
"github.com/codeGROOVE-dev/slacker/pkg/bot/cache"
1111
"github.com/codeGROOVE-dev/slacker/pkg/github"
12+
slackapi "github.com/codeGROOVE-dev/slacker/pkg/slack"
1213
"github.com/codeGROOVE-dev/slacker/pkg/state"
1314
"github.com/slack-go/slack"
1415
)
1516

1617
// mockStateStore implements StateStore interface from bot package.
18+
//
19+
//nolint:govet // fieldalignment optimization would reduce test readability
1720
type mockStateStore struct {
1821
markProcessedErr error
1922
saveThreadErr error
23+
saveDMMessageErr error
2024
threads map[string]cache.ThreadInfo
2125
dmTimes map[string]time.Time
2226
dmUsers map[string][]string
27+
dmMessages map[string]state.DMInfo
28+
pendingDMs []*state.PendingDM
2329
processedEvents map[string]bool
2430
lastNotifications map[string]time.Time
2531
mu sync.Mutex
@@ -85,6 +91,48 @@ func (m *mockStateStore) ListDMUsers(ctx context.Context, prURL string) []string
8591
return []string{}
8692
}
8793

94+
// DMMessage returns DM message info for a user and PR.
95+
func (m *mockStateStore) DMMessage(ctx context.Context, userID, prURL string) (state.DMInfo, bool) {
96+
m.mu.Lock()
97+
defer m.mu.Unlock()
98+
key := userID + ":" + prURL
99+
if m.dmMessages != nil {
100+
if info, ok := m.dmMessages[key]; ok {
101+
return info, true
102+
}
103+
}
104+
return state.DMInfo{}, false
105+
}
106+
107+
// SaveDMMessage saves DM message info for a user and PR.
108+
func (m *mockStateStore) SaveDMMessage(ctx context.Context, userID, prURL string, info state.DMInfo) error {
109+
m.mu.Lock()
110+
defer m.mu.Unlock()
111+
if m.saveDMMessageErr != nil {
112+
return m.saveDMMessageErr
113+
}
114+
key := userID + ":" + prURL
115+
if m.dmMessages == nil {
116+
m.dmMessages = make(map[string]state.DMInfo)
117+
}
118+
m.dmMessages[key] = info
119+
// Also track this user for ListDMUsers
120+
if m.dmUsers == nil {
121+
m.dmUsers = make(map[string][]string)
122+
}
123+
found := false
124+
for _, u := range m.dmUsers[prURL] {
125+
if u == userID {
126+
found = true
127+
break
128+
}
129+
}
130+
if !found {
131+
m.dmUsers[prURL] = append(m.dmUsers[prURL], userID)
132+
}
133+
return nil
134+
}
135+
88136
func (m *mockStateStore) WasProcessed(ctx context.Context, eventKey string) bool {
89137
m.mu.Lock()
90138
defer m.mu.Unlock()
@@ -129,16 +177,35 @@ func (m *mockStateStore) RecordNotification(ctx context.Context, prURL string, n
129177
}
130178

131179
// QueuePendingDM implements notify.Store interface for DM queue management.
132-
func (*mockStateStore) QueuePendingDM(_ *state.PendingDM) error {
133-
return nil // No-op for tests
180+
func (m *mockStateStore) QueuePendingDM(ctx context.Context, dm *state.PendingDM) error {
181+
m.mu.Lock()
182+
defer m.mu.Unlock()
183+
m.pendingDMs = append(m.pendingDMs, dm)
184+
return nil
134185
}
135186

136-
func (*mockStateStore) PendingDMs(_ time.Time) ([]state.PendingDM, error) {
137-
return nil, nil // Return empty list for tests
187+
func (m *mockStateStore) PendingDMs(ctx context.Context, before time.Time) ([]state.PendingDM, error) {
188+
m.mu.Lock()
189+
defer m.mu.Unlock()
190+
var result []state.PendingDM
191+
for _, dm := range m.pendingDMs {
192+
if dm.SendAfter.Before(before) {
193+
result = append(result, *dm)
194+
}
195+
}
196+
return result, nil
138197
}
139198

140-
func (*mockStateStore) RemovePendingDM(_ string) error {
141-
return nil // No-op for tests
199+
func (m *mockStateStore) RemovePendingDM(ctx context.Context, id string) error {
200+
m.mu.Lock()
201+
defer m.mu.Unlock()
202+
for i, dm := range m.pendingDMs {
203+
if dm.ID == id {
204+
m.pendingDMs = append(m.pendingDMs[:i], m.pendingDMs[i+1:]...)
205+
break
206+
}
207+
}
208+
return nil
142209
}
143210

144211
func (*mockStateStore) Close() error {
@@ -149,26 +216,30 @@ func (*mockStateStore) Close() error {
149216
//
150217
//nolint:govet // fieldalignment optimization would reduce test readability
151218
type mockSlackClient struct {
152-
mu sync.Mutex
153-
postThreadFunc func(ctx context.Context, channelID, text string, attachments []slack.Attachment) (string, error)
154-
updateMessageFunc func(ctx context.Context, channelID, timestamp, text string) error
155-
updateDMMessageFunc func(ctx context.Context, userID, timestamp, text string) error
156-
channelHistoryFunc func(ctx context.Context, channelID string, oldest, latest string, limit int) (*slack.GetConversationHistoryResponse, error)
157-
resolveChannelFunc func(ctx context.Context, channelName string) string
158-
botInChannelFunc func(ctx context.Context, channelID string) bool
159-
botInfoFunc func(ctx context.Context) (*slack.AuthTestResponse, error)
160-
workspaceInfoFunc func(ctx context.Context) (*slack.TeamInfo, error)
161-
publishHomeFunc func(ctx context.Context, userID string, blocks []slack.Block) error
162-
apiFunc func() *slack.Client
219+
mu sync.Mutex
220+
postThreadFunc func(ctx context.Context, channelID, text string, attachments []slack.Attachment) (string, error)
221+
updateMessageFunc func(ctx context.Context, channelID, timestamp, text string) error
222+
updateDMMessageFunc func(ctx context.Context, userID, timestamp, text string) error
223+
sendDirectMessageFunc func(ctx context.Context, userID, text string) (dmChannelID, messageTS string, err error)
224+
isUserInChannelFunc func(ctx context.Context, channelID, userID string) bool
225+
findDMMessagesFunc func(ctx context.Context, userID, prURL string, since time.Time) ([]slackapi.DMLocation, error)
226+
channelHistoryFunc func(ctx context.Context, channelID string, oldest, latest string, limit int) (*slack.GetConversationHistoryResponse, error)
227+
resolveChannelFunc func(ctx context.Context, channelName string) string
228+
botInChannelFunc func(ctx context.Context, channelID string) bool
229+
botInfoFunc func(ctx context.Context) (*slack.AuthTestResponse, error)
230+
workspaceInfoFunc func(ctx context.Context) (*slack.TeamInfo, error)
231+
publishHomeFunc func(ctx context.Context, userID string, blocks []slack.Block) error
232+
apiFunc func() *slack.Client
163233

164234
// For direct workspace info control
165235
workspaceInfo *slack.TeamInfo
166236
workspaceInfoErr bool
167237

168238
// Tracking for test assertions
169-
postedMessages []mockPostedMessage
170-
updatedMessages []mockUpdatedMessage
171-
updatedDMMessage []mockUpdatedDMMessage
239+
postedMessages []mockPostedMessage
240+
updatedMessages []mockUpdatedMessage
241+
updatedDMMessage []mockUpdatedDMMessage
242+
sentDirectMessages []mockSentDirectMessage
172243
}
173244

174245
type mockPostedMessage struct {
@@ -189,6 +260,11 @@ type mockUpdatedDMMessage struct {
189260
Text string
190261
}
191262

263+
type mockSentDirectMessage struct {
264+
UserID string
265+
Text string
266+
}
267+
192268
func (m *mockSlackClient) PostThread(ctx context.Context, channelID, text string, attachments []slack.Attachment) (string, error) {
193269
m.mu.Lock()
194270
m.postedMessages = append(m.postedMessages, mockPostedMessage{
@@ -287,6 +363,37 @@ func (m *mockSlackClient) API() *slack.Client {
287363
return nil
288364
}
289365

366+
// SendDirectMessage sends a DM to a user.
367+
func (m *mockSlackClient) SendDirectMessage(ctx context.Context, userID, text string) (dmChannelID, messageTS string, err error) {
368+
m.mu.Lock()
369+
m.sentDirectMessages = append(m.sentDirectMessages, mockSentDirectMessage{
370+
UserID: userID,
371+
Text: text,
372+
})
373+
m.mu.Unlock()
374+
if m.sendDirectMessageFunc != nil {
375+
return m.sendDirectMessageFunc(ctx, userID, text)
376+
}
377+
return "D" + userID, "1234567890.123456", nil
378+
}
379+
380+
// IsUserInChannel checks if a user is in a channel.
381+
func (m *mockSlackClient) IsUserInChannel(ctx context.Context, channelID, userID string) bool {
382+
if m.isUserInChannelFunc != nil {
383+
return m.isUserInChannelFunc(ctx, channelID, userID)
384+
}
385+
return false
386+
}
387+
388+
// FindDMMessagesInHistory searches DM history for messages containing a PR URL.
389+
func (m *mockSlackClient) FindDMMessagesInHistory(ctx context.Context, userID, prURL string, since time.Time) ([]slackapi.DMLocation, error) {
390+
if m.findDMMessagesFunc != nil {
391+
return m.findDMMessagesFunc(ctx, userID, prURL, since)
392+
}
393+
// Default: return empty (no DMs found in history)
394+
return nil, nil
395+
}
396+
290397
// mockUserMapper is a simple mock for user mapping in tests.
291398
type mockUserMapper struct {
292399
slackHandleFunc func(ctx context.Context, githubUser, org, domain string) (string, error)

0 commit comments

Comments
 (0)