Files
wehub-resource-sync f99010fae1
CI / lint (push) Failing after 1s
CI / frontend (push) Failing after 1s
CI / scripts (push) Failing after 1s
CI / Go Test (ubuntu-latest) (push) Failing after 0s
CI / frontend-node-25 (push) Failing after 1s
CI / docs (push) Failing after 0s
CI / coverage (push) Failing after 0s
CI / e2e (push) Failing after 0s
Docker / build-and-push (push) Failing after 1s
CI / integration (push) Failing after 4m43s
CI / Go Test (windows-latest) (push) Has been cancelled
CI / Desktop Unit Tests (Windows) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Linux (arm64)) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Linux) (push) Has been cancelled
Desktop Artifacts / Desktop Build (Windows) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (aarch64)) (push) Has been cancelled
Desktop Artifacts (macOS) / Desktop Build (macOS (x86_64)) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:30:36 +08:00

88 lines
2.9 KiB
Go

package server
import (
"net/http"
"net/http/httptest"
"testing"
"github.com/stretchr/testify/assert"
"go.kenn.io/agentsview/internal/config"
)
// TestHandleSearchContentInvalidParams covers the handler's request-validation
// rejections, which all fire before the SessionService is consulted (so a nil
// backend is fine).
func TestHandleSearchContentInvalidParams(t *testing.T) {
t.Parallel()
cases := []struct {
name string
query string
remoteAddr string
xff string
wantStatus int
}{
{"empty pattern", "", "127.0.0.1:1234", "", http.StatusBadRequest},
{"invalid mode", "pattern=x&mode=bad", "127.0.0.1:1234", "", http.StatusBadRequest},
{"invalid limit", "pattern=x&limit=abc", "127.0.0.1:1234", "", http.StatusBadRequest},
{"invalid cursor", "pattern=x&cursor=abc", "127.0.0.1:1234", "", http.StatusBadRequest},
{"reveal from remote", "pattern=x&reveal=true", "203.0.113.5:1234", "", http.StatusForbidden},
// A reverse proxy reaches the loopback backend, so RemoteAddr is
// loopback; the forwarding header marks it as proxied, so reveal
// must still be rejected.
{"reveal via proxied loopback", "pattern=x&reveal=true", "127.0.0.1:1234", "203.0.113.5", http.StatusForbidden},
}
for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
srv := &Server{
cfg: config.Config{Host: "127.0.0.1"},
mux: http.NewServeMux(),
}
srv.routes()
req := httptest.NewRequest(http.MethodGet,
"/api/v1/search/content?"+tt.query, nil)
req.RemoteAddr = tt.remoteAddr
if tt.xff != "" {
req.Header.Set("X-Forwarded-For", tt.xff)
}
w := httptest.NewRecorder()
srv.mux.ServeHTTP(w, req)
assert.Equal(t, tt.wantStatus, w.Code, "body: %s", w.Body.String())
})
}
}
// TestIsLocalhostRequest documents the security contract of the helper that
// gates secret exposure (auth token, unredacted snippets): a request is local
// only when it arrived directly on a loopback address with no reverse-proxy
// forwarding headers.
func TestIsLocalhostRequest(t *testing.T) {
t.Parallel()
cases := []struct {
name string
remoteAddr string
header string
value string
want bool
}{
{"direct loopback", "127.0.0.1:1234", "", "", true},
{"direct ipv6 loopback", "[::1]:1234", "", "", true},
{"non-loopback", "203.0.113.5:1234", "", "", false},
{"proxied loopback x-forwarded-for", "127.0.0.1:1234", "X-Forwarded-For", "203.0.113.5", false},
{"proxied loopback x-real-ip", "127.0.0.1:1234", "X-Real-IP", "203.0.113.5", false},
{"proxied loopback forwarded", "127.0.0.1:1234", "Forwarded", "for=203.0.113.5", false},
}
for _, tt := range cases {
t.Run(tt.name, func(t *testing.T) {
t.Parallel()
req := httptest.NewRequest(http.MethodGet, "/api/v1/settings", nil)
req.RemoteAddr = tt.remoteAddr
if tt.header != "" {
req.Header.Set(tt.header, tt.value)
}
assert.Equal(t, tt.want, isLocalhostRequest(req))
})
}
}