package main import "testing" func TestRenderGatewayConfAuthPerRoute(t *testing.T) { cfg := &GatewayConfig{ ListenPort: 8081, AuthURL: "http://127.0.0.1:4001/_auth", LoginURL: "https://login.example.com/login", Routes: []Route{ {Host: "protected.example.com", Upstream: "http://10.0.0.1:3000", RequireLogin: true, Websocket: true, MaxBodyMB: 64}, {Host: "open.example.com", Upstream: "http://10.0.0.2:8000"}, }, } out, err := RenderGatewayConf(cfg) if err != nil { t.Fatalf("渲染失败: %v", err) } // 受保护的域名要有 auth_request 与登录跳转 if !contains(out, "server_name protected.example.com;") { t.Fatalf("缺少受保护域名的 server 块:\n%s", out) } if !contains(out, "auth_request /_auth;") { t.Fatalf("受保护域名缺少 auth_request:\n%s", out) } if !contains(out, "location @login") { t.Fatalf("缺少登录跳转") } if !contains(out, "proxy_set_header Upgrade $http_upgrade;") { t.Fatalf("缺少 websocket 透传") } if !contains(out, "client_max_body_size 64m;") { t.Fatalf("缺少上传上限") } // 不受保护的域名不得出现 auth_request("哪些服务受保护"是按路由决定的) open := section(out, "server_name open.example.com;") if open == "" { t.Fatalf("缺少开放域名的 server 块") } if contains(open, "auth_request") { t.Fatalf("未受保护的域名不应有 auth_request:\n%s", open) } if contains(open, "client_max_body_size") { t.Fatalf("未设置上限时不应生成 client_max_body_size:\n%s", open) } } func TestRenderGatewayConfRejectsBadInput(t *testing.T) { base := func(r Route) *GatewayConfig { return &GatewayConfig{ListenPort: 8081, AuthURL: "http://x/_auth", Routes: []Route{r}} } cases := []struct { name string cfg *GatewayConfig }{ {"空域名", base(Route{Host: "", Upstream: "http://a"})}, {"域名带协议", base(Route{Host: "http://a.com", Upstream: "http://a"})}, {"后端非 http", base(Route{Host: "a.com", Upstream: "10.0.0.1:80"})}, {"域名重复", &GatewayConfig{ListenPort: 8081, AuthURL: "http://x/_auth", Routes: []Route{ {Host: "a.com", Upstream: "http://a"}, {Host: "a.com", Upstream: "http://b"}, }}}, {"需认证但无认证地址", &GatewayConfig{ListenPort: 8081, Routes: []Route{{Host: "a.com", Upstream: "http://a", RequireLogin: true}}}}, {"启用HTTPS但无证书", &GatewayConfig{ListenPort: 8081, ListenHTTPS: true, Routes: []Route{{Host: "a.com", Upstream: "http://a"}}}}, } for _, c := range cases { if _, err := RenderGatewayConf(c.cfg); err == nil { t.Errorf("%s: 应当报错但没有", c.name) } } } func TestGatewayRoundTrip(t *testing.T) { dir := t.TempDir() oldFile, oldOut := gatewayFile, gatewayOutDir gatewayFile = dir + "/gateway.yml" gatewayOutDir = dir + "/gateway" defer func() { gatewayFile, gatewayOutDir = oldFile, oldOut }() // 文件不存在时应给出默认配置而不是报错 cfg, err := loadGateway() if err != nil { t.Fatalf("缺文件应回落默认配置: %v", err) } if cfg.ListenPort != 8081 || len(cfg.Routes) != 0 { t.Fatalf("默认配置不对: %+v", cfg) } cfg.Routes = []Route{{Host: "a.com", Upstream: "http://a", RequireLogin: true}} if err := saveGateway(cfg); err != nil { t.Fatalf("保存失败: %v", err) } got, err := loadGateway() if err != nil || len(got.Routes) != 1 || !got.Routes[0].RequireLogin { t.Fatalf("回读不一致: %+v err=%v", got, err) } } func contains(hay, needle string) bool { return len(hay) >= len(needle) && (func() bool { for i := 0; i+len(needle) <= len(hay); i++ { if hay[i:i+len(needle)] == needle { return true } } return false })() } // section 取出 server_name 所属的那个 server 块(到下一个 server{ 之前) func section(all, marker string) string { i := indexOf(all, marker) if i < 0 { return "" } start := i for start > 0 && all[start-1] != '\n' { start-- } rest := all[i:] if j := indexOf(rest, "\nserver {"); j >= 0 { return rest[:j] } return rest } func indexOf(hay, needle string) int { for i := 0; i+len(needle) <= len(hay); i++ { if hay[i:i+len(needle)] == needle { return i } } return -1 }