Files
teabot/internal/gitea/client_test.go
T
unkinben 1b4448afb4
ci/woodpecker/pr/test Pipeline failed
ci/woodpecker/pr/build Pipeline was successful
ci/woodpecker/pr/pre-commit Pipeline was successful
Add teabot daemon implementation
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
2026-07-26 23:36:21 +10:00

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)
}
})
}
}