mirror of
https://github.com/coder/coder.git
synced 2026-06-03 21:18:24 +00:00
579daaff70
Fixes CODAGT-146 Add GitLab support to the gitprovider package for gitsync/chatd PR diff flows. This is a squashed stack of 3 PRs: #25651 - refactor(coderd/externalauth): prepare gitprovider for multi-provider support - Change gitprovider.New to return (Provider, error) - Extract shared helpers (parseRetryAfter, checkRateLimitError, countDiffLines, escapePathPreserveSlashes) from github.go - Update all callers (db2sdk, exp_chats, gitsync) for new signature - Add error logging for provider construction failures - Thread context through provider resolution #25652 - feat(coderd/externalauth/gitprovider): add GitLab provider - Implement full Provider interface: FetchPullRequestStatus, FetchPullRequestDiff, FetchBranchDiff, ResolveBranchPullRequest - Handle nested groups, forks, and self-hosted instances - Rate limit detection on both library and raw HTTP paths - URL parsing/building with NormalizePullRequestURL support - Unit tests covering error paths, URL parsing, state mapping - Document GitLab configuration and known limitations #25653 - test(coderd/externalauth/gitprovider): add GitLab VCR integration tests - FetchPullRequestStatus: 4 fixtures (open, conflicts, merged, closed) - FetchPullRequestDiff: 4 fixtures - FetchBranchDiff: 3 fixtures (open, deleted, fork) - ResolveBranchPullRequest: 3 fixtures - go-vcr cassettes with sanitized GitLab API responses
151 lines
3.6 KiB
Go
151 lines
3.6 KiB
Go
package gitprovider
|
|
|
|
import (
|
|
"net/http"
|
|
"strconv"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
"github.com/coder/quartz"
|
|
)
|
|
|
|
func TestCountDiffLines(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
diff string
|
|
additions int32
|
|
deletions int32
|
|
}{
|
|
{
|
|
name: "Empty",
|
|
},
|
|
{
|
|
name: "OnlyAdditions",
|
|
diff: "+a\n+b\n+c\n",
|
|
additions: 3,
|
|
},
|
|
{
|
|
name: "OnlyDeletions",
|
|
diff: "-a\n-b\n",
|
|
deletions: 2,
|
|
},
|
|
{
|
|
name: "MixedWithHeaders",
|
|
diff: "--- a/file.txt\n+++ b/file.txt\n@@ -1,2 +1,3 @@\n unchanged\n-old\n+new\n+another\n",
|
|
additions: 2,
|
|
deletions: 1,
|
|
},
|
|
{
|
|
name: "NoTrailingNewline",
|
|
diff: "@@ -1 +1 @@\n-old\n+new",
|
|
additions: 1,
|
|
deletions: 1,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
additions, deletions := countDiffLines(tt.diff)
|
|
assert.Equal(t, tt.additions, additions)
|
|
assert.Equal(t, tt.deletions, deletions)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestParseRetryAfter(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
clk := quartz.NewMock(t)
|
|
clk.Set(time.Date(2026, 5, 25, 12, 0, 0, 0, time.UTC))
|
|
|
|
t.Run("RetryAfterSeconds", func(t *testing.T) {
|
|
t.Parallel()
|
|
h := http.Header{}
|
|
h.Set("Retry-After", "120")
|
|
d := parseRetryAfter(h, "X-Ratelimit-Reset", clk)
|
|
assert.Equal(t, 120*time.Second, d)
|
|
})
|
|
|
|
t.Run("GitHubResetHeader", func(t *testing.T) {
|
|
t.Parallel()
|
|
future := clk.Now().Add(90 * time.Second)
|
|
h := http.Header{}
|
|
h.Set("X-Ratelimit-Reset", strconv.FormatInt(future.Unix(), 10))
|
|
d := parseRetryAfter(h, "X-Ratelimit-Reset", clk)
|
|
assert.WithinDuration(t, future, clk.Now().Add(d), time.Second)
|
|
})
|
|
|
|
t.Run("GitLabResetHeader", func(t *testing.T) {
|
|
t.Parallel()
|
|
future := clk.Now().Add(45 * time.Second)
|
|
h := http.Header{}
|
|
h.Set("RateLimit-Reset", strconv.FormatInt(future.Unix(), 10))
|
|
d := parseRetryAfter(h, "RateLimit-Reset", clk)
|
|
assert.WithinDuration(t, future, clk.Now().Add(d), time.Second)
|
|
})
|
|
|
|
t.Run("NoHeaders", func(t *testing.T) {
|
|
t.Parallel()
|
|
h := http.Header{}
|
|
d := parseRetryAfter(h, "X-Ratelimit-Reset", clk)
|
|
assert.Equal(t, time.Duration(0), d)
|
|
})
|
|
|
|
t.Run("InvalidValue", func(t *testing.T) {
|
|
t.Parallel()
|
|
h := http.Header{}
|
|
h.Set("Retry-After", "not-a-number")
|
|
d := parseRetryAfter(h, "X-Ratelimit-Reset", clk)
|
|
assert.Equal(t, time.Duration(0), d)
|
|
})
|
|
|
|
t.Run("RetryAfterTakesPrecedence", func(t *testing.T) {
|
|
t.Parallel()
|
|
h := http.Header{}
|
|
h.Set("Retry-After", "60")
|
|
h.Set("X-Ratelimit-Reset", strconv.FormatInt(clk.Now().Add(120*time.Second).Unix(), 10))
|
|
d := parseRetryAfter(h, "X-Ratelimit-Reset", clk)
|
|
assert.Equal(t, 60*time.Second, d)
|
|
})
|
|
|
|
t.Run("NilClock", func(t *testing.T) {
|
|
t.Parallel()
|
|
h := http.Header{}
|
|
h.Set("Retry-After", "1")
|
|
d := parseRetryAfter(h, "X-Ratelimit-Reset", nil)
|
|
assert.Equal(t, time.Second, d)
|
|
})
|
|
}
|
|
|
|
func TestMapGitLabState(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
tests := []struct {
|
|
name string
|
|
input string
|
|
expect PRState
|
|
}{
|
|
{name: "opened", input: "opened", expect: PRStateOpen},
|
|
{name: "Opened_mixed_case", input: "Opened", expect: PRStateOpen},
|
|
{name: "merged", input: "merged", expect: PRStateMerged},
|
|
{name: "closed", input: "closed", expect: PRStateClosed},
|
|
{name: "locked", input: "locked", expect: PRStateClosed},
|
|
{name: "unknown_defaults_to_closed", input: "something_else", expect: PRStateClosed},
|
|
{name: "empty_defaults_to_closed", input: "", expect: PRStateClosed},
|
|
{name: "whitespace_trimmed", input: " opened ", expect: PRStateOpen},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
got := mapGitLabState(tt.input)
|
|
assert.Equal(t, tt.expect, got)
|
|
})
|
|
}
|
|
}
|