515 lines
15 KiB
Go
515 lines
15 KiB
Go
package gitea_test
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
"gitea.weiker.me/rodin/review-bot/gitea"
|
|
"gitea.weiker.me/rodin/review-bot/vcs"
|
|
)
|
|
|
|
func TestAdapter_GetPullRequest(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode(map[string]any{
|
|
"title": "Test PR",
|
|
"body": "PR body",
|
|
"head": map[string]any{
|
|
"sha": "abc123",
|
|
"ref": "feature-branch",
|
|
},
|
|
"base": map[string]any{
|
|
"ref": "main",
|
|
},
|
|
})
|
|
}))
|
|
defer server.Close()
|
|
|
|
client := gitea.NewClient(server.URL, "token")
|
|
adapter := gitea.NewAdapter(client)
|
|
|
|
pr, err := adapter.GetPullRequest(context.Background(), "owner", "repo", 42)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if pr.Number != 42 {
|
|
t.Errorf("Number = %d, want 42", pr.Number)
|
|
}
|
|
if pr.Title != "Test PR" {
|
|
t.Errorf("Title = %q, want %q", pr.Title, "Test PR")
|
|
}
|
|
if pr.Body != "PR body" {
|
|
t.Errorf("Body = %q, want %q", pr.Body, "PR body")
|
|
}
|
|
if pr.Head.SHA != "abc123" {
|
|
t.Errorf("Head.SHA = %q, want %q", pr.Head.SHA, "abc123")
|
|
}
|
|
if pr.Head.Ref != "feature-branch" {
|
|
t.Errorf("Head.Ref = %q, want %q", pr.Head.Ref, "feature-branch")
|
|
}
|
|
if pr.Base.Ref != "main" {
|
|
t.Errorf("Base.Ref = %q, want %q", pr.Base.Ref, "main")
|
|
}
|
|
}
|
|
|
|
func TestAdapter_GetPullRequestFiles(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode([]map[string]any{
|
|
{"filename": "main.go", "status": "modified"},
|
|
{"filename": "new.go", "status": "added"},
|
|
})
|
|
}))
|
|
defer server.Close()
|
|
|
|
client := gitea.NewClient(server.URL, "token")
|
|
adapter := gitea.NewAdapter(client)
|
|
|
|
files, err := adapter.GetPullRequestFiles(context.Background(), "owner", "repo", 1)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if len(files) != 2 {
|
|
t.Fatalf("got %d files, want 2", len(files))
|
|
}
|
|
if files[0].Filename != "main.go" || files[0].Status != "modified" {
|
|
t.Errorf("files[0] = %+v", files[0])
|
|
}
|
|
if files[1].Filename != "new.go" || files[1].Status != "added" {
|
|
t.Errorf("files[1] = %+v", files[1])
|
|
}
|
|
}
|
|
|
|
func TestAdapter_ListReviews(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode([]map[string]any{
|
|
{
|
|
"id": 1,
|
|
"body": "LGTM",
|
|
"user": map[string]any{"login": "reviewer1"},
|
|
"state": "APPROVED",
|
|
"stale": false,
|
|
"commit_id": "abc123",
|
|
},
|
|
{
|
|
"id": 2,
|
|
"body": "Needs work",
|
|
"user": map[string]any{"login": "reviewer2"},
|
|
"state": "REQUEST_CHANGES",
|
|
"stale": true,
|
|
"commit_id": "def456",
|
|
},
|
|
})
|
|
}))
|
|
defer server.Close()
|
|
|
|
client := gitea.NewClient(server.URL, "token")
|
|
adapter := gitea.NewAdapter(client)
|
|
|
|
reviews, err := adapter.ListReviews(context.Background(), "owner", "repo", 1)
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if len(reviews) != 2 {
|
|
t.Fatalf("got %d reviews, want 2", len(reviews))
|
|
}
|
|
if reviews[0].ID != 1 || reviews[0].Body != "LGTM" || reviews[0].User.Login != "reviewer1" {
|
|
t.Errorf("reviews[0] = %+v", reviews[0])
|
|
}
|
|
if reviews[0].State != "APPROVED" || reviews[0].Stale || reviews[0].CommitID != "abc123" {
|
|
t.Errorf("reviews[0] state/stale/commit = %v/%v/%v", reviews[0].State, reviews[0].Stale, reviews[0].CommitID)
|
|
}
|
|
if reviews[1].ID != 2 || !reviews[1].Stale || reviews[1].State != "REQUEST_CHANGES" {
|
|
t.Errorf("reviews[1] = %+v", reviews[1])
|
|
}
|
|
}
|
|
|
|
func TestAdapter_GetCommitStatuses(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode([]map[string]any{
|
|
{
|
|
"status": "success",
|
|
"context": "ci/test",
|
|
"description": "All tests pass",
|
|
"target_url": "https://ci.example.com/1",
|
|
},
|
|
})
|
|
}))
|
|
defer server.Close()
|
|
|
|
client := gitea.NewClient(server.URL, "token")
|
|
adapter := gitea.NewAdapter(client)
|
|
|
|
statuses, err := adapter.GetCommitStatuses(context.Background(), "owner", "repo", "abc123")
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if len(statuses) != 1 {
|
|
t.Fatalf("got %d statuses, want 1", len(statuses))
|
|
}
|
|
if statuses[0].Status != "success" {
|
|
t.Errorf("Status = %q, want %q", statuses[0].Status, "success")
|
|
}
|
|
if statuses[0].Context != "ci/test" {
|
|
t.Errorf("Context = %q, want %q", statuses[0].Context, "ci/test")
|
|
}
|
|
if statuses[0].Description != "All tests pass" {
|
|
t.Errorf("Description = %q, want %q", statuses[0].Description, "All tests pass")
|
|
}
|
|
if statuses[0].TargetURL != "https://ci.example.com/1" {
|
|
t.Errorf("TargetURL = %q, want %q", statuses[0].TargetURL, "https://ci.example.com/1")
|
|
}
|
|
}
|
|
|
|
func TestAdapter_PostReview_EventTranslation(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
event vcs.ReviewEvent
|
|
wantEvent string
|
|
}{
|
|
{"APPROVE becomes APPROVED", vcs.ReviewEventApprove, "APPROVED"},
|
|
{"REQUEST_CHANGES stays", vcs.ReviewEventRequestChanges, "REQUEST_CHANGES"},
|
|
{"COMMENT stays", vcs.ReviewEventComment, "COMMENT"},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
var gotEvent string
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
var payload struct {
|
|
Event string `json:"event"`
|
|
}
|
|
json.NewDecoder(r.Body).Decode(&payload)
|
|
gotEvent = payload.Event
|
|
json.NewEncoder(w).Encode(map[string]any{
|
|
"id": 1,
|
|
"body": "test",
|
|
"user": map[string]any{"login": "bot"},
|
|
})
|
|
}))
|
|
defer server.Close()
|
|
|
|
client := gitea.NewClient(server.URL, "token")
|
|
adapter := gitea.NewAdapter(client)
|
|
|
|
_, err := adapter.PostReview(context.Background(), "owner", "repo", 1, vcs.ReviewRequest{
|
|
Body: "test",
|
|
Event: tt.event,
|
|
// No comments → no diff fetch needed
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if gotEvent != tt.wantEvent {
|
|
t.Errorf("event = %q, want %q", gotEvent, tt.wantEvent)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestAdapter_PostReview_CommitID(t *testing.T) {
|
|
var gotCommitID string
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
var payload struct {
|
|
CommitID string `json:"commit_id"`
|
|
}
|
|
json.NewDecoder(r.Body).Decode(&payload)
|
|
gotCommitID = payload.CommitID
|
|
json.NewEncoder(w).Encode(map[string]any{
|
|
"id": 1,
|
|
"body": "test",
|
|
"user": map[string]any{"login": "bot"},
|
|
"commit_id": payload.CommitID,
|
|
})
|
|
}))
|
|
defer server.Close()
|
|
|
|
client := gitea.NewClient(server.URL, "token")
|
|
adapter := gitea.NewAdapter(client)
|
|
|
|
_, err := adapter.PostReview(context.Background(), "owner", "repo", 1, vcs.ReviewRequest{
|
|
Body: "test",
|
|
Event: vcs.ReviewEventApprove,
|
|
CommitID: "sha256abc",
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if gotCommitID != "sha256abc" {
|
|
t.Errorf("expected commit_id %q forwarded to client, got %q", "sha256abc", gotCommitID)
|
|
}
|
|
}
|
|
|
|
func TestAdapter_PostReview_WithComments_PositionTranslation(t *testing.T) {
|
|
diff := `diff --git a/main.go b/main.go
|
|
--- a/main.go
|
|
+++ b/main.go
|
|
@@ -1,3 +1,4 @@
|
|
package main
|
|
|
|
+// new comment at line 3
|
|
func main() {}
|
|
`
|
|
var gotComments []struct {
|
|
Path string `json:"path"`
|
|
NewPosition int64 `json:"new_position"`
|
|
Body string `json:"body"`
|
|
}
|
|
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
if strings.HasSuffix(r.URL.Path, ".diff") {
|
|
// Diff request
|
|
w.Write([]byte(diff))
|
|
return
|
|
}
|
|
if strings.HasSuffix(r.URL.Path, "/reviews") {
|
|
// Review post
|
|
var payload struct {
|
|
Comments []struct {
|
|
Path string `json:"path"`
|
|
NewPosition int64 `json:"new_position"`
|
|
Body string `json:"body"`
|
|
} `json:"comments"`
|
|
}
|
|
json.NewDecoder(r.Body).Decode(&payload)
|
|
gotComments = payload.Comments
|
|
json.NewEncoder(w).Encode(map[string]any{
|
|
"id": 1,
|
|
"body": "review",
|
|
"user": map[string]any{"login": "bot"},
|
|
})
|
|
return
|
|
}
|
|
t.Errorf("unexpected request: %s %s", r.Method, r.URL.Path)
|
|
w.WriteHeader(http.StatusNotFound)
|
|
}))
|
|
defer server.Close()
|
|
|
|
client := gitea.NewClient(server.URL, "token")
|
|
adapter := gitea.NewAdapter(client)
|
|
|
|
// Position 4 in this diff is "+// new comment at line 3" → new line 3
|
|
_, err := adapter.PostReview(context.Background(), "owner", "repo", 1, vcs.ReviewRequest{
|
|
Body: "review",
|
|
Event: vcs.ReviewEventRequestChanges,
|
|
Comments: []vcs.ReviewComment{
|
|
{
|
|
Path: "main.go",
|
|
Position: 4,
|
|
CommitID: "abc123",
|
|
Body: "needs fix",
|
|
},
|
|
},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
|
|
if len(gotComments) != 1 {
|
|
t.Fatalf("got %d comments, want 1", len(gotComments))
|
|
}
|
|
if gotComments[0].Path != "main.go" {
|
|
t.Errorf("path = %q, want %q", gotComments[0].Path, "main.go")
|
|
}
|
|
if gotComments[0].NewPosition != 3 {
|
|
t.Errorf("new_position = %d, want 3", gotComments[0].NewPosition)
|
|
}
|
|
if gotComments[0].Body != "needs fix" {
|
|
t.Errorf("body = %q, want %q", gotComments[0].Body, "needs fix")
|
|
}
|
|
}
|
|
|
|
func TestAdapter_DismissReview(t *testing.T) {
|
|
var deleteCalled bool
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method == http.MethodDelete {
|
|
deleteCalled = true
|
|
w.WriteHeader(204)
|
|
return
|
|
}
|
|
w.WriteHeader(404)
|
|
}))
|
|
defer server.Close()
|
|
|
|
client := gitea.NewClient(server.URL, "token")
|
|
adapter := gitea.NewAdapter(client)
|
|
|
|
err := adapter.DismissReview(context.Background(), "owner", "repo", 1, 99, "stale review")
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if !deleteCalled {
|
|
t.Error("expected delete to be called")
|
|
}
|
|
}
|
|
|
|
func TestAdapter_Underlying(t *testing.T) {
|
|
client := gitea.NewClient("http://example.com", "token")
|
|
adapter := gitea.NewAdapter(client)
|
|
if adapter.Underlying() != client {
|
|
t.Error("Underlying() should return the wrapped client")
|
|
}
|
|
}
|
|
|
|
func TestAdapter_ListContents(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewEncoder(w).Encode([]map[string]any{
|
|
{"name": "main.go", "path": "src/main.go", "type": "file"},
|
|
{"name": "util", "path": "src/util", "type": "dir"},
|
|
})
|
|
}))
|
|
defer server.Close()
|
|
|
|
client := gitea.NewClient(server.URL, "token")
|
|
adapter := gitea.NewAdapter(client)
|
|
|
|
entries, err := adapter.ListContents(context.Background(), "owner", "repo", "src")
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if len(entries) != 2 {
|
|
t.Fatalf("got %d entries, want 2", len(entries))
|
|
}
|
|
if entries[0].Name != "main.go" || entries[0].Type != "file" {
|
|
t.Errorf("entries[0] = %+v", entries[0])
|
|
}
|
|
if entries[1].Name != "util" || entries[1].Type != "dir" {
|
|
t.Errorf("entries[1] = %+v", entries[1])
|
|
}
|
|
}
|
|
|
|
func TestAdapter_GetFileContent_RefRouting(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
// When ref is provided, the URL should contain ?ref=
|
|
if r.URL.RawQuery != "" && strings.Contains(r.URL.RawQuery, "ref=") {
|
|
w.Write([]byte("content-at-ref"))
|
|
} else {
|
|
w.Write([]byte("content-default"))
|
|
}
|
|
}))
|
|
defer server.Close()
|
|
|
|
client := gitea.NewClient(server.URL, "token")
|
|
adapter := gitea.NewAdapter(client)
|
|
|
|
// Empty ref → routes to GetFileContent (no ?ref= query param)
|
|
got, err := adapter.GetFileContent(context.Background(), "owner", "repo", "main.go", "")
|
|
if err != nil {
|
|
t.Fatalf("GetFileContent(ref=\"\"): %v", err)
|
|
}
|
|
if got != "content-default" {
|
|
t.Errorf("GetFileContent(ref=\"\") = %q, want %q", got, "content-default")
|
|
}
|
|
|
|
// Non-empty ref → routes to GetFileContentRef (with ?ref= query param)
|
|
got, err = adapter.GetFileContent(context.Background(), "owner", "repo", "main.go", "abc123")
|
|
if err != nil {
|
|
t.Fatalf("GetFileContent(ref=\"abc123\"): %v", err)
|
|
}
|
|
if got != "content-at-ref" {
|
|
t.Errorf("GetFileContent(ref=\"abc123\") = %q, want %q", got, "content-at-ref")
|
|
}
|
|
}
|
|
|
|
func TestAdapter_RequestReviewerSelf(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.Method != http.MethodPost {
|
|
t.Errorf("expected POST, got %s", r.Method)
|
|
}
|
|
expected := "/api/v1/repos/owner/repo/pulls/5/requested_reviewers"
|
|
if r.URL.Path != expected {
|
|
t.Errorf("path = %q, want %q", r.URL.Path, expected)
|
|
}
|
|
w.WriteHeader(http.StatusCreated)
|
|
}))
|
|
defer server.Close()
|
|
|
|
client := gitea.NewClient(server.URL, "token")
|
|
adapter := gitea.NewAdapter(client)
|
|
|
|
err := adapter.RequestReviewerSelf(context.Background(), "owner", "repo", 5, "bot-user")
|
|
if err != nil {
|
|
t.Fatalf("RequestReviewerSelf() error = %v", err)
|
|
}
|
|
}
|
|
|
|
func TestAdapter_PostReview_CommitID_Threading(t *testing.T) {
|
|
var gotPayload struct {
|
|
Body string `json:"body"`
|
|
Event string `json:"event"`
|
|
CommitID string `json:"commit_id"`
|
|
}
|
|
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewDecoder(r.Body).Decode(&gotPayload)
|
|
json.NewEncoder(w).Encode(map[string]any{
|
|
"id": 1,
|
|
"body": "test",
|
|
"user": map[string]any{"login": "bot"},
|
|
"commit_id": "abc123def456",
|
|
})
|
|
}))
|
|
defer server.Close()
|
|
|
|
client := gitea.NewClient(server.URL, "token")
|
|
adapter := gitea.NewAdapter(client)
|
|
|
|
review, err := adapter.PostReview(context.Background(), "owner", "repo", 1, vcs.ReviewRequest{
|
|
Body: "LGTM",
|
|
Event: vcs.ReviewEventApprove,
|
|
CommitID: "abc123def456",
|
|
// No comments → no diff fetch needed
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
if gotPayload.CommitID != "abc123def456" {
|
|
t.Errorf("commit_id = %q, want %q", gotPayload.CommitID, "abc123def456")
|
|
}
|
|
if review.CommitID != "abc123def456" {
|
|
t.Errorf("review.CommitID = %q, want %q", review.CommitID, "abc123def456")
|
|
}
|
|
}
|
|
|
|
func TestAdapter_PostReview_EmptyCommitID_Omitted(t *testing.T) {
|
|
var gotRawPayload map[string]any
|
|
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
json.NewDecoder(r.Body).Decode(&gotRawPayload)
|
|
json.NewEncoder(w).Encode(map[string]any{
|
|
"id": 1,
|
|
"body": "test",
|
|
"user": map[string]any{"login": "bot"},
|
|
})
|
|
}))
|
|
defer server.Close()
|
|
|
|
client := gitea.NewClient(server.URL, "token")
|
|
adapter := gitea.NewAdapter(client)
|
|
|
|
_, err := adapter.PostReview(context.Background(), "owner", "repo", 1, vcs.ReviewRequest{
|
|
Body: "looks good",
|
|
Event: vcs.ReviewEventComment,
|
|
// CommitID intentionally empty
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("unexpected error: %v", err)
|
|
}
|
|
// With empty CommitID and omitempty tag, the field should not appear in JSON
|
|
if _, exists := gotRawPayload["commit_id"]; exists {
|
|
t.Errorf("commit_id should be omitted when empty, but was present: %v", gotRawPayload["commit_id"])
|
|
}
|
|
}
|