1b4448afb4
teabot watches Gitea repos and dispatches one-shot Claude Code sessions in Docker containers to work issues and review PRs, acting as configurable bot personalities. Claude-Session: https://claude.ai/code/session_015ur3i7D2azsMAWTSVABApv
136 lines
4.4 KiB
Go
136 lines
4.4 KiB
Go
package gitea
|
|
|
|
import (
|
|
"context"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func newTestClient(t *testing.T, h http.Handler) *Client {
|
|
t.Helper()
|
|
srv := httptest.NewServer(h)
|
|
t.Cleanup(srv.Close)
|
|
c := NewClient(srv.URL, "test-token")
|
|
return c
|
|
}
|
|
|
|
func TestListIssuesExcludesPullsAndSendsAuth(t *testing.T) {
|
|
var gotAuth, gotType, gotState, gotSince string
|
|
c := newTestClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
gotAuth = r.Header.Get("Authorization")
|
|
gotType = r.URL.Query().Get("type")
|
|
gotState = r.URL.Query().Get("state")
|
|
gotSince = r.URL.Query().Get("since")
|
|
w.Header().Set("Content-Type", "application/json")
|
|
// One real issue and one PR-shaped issue that must be filtered out.
|
|
_, _ = w.Write([]byte(`[
|
|
{"id":1,"number":5,"title":"real issue","user":{"login":"alice"}},
|
|
{"id":2,"number":6,"title":"a pr","user":{"login":"bob"},"pull_request":{"merged":false}}
|
|
]`))
|
|
}))
|
|
|
|
since := time.Date(2026, 1, 2, 3, 4, 5, 0, time.UTC)
|
|
issues, err := c.ListIssues(context.Background(), "unkin/teabot", since)
|
|
if err != nil {
|
|
t.Fatalf("ListIssues: %v", err)
|
|
}
|
|
if len(issues) != 1 || issues[0].Index != 5 {
|
|
t.Fatalf("expected 1 non-PR issue #5, got %+v", issues)
|
|
}
|
|
if gotAuth != "token test-token" {
|
|
t.Errorf("Authorization = %q", gotAuth)
|
|
}
|
|
if gotType != "issues" || gotState != "open" {
|
|
t.Errorf("query type=%q state=%q", gotType, gotState)
|
|
}
|
|
if gotSince != "2026-01-02T03:04:05Z" {
|
|
t.Errorf("since = %q", gotSince)
|
|
}
|
|
}
|
|
|
|
func TestListPulls(t *testing.T) {
|
|
c := newTestClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Query().Get("state") != "open" {
|
|
t.Errorf("state = %q", r.URL.Query().Get("state"))
|
|
}
|
|
_, _ = w.Write([]byte(`[{"id":1,"number":7,"title":"add feature","user":{"login":"carol"},"head":{"ref":"benvin/x"},"base":{"ref":"main"}}]`))
|
|
}))
|
|
pulls, err := c.ListPulls(context.Background(), "unkin/teabot")
|
|
if err != nil {
|
|
t.Fatalf("ListPulls: %v", err)
|
|
}
|
|
if len(pulls) != 1 || pulls[0].Index != 7 || pulls[0].Head.Ref != "benvin/x" {
|
|
t.Fatalf("unexpected pulls: %+v", pulls)
|
|
}
|
|
}
|
|
|
|
func TestListComments(t *testing.T) {
|
|
c := newTestClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
_, _ = w.Write([]byte(`[{"id":42,"body":"looks good","user":{"login":"dave"},"issue_url":"https://git.unkin.net/api/v1/repos/unkin/teabot/issues/5"}]`))
|
|
}))
|
|
comments, err := c.ListComments(context.Background(), "unkin/teabot", time.Time{})
|
|
if err != nil {
|
|
t.Fatalf("ListComments: %v", err)
|
|
}
|
|
if len(comments) != 1 || comments[0].ID != 42 {
|
|
t.Fatalf("unexpected comments: %+v", comments)
|
|
}
|
|
}
|
|
|
|
func TestGetPullDiff(t *testing.T) {
|
|
c := newTestClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path != "/api/v1/repos/unkin/teabot/pulls/7.diff" {
|
|
t.Errorf("path = %q", r.URL.Path)
|
|
}
|
|
_, _ = w.Write([]byte("diff --git a/x b/x\n+hello\n"))
|
|
}))
|
|
diff, err := c.GetPullDiff(context.Background(), "unkin/teabot", 7)
|
|
if err != nil {
|
|
t.Fatalf("GetPullDiff: %v", err)
|
|
}
|
|
if diff == "" || diff[:4] != "diff" {
|
|
t.Errorf("unexpected diff: %q", diff)
|
|
}
|
|
}
|
|
|
|
func TestGetErrorsOnNon2xx(t *testing.T) {
|
|
c := newTestClient(t, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
http.Error(w, "nope", http.StatusForbidden)
|
|
}))
|
|
if _, err := c.ListIssues(context.Background(), "a/b", time.Time{}); err == nil {
|
|
t.Error("expected error on 403")
|
|
}
|
|
}
|
|
|
|
func TestSplitRepoValidation(t *testing.T) {
|
|
c := NewClient("https://example.com", "t")
|
|
if _, err := c.ListIssues(context.Background(), "noslash", time.Time{}); err == nil {
|
|
t.Error("expected error for repo without slash")
|
|
}
|
|
}
|
|
|
|
func TestIssueIndexFromCommentURL(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
comment Comment
|
|
want int64
|
|
ok bool
|
|
}{
|
|
{"issue url", Comment{IssueURL: "https://git.unkin.net/api/v1/repos/unkin/teabot/issues/5"}, 5, true},
|
|
{"pr url", Comment{PRURL: "https://git.unkin.net/api/v1/repos/unkin/teabot/pulls/12"}, 12, true},
|
|
{"trailing slash", Comment{IssueURL: "https://x/issues/8/"}, 8, true},
|
|
{"no url", Comment{}, 0, false},
|
|
{"non-numeric", Comment{IssueURL: "https://x/issues/abc"}, 0, false},
|
|
}
|
|
for _, tc := range cases {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
got, ok := IssueIndexFromCommentURL(tc.comment)
|
|
if ok != tc.ok || got != tc.want {
|
|
t.Errorf("got (%d,%v), want (%d,%v)", got, ok, tc.want, tc.ok)
|
|
}
|
|
})
|
|
}
|
|
}
|