Files
review-bot/gitea/client_test.go
Rodin 56f5abda3c
CI / test (pull_request) Successful in 14s
CI / review (gpt-5-mini, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 1m57s
CI / review (gpt-5, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 2m2s
feat: multi-repo patterns + directory recursion
patterns-repo now accepts a comma-separated list of repos:
  PATTERNS_REPO="rodin/elixir-patterns,rodin/phoenix-conventions"

patterns-files accepts files AND directories:
  PATTERNS_FILES="README.md,docs/"

When a path is a directory, all files within it are fetched
recursively via the Gitea contents API. Only .md, .txt, .yml,
and .yaml files are included as pattern content.

New API methods:
- ListContents: list files/dirs at a path via contents API
- GetAllFilesInPath: recursively fetch all file contents

This allows a single review action to pull idioms from multiple
pattern repos (e.g. elixir-patterns + phoenix-conventions) and
include entire directories of documentation as review criteria.
2026-05-01 12:14:19 -07:00

296 lines
8.9 KiB
Go

package gitea
import (
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
"testing"
)
func TestGetPullRequest(t *testing.T) {
pr := PullRequest{
Title: "Add feature X",
Body: "This adds feature X.",
}
pr.Head.Sha = "abc123"
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/api/v1/repos/owner/repo/pulls/1" {
t.Errorf("unexpected path: %s", r.URL.Path)
}
if r.Header.Get("Authorization") != "token test-token" {
t.Errorf("unexpected auth header: %s", r.Header.Get("Authorization"))
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(pr)
}))
defer server.Close()
client := NewClient(server.URL, "test-token")
got, err := client.GetPullRequest("owner", "repo", 1)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got.Title != "Add feature X" {
t.Errorf("expected title %q, got %q", "Add feature X", got.Title)
}
if got.Body != "This adds feature X." {
t.Errorf("expected body %q, got %q", "This adds feature X.", got.Body)
}
if got.Head.Sha != "abc123" {
t.Errorf("expected sha %q, got %q", "abc123", got.Head.Sha)
}
}
func TestGetPullRequestDiff(t *testing.T) {
expectedDiff := "diff --git a/file.go b/file.go\n--- a/file.go\n+++ b/file.go\n@@ -1 +1 @@\n-old\n+new\n"
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/api/v1/repos/owner/repo/pulls/5.diff" {
t.Errorf("unexpected path: %s", r.URL.Path)
}
w.Write([]byte(expectedDiff))
}))
defer server.Close()
client := NewClient(server.URL, "test-token")
got, err := client.GetPullRequestDiff("owner", "repo", 5)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got != expectedDiff {
t.Errorf("expected diff %q, got %q", expectedDiff, got)
}
}
func TestGetCommitStatuses(t *testing.T) {
statuses := []CommitStatus{
{Status: "success", Context: "ci/test", Description: "All tests passed"},
{Status: "failure", Context: "ci/lint", Description: "Lint failed"},
}
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/api/v1/repos/owner/repo/commits/abc123/statuses" {
t.Errorf("unexpected path: %s", r.URL.Path)
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(statuses)
}))
defer server.Close()
client := NewClient(server.URL, "test-token")
got, err := client.GetCommitStatuses("owner", "repo", "abc123")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(got) != 2 {
t.Fatalf("expected 2 statuses, got %d", len(got))
}
if got[0].Status != "success" {
t.Errorf("expected first status %q, got %q", "success", got[0].Status)
}
if got[1].Status != "failure" {
t.Errorf("expected second status %q, got %q", "failure", got[1].Status)
}
}
func TestPostReview(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
t.Errorf("expected POST, got %s", r.Method)
}
if r.URL.Path != "/api/v1/repos/owner/repo/pulls/3/reviews" {
t.Errorf("unexpected path: %s", r.URL.Path)
}
if r.Header.Get("Content-Type") != "application/json" {
t.Errorf("unexpected content type: %s", r.Header.Get("Content-Type"))
}
var payload struct {
Body string `json:"body"`
Event string `json:"event"`
}
if err := json.NewDecoder(r.Body).Decode(&payload); err != nil {
t.Fatalf("failed to decode payload: %v", err)
}
if payload.Body != "LGTM" {
t.Errorf("expected body %q, got %q", "LGTM", payload.Body)
}
if payload.Event != "APPROVED" {
t.Errorf("expected event %q, got %q", "APPROVED", payload.Event)
}
w.WriteHeader(http.StatusOK)
w.Write([]byte(`{}`))
}))
defer server.Close()
client := NewClient(server.URL, "test-token")
err := client.PostReview("owner", "repo", 3, "APPROVED", "LGTM")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
}
func TestGetPullRequest_Non200(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusNotFound)
w.Write([]byte(`{"message":"not found"}`))
}))
defer server.Close()
client := NewClient(server.URL, "test-token")
_, err := client.GetPullRequest("owner", "repo", 999)
if err == nil {
t.Fatal("expected error for 404, got nil")
}
}
func TestGetPullRequest_BadJSON(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`not json`))
}))
defer server.Close()
client := NewClient(server.URL, "test-token")
_, err := client.GetPullRequest("owner", "repo", 1)
if err == nil {
t.Fatal("expected error for bad JSON, got nil")
}
}
func TestPostReview_Non200(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.WriteHeader(http.StatusForbidden)
w.Write([]byte(`{"message":"forbidden"}`))
}))
defer server.Close()
client := NewClient(server.URL, "test-token")
err := client.PostReview("owner", "repo", 1, "APPROVED", "test")
if err == nil {
t.Fatal("expected error for 403, got nil")
}
}
func TestGetFileContent(t *testing.T) {
expected := "# Conventions\n- Be nice\n"
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/api/v1/repos/owner/repo/raw/CONVENTIONS.md" {
t.Errorf("unexpected path: %s", r.URL.Path)
}
w.Write([]byte(expected))
}))
defer server.Close()
client := NewClient(server.URL, "test-token")
got, err := client.GetFileContent("owner", "repo", "CONVENTIONS.md")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got != expected {
t.Errorf("expected %q, got %q", expected, got)
}
}
func TestGetPullRequestFiles(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/api/v1/repos/owner/repo/pulls/1/files" {
t.Errorf("unexpected path: %s", r.URL.Path)
}
w.Header().Set("Content-Type", "application/json")
w.Write([]byte(`[{"filename":"main.go","status":"modified"},{"filename":"old.go","status":"removed"}]`))
}))
defer server.Close()
client := NewClient(server.URL, "test-token")
files, err := client.GetPullRequestFiles("owner", "repo", 1)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(files) != 2 {
t.Fatalf("expected 2 files, got %d", len(files))
}
if files[0].Filename != "main.go" || files[0].Status != "modified" {
t.Errorf("unexpected first file: %+v", files[0])
}
}
func TestGetFileContentRef(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/api/v1/repos/owner/repo/raw/main.go" {
t.Errorf("unexpected path: %s", r.URL.Path)
}
if r.URL.Query().Get("ref") != "feature-branch" {
t.Errorf("unexpected ref: %s", r.URL.Query().Get("ref"))
}
w.Write([]byte("package main\n"))
}))
defer server.Close()
client := NewClient(server.URL, "test-token")
content, err := client.GetFileContentRef("owner", "repo", "main.go", "feature-branch")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if content != "package main\n" {
t.Errorf("unexpected content: %q", content)
}
}
func TestListContents(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/api/v1/repos/owner/repo/contents/docs" {
t.Errorf("unexpected path: %s", r.URL.Path)
}
w.Header().Set("Content-Type", "application/json")
fmt.Fprintf(w, `[{"name":"guide.md","path":"docs/guide.md","type":"file"},{"name":"sub","path":"docs/sub","type":"dir"}]`)
}))
defer server.Close()
client := NewClient(server.URL, "test-token")
entries, err := client.ListContents("owner", "repo", "docs")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(entries) != 2 {
t.Fatalf("expected 2 entries, got %d", len(entries))
}
if entries[0].Type != "file" || entries[0].Path != "docs/guide.md" {
t.Errorf("unexpected first entry: %+v", entries[0])
}
if entries[1].Type != "dir" {
t.Errorf("expected dir type, got %s", entries[1].Type)
}
}
func TestGetAllFilesInPath_File(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/api/v1/repos/owner/repo/contents/README.md" {
// Gitea returns 404 for contents API on files (it's not a dir)
http.NotFound(w, r)
return
}
if r.URL.Path == "/api/v1/repos/owner/repo/raw/README.md" {
fmt.Fprintf(w, "# Hello")
return
}
http.NotFound(w, r)
}))
defer server.Close()
client := NewClient(server.URL, "test-token")
files, err := client.GetAllFilesInPath("owner", "repo", "README.md")
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if len(files) != 1 {
t.Fatalf("expected 1 file, got %d", len(files))
}
if files["README.md"] != "# Hello" {
t.Errorf("unexpected content: %q", files["README.md"])
}
}