Files
MCP/pkg/gitea/attachment_test.go
Renovate Bot e2052d903f chore(deps): update dependencies (#243)
This PR contains the following updates:

| Package | Type | Update | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) |
|---|---|---|---|---|---|
| [docker/setup-buildx-action](https://github.com/docker/setup-buildx-action) ([changelog](https://github.com/docker/setup-buildx-action/compare/bb05f3f5519dd87d3ba754cc423b652a5edd6d2c..37fe631027851001ddb9b187196cc803df7f5f0e)) | action | digest | `bb05f3f` → `37fe631` |  |  |
| [go](https://go.dev/) ([source](https://github.com/golang/go)) | toolchain | minor | `1.26.6` → `1.27.0` | ![age](https://developer.mend.io/api/mc/badges/age/golang-version/go/1.27.0?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/golang-version/go/1.26.6/1.27.0?slim=true) |
| golang.org/x/vuln |  | minor | `v1.6.0` → `v1.7.0` | ![age](https://developer.mend.io/api/mc/badges/age/go/golang.org%2fx%2fvuln/v1.7.0?slim=true) | ![confidence](https://developer.mend.io/api/mc/badges/confidence/go/golang.org%2fx%2fvuln/v1.6.0/v1.7.0?slim=true) |

---

### Configuration

📅 **Schedule**: (UTC)

- Branch creation
  - Between 12:00 AM and 03:59 AM, only on Monday (`* 0-3 * * 1`)
- Automerge
  - At any time (no schedule defined)

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.

👻 **Immortal**: This PR will be recreated if closed unmerged. Get [config help](https://github.com/renovatebot/renovate/discussions) if that's undesired.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box

---

This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4xOTEuMiIsInVwZGF0ZWRJblZlciI6IjQzLjE5MS4yIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119-->

---------

Co-authored-by: silverwind <me@silverwind.io>
Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/243
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Renovate Bot <renovate-bot@gitea.com>
2026-08-24 17:21:20 +00:00

208 lines
6.1 KiB
Go

package gitea
import (
"context"
"errors"
"net/http"
"net/http/httptest"
"net/url"
"os"
"path/filepath"
"strings"
"testing"
"gitea.com/gitea/gitea-mcp/pkg/flag"
)
func TestBuildAttachmentURLRejectsExternalURL(t *testing.T) {
origHost := flag.Host
flag.Host = "https://example.com"
defer func() { flag.Host = origHost }()
if _, err := buildAttachmentURL("https://evil.example.com/attachments/1"); err == nil {
t.Fatal("expected error for full URL attachment path")
}
}
func TestBuildAttachmentURLPreservesHostPathPrefix(t *testing.T) {
origHost := flag.Host
flag.Host = "https://example.com/gitea"
defer func() { flag.Host = origHost }()
got, err := buildAttachmentURL("/attachments/uuid-1")
if err != nil {
t.Fatalf("buildAttachmentURL() error = %v", err)
}
if got != "https://example.com/gitea/attachments/uuid-1" {
t.Fatalf("buildAttachmentURL() = %q", got)
}
}
func TestDownloadAttachmentUsesTokenAndReturnsContentType(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/attachments/uuid-1" {
t.Fatalf("path = %s", r.URL.Path)
}
if got := r.Header.Get("Authorization"); got != "token secret-token" {
t.Fatalf("Authorization = %q", got)
}
if got := r.Header.Get("Accept"); got != "*/*" {
t.Fatalf("Accept = %q", got)
}
w.Header().Set("Content-Type", "image/png")
_, _ = w.Write([]byte("png-bytes"))
}))
defer server.Close()
origHost, origToken := flag.Host, flag.Token
flag.Host, flag.Token = server.URL, "secret-token"
defer func() { flag.Host, flag.Token = origHost, origToken }()
raw, contentType, status, err := DownloadAttachment(context.Background(), "/attachments/uuid-1", "*/*")
if err != nil {
t.Fatalf("DownloadAttachment() error = %v", err)
}
if status != http.StatusOK {
t.Fatalf("DownloadAttachment() status = %d, want %d", status, http.StatusOK)
}
if contentType != "image/png" {
t.Fatalf("contentType = %q", contentType)
}
if string(raw) != "png-bytes" {
t.Fatalf("body = %q", string(raw))
}
}
func TestDownloadAttachmentRejectsCrossOriginRedirect(t *testing.T) {
redirected := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
t.Fatalf("cross-origin redirect should not be followed: %s", r.URL.String())
}))
defer redirected.Close()
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, redirected.URL+"/attachments/uuid-1", http.StatusFound)
}))
defer server.Close()
origHost, origToken := flag.Host, flag.Token
flag.Host, flag.Token = server.URL, "secret-token"
defer func() { flag.Host, flag.Token = origHost, origToken }()
_, _, _, err := DownloadAttachment(context.Background(), "/attachments/uuid-1", "*/*")
if err == nil {
t.Fatal("expected error for cross-origin redirect")
}
want := "do request: Get \"" + redirected.URL + "/attachments/uuid-1\": attachment redirect changed origin"
if err.Error() != want {
t.Fatalf("unexpected error: %v", err)
}
}
func TestDownloadAttachmentRejectsSameOriginRedirectLoopAtHopLimit(t *testing.T) {
var serverURL string
redirects := 0
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
redirects++
http.Redirect(w, r, serverURL+"/attachments/uuid-1", http.StatusFound)
}))
defer server.Close()
serverURL = server.URL
origHost := flag.Host
flag.Host = server.URL
defer func() { flag.Host = origHost }()
_, _, _, err := DownloadAttachment(context.Background(), "/attachments/uuid-1", "*/*")
if err == nil {
t.Fatal("expected redirect limit error")
}
if !strings.Contains(err.Error(), "stopped after 10 redirects") {
t.Fatalf("unexpected error: %v", err)
}
if redirects != 10 {
t.Fatalf("redirects = %d, want 10", redirects)
}
}
func TestWriteAttachmentStreamsBodyToFile(t *testing.T) {
path := filepath.Join(t.TempDir(), "attachment.bin")
written, err := WriteAttachment(strings.NewReader(strings.Repeat("z", 64)), path)
if err != nil {
t.Fatalf("WriteAttachment() error = %v", err)
}
if written != 64 {
t.Fatalf("written = %d, want 64", written)
}
data, err := os.ReadFile(path)
if err != nil {
t.Fatalf("ReadFile() error = %v", err)
}
if string(data) != strings.Repeat("z", 64) {
t.Fatalf("body mismatch")
}
}
func TestWriteAttachmentCreatesPrivateFile(t *testing.T) {
path := filepath.Join(t.TempDir(), "attachment.bin")
if _, err := WriteAttachment(strings.NewReader("secret"), path); err != nil {
t.Fatalf("WriteAttachment() error = %v", err)
}
info, err := os.Stat(path)
if err != nil {
t.Fatalf("Stat() error = %v", err)
}
if got := info.Mode().Perm() & 0o077; got != 0 {
t.Fatalf("permissions = %03o, want no group/other bits", info.Mode().Perm())
}
}
func TestWriteAttachmentTightensExistingFilePermissions(t *testing.T) {
path := filepath.Join(t.TempDir(), "attachment.bin")
if err := os.WriteFile(path, []byte("public"), 0o644); err != nil {
t.Fatalf("WriteFile() error = %v", err)
}
if _, err := WriteAttachment(strings.NewReader("secret"), path); err != nil {
t.Fatalf("WriteAttachment() error = %v", err)
}
info, err := os.Stat(path)
if err != nil {
t.Fatalf("Stat() error = %v", err)
}
if got := info.Mode().Perm(); got != 0o600 {
t.Fatalf("permissions = %03o, want 600", got)
}
}
func TestDownloadAttachmentErrorsOnNon2xx(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
http.Error(w, "nope", http.StatusForbidden)
}))
defer server.Close()
origHost := flag.Host
flag.Host = server.URL
defer func() { flag.Host = origHost }()
_, _, status, err := DownloadAttachment(context.Background(), "/attachments/uuid-1", "*/*")
if status != http.StatusForbidden {
t.Fatalf("status = %d, want %d", status, http.StatusForbidden)
}
if _, ok := errors.AsType[*HTTPError](err); !ok {
t.Fatalf("expected HTTPError, got %T", err)
}
}
func TestAttachmentHTTPClientHasNoTotalTimeout(t *testing.T) {
origin := &url.URL{Scheme: "https", Host: "example.com"}
client := attachmentHTTPClient(origin)
if client.Timeout != 0 {
t.Fatalf("Timeout = %v, want 0", client.Timeout)
}
}