Files
Denozordec 374b4e0d71
Publish telemt-api gateway Docker image / test (push) Failing after 26s
Publish telemt-api gateway Docker image / build-and-push (push) Has been skipped
Refactor reverse proxy and enhance API routing
- Replaced the existing reverse proxy implementation with a new alias forwarding mechanism, improving path handling and request normalization.
- Updated the gateway to utilize the new forwarding approach, ensuring consistent handling of API requests and proper error management.
- Enhanced tests to validate the new routing behavior, including handling of double slashes and user endpoint requests.
- Improved documentation in GATEWAY_RUN.md to clarify the updated API routing and configuration requirements.
2026-03-30 11:47:50 +07:00

261 lines
6.7 KiB
Go

package proxy
import (
"context"
"net/http"
"net/http/httptest"
"net/url"
"testing"
)
// captureTransport records the outgoing request and returns 200 without dialing.
type captureTransport struct {
got *http.Request
}
func (c *captureTransport) RoundTrip(req *http.Request) (*http.Response, error) {
c.got = req.Clone(req.Context())
return &http.Response{
StatusCode: http.StatusOK,
Body: http.NoBody,
Header: make(http.Header),
}, nil
}
func TestForwardDoubleSlashPathMatchesStripPrefix(t *testing.T) {
target, err := url.Parse("http://127.0.0.1:9")
if err != nil {
t.Fatal(err)
}
cap := &captureTransport{}
h := NewAliasForward(target, "/api/mtg", "/v1", "", cap, nil)
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "http://127.0.0.1:9/", nil)
if err != nil {
t.Fatal(err)
}
req.URL.Path = "/api//mtg/health"
h.ServeHTTP(httptest.NewRecorder(), req)
if cap.got == nil {
t.Fatal("no outgoing request captured")
}
want, err := url.Parse("http://127.0.0.1:9/v1/health")
if err != nil {
t.Fatal(err)
}
assertSameURL(t, cap.got.URL, want)
}
func TestForwardRewritesPath(t *testing.T) {
target, err := url.Parse("http://127.0.0.1:9")
if err != nil {
t.Fatal(err)
}
cap := &captureTransport{}
h := NewAliasForward(target, "/api/main_srv", "/v1", "", cap, nil)
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "http://127.0.0.1:9/api/main_srv/health", nil)
if err != nil {
t.Fatal(err)
}
h.ServeHTTP(httptest.NewRecorder(), req)
if cap.got == nil {
t.Fatal("no outgoing request captured")
}
want, err := url.Parse("http://127.0.0.1:9/v1/health")
if err != nil {
t.Fatal(err)
}
assertSameURL(t, cap.got.URL, want)
}
func TestForwardRewritesPathWithAPIBasePath(t *testing.T) {
target, err := url.Parse("http://127.0.0.1:9/api/")
if err != nil {
t.Fatal(err)
}
cap := &captureTransport{}
h := NewAliasForward(target, "/api/main_srv", "/v1", "", cap, nil)
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "http://127.0.0.1:9/api/main_srv/health", nil)
if err != nil {
t.Fatal(err)
}
h.ServeHTTP(httptest.NewRecorder(), req)
if cap.got == nil {
t.Fatal("no outgoing request captured")
}
want, err := url.Parse("http://127.0.0.1:9/api/v1/health")
if err != nil {
t.Fatal(err)
}
assertSameURL(t, cap.got.URL, want)
}
func TestForwardGETUsersListUsesStatsUsers(t *testing.T) {
target, err := url.Parse("http://127.0.0.1:9")
if err != nil {
t.Fatal(err)
}
cap := &captureTransport{}
h := NewAliasForward(target, "/api/mtg", "/v1", "", cap, nil)
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "http://127.0.0.1:9/api/mtg/users", nil)
if err != nil {
t.Fatal(err)
}
h.ServeHTTP(httptest.NewRecorder(), req)
if cap.got == nil {
t.Fatal("no outgoing request captured")
}
want, err := url.Parse("http://127.0.0.1:9/v1/stats/users")
if err != nil {
t.Fatal(err)
}
assertSameURL(t, cap.got.URL, want)
}
func TestForwardPOSTUsersCreateNotRewritten(t *testing.T) {
target, err := url.Parse("http://127.0.0.1:9")
if err != nil {
t.Fatal(err)
}
cap := &captureTransport{}
h := NewAliasForward(target, "/api/mtg", "/v1", "", cap, nil)
req, err := http.NewRequestWithContext(context.Background(), http.MethodPost, "http://127.0.0.1:9/api/mtg/users", nil)
if err != nil {
t.Fatal(err)
}
h.ServeHTTP(httptest.NewRecorder(), req)
if cap.got == nil {
t.Fatal("no outgoing request captured")
}
want, err := url.Parse("http://127.0.0.1:9/v1/users")
if err != nil {
t.Fatal(err)
}
assertSameURL(t, cap.got.URL, want)
}
func TestForwardGETUsersByNameNotRewritten(t *testing.T) {
target, err := url.Parse("http://127.0.0.1:9")
if err != nil {
t.Fatal(err)
}
cap := &captureTransport{}
h := NewAliasForward(target, "/api/mtg", "/v1", "", cap, nil)
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "http://127.0.0.1:9/api/mtg/users/alice", nil)
if err != nil {
t.Fatal(err)
}
h.ServeHTTP(httptest.NewRecorder(), req)
if cap.got == nil {
t.Fatal("no outgoing request captured")
}
want, err := url.Parse("http://127.0.0.1:9/v1/users/alice")
if err != nil {
t.Fatal(err)
}
assertSameURL(t, cap.got.URL, want)
}
func TestForwardNestedStatsUsers(t *testing.T) {
target, err := url.Parse("http://127.0.0.1:9/api/")
if err != nil {
t.Fatal(err)
}
cap := &captureTransport{}
h := NewAliasForward(target, "/api/gt2", "/v1", "", cap, nil)
req, err := http.NewRequestWithContext(context.Background(), http.MethodGet, "http://127.0.0.1:9/api/gt2/stats/users", nil)
if err != nil {
t.Fatal(err)
}
h.ServeHTTP(httptest.NewRecorder(), req)
if cap.got == nil {
t.Fatal("no outgoing request captured")
}
want, err := url.Parse("http://127.0.0.1:9/api/v1/stats/users")
if err != nil {
t.Fatal(err)
}
assertSameURL(t, cap.got.URL, want)
}
func TestForwardOutgoingHostIsUpstreamAuthority(t *testing.T) {
target, err := url.Parse("http://172.20.0.3:9091")
if err != nil {
t.Fatal(err)
}
cap := &captureTransport{}
h := NewAliasForward(target, "/api/mtg", "/v1", "", cap, nil)
req := httptest.NewRequest(http.MethodGet, "http://public.example/api/mtg/health", nil)
req.Host = "public.example:8888"
req.Header.Set("Host", "public.example:8888")
h.ServeHTTP(httptest.NewRecorder(), req)
if cap.got == nil {
t.Fatal("no outgoing request captured")
}
if cap.got.URL.Host != "172.20.0.3:9091" {
t.Fatalf("URL.Host=%q", cap.got.URL.Host)
}
if cap.got.Host != "172.20.0.3:9091" {
t.Fatalf("Request.Host=%q want 172.20.0.3:9091", cap.got.Host)
}
if h := cap.got.Header.Get("Host"); h != "" {
t.Fatalf("Header Host should be empty (use Request.Host); got %q", h)
}
}
func TestJoinPathPrefixRoundTrip(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/v1/health" {
http.Error(w, "bad path", http.StatusBadRequest)
return
}
w.WriteHeader(http.StatusOK)
}))
defer srv.Close()
base, err := url.Parse(srv.URL)
if err != nil {
t.Fatal(err)
}
joined := JoinPathPrefix(base, "/v1", "health")
req, err := http.NewRequest(http.MethodGet, joined.String(), nil)
if err != nil {
t.Fatal(err)
}
resp, err := DirectTransport().RoundTrip(req)
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
t.Fatalf("status %d", resp.StatusCode)
}
}
func assertSameURL(t *testing.T, got, want *url.URL) {
t.Helper()
if got == nil || want == nil {
t.Fatalf("nil URL: got=%v want=%v", got, want)
}
if got.String() == want.String() {
return
}
if got.Scheme == want.Scheme && got.Host == want.Host && got.RawQuery == want.RawQuery && got.EscapedPath() == want.EscapedPath() {
return
}
t.Fatalf("URL mismatch\ngot %q\nwant %q", got.String(), want.String())
}