feat(gitea): harden GetPullRequestDiff against unbounded diff size
CI / test (pull_request) Successful in 23s
CI / review (anthropic--claude-4.6-sonnet, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 31s
CI / review (gpt-5, security, ., rodin/security-patterns, SECURITY_REVIEW.md, SECURITY_REVIEW_TOKEN) (pull_request) Successful in 1m16s
CI / review (gpt-5, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 1m38s
CI / test (pull_request) Successful in 23s
CI / review (anthropic--claude-4.6-sonnet, sonnet, SONNET_REVIEW_TOKEN) (pull_request) Successful in 31s
CI / review (gpt-5, security, ., rodin/security-patterns, SECURITY_REVIEW.md, SECURITY_REVIEW_TOKEN) (pull_request) Successful in 1m16s
CI / review (gpt-5, gpt, GPT_REVIEW_TOKEN) (pull_request) Successful in 1m38s
Add a configurable MaxDiffSize field to Client that limits how much data GetPullRequestDiff will read into memory. The default is 10 MB (DefaultMaxDiffSize). When the diff exceeds the limit, ErrDiffTooLarge is returned, allowing callers to skip position translation gracefully. Implementation uses io.LimitReader to read maxBytes+1, detecting overflow without buffering the entire response. Setting MaxDiffSize to -1 disables the limit entirely. Closes #92
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
package gitea
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestGetPullRequestDiff_ExceedsMaxSize(t *testing.T) {
|
||||
// Create a diff that exceeds a small limit
|
||||
largeDiff := strings.Repeat("+ added line\n", 1000) // ~13 KB
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(largeDiff))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client := NewClient(server.URL, "test-token")
|
||||
client.MaxDiffSize = 100 // 100 bytes limit
|
||||
client.RetryBackoff = []time.Duration{} // no delay in tests
|
||||
|
||||
_, err := client.GetPullRequestDiff(context.Background(), "owner", "repo", 1)
|
||||
if err == nil {
|
||||
t.Fatal("expected error for oversized diff, got nil")
|
||||
}
|
||||
if !errors.Is(err, ErrDiffTooLarge) {
|
||||
t.Errorf("expected ErrDiffTooLarge, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPullRequestDiff_WithinMaxSize(t *testing.T) {
|
||||
smallDiff := "diff --git a/f.go b/f.go\n--- a/f.go\n+++ b/f.go\n@@ -1 +1 @@\n-old\n+new\n"
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(smallDiff))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client := NewClient(server.URL, "test-token")
|
||||
client.MaxDiffSize = 1024 // 1 KB limit — more than enough
|
||||
client.RetryBackoff = []time.Duration{}
|
||||
|
||||
got, err := client.GetPullRequestDiff(context.Background(), "owner", "repo", 1)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if got != smallDiff {
|
||||
t.Errorf("expected diff %q, got %q", smallDiff, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPullRequestDiff_ExactlyAtLimit(t *testing.T) {
|
||||
// A diff that is exactly at the limit should succeed
|
||||
exactDiff := strings.Repeat("x", 50)
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(exactDiff))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client := NewClient(server.URL, "test-token")
|
||||
client.MaxDiffSize = 50 // exactly the size of the diff
|
||||
client.RetryBackoff = []time.Duration{}
|
||||
|
||||
got, err := client.GetPullRequestDiff(context.Background(), "owner", "repo", 1)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error for diff at exact limit: %v", err)
|
||||
}
|
||||
if got != exactDiff {
|
||||
t.Errorf("expected diff to match, got length %d", len(got))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPullRequestDiff_OneByteOverLimit(t *testing.T) {
|
||||
// A diff that is one byte over the limit should fail
|
||||
overDiff := strings.Repeat("x", 51)
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(overDiff))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client := NewClient(server.URL, "test-token")
|
||||
client.MaxDiffSize = 50
|
||||
client.RetryBackoff = []time.Duration{}
|
||||
|
||||
_, err := client.GetPullRequestDiff(context.Background(), "owner", "repo", 1)
|
||||
if err == nil {
|
||||
t.Fatal("expected error for diff one byte over limit")
|
||||
}
|
||||
if !errors.Is(err, ErrDiffTooLarge) {
|
||||
t.Errorf("expected ErrDiffTooLarge, got: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPullRequestDiff_DisabledLimit(t *testing.T) {
|
||||
// When MaxDiffSize is -1, no limit is enforced
|
||||
largeDiff := strings.Repeat("x", 10000)
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(largeDiff))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client := NewClient(server.URL, "test-token")
|
||||
client.MaxDiffSize = -1 // disabled
|
||||
client.RetryBackoff = []time.Duration{}
|
||||
|
||||
got, err := client.GetPullRequestDiff(context.Background(), "owner", "repo", 1)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error with disabled limit: %v", err)
|
||||
}
|
||||
if got != largeDiff {
|
||||
t.Errorf("expected full diff with disabled limit, got length %d", len(got))
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPullRequestDiff_DefaultLimit(t *testing.T) {
|
||||
// With zero MaxDiffSize (default), should use DefaultMaxDiffSize.
|
||||
// A small diff should succeed without setting MaxDiffSize.
|
||||
smallDiff := "diff content"
|
||||
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
w.Write([]byte(smallDiff))
|
||||
}))
|
||||
defer server.Close()
|
||||
|
||||
client := NewClient(server.URL, "test-token")
|
||||
// MaxDiffSize is zero (default) — should use DefaultMaxDiffSize (10 MB)
|
||||
client.RetryBackoff = []time.Duration{}
|
||||
|
||||
got, err := client.GetPullRequestDiff(context.Background(), "owner", "repo", 1)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error with default limit: %v", err)
|
||||
}
|
||||
if got != smallDiff {
|
||||
t.Errorf("expected diff %q, got %q", smallDiff, got)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user