196 lines
5.9 KiB
Go
196 lines
5.9 KiB
Go
package main
|
|
|
|
// gitlab_extra_test.go — error taxonomy, insecure client seam, decode failures.
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strconv"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestGitLabErrorType(t *testing.T) {
|
|
err := &GitLabError{msg: "upstream sad"}
|
|
if err.Error() != "upstream sad" {
|
|
t.Fatalf("GitLabError.Error = %q", err.Error())
|
|
}
|
|
}
|
|
|
|
func TestNewGitLabWithClientInsecure(t *testing.T) {
|
|
store := openTestStore(t)
|
|
gl := NewGitLabWithClient(store, "https://gl.example/", true)
|
|
if !gl.insecure {
|
|
t.Fatal("insecure flag not set")
|
|
}
|
|
hc := gl.httpClient()
|
|
if hc.Transport == nil {
|
|
t.Fatal("insecure client must install a custom transport")
|
|
}
|
|
plain := NewGitLab(store, "https://gl.example/")
|
|
if plain.httpClient().Transport != nil {
|
|
t.Fatal("default client must not override the transport")
|
|
}
|
|
if !strings.HasSuffix(gl.baseURL, "example") || strings.HasSuffix(gl.baseURL, "/") {
|
|
t.Fatalf("baseURL trailing slash not trimmed: %q", gl.baseURL)
|
|
}
|
|
}
|
|
|
|
func TestGitLabConnectDecodeAndEmptyUser(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
body string
|
|
want string
|
|
}{
|
|
{"decode-failure", "not json", "decode"},
|
|
{"empty-login", `{}`, "no login"},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
up := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
_, _ = w.Write([]byte(tc.body))
|
|
}))
|
|
t.Cleanup(up.Close)
|
|
gl := NewGitLab(openTestStore(t), up.URL)
|
|
_, err := gl.Connect(context.Background(), "pat")
|
|
if err == nil || !strings.Contains(err.Error(), tc.want) {
|
|
t.Fatalf("Connect = %v, want containing %q", err, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestGitLabRequestFailures(t *testing.T) {
|
|
store := openTestStore(t)
|
|
|
|
// unparseable base URL → request construction error
|
|
gl := NewGitLab(store, "https://gl.example/\n")
|
|
if _, err := gl.Connect(context.Background(), "pat"); err == nil {
|
|
t.Fatal("connect against unparseable URL must fail")
|
|
}
|
|
|
|
// transport failure (cancelled context) wraps into GitLabError
|
|
gl2 := NewGitLab(store, "https://gl.example")
|
|
ctx, cancel := context.WithCancel(context.Background())
|
|
cancel()
|
|
_, err := gl2.Connect(ctx, "pat")
|
|
if err == nil || !strings.Contains(err.Error(), "gitlab request failed") {
|
|
t.Fatalf("connect with dead context = %v, want GitLabError", err)
|
|
}
|
|
|
|
// store failures surface from token() and Disconnect
|
|
dead := openTestStore(t)
|
|
_ = dead.Close()
|
|
gl3 := NewGitLab(dead, "https://gl.example")
|
|
if _, err := gl3.Repos(context.Background()); err == nil || !strings.Contains(err.Error(), "closed") {
|
|
t.Fatalf("repos with closed store = %v", err)
|
|
}
|
|
if err := gl3.Disconnect(); err == nil {
|
|
t.Fatal("disconnect with closed store must fail")
|
|
}
|
|
}
|
|
|
|
func TestGitLabStatusStoreFailure(t *testing.T) {
|
|
dead := openTestStore(t)
|
|
_ = dead.Close()
|
|
gl := NewGitLab(dead, "https://gl.example")
|
|
status := gl.Status()
|
|
if status["connected"] != false {
|
|
t.Fatalf("status with broken store = %v, want connected=false", status)
|
|
}
|
|
}
|
|
|
|
func TestGitLabConnectStoreFailures(t *testing.T) {
|
|
up := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
_, _ = w.Write([]byte(`{"login":"alice"}`))
|
|
}))
|
|
t.Cleanup(up.Close)
|
|
dead := openTestStore(t)
|
|
_ = dead.Close()
|
|
gl := NewGitLab(dead, up.URL)
|
|
if _, err := gl.Connect(context.Background(), "pat"); err == nil {
|
|
t.Fatal("Connect with closed store must fail at SetSetting")
|
|
}
|
|
}
|
|
|
|
func TestGitLabReposSkipsEmptyPaths(t *testing.T) {
|
|
up := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
_, _ = w.Write([]byte(`[{"full_name":"","name":"X"}]`))
|
|
}))
|
|
t.Cleanup(up.Close)
|
|
store := openTestStore(t)
|
|
gl := NewGitLab(store, up.URL)
|
|
if err := store.SetSetting(settingGitLabToken, "pat"); err != nil {
|
|
t.Fatalf("set token: %v", err)
|
|
}
|
|
repos, err := gl.Repos(context.Background())
|
|
if err != nil {
|
|
t.Fatalf("repos: %v", err)
|
|
}
|
|
if len(repos) != 0 {
|
|
t.Fatalf("repos = %+v, want empty (blank path skipped)", repos)
|
|
}
|
|
}
|
|
|
|
// newPagedGitLab serves full pages until pagesToFull+1, then a short page.
|
|
func newPagedGitLab(t *testing.T, alwaysFull bool) *GitLab {
|
|
t.Helper()
|
|
mux := http.NewServeMux()
|
|
mux.HandleFunc("/api/v1/user/repos", func(w http.ResponseWriter, r *http.Request) {
|
|
page, _ := strconv.Atoi(r.URL.Query().Get("page"))
|
|
if page < 1 {
|
|
page = 1
|
|
}
|
|
count := projectsPerPage
|
|
if !alwaysFull && page > 2 {
|
|
count = 1
|
|
}
|
|
var b strings.Builder
|
|
b.WriteString("[")
|
|
for i := 0; i < count; i++ {
|
|
if i > 0 {
|
|
b.WriteString(",")
|
|
}
|
|
fmt.Fprintf(&b, `{"full_name":"team/p%d-%d","name":"P","owner":{"login":"team"},`+
|
|
`"updated_at":"2024-01-01T00:00:00Z","html_url":"https://gl","default_branch":"main"}`, page, i)
|
|
}
|
|
b.WriteString("]")
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_, _ = w.Write([]byte(b.String()))
|
|
})
|
|
up := httptest.NewServer(mux)
|
|
t.Cleanup(up.Close)
|
|
store := openTestStore(t)
|
|
if err := store.SetSetting(settingGitLabToken, "pat"); err != nil {
|
|
t.Fatalf("set token: %v", err)
|
|
}
|
|
return NewGitLab(store, up.URL)
|
|
}
|
|
|
|
func TestGitLabReposPaginatesUntilShortPage(t *testing.T) {
|
|
gl := newPagedGitLab(t, false) // 50 + 50 + 1 (short) → stops after page 3
|
|
repos, err := gl.Repos(context.Background())
|
|
if err != nil {
|
|
t.Fatalf("repos: %v", err)
|
|
}
|
|
if len(repos) != 2*projectsPerPage+1 {
|
|
t.Fatalf("repos = %d, want %d (pages 1-2 full + short page 3)", len(repos), 2*projectsPerPage+1)
|
|
}
|
|
if repos[0].Path != "team/p1-0" || repos[len(repos)-1].Path != "team/p3-0" {
|
|
t.Fatalf("first/last = %q..%q, want page1..page3", repos[0].Path, repos[len(repos)-1].Path)
|
|
}
|
|
}
|
|
|
|
func TestGitLabReposPaginationCapped(t *testing.T) {
|
|
gl := newPagedGitLab(t, true) // upstream always returns full pages
|
|
repos, err := gl.Repos(context.Background())
|
|
if err != nil {
|
|
t.Fatalf("repos: %v", err)
|
|
}
|
|
if len(repos) != maxRepoPages*projectsPerPage {
|
|
t.Fatalf("repos = %d, want capped at %d", len(repos), maxRepoPages*projectsPerPage)
|
|
}
|
|
}
|