mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-05-07 00:37:36 +00:00
152 lines
5 KiB
Go
152 lines
5 KiB
Go
package relay
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"io"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/rs/zerolog"
|
|
)
|
|
|
|
func decodeProxyResponse(t *testing.T, data []byte) ProxyResponse {
|
|
t.Helper()
|
|
var resp ProxyResponse
|
|
if err := json.Unmarshal(data, &resp); err != nil {
|
|
t.Fatalf("unmarshal response: %v", err)
|
|
}
|
|
return resp
|
|
}
|
|
|
|
func decodeProxyErrorMessage(t *testing.T, resp ProxyResponse) string {
|
|
t.Helper()
|
|
bodyBytes, err := base64.StdEncoding.DecodeString(resp.Body)
|
|
if err != nil {
|
|
t.Fatalf("decode body: %v", err)
|
|
}
|
|
var payload map[string]string
|
|
if err := json.Unmarshal(bodyBytes, &payload); err != nil {
|
|
t.Fatalf("unmarshal error payload: %v", err)
|
|
}
|
|
return payload["error"]
|
|
}
|
|
|
|
func TestNewHTTPProxy_ConfiguresClients(t *testing.T) {
|
|
proxy := NewHTTPProxy("127.0.0.1:7655", zerolog.Nop())
|
|
|
|
if proxy.localAddr != "127.0.0.1:7655" {
|
|
t.Errorf("localAddr: got %q, want %q", proxy.localAddr, "127.0.0.1:7655")
|
|
}
|
|
if proxy.client == nil {
|
|
t.Fatal("client is nil")
|
|
}
|
|
if proxy.streamClient == nil {
|
|
t.Fatal("streamClient is nil")
|
|
}
|
|
if proxy.client.Timeout != proxyRequestTimeout {
|
|
t.Errorf("client timeout: got %v, want %v", proxy.client.Timeout, proxyRequestTimeout)
|
|
}
|
|
|
|
req, err := http.NewRequest(http.MethodGet, "http://example.com", nil)
|
|
if err != nil {
|
|
t.Fatalf("new request: %v", err)
|
|
}
|
|
if err := proxy.client.CheckRedirect(req, nil); err != http.ErrUseLastResponse {
|
|
t.Errorf("client redirect policy: got %v, want %v", err, http.ErrUseLastResponse)
|
|
}
|
|
if err := proxy.streamClient.CheckRedirect(req, nil); err != http.ErrUseLastResponse {
|
|
t.Errorf("stream client redirect policy: got %v, want %v", err, http.ErrUseLastResponse)
|
|
}
|
|
}
|
|
|
|
func TestHTTPProxy_HandleRequest_ErrorBranches(t *testing.T) {
|
|
logger := zerolog.Nop()
|
|
|
|
t.Run("rejects invalid base64 body", func(t *testing.T) {
|
|
proxy := NewHTTPProxy("127.0.0.1:1", logger)
|
|
req := ProxyRequest{ID: "req_bad_b64", Method: http.MethodPost, Path: "/api/echo", Body: "%%%not-base64%%%"}
|
|
payload, _ := json.Marshal(req)
|
|
|
|
resp := decodeProxyResponse(t, mustHandleRequest(t, proxy, payload))
|
|
if resp.Status != http.StatusBadRequest {
|
|
t.Errorf("status: got %d, want %d", resp.Status, http.StatusBadRequest)
|
|
}
|
|
if got := decodeProxyErrorMessage(t, resp); got != "invalid base64 body" {
|
|
t.Errorf("error message: got %q, want %q", got, "invalid base64 body")
|
|
}
|
|
})
|
|
|
|
t.Run("rejects oversized request body", func(t *testing.T) {
|
|
proxy := NewHTTPProxy("127.0.0.1:1", logger)
|
|
req := ProxyRequest{
|
|
ID: "req_oversize",
|
|
Method: http.MethodPost,
|
|
Path: "/api/echo",
|
|
Body: base64.StdEncoding.EncodeToString(make([]byte, maxProxyBodySize+1)),
|
|
}
|
|
payload, _ := json.Marshal(req)
|
|
|
|
resp := decodeProxyResponse(t, mustHandleRequest(t, proxy, payload))
|
|
if resp.Status != http.StatusRequestEntityTooLarge {
|
|
t.Errorf("status: got %d, want %d", resp.Status, http.StatusRequestEntityTooLarge)
|
|
}
|
|
if got := decodeProxyErrorMessage(t, resp); !strings.Contains(got, "request body exceeds") {
|
|
t.Errorf("error message: got %q, expected request-body size error", got)
|
|
}
|
|
})
|
|
|
|
t.Run("rejects oversized response body", func(t *testing.T) {
|
|
mockAPI := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
w.WriteHeader(http.StatusOK)
|
|
_, _ = w.Write(make([]byte, maxProxyBodySize+1))
|
|
}))
|
|
defer mockAPI.Close()
|
|
|
|
proxy := NewHTTPProxy(strings.TrimPrefix(mockAPI.URL, "http://"), logger)
|
|
req := ProxyRequest{ID: "resp_oversize", Method: http.MethodGet, Path: "/api/large"}
|
|
payload, _ := json.Marshal(req)
|
|
|
|
resp := decodeProxyResponse(t, mustHandleRequest(t, proxy, payload))
|
|
if resp.Status != http.StatusRequestEntityTooLarge {
|
|
t.Errorf("status: got %d, want %d", resp.Status, http.StatusRequestEntityTooLarge)
|
|
}
|
|
if got := decodeProxyErrorMessage(t, resp); !strings.Contains(got, "response body exceeds") {
|
|
t.Errorf("error message: got %q, expected response-body size error", got)
|
|
}
|
|
})
|
|
|
|
t.Run("normalizes paths without leading slash", func(t *testing.T) {
|
|
mockAPI := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
_, _ = io.WriteString(w, r.URL.Path)
|
|
}))
|
|
defer mockAPI.Close()
|
|
|
|
proxy := NewHTTPProxy(strings.TrimPrefix(mockAPI.URL, "http://"), logger)
|
|
req := ProxyRequest{ID: "req_path", Method: http.MethodGet, Path: "api/normalized"}
|
|
payload, _ := json.Marshal(req)
|
|
|
|
resp := decodeProxyResponse(t, mustHandleRequest(t, proxy, payload))
|
|
if resp.Status != http.StatusOK {
|
|
t.Fatalf("status: got %d, want %d", resp.Status, http.StatusOK)
|
|
}
|
|
body, err := base64.StdEncoding.DecodeString(resp.Body)
|
|
if err != nil {
|
|
t.Fatalf("decode body: %v", err)
|
|
}
|
|
if got, want := string(body), "/api/normalized"; got != want {
|
|
t.Errorf("proxied path: got %q, want %q", got, want)
|
|
}
|
|
})
|
|
}
|
|
|
|
func mustHandleRequest(t *testing.T, proxy *HTTPProxy, payload []byte) []byte {
|
|
t.Helper()
|
|
resp, err := proxy.HandleRequest(payload, "token")
|
|
if err != nil {
|
|
t.Fatalf("HandleRequest() error = %v", err)
|
|
}
|
|
return resp
|
|
}
|