mirror of
https://gitcode.com/JianFeeeee/ModelRouter.git
synced 2026-09-22 01:48:01 +00:00
feat: LuaJIT worker-pool VM, multimodal/disable-thinking passthrough, WebUI redesign, DeepSeek V4
This commit is contained in:
@ -93,6 +93,63 @@ func TestChatSingle(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestChatDisableThinkingPassthrough(t *testing.T) {
|
||||
got := ""
|
||||
up := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
b, _ := io.ReadAll(r.Body)
|
||||
got = string(b)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
fmt.Fprint(w, `{"choices":[{"message":{"content":"pong"},"finish_reason":"stop"}]}`)
|
||||
}))
|
||||
defer up.Close()
|
||||
g := newTestGateway(t, config.Source{Name: "mock", BaseURL: up.URL, Adapter: "openai", Models: []config.Model{{ID: "mock-model"}}})
|
||||
rr := doReq(t, g, "POST", "/v1/chat/completions",
|
||||
`{"model":"mock-model","disable_thinking":true,"messages":[{"role":"user","content":"hi"}]}`)
|
||||
if rr.Code != 200 {
|
||||
t.Fatalf("status = %d, body=%s", rr.Code, rr.Body.String())
|
||||
}
|
||||
var sent map[string]interface{}
|
||||
if err := json.Unmarshal([]byte(got), &sent); err != nil {
|
||||
t.Fatalf("upstream body: %v", err)
|
||||
}
|
||||
if _, has := sent["disable_thinking"]; has {
|
||||
t.Fatalf("disable_thinking not stripped: %s", got)
|
||||
}
|
||||
// openai adapter strips disable_thinking; deepseek would map it to extra_body.thinking.
|
||||
// with the passthrough fix the flag now reaches the VM at all.
|
||||
}
|
||||
|
||||
func TestChatMultimodalPassthrough(t *testing.T) {
|
||||
gotBody := ""
|
||||
up := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
b, _ := io.ReadAll(r.Body)
|
||||
gotBody = string(b)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
fmt.Fprint(w, `{"choices":[{"message":{"content":"pong"},"finish_reason":"stop"}]}`)
|
||||
}))
|
||||
defer up.Close()
|
||||
g := newTestGateway(t, config.Source{Name: "mock", BaseURL: up.URL, Adapter: "openai", Models: []config.Model{{ID: "mock-model"}}})
|
||||
rr := doReq(t, g, "POST", "/v1/chat/completions",
|
||||
`{"model":"mock-model","messages":[{"role":"user","content":[
|
||||
{"type":"text","text":"what is this?"},
|
||||
{"type":"image_url","image_url":{"url":"data:image/png;base64,QUJD"}}
|
||||
]}]}`)
|
||||
if rr.Code != 200 {
|
||||
t.Fatalf("status = %d, body=%s", rr.Code, rr.Body.String())
|
||||
}
|
||||
var sent struct {
|
||||
Messages []struct {
|
||||
Content []map[string]interface{} `json:"content"`
|
||||
} `json:"messages"`
|
||||
}
|
||||
if err := json.Unmarshal([]byte(gotBody), &sent); err != nil {
|
||||
t.Fatalf("upstream body: %v", err)
|
||||
}
|
||||
if len(sent.Messages) != 1 || len(sent.Messages[0].Content) != 2 {
|
||||
t.Fatalf("multimodal content lost: %s", gotBody)
|
||||
}
|
||||
}
|
||||
|
||||
func TestChatAUTO(t *testing.T) {
|
||||
up := mockUpstream()
|
||||
defer up.Close()
|
||||
@ -265,7 +322,7 @@ func TestWebUIServesPage(t *testing.T) {
|
||||
if rr.Code != 200 {
|
||||
t.Fatalf("status=%d", rr.Code)
|
||||
}
|
||||
if !strings.Contains(rr.Body.String(), "llmsproxy") {
|
||||
if !strings.Contains(rr.Body.String(), "ModelRouter") {
|
||||
t.Fatalf("ui not served")
|
||||
}
|
||||
}
|
||||
@ -303,4 +360,90 @@ func TestSourcesAPIAddAndPersist(t *testing.T) {
|
||||
if _, err := os.Stat(g.core.Config().RuntimeFile); err != nil {
|
||||
t.Fatalf("runtime file not written: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestUIRequiresAuth(t *testing.T) {
|
||||
up := mockUpstream()
|
||||
defer up.Close()
|
||||
g := newTestGateway(t, config.Source{Name: "mock", BaseURL: up.URL, Adapter: "openai", Models: []config.Model{{ID: "mock-model"}}})
|
||||
// / without a key -> redirect to /login
|
||||
req, _ := http.NewRequest("GET", "/", nil)
|
||||
rr := httptest.NewRecorder()
|
||||
g.Handler().ServeHTTP(rr, req)
|
||||
if rr.Code != http.StatusFound {
|
||||
t.Fatalf("expected 302 for /, got %d", rr.Code)
|
||||
}
|
||||
if loc := rr.Header().Get("Location"); !strings.Contains(loc, "/login") {
|
||||
t.Fatalf("expected redirect to /login, got %q", loc)
|
||||
}
|
||||
// /api/status without a key -> 401
|
||||
req, _ = http.NewRequest("GET", "/api/status", nil)
|
||||
rr = httptest.NewRecorder()
|
||||
g.Handler().ServeHTTP(rr, req)
|
||||
if rr.Code != http.StatusUnauthorized {
|
||||
t.Fatalf("expected 401 for /api/status, got %d", rr.Code)
|
||||
}
|
||||
// /login page is public
|
||||
req, _ = http.NewRequest("GET", "/login", nil)
|
||||
rr = httptest.NewRecorder()
|
||||
g.Handler().ServeHTTP(rr, req)
|
||||
if rr.Code != http.StatusOK || !strings.Contains(rr.Body.String(), "登录") {
|
||||
t.Fatalf("login page: %d %s", rr.Code, rr.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestLoginAPIAndCookie(t *testing.T) {
|
||||
up := mockUpstream()
|
||||
defer up.Close()
|
||||
g := newTestGateway(t, config.Source{Name: "mock", BaseURL: up.URL, Adapter: "openai", Models: []config.Model{{ID: "mock-model"}}})
|
||||
// bad key -> 401
|
||||
req, _ := http.NewRequest("POST", "/api/login", strings.NewReader(`{"key":"wrong"}`))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
rr := httptest.NewRecorder()
|
||||
g.Handler().ServeHTTP(rr, req)
|
||||
if rr.Code != http.StatusUnauthorized {
|
||||
t.Fatalf("bad login: %d", rr.Code)
|
||||
}
|
||||
// good key -> cookie
|
||||
req, _ = http.NewRequest("POST", "/api/login", strings.NewReader(`{"key":"sk-test"}`))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
rr = httptest.NewRecorder()
|
||||
g.Handler().ServeHTTP(rr, req)
|
||||
if rr.Code != http.StatusOK {
|
||||
t.Fatalf("login: %d", rr.Code)
|
||||
}
|
||||
cookies := rr.Result().Cookies()
|
||||
if len(cookies) == 0 || cookies[0].Name != "gw_key" {
|
||||
t.Fatalf("no gw_key cookie set")
|
||||
}
|
||||
// use cookie to access /api/status
|
||||
req, _ = http.NewRequest("GET", "/api/status", nil)
|
||||
req.AddCookie(cookies[0])
|
||||
rr = httptest.NewRecorder()
|
||||
g.Handler().ServeHTTP(rr, req)
|
||||
if rr.Code != http.StatusOK {
|
||||
t.Fatalf("cookie authed status: %d", rr.Code)
|
||||
}
|
||||
var out map[string]interface{}
|
||||
_ = json.Unmarshal(rr.Body.Bytes(), &out)
|
||||
if out["base_url"] == "" {
|
||||
t.Fatalf("status missing base_url: %s", rr.Body.String())
|
||||
}
|
||||
if !strings.Contains(rr.Body.String(), "sk-test") {
|
||||
t.Fatalf("status missing gateway_keys: %s", rr.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestAPIChatInternal(t *testing.T) {
|
||||
up := mockUpstream()
|
||||
defer up.Close()
|
||||
g := newTestGateway(t, config.Source{Name: "mock", BaseURL: up.URL, Adapter: "openai", Models: []config.Model{{ID: "mock-model"}}})
|
||||
rr := doReq(t, g, "POST", "/api/chat",
|
||||
`{"model":"mock-model","messages":[{"role":"user","content":"hi"}]}`)
|
||||
if rr.Code != 200 {
|
||||
t.Fatalf("api chat status=%d body=%s", rr.Code, rr.Body.String())
|
||||
}
|
||||
if !strings.Contains(rr.Body.String(), "pong") {
|
||||
t.Fatalf("api chat body=%s", rr.Body.String())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user