232 lines
6.6 KiB
Go
232 lines
6.6 KiB
Go
package main
|
||
|
||
import (
|
||
"net/http"
|
||
"net/http/httptest"
|
||
"strings"
|
||
"testing"
|
||
"testing/fstest"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
)
|
||
|
||
func TestSetupStaticFiles(t *testing.T) {
|
||
gin.SetMode(gin.TestMode)
|
||
|
||
r := gin.New()
|
||
setupStaticFilesWithFS(r, fstest.MapFS{
|
||
"index.html": {Data: []byte("<html>fallback</html>")},
|
||
"icon.png": {Data: []byte("png")},
|
||
"assets/test.js": {Data: []byte("console.log('test')")},
|
||
"assets/test.css": {Data: []byte("body {}")},
|
||
"assets/test.svg": {Data: []byte("<svg></svg>")},
|
||
"assets/test.woff": {Data: []byte("font")},
|
||
})
|
||
|
||
t.Run("API 404", func(t *testing.T) {
|
||
req := httptest.NewRequest("GET", "/api/test", nil)
|
||
w := httptest.NewRecorder()
|
||
r.ServeHTTP(w, req)
|
||
|
||
if w.Code != 404 {
|
||
t.Errorf("期望状态码 404, 实际 %d", w.Code)
|
||
}
|
||
})
|
||
|
||
t.Run("OpenAI proxy prefix 404", func(t *testing.T) {
|
||
req := httptest.NewRequest("GET", "/openai/", nil)
|
||
w := httptest.NewRecorder()
|
||
r.ServeHTTP(w, req)
|
||
|
||
if w.Code != http.StatusNotFound {
|
||
t.Errorf("期望状态码 404, 实际 %d", w.Code)
|
||
}
|
||
if !strings.Contains(w.Body.String(), "not found") {
|
||
t.Errorf("期望返回 API 风格错误,实际 %s", w.Body.String())
|
||
}
|
||
})
|
||
|
||
t.Run("Anthropic proxy prefix 404", func(t *testing.T) {
|
||
req := httptest.NewRequest("GET", "/anthropic/", nil)
|
||
w := httptest.NewRecorder()
|
||
r.ServeHTTP(w, req)
|
||
|
||
if w.Code != http.StatusNotFound {
|
||
t.Errorf("期望状态码 404, 实际 %d", w.Code)
|
||
}
|
||
if !strings.Contains(w.Body.String(), "not found") {
|
||
t.Errorf("期望返回 API 风格错误,实际 %s", w.Body.String())
|
||
}
|
||
})
|
||
|
||
t.Run("SPA fallback", func(t *testing.T) {
|
||
req := httptest.NewRequest("GET", "/providers", nil)
|
||
w := httptest.NewRecorder()
|
||
r.ServeHTTP(w, req)
|
||
|
||
if w.Code != 200 {
|
||
t.Errorf("期望状态码 200, 实际 %d", w.Code)
|
||
}
|
||
})
|
||
|
||
t.Run("MIME type for JS", func(t *testing.T) {
|
||
req := httptest.NewRequest("GET", "/assets/test.js", nil)
|
||
w := httptest.NewRecorder()
|
||
r.ServeHTTP(w, req)
|
||
|
||
if w.Code != http.StatusOK {
|
||
t.Fatalf("期望状态码 200, 实际 %d", w.Code)
|
||
}
|
||
expected := "application/javascript"
|
||
if w.Header().Get("Content-Type") != expected {
|
||
t.Errorf("期望 Content-Type %s, 实际 %s", expected, w.Header().Get("Content-Type"))
|
||
}
|
||
})
|
||
|
||
t.Run("MIME type for CSS", func(t *testing.T) {
|
||
req := httptest.NewRequest("GET", "/assets/test.css", nil)
|
||
w := httptest.NewRecorder()
|
||
r.ServeHTTP(w, req)
|
||
|
||
if w.Code != http.StatusOK {
|
||
t.Fatalf("期望状态码 200, 实际 %d", w.Code)
|
||
}
|
||
expected := "text/css"
|
||
if w.Header().Get("Content-Type") != expected {
|
||
t.Errorf("期望 Content-Type %s, 实际 %s", expected, w.Header().Get("Content-Type"))
|
||
}
|
||
})
|
||
|
||
t.Log("静态文件服务测试通过")
|
||
}
|
||
|
||
func TestSetupStaticFilesWithFS_IconPNG(t *testing.T) {
|
||
gin.SetMode(gin.TestMode)
|
||
|
||
r := gin.New()
|
||
setupStaticFilesWithFS(r, fstest.MapFS{
|
||
"icon.png": {Data: []byte("png")},
|
||
"index.html": {Data: []byte("<html>fallback</html>")},
|
||
})
|
||
|
||
req := httptest.NewRequest("GET", "/icon.png", nil)
|
||
w := httptest.NewRecorder()
|
||
r.ServeHTTP(w, req)
|
||
|
||
if w.Code != http.StatusOK {
|
||
t.Fatalf("期望状态码 200, 实际 %d", w.Code)
|
||
}
|
||
if w.Header().Get("Content-Type") != "image/png" {
|
||
t.Fatalf("期望 Content-Type image/png, 实际 %s", w.Header().Get("Content-Type"))
|
||
}
|
||
if w.Body.String() != "png" {
|
||
t.Fatalf("期望返回 PNG 内容,实际 %q", w.Body.String())
|
||
}
|
||
}
|
||
|
||
func TestWithProtocolAndStaticRoutes(t *testing.T) {
|
||
gin.SetMode(gin.TestMode)
|
||
|
||
r := gin.New()
|
||
|
||
var gotProtocol string
|
||
var gotPath string
|
||
r.Any("/openai/*path", withProtocol("openai", func(c *gin.Context) {
|
||
gotProtocol = c.Param("protocol")
|
||
gotPath = c.Param("path")
|
||
c.JSON(http.StatusOK, gin.H{"protocol": gotProtocol, "path": gotPath})
|
||
}))
|
||
r.Any("/anthropic/*path", withProtocol("anthropic", func(c *gin.Context) {
|
||
gotProtocol = c.Param("protocol")
|
||
gotPath = c.Param("path")
|
||
c.JSON(http.StatusOK, gin.H{"protocol": gotProtocol, "path": gotPath})
|
||
}))
|
||
setupStaticFilesWithFS(r, fstest.MapFS{
|
||
"index.html": {Data: []byte("<html>fallback</html>")},
|
||
"assets/test.js": {Data: []byte("console.log('test')")},
|
||
})
|
||
|
||
t.Run("OpenAI route enters proxy handler wrapper", func(t *testing.T) {
|
||
gotProtocol = ""
|
||
gotPath = ""
|
||
|
||
req := httptest.NewRequest("POST", "/openai/v1/chat/completions", nil)
|
||
w := httptest.NewRecorder()
|
||
r.ServeHTTP(w, req)
|
||
|
||
if w.Code != http.StatusOK {
|
||
t.Errorf("期望状态码 200, 实际 %d", w.Code)
|
||
}
|
||
if gotProtocol != "openai" {
|
||
t.Errorf("期望 protocol=openai, 实际 %s", gotProtocol)
|
||
}
|
||
if gotPath != "/v1/chat/completions" {
|
||
t.Errorf("期望 path=/v1/chat/completions, 实际 %s", gotPath)
|
||
}
|
||
})
|
||
|
||
t.Run("Anthropic route enters proxy handler wrapper", func(t *testing.T) {
|
||
gotProtocol = ""
|
||
gotPath = ""
|
||
|
||
req := httptest.NewRequest("POST", "/anthropic/v1/messages", nil)
|
||
w := httptest.NewRecorder()
|
||
r.ServeHTTP(w, req)
|
||
|
||
if w.Code != http.StatusOK {
|
||
t.Errorf("期望状态码 200, 实际 %d", w.Code)
|
||
}
|
||
if gotProtocol != "anthropic" {
|
||
t.Errorf("期望 protocol=anthropic, 实际 %s", gotProtocol)
|
||
}
|
||
if gotPath != "/v1/messages" {
|
||
t.Errorf("期望 path=/v1/messages, 实际 %s", gotPath)
|
||
}
|
||
})
|
||
|
||
t.Run("Static assets are not hijacked", func(t *testing.T) {
|
||
gotProtocol = ""
|
||
gotPath = ""
|
||
|
||
req := httptest.NewRequest("GET", "/assets/test.js", nil)
|
||
w := httptest.NewRecorder()
|
||
r.ServeHTTP(w, req)
|
||
|
||
if gotProtocol != "" || gotPath != "" {
|
||
t.Errorf("静态资源不应进入代理包装器,实际 protocol=%s path=%s", gotProtocol, gotPath)
|
||
}
|
||
if w.Code != http.StatusOK {
|
||
t.Fatalf("期望静态资源返回 200, 实际 %d", w.Code)
|
||
}
|
||
if !strings.HasPrefix(w.Header().Get("Content-Type"), "application/javascript") {
|
||
t.Errorf("期望 JS Content-Type, 实际 %s", w.Header().Get("Content-Type"))
|
||
}
|
||
})
|
||
|
||
t.Run("SPA path keeps fallback", func(t *testing.T) {
|
||
req := httptest.NewRequest("GET", "/providers", nil)
|
||
w := httptest.NewRecorder()
|
||
r.ServeHTTP(w, req)
|
||
|
||
if w.Code != http.StatusOK {
|
||
t.Errorf("期望状态码 200, 实际 %d", w.Code)
|
||
}
|
||
if !strings.Contains(w.Header().Get("Content-Type"), "text/html") {
|
||
t.Errorf("期望返回 HTML,实际 %s", w.Header().Get("Content-Type"))
|
||
}
|
||
})
|
||
|
||
t.Run("Unknown proxy-like path does not return index html", func(t *testing.T) {
|
||
req := httptest.NewRequest("GET", "/openai/unknown", nil)
|
||
w := httptest.NewRecorder()
|
||
r.ServeHTTP(w, req)
|
||
|
||
if w.Code != http.StatusOK {
|
||
t.Errorf("显式代理路由应进入代理包装器,实际状态码 %d", w.Code)
|
||
}
|
||
if gotProtocol != "openai" || gotPath != "/unknown" {
|
||
t.Errorf("期望 unknown 代理路径进入 openai 包装器,实际 protocol=%s path=%s", gotProtocol, gotPath)
|
||
}
|
||
})
|
||
}
|