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 ,
0 commit comments