Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions adapters/linear/harden_capability_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,8 +205,7 @@ func TestRateLimitDeadlineReturnsTypedRateLimited(t *testing.T) {

select {
case err := <-done:
var rl *linear.RateLimited
if !errors.As(err, &rl) {
if _, ok := errors.AsType[*linear.RateLimited](err); !ok {
t.Fatalf("err = %v, want *linear.RateLimited (deadline-bounded, not slept off)", err)
}
if elapsed := time.Since(start); elapsed > time.Second {
Expand Down
8 changes: 4 additions & 4 deletions adapters/linear/history.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"context"
"encoding/json"
"fmt"
"slices"

"github.com/coder/chat"
)
Expand Down Expand Up @@ -119,8 +120,7 @@ func (a *Adapter) readAgentSessionHistory(ctx context.Context, thread threadPayl
nodes := resp.Data.AgentSession.Activities.Nodes
messages := make([]chat.Message, 0, len(nodes))
// Linear returns the page createdAt-ascending; reverse for newest-first.
for i := len(nodes) - 1; i >= 0; i-- {
node := nodes[i]
for _, node := range slices.Backward(nodes) {
messages = append(messages, chat.Message{
ID: node.ID,
Text: node.Content.Body,
Expand Down Expand Up @@ -164,8 +164,8 @@ func (a *Adapter) readCommentThreadHistory(ctx context.Context, thread threadPay
children := resp.Data.Thread.Children.Nodes
messages := make([]chat.Message, 0, len(children)+1)
// Linear returns the page createdAt-ascending; reverse for newest-first.
for i := len(children) - 1; i >= 0; i-- {
messages = append(messages, historyCommentMessage(thread.Organization, children[i]))
for _, c := range slices.Backward(children) {
messages = append(messages, historyCommentMessage(thread.Organization, c))
}
// No older replies remain, so the root comment closes the oldest page.
if !resp.Data.Thread.Children.PageInfo.HasPreviousPage {
Expand Down
3 changes: 1 addition & 2 deletions adapters/linear/history_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -623,8 +623,7 @@ func TestLinearReadHistoryRateLimitObservedAndErrors(t *testing.T) {

id := linear.EncodeAgentSessionThreadIDForTest("ORG1", "ISSUE1", "S1")
msgs, err := hr.ReadHistory(context.Background(), id, chat.HistoryQuery{})
var limited *linear.RateLimited
if !errors.As(err, &limited) {
if _, ok := errors.AsType[*linear.RateLimited](err); !ok {
t.Fatalf("err = %v, want *linear.RateLimited on a throttled read", err)
}
if msgs != nil {
Expand Down
10 changes: 5 additions & 5 deletions adapters/linear/linear.go
Original file line number Diff line number Diff line change
Expand Up @@ -246,8 +246,7 @@ func (a *Adapter) Webhook(dispatch chat.DispatchFunc) http.Handler {
}
body, err := io.ReadAll(http.MaxBytesReader(w, r.Body, maxWebhookBodyBytes))
if err != nil {
var maxBytesErr *http.MaxBytesError
if errors.As(err, &maxBytesErr) {
if _, ok := errors.AsType[*http.MaxBytesError](err); ok {
http.Error(w, "linear webhook too large", http.StatusRequestEntityTooLarge)
return
}
Expand Down Expand Up @@ -926,10 +925,11 @@ func encodeThreadID(payload threadPayload) (chat.ThreadID, error) {

func decodeThreadID(id chat.ThreadID) (threadPayload, error) {
const prefix = "linear:v1:"
if !strings.HasPrefix(string(id), prefix) {
rest, ok := strings.CutPrefix(string(id), prefix)
if !ok {
return threadPayload{}, fmt.Errorf("linear: malformed thread id %q", id)
}
body, err := base64.RawURLEncoding.DecodeString(strings.TrimPrefix(string(id), prefix))
body, err := base64.RawURLEncoding.DecodeString(rest)
if err != nil {
return threadPayload{}, fmt.Errorf("linear: decode thread id: %w", err)
}
Expand Down Expand Up @@ -1179,7 +1179,7 @@ func verifyGrantedScopes(requested []string, granted string) error {

func parseGrantedScopes(value string) map[string]struct{} {
out := map[string]struct{}{}
for _, scope := range strings.FieldsFunc(value, func(r rune) bool { return r == ',' || r == ' ' || r == '\t' || r == '\n' || r == '\r' }) {
for scope := range strings.FieldsFuncSeq(value, func(r rune) bool { return r == ',' || r == ' ' || r == '\t' || r == '\n' || r == '\r' }) {
scope = strings.TrimSpace(scope)
if scope != "" {
out[scope] = struct{}{}
Expand Down
7 changes: 3 additions & 4 deletions adapters/linear/linear_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"io"
"net/http"
"net/http/httptest"
"slices"
"strings"
"sync"
"testing"
Expand Down Expand Up @@ -684,10 +685,8 @@ func containsToken(v any) bool {
}
}
case []any:
for _, val := range t {
if containsToken(val) {
return true
}
if slices.ContainsFunc(t, containsToken) {
return true
}
}
return false
Expand Down
4 changes: 2 additions & 2 deletions adapters/slack/history.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,9 @@ func (a *Adapter) ReadHistory(ctx context.Context, id chat.ThreadID, q chat.Hist
type conversationsReadPayload struct {
Channel string `json:"channel"`
TS string `json:"ts,omitempty"`
Limit int `json:"limit,omitempty"`
Limit int `json:"limit,omitzero"`
Latest string `json:"latest,omitempty"`
Inclusive bool `json:"inclusive,omitempty"`
Inclusive bool `json:"inclusive,omitzero"`
}

type conversationsHistoryResponse struct {
Expand Down
3 changes: 1 addition & 2 deletions adapters/slack/history_hardening_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -123,8 +123,7 @@ func TestSlackReadHistoryRateLimitObservedAndErrors(t *testing.T) {

id := slack.EncodeThreadReplyThreadIDForTest("T1", "C1", "111.000")
msgs, err := hr.ReadHistory(context.Background(), id, chat.HistoryQuery{Limit: 5})
var limited *slack.RateLimited
if !errors.As(err, &limited) {
if _, ok := errors.AsType[*slack.RateLimited](err); !ok {
t.Fatalf("err = %v, want *slack.RateLimited on a throttled read", err)
}
if msgs != nil {
Expand Down
6 changes: 3 additions & 3 deletions adapters/slack/interactive_hardening_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ func TestSlackCommandDedupedByEventIdentity(t *testing.T) {
}

form := signedCommandForm()
for i := 0; i < 3; i++ {
for i := range 3 {
rec := serveSignedSlackForm(t, handler, now, form)
if rec.Code != http.StatusOK {
t.Fatalf("delivery %d status = %d", i, rec.Code)
Expand Down Expand Up @@ -100,7 +100,7 @@ func TestSlackInteractionDedupedByEventIdentity(t *testing.T) {
"response_url":"https://hooks.slack.com/actions/T1/999",
"actions":[{"action_id":"approve","block_id":"b1","value":"yes","type":"button"}]
}`
for i := 0; i < 3; i++ {
for i := range 3 {
rec := serveSignedSlackInteractivity(t, handler, now, payload)
if rec.Code != http.StatusOK {
t.Fatalf("delivery %d status = %d", i, rec.Code)
Expand Down Expand Up @@ -154,7 +154,7 @@ func TestSlackRepeatInteractionsAreDistinctEvents(t *testing.T) {
for _, actionTS := range []string{"1700000001.000100", "1700000002.000200", "1700000003.000300"} {
payload := activation(actionTS)
// Deliver each activation twice: the original and a redelivery.
for delivery := 0; delivery < 2; delivery++ {
for delivery := range 2 {
rec := serveSignedSlackInteractivity(t, handler, now, payload)
if rec.Code != http.StatusOK {
t.Fatalf("activation %s delivery %d status = %d", actionTS, delivery, rec.Code)
Expand Down
14 changes: 4 additions & 10 deletions adapters/slack/multitenant_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"errors"
"fmt"
"net/http"
"slices"
"sync"
"testing"
"time"
Expand Down Expand Up @@ -57,12 +58,7 @@ func (s *fakeInstallStore) callCount() int {
func (s *fakeInstallStore) lookedUp(tenant string) bool {
s.mu.Lock()
defer s.mu.Unlock()
for _, c := range s.calls {
if c == "slack:"+tenant {
return true
}
}
return false
return slices.Contains(s.calls, "slack:"+tenant)
}

func TestSlackConstructionModeSelection(t *testing.T) {
Expand Down Expand Up @@ -400,15 +396,13 @@ func TestSlackMultiTenantConcurrentTenantsDoNotCross(t *testing.T) {
}
var wg sync.WaitGroup
for _, team := range []string{"T1", "T2"} {
wg.Add(1)
go func(team string) {
defer wg.Done()
wg.Go(func() {
rec := serveSignedSlackWebhook(t, handler, now,
slackEventBody(team, "Ev"+team, "U"+team, "<@UBOT"+team[1:]+"> hi"), "", "")
if rec.Code != http.StatusOK {
t.Errorf("team %s status = %d", team, rec.Code)
}
}(team)
})
}
wg.Wait()

Expand Down
9 changes: 3 additions & 6 deletions adapters/slack/ratelimit_hardening_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -258,8 +258,7 @@ func TestSlackPostNeverSleepsPastContextDeadline(t *testing.T) {

select {
case err := <-done:
var limited *slack.RateLimited
if !errors.As(err, &limited) {
if _, ok := errors.AsType[*slack.RateLimited](err); !ok {
t.Fatalf("err = %v, want *slack.RateLimited (deadline-bounded, not slept off)", err)
}
case <-time.After(5 * time.Second):
Expand Down Expand Up @@ -306,8 +305,7 @@ func TestSlackPostDoesNotRetryNonThrottlingError(t *testing.T) {
if postErr == nil {
t.Fatal("expected an error from a 401 auth failure")
}
var limited *slack.RateLimited
if errors.As(postErr, &limited) {
if _, ok := errors.AsType[*slack.RateLimited](postErr); ok {
t.Fatal("a non-throttling 401 must not surface as RateLimited")
}
mu.Lock()
Expand Down Expand Up @@ -337,8 +335,7 @@ func TestSlackDefaultPolicyCeilingUnderAckWindow(t *testing.T) {
_, err := adapter.PostMessage(context.Background(), ref, chat.Text("hi"))
elapsed := time.Since(start)

var limited *slack.RateLimited
if !errors.As(err, &limited) {
if _, ok := errors.AsType[*slack.RateLimited](err); !ok {
t.Fatalf("err = %v, want *slack.RateLimited", err)
}
if elapsed >= 3*time.Second {
Expand Down
10 changes: 5 additions & 5 deletions adapters/slack/slack.go
Original file line number Diff line number Diff line change
Expand Up @@ -222,8 +222,7 @@ func (a *Adapter) Webhook(dispatch chat.DispatchFunc) http.Handler {

body, err := io.ReadAll(http.MaxBytesReader(w, r.Body, maxWebhookBodyBytes))
if err != nil {
var maxBytesErr *http.MaxBytesError
if errors.As(err, &maxBytesErr) {
if _, ok := errors.AsType[*http.MaxBytesError](err); ok {
http.Error(w, "slack payload too large", http.StatusRequestEntityTooLarge)
return
}
Expand Down Expand Up @@ -731,7 +730,7 @@ type threadPayload struct {
Team string `json:"team"`
Channel string `json:"channel"`
Root string `json:"root,omitempty"`
Direct bool `json:"direct,omitempty"`
Direct bool `json:"direct,omitzero"`
}

func encodeThreadID(payload threadPayload) (chat.ThreadID, error) {
Expand All @@ -753,10 +752,11 @@ func encodeThreadID(payload threadPayload) (chat.ThreadID, error) {

func decodeThreadID(id chat.ThreadID) (threadPayload, error) {
const prefix = "slack:v1:"
if !strings.HasPrefix(string(id), prefix) {
rest, ok := strings.CutPrefix(string(id), prefix)
if !ok {
return threadPayload{}, fmt.Errorf("slack: malformed thread id %q", id)
}
body, err := base64.RawURLEncoding.DecodeString(strings.TrimPrefix(string(id), prefix))
body, err := base64.RawURLEncoding.DecodeString(rest)
if err != nil {
return threadPayload{}, fmt.Errorf("slack: decode thread id: %w", err)
}
Expand Down
6 changes: 1 addition & 5 deletions adapters/slack/slack_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -470,7 +470,7 @@ func TestPostingTextMarkdownEphemeralAndExplicitFallback(t *testing.T) {
t.Fatalf("fallback sent = %#v", sent)
}

api.assertPost(t, 0, slackPost{Channel: "C1", ThreadTS: "111.000", Text: "plain reply", Mrkdwn: boolPtr(false)})
api.assertPost(t, 0, slackPost{Channel: "C1", ThreadTS: "111.000", Text: "plain reply", Mrkdwn: new(false)})
api.assertPost(t, 1, slackPost{Channel: "C1", ThreadTS: "111.000", MarkdownText: "**portable**"})
api.assertPost(t, 2, slackPost{Channel: "D-fallback", MarkdownText: "**private fallback**"})
}
Expand Down Expand Up @@ -740,10 +740,6 @@ func (s *slackAPIServer) authForPost(t *testing.T, index int) string {
return s.postAuth[index]
}

func boolPtr(value bool) *bool {
return &value
}

func decodeJSON(t *testing.T, body io.Reader, dest any) {
t.Helper()
if err := json.NewDecoder(body).Decode(dest); err != nil {
Expand Down
22 changes: 4 additions & 18 deletions admission_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"log/slog"
"net/http"
"net/http/httptest"
"slices"
"strings"
"sync"
"testing"
Expand Down Expand Up @@ -169,12 +170,7 @@ func TestAdmissionRejectsAtMaxDetachedWithoutDedupeMark(t *testing.T) {
return status == http.StatusOK
}, "capacity did not free after handler completion")
eventually(t, 5*time.Second, func() bool {
for _, id := range handlers.handledIDs() {
if id == "event-2" {
return true
}
}
return false
return slices.Contains(handlers.handledIDs(), "event-2")
}, "redelivered rejected event was not handled (dedupe-marked before rejection?)")
}

Expand Down Expand Up @@ -611,12 +607,7 @@ func TestAdmissionCountsParkedQueueWaiters(t *testing.T) {

handlers.release()
eventually(t, 5*time.Second, func() bool {
for _, id := range handlers.handledIDs() {
if id == "event-2" {
return true
}
}
return false
return slices.Contains(handlers.handledIDs(), "event-2")
}, "queued waiter did not run after the in-flight handler finished")
}

Expand Down Expand Up @@ -659,12 +650,7 @@ func TestAdmissionCountsConcurrentSlotWaiters(t *testing.T) {

// hasOutcome reports whether the observer recorded the terminal outcome.
func hasOutcome(observer *recordingObserver, want chat.DispatchOutcome) bool {
for _, outcome := range observer.terminalOutcomes() {
if outcome == want {
return true
}
}
return false
return slices.Contains(observer.terminalOutcomes(), want)
}

// assertAdmissionRejectedAttrs verifies the admission_rejected observation
Expand Down
2 changes: 1 addition & 1 deletion burst.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ func (c *Chat) dispatchBurstBatch(scope string, batch []preludeWork) {
// finalRelease is the last member's admission release, deferred past the
// lock cleanup below.
var finalRelease func()
for i := 0; i < len(batch); i++ {
for i := range batch {
work := batch[i]
if batchCtx.Err() != nil {
// Lease loss or Runtime Shutdown: members that never started are
Expand Down
8 changes: 3 additions & 5 deletions burst_hardening_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -830,13 +830,11 @@ func TestBurstDispatchRacingShutdownRejectedOrDrained(t *testing.T) {
statuses := make([]int, deliveries)
bodies := make([]string, deliveries)
var wg sync.WaitGroup
for i := 0; i < deliveries; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
for i := range deliveries {
wg.Go(func() {
id := "race-event-" + string(rune('a'+i))
statuses[i], bodies[i] = postEventBody(t, bot, "fake", mentionEvent(id, "fake:v1:thread-race"))
}(i)
})
}
time.Sleep(10 * time.Millisecond)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
Expand Down
4 changes: 2 additions & 2 deletions command_interaction_hardening_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,13 @@ func TestCommandAndInteractionDedupedByEventIdentity(t *testing.T) {
})

cmd := commandEvent("cmd-dup", "fake:v1:thread-cmd")
for i := 0; i < 3; i++ {
for i := range 3 {
if status := postEvent(t, bot, "fake", cmd); status != http.StatusOK {
t.Fatalf("command delivery %d status = %d", i, status)
}
}
intr := interactionEvent("int-dup", "fake:v1:thread-int")
for i := 0; i < 3; i++ {
for i := range 3 {
if status := postEvent(t, bot, "fake", intr); status != http.StatusOK {
t.Fatalf("interaction delivery %d status = %d", i, status)
}
Expand Down
6 changes: 2 additions & 4 deletions dispatch_deferred_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -604,14 +604,12 @@ func TestQueueStrategyDispatchesMostRecentSupersededFollowUp(t *testing.T) {
// recent dispatches, the older two are superseded.
var wg sync.WaitGroup
for _, id := range []string{"q1", "q2", "q3"} {
wg.Add(1)
go func(id string) {
defer wg.Done()
wg.Go(func() {
res := postEventResultFor(bot, "fake", mentionEvent(id, "fake:v1:thread-1"))
if res.err != nil || res.status != http.StatusOK {
t.Errorf("follow-up %s status=%d err=%v", id, res.status, res.err)
}
}(id)
})
// Stagger so the supersession order is deterministic (q3 is newest).
time.Sleep(10 * time.Millisecond)
}
Expand Down
Loading