Skip to content

Commit d48c78a

Browse files
committed
swap message order
1 parent 905b62d commit d48c78a

16 files changed

Lines changed: 156 additions & 823 deletions

cmd/server/main.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -730,6 +730,7 @@ func runBotCoordinators(
730730
defer cleanupTicker.Stop()
731731

732732
// Run cleanup once on startup
733+
//nolint:contextcheck // Background cleanup should complete even during shutdown
733734
go func() {
734735
if err := stateStore.Cleanup(context.Background()); err != nil {
735736
slog.Warn("initial state cleanup failed", "error", err)
@@ -764,6 +765,7 @@ func runBotCoordinators(
764765

765766
case <-cleanupTicker.C:
766767
// Periodic cleanup of old state data
768+
//nolint:contextcheck // Background cleanup should complete even during shutdown
767769
go func() {
768770
if err := stateStore.Cleanup(context.Background()); err != nil {
769771
slog.Warn("state cleanup failed", "error", err)

pkg/bot/bot.go

Lines changed: 98 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"errors"
77
"fmt"
88
"log/slog"
9+
"os"
910
"strconv"
1011
"strings"
1112
"sync"
@@ -37,6 +38,22 @@ type prContext struct {
3738
Number int
3839
}
3940

41+
// messageUpdateParams groups parameters for message update operations.
42+
type messageUpdateParams struct {
43+
CheckResult *turn.CheckResponse
44+
Event any
45+
ChannelID string
46+
ChannelName string
47+
ChannelDisplay string
48+
ThreadTS string
49+
OldState string
50+
CurrentText string
51+
PRState string
52+
Owner string
53+
Repo string
54+
PRNumber int
55+
}
56+
4057
// Coordinator coordinates between GitHub, Slack, and notifications for a single org.
4158
//
4259
//nolint:govet // Field order optimized for logical grouping over memory alignment
@@ -1066,12 +1083,12 @@ func (c *Coordinator) updateDMMessagesForPR(ctx context.Context, pr prUpdateInfo
10661083
}
10671084

10681085
message := fmt.Sprintf(
1069-
"%s %s <%s|%s#%d> · %s → %s",
1086+
"%s <%s|%s#%d> · %s · %s → %s",
10701087
prefix,
1071-
pr.title,
10721088
prURL,
10731089
repo,
10741090
prNumber,
1091+
pr.title,
10751092
pr.author,
10761093
action,
10771094
)
@@ -1361,7 +1378,20 @@ func (c *Coordinator) processPRForChannel(
13611378

13621379
// Update message if needed
13631380
if !wasNewlyCreated {
1364-
c.updateMessageIfNeeded(ctx, channelID, channelDisplay, threadTS, oldState, currentText, prState, owner, repo, prNumber, event, checkResult)
1381+
c.updateMessageIfNeeded(ctx, messageUpdateParams{
1382+
ChannelID: channelID,
1383+
ChannelName: channelName,
1384+
ChannelDisplay: channelDisplay,
1385+
ThreadTS: threadTS,
1386+
OldState: oldState,
1387+
CurrentText: currentText,
1388+
PRState: prState,
1389+
Owner: owner,
1390+
Repo: repo,
1391+
PRNumber: prNumber,
1392+
Event: event,
1393+
CheckResult: checkResult,
1394+
})
13651395
}
13661396

13671397
slog.Info("successfully processed PR in channel",
@@ -1442,95 +1472,99 @@ func (c *Coordinator) trackUserTagsForDMDelay(
14421472
}
14431473

14441474
// updateMessageIfNeeded builds the expected message and updates if different from current.
1445-
//
1446-
//nolint:revive // Function complexity justified by comprehensive message update logic
1447-
func (c *Coordinator) updateMessageIfNeeded(ctx context.Context, channelID, channelDisplay, threadTS, oldState, currentText, prState, owner, repo string, prNumber int, event struct {
1448-
Action string `json:"action"`
1449-
PullRequest struct {
1450-
HTMLURL string `json:"html_url"`
1451-
Title string `json:"title"`
1452-
CreatedAt time.Time `json:"created_at"`
1453-
User struct {
1454-
Login string `json:"login"`
1455-
} `json:"user"`
1475+
func (c *Coordinator) updateMessageIfNeeded(ctx context.Context, params messageUpdateParams) {
1476+
event, ok := params.Event.(struct {
1477+
Action string `json:"action"`
1478+
PullRequest struct {
1479+
HTMLURL string `json:"html_url"`
1480+
Title string `json:"title"`
1481+
CreatedAt time.Time `json:"created_at"`
1482+
User struct {
1483+
Login string `json:"login"`
1484+
} `json:"user"`
1485+
Number int `json:"number"`
1486+
} `json:"pull_request"`
14561487
Number int `json:"number"`
1457-
} `json:"pull_request"`
1458-
Number int `json:"number"`
1459-
}, checkResult *turn.CheckResponse,
1460-
) {
1461-
domain := c.configManager.Domain(owner)
1462-
params := notify.MessageParams{
1463-
CheckResult: checkResult,
1464-
Owner: owner,
1465-
Repo: repo,
1466-
PRNumber: prNumber,
1488+
})
1489+
if !ok {
1490+
slog.Error("invalid event type in messageUpdateParams", "expected", "pull_request_event")
1491+
return
1492+
}
1493+
1494+
domain := c.configManager.Domain(params.Owner)
1495+
msgParams := notify.MessageParams{
1496+
CheckResult: params.CheckResult,
1497+
Owner: params.Owner,
1498+
Repo: params.Repo,
1499+
PRNumber: params.PRNumber,
14671500
Title: event.PullRequest.Title,
14681501
Author: event.PullRequest.User.Login,
14691502
HTMLURL: event.PullRequest.HTMLURL,
14701503
Domain: domain,
1504+
ChannelName: params.ChannelName,
14711505
UserMapper: c.userMapper,
14721506
}
1473-
expectedText := notify.FormatChannelMessageBase(ctx, params) + notify.FormatNextActionsSuffix(ctx, params)
1507+
expectedText := notify.FormatChannelMessageBase(ctx, msgParams) + notify.FormatNextActionsSuffix(ctx, msgParams)
14741508

1475-
if currentText == expectedText {
1509+
if params.CurrentText == expectedText {
14761510
slog.Debug("message already matches expected content, no update needed",
14771511
"workspace", c.workspaceName,
1478-
logFieldPR, fmt.Sprintf(prFormatString, owner, repo, prNumber),
1479-
"channel", channelDisplay,
1480-
"thread_ts", threadTS,
1481-
"pr_state", prState)
1512+
logFieldPR, fmt.Sprintf(prFormatString, params.Owner, params.Repo, params.PRNumber),
1513+
"channel", params.ChannelDisplay,
1514+
"thread_ts", params.ThreadTS,
1515+
"pr_state", params.PRState)
14821516
return
14831517
}
14841518

14851519
slog.Info("updating message - content changed",
14861520
"workspace", c.workspaceName,
1487-
logFieldPR, fmt.Sprintf(prFormatString, owner, repo, prNumber),
1488-
"channel", channelDisplay,
1489-
"channel_id", channelID,
1490-
"thread_ts", threadTS,
1491-
"pr_state", prState,
1492-
"old_state", oldState,
1493-
"current_message_preview", currentText[:min(100, len(currentText))],
1521+
logFieldPR, fmt.Sprintf(prFormatString, params.Owner, params.Repo, params.PRNumber),
1522+
"channel", params.ChannelDisplay,
1523+
"channel_id", params.ChannelID,
1524+
"thread_ts", params.ThreadTS,
1525+
"pr_state", params.PRState,
1526+
"old_state", params.OldState,
1527+
"current_message_preview", params.CurrentText[:min(100, len(params.CurrentText))],
14941528
"expected_message_preview", expectedText[:min(100, len(expectedText))])
14951529

1496-
if err := c.slack.UpdateMessage(ctx, channelID, threadTS, expectedText); err != nil {
1530+
if err := c.slack.UpdateMessage(ctx, params.ChannelID, params.ThreadTS, expectedText); err != nil {
14971531
slog.Error("failed to update PR message",
14981532
"workspace", c.workspaceName,
1499-
logFieldPR, fmt.Sprintf(prFormatString, owner, repo, prNumber),
1500-
"channel", channelDisplay,
1501-
"channel_id", channelID,
1502-
"thread_ts", threadTS,
1533+
logFieldPR, fmt.Sprintf(prFormatString, params.Owner, params.Repo, params.PRNumber),
1534+
"channel", params.ChannelDisplay,
1535+
"channel_id", params.ChannelID,
1536+
"thread_ts", params.ThreadTS,
15031537
"error", err,
15041538
"impact", "message_update_skipped_will_retry_via_polling",
15051539
"next_poll_in", "5m")
15061540
return
15071541
}
15081542

15091543
// Save updated thread info
1510-
c.saveThread(ctx, owner, repo, prNumber, channelID, cache.ThreadInfo{
1511-
ThreadTS: threadTS,
1512-
ChannelID: channelID,
1513-
LastState: prState,
1544+
c.saveThread(ctx, params.Owner, params.Repo, params.PRNumber, params.ChannelID, cache.ThreadInfo{
1545+
ThreadTS: params.ThreadTS,
1546+
ChannelID: params.ChannelID,
1547+
LastState: params.PRState,
15141548
MessageText: expectedText,
15151549
})
15161550
slog.Info("successfully updated PR message",
15171551
"workspace", c.workspaceName,
1518-
logFieldPR, fmt.Sprintf(prFormatString, owner, repo, prNumber),
1519-
"channel", channelDisplay,
1520-
"channel_id", channelID,
1521-
"thread_ts", threadTS,
1522-
"pr_state", prState)
1552+
logFieldPR, fmt.Sprintf(prFormatString, params.Owner, params.Repo, params.PRNumber),
1553+
"channel", params.ChannelDisplay,
1554+
"channel_id", params.ChannelID,
1555+
"thread_ts", params.ThreadTS,
1556+
"pr_state", params.PRState)
15231557

15241558
// Also update DM messages for blocked users
15251559
c.updateDMMessagesForPR(ctx, prUpdateInfo{
1526-
owner: owner,
1527-
repo: repo,
1528-
number: prNumber,
1560+
owner: params.Owner,
1561+
repo: params.Repo,
1562+
number: params.PRNumber,
15291563
title: event.PullRequest.Title,
15301564
author: event.PullRequest.User.Login,
1531-
state: prState,
1565+
state: params.PRState,
15321566
url: event.PullRequest.HTMLURL,
1533-
checkRes: checkResult,
1567+
checkRes: params.CheckResult,
15341568
})
15351569
}
15361570

@@ -1554,8 +1588,14 @@ func (c *Coordinator) handlePullRequestFromSprinkler(
15541588
return
15551589
}
15561590

1557-
// Create and authenticate turnclient
1558-
turnClient, err := turn.NewDefaultClient()
1591+
// Create and authenticate turnclient (allow test backend override for mocking)
1592+
var turnClient *turn.Client
1593+
var err error
1594+
if testBackend := os.Getenv("TURN_TEST_BACKEND"); testBackend != "" {
1595+
turnClient, err = turn.NewClient(testBackend)
1596+
} else {
1597+
turnClient, err = turn.NewDefaultClient()
1598+
}
15591599
if err != nil {
15601600
slog.Error("failed to create turnclient",
15611601
logFieldOwner, owner,

pkg/bot/bot_sprinkler.go

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"errors"
66
"fmt"
77
"log/slog"
8+
"os"
89
"strconv"
910
"strings"
1011
"time"
@@ -131,8 +132,14 @@ func (c *Coordinator) lookupPRsForCheckEvent(ctx context.Context, event client.E
131132
// Get GitHub token
132133
githubToken := c.github.InstallationToken(ctx)
133134
if githubToken != "" {
134-
// Create turnclient
135-
turnClient, tcErr := turn.NewDefaultClient()
135+
// Create turnclient (allow test backend override for mocking)
136+
var turnClient *turn.Client
137+
var tcErr error
138+
if testBackend := os.Getenv("TURN_TEST_BACKEND"); testBackend != "" {
139+
turnClient, tcErr = turn.NewClient(testBackend)
140+
} else {
141+
turnClient, tcErr = turn.NewDefaultClient()
142+
}
136143
if tcErr == nil {
137144
turnClient.SetAuthToken(githubToken)
138145

pkg/bot/bot_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,33 @@ package bot
33
import (
44
"context"
55
"errors"
6+
"os"
67
"testing"
78

89
"github.com/codeGROOVE-dev/slacker/pkg/bot/cache"
910
"github.com/slack-go/slack"
1011
)
1112

13+
// TestMain sets up the test environment before running tests.
14+
func TestMain(m *testing.M) {
15+
// Create a shared mock turnclient server for all tests
16+
mockServer := mockTurnServer(&testing.T{})
17+
18+
// Set environment variable so all turnclient calls use the mock
19+
if err := os.Setenv("TURN_TEST_BACKEND", mockServer.URL); err != nil {
20+
panic("failed to set TURN_TEST_BACKEND: " + err.Error())
21+
}
22+
23+
// Run tests
24+
code := m.Run()
25+
26+
// Cleanup (before os.Exit to avoid exitAfterDefer lint error)
27+
mockServer.Close()
28+
_ = os.Unsetenv("TURN_TEST_BACKEND") // Best effort cleanup
29+
30+
os.Exit(code)
31+
}
32+
1233
func TestNew(t *testing.T) {
1334
ctx := context.Background()
1435

pkg/bot/handle_pr_comprehensive_test.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -272,7 +272,6 @@ func TestHandlePullRequestEventWithData_DuplicateBlockedUsers(t *testing.T) {
272272

273273
// TestHandlePullRequestEventWithData_ExtractStateFromTurnclient tests state extraction.
274274
func TestHandlePullRequestEventWithData_ExtractStateFromTurnclient(t *testing.T) {
275-
276275
c := NewTestCoordinator().
277276
WithState(NewMockState().Build()).
278277
WithSlack(NewMockSlack().Build()).

pkg/bot/integration_test.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ import (
1919

2020
// TestUserMappingIntegration tests the complete flow of mapping GitHub users to Slack users.
2121
func TestUserMappingIntegration(t *testing.T) {
22-
2322
// Setup mock Slack server
2423
mockSlack := slacktest.New()
2524
defer mockSlack.Close()

0 commit comments

Comments
 (0)