Skip to content

Commit f533c1a

Browse files
committed
test: cover nested external base path proxy routing
1 parent 2689e36 commit f533c1a

1 file changed

Lines changed: 111 additions & 0 deletions

File tree

internal/external/reconciler_test.go

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,12 @@ package external
22

33
import (
44
"context"
5+
"encoding/json"
6+
"net/http"
7+
"net/http/httptest"
8+
"strings"
59
"testing"
10+
"time"
611

712
state "github.com/jguan/aima/internal"
813
"github.com/jguan/aima/internal/proxy"
@@ -76,6 +81,112 @@ func TestReconcileBackendsPreservesHTTPSScheme(t *testing.T) {
7681
}
7782
}
7883

84+
func TestReconciledNestedV1BasePathForwardsThroughProxy(t *testing.T) {
85+
type chatRequest struct {
86+
Path string
87+
Model string
88+
}
89+
90+
chatRequests := make(chan chatRequest, 1)
91+
upstream := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
92+
switch r.URL.Path {
93+
case "/api/v1/models":
94+
_ = json.NewEncoder(w).Encode(map[string]any{
95+
"object": "list",
96+
"data": []map[string]any{{"id": "nested-model"}},
97+
})
98+
case "/api/v1/chat/completions":
99+
var req struct {
100+
Model string `json:"model"`
101+
}
102+
_ = json.NewDecoder(r.Body).Decode(&req)
103+
chatRequests <- chatRequest{Path: r.URL.Path, Model: req.Model}
104+
_ = json.NewEncoder(w).Encode(map[string]any{"id": "chatcmpl-1"})
105+
default:
106+
http.NotFound(w, r)
107+
}
108+
}))
109+
defer upstream.Close()
110+
111+
ctx := context.Background()
112+
svc, err := Probe(ctx, upstream.URL+"/api/v1/models", upstream.Client())
113+
if err != nil {
114+
t.Fatalf("Probe: %v", err)
115+
}
116+
if svc.BaseURL != upstream.URL+"/api/v1" {
117+
t.Fatalf("BaseURL = %q, want %q", svc.BaseURL, upstream.URL+"/api/v1")
118+
}
119+
120+
proxyServer := proxy.NewServer(proxy.WithAddr("127.0.0.1:0"))
121+
imported, err := ReconcileBackends(proxyServer, OverviewFromScan(svc), svc.Models)
122+
if err != nil {
123+
t.Fatalf("ReconcileBackends: %v", err)
124+
}
125+
if imported != 1 {
126+
t.Fatalf("imported = %d, want 1", imported)
127+
}
128+
129+
proxyCtx, cancelProxy := context.WithCancel(context.Background())
130+
ready := make(chan string, 1)
131+
proxyErr := make(chan error, 1)
132+
proxyServer.SetOnReady(func(addr string) {
133+
ready <- addr
134+
})
135+
go func() {
136+
proxyErr <- proxyServer.Start(proxyCtx)
137+
}()
138+
defer func() {
139+
cancelProxy()
140+
select {
141+
case err := <-proxyErr:
142+
if err != nil {
143+
t.Errorf("proxy Start: %v", err)
144+
}
145+
case <-time.After(time.Second):
146+
t.Error("proxy did not stop after context cancellation")
147+
}
148+
}()
149+
150+
var proxyAddr string
151+
select {
152+
case proxyAddr = <-ready:
153+
case err := <-proxyErr:
154+
t.Fatalf("proxy stopped before ready: %v", err)
155+
case <-time.After(2 * time.Second):
156+
t.Fatal("proxy did not become ready")
157+
}
158+
159+
body := `{"model":"nested-model","messages":[{"role":"user","content":"hi"}]}`
160+
requestCtx, cancelRequest := context.WithTimeout(context.Background(), 2*time.Second)
161+
defer cancelRequest()
162+
req, err := http.NewRequestWithContext(requestCtx, http.MethodPost, "http://"+proxyAddr+"/v1/chat/completions", strings.NewReader(body))
163+
if err != nil {
164+
t.Fatalf("NewRequest: %v", err)
165+
}
166+
req.Header.Set("Content-Type", "application/json")
167+
client := &http.Client{Timeout: 2 * time.Second}
168+
resp, err := client.Do(req)
169+
if err != nil {
170+
t.Fatalf("proxy request: %v", err)
171+
}
172+
defer resp.Body.Close()
173+
if resp.StatusCode != http.StatusOK {
174+
t.Fatalf("proxy status = %d, want %d", resp.StatusCode, http.StatusOK)
175+
}
176+
177+
select {
178+
case got := <-chatRequests:
179+
if got.Path != "/api/v1/chat/completions" {
180+
t.Fatalf("upstream path = %q, want /api/v1/chat/completions", got.Path)
181+
}
182+
if got.Model != "nested-model" {
183+
t.Fatalf("upstream model = %q, want nested-model", got.Model)
184+
}
185+
case <-time.After(time.Second):
186+
t.Fatal("upstream did not receive chat request")
187+
}
188+
}
189+
79190
func TestReconcileBackendsRejectsHealthzService(t *testing.T) {
80191
proxyServer := proxy.NewServer()
81192
service := Overview{

0 commit comments

Comments
 (0)