gateway/app_test.go

230 lines
6.8 KiB
Go
Raw Normal View History

package main
import (
"apigo.cc/go/log"
"apigo.cc/go/redis"
"apigo.cc/go/service"
"context"
"encoding/json"
"io"
"net"
gohttp "net/http"
"os"
"testing"
"time"
)
func TestGateway(t *testing.T) {
// 1. 设置服务发现和 Redis 依赖
registry := "127.0.0.1:6379"
rd := redis.GetRedis(registry, log.DefaultLogger)
if rd.Error != nil {
t.Skip("Redis is not available, skipping gateway integration tests")
return
}
// 清理测试数据
rd.Do("DEL", "gateway:host:gw.test", "gateway:hosts")
// 2. 启动一个独立的后端测试服务 test-backend (不使用全局 DefaultServer)
backend := service.NewWebServer()
backend.Config.App = "test-backend"
backend.Config.Listen = ":0"
backend.Config.Register = registry
backend.Host("*").GET("/hello", func(req *service.Request) string {
return "hello from backend, path: " + req.RequestURI
})
backend.Host("*").GET("/hello/world", func(req *service.Request) string {
return "hello from backend, path: " + req.RequestURI
})
backendCtx, backendCancel := context.WithCancel(context.Background())
defer backendCancel()
go backend.Start(backendCtx, log.DefaultLogger)
defer backend.Stop(backendCtx)
time.Sleep(500 * time.Millisecond) // 等待后端启动和注册
// 3. 配置 Gateway (使用全局 service)
GatewayConf.Redis = registry
GatewayConf.Prefix = "gateway"
// 初始化网关的配置到 Redis
proxyRules := []service.ProxyRule{
{Path: "^/api/(.*)$", ToApp: "test-backend", ToPath: "/$1"},
{Path: "/direct", ToApp: "test-backend", ToPath: "/hello"},
{Path: "/v2/*", ToApp: "test-backend", ToPath: "/hello/*"},
}
rewriteRules := []service.RewriteRule{
{Path: "^/old-api/(.*)$", ToPath: "/api/$1"},
}
proxyJson, _ := json.Marshal(proxyRules)
rewriteJson, _ := json.Marshal(rewriteRules)
rd.Do("HSET", "gateway:host:gw.test", "proxies", string(proxyJson))
rd.Do("HSET", "gateway:host:gw.test", "rewrites", string(rewriteJson))
// 创建临时静态目录
tmpDir, _ := os.MkdirTemp("", "gateway-static")
defer os.RemoveAll(tmpDir)
_ = os.WriteFile(tmpDir+"/index.html", []byte("static content"), 0644)
staticConfig := map[string]string{
"/ui": tmpDir,
}
staticJson, _ := json.Marshal(staticConfig)
rd.Do("HSET", "gateway:host:gw.test", "statics", string(staticJson))
// 4. 启动 Gateway 应用
app := NewGatewayApp()
// 设置独立的端口给 Gateway 的 WebServer
service.Config.App = "gateway"
service.Config.Listen = ":0"
service.Config.Register = registry
service.Config.Calls = map[string]service.CallConfig{
"test-backend": {Timeout: 1000},
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// 这里假设 GatewayApp.Start 内部会调用 service.Start()
// 而 service.Start() 在新版本中如果是阻塞的,我们需要在 goroutine 中运行
go app.Start(ctx, log.DefaultLogger)
defer app.Stop(ctx)
time.Sleep(500 * time.Millisecond)
gwAddr := service.DefaultServer.Addr
if gwAddr == "" {
t.Fatalf("Gateway address is empty")
}
_, gwPort, _ := net.SplitHostPort(gwAddr)
client := &gohttp.Client{Timeout: 5 * time.Second}
// ============================================
// 测试 1: 直接代理匹配 (Discover)
// ============================================
req, _ := gohttp.NewRequest("GET", "http://127.0.0.1:"+gwPort+"/direct", nil)
req.Host = "gw.test"
res, err := client.Do(req)
var body string
if err == nil && res != nil {
b, _ := io.ReadAll(res.Body)
res.Body.Close()
body = string(b)
}
if err != nil || body != "hello from backend, path: /hello" {
t.Fatalf("Proxy /direct failed, got: %s, err: %v", body, err)
}
// ============================================
// 测试 2: 正则代理匹配 (Discover)
// ============================================
req, _ = gohttp.NewRequest("GET", "http://127.0.0.1:"+gwPort+"/api/hello?a=1", nil)
req.Host = "gw.test"
res, err = client.Do(req)
body = ""
if err == nil && res != nil {
b, _ := io.ReadAll(res.Body)
res.Body.Close()
body = string(b)
}
if err != nil || body != "hello from backend, path: /hello" {
t.Fatalf("Proxy /api/ failed, got: %s", body)
}
// ============================================
// 测试 2.1: 极速通配符前缀匹配 (Discover)
// ============================================
req, _ = gohttp.NewRequest("GET", "http://127.0.0.1:"+gwPort+"/v2/world?x=2", nil)
req.Host = "gw.test"
res, err = client.Do(req)
body = ""
if err == nil && res != nil {
b, _ := io.ReadAll(res.Body)
res.Body.Close()
body = string(b)
}
if err != nil || body != "hello from backend, path: /hello/world" {
t.Fatalf("Proxy /v2/* failed, got: %s", body)
}
// ============================================
// 测试 3: Rewrite 后再 Proxy
// ============================================
req, _ = gohttp.NewRequest("GET", "http://127.0.0.1:"+gwPort+"/old-api/hello", nil)
req.Host = "gw.test"
res, err = client.Do(req)
body = ""
if err == nil && res != nil {
b, _ := io.ReadAll(res.Body)
res.Body.Close()
body = string(b)
}
if err != nil || body != "hello from backend, path: /hello" {
t.Fatalf("Rewrite + Proxy failed, got: %s", body)
}
// ============================================
// 测试 4: 静态文件服务
// ============================================
req, _ = gohttp.NewRequest("GET", "http://127.0.0.1:"+gwPort+"/ui/index.html", nil)
req.Host = "gw.test"
res, err = client.Do(req)
body = ""
if err == nil && res != nil {
b, _ := io.ReadAll(res.Body)
res.Body.Close()
body = string(b)
}
if err != nil || body != "static content" {
t.Fatalf("Static service failed, got: %s", body)
}
// ============================================
// 测试 5: 通过 Redis Pub/Sub 动态更新路由
// ============================================
// 新增一个新代理规则,删除老的
newProxyRules := []service.ProxyRule{
{Path: "/new-direct", ToApp: "test-backend", ToPath: "/hello"},
}
newProxyJson, _ := json.Marshal(newProxyRules)
rd.Do("HSET", "gateway:host:gw.test", "proxies", string(newProxyJson))
// 发布更新消息
rd.Do("PUBLISH", "gateway:channel", `"gw.test"`)
// 等待接收和更新
time.Sleep(300 * time.Millisecond)
// 测试新路由应该生效
req, _ = gohttp.NewRequest("GET", "http://127.0.0.1:"+gwPort+"/new-direct", nil)
req.Host = "gw.test"
res, err = client.Do(req)
body = ""
if err == nil && res != nil {
b, _ := io.ReadAll(res.Body)
res.Body.Close()
body = string(b)
}
if err != nil || body != "hello from backend, path: /hello" {
t.Fatalf("Pub/Sub dynamic proxy failed, got: %s", body)
}
// 测试老路由应该 404 (原子替换)
req, _ = gohttp.NewRequest("GET", "http://127.0.0.1:"+gwPort+"/direct", nil)
req.Host = "gw.test"
res, err = client.Do(req)
if err != nil {
t.Fatalf("Request failed: %v", err)
}
if res.StatusCode != 404 {
t.Fatalf("Atomic update failed, old route still exists")
}
}