package main import ( "os" "strings" "testing" ) const sampleConf = `map $host $backend { hostnames; default ""; gitea.example.com http://127.0.0.1:3000; login.example.com http://127.0.0.1:4001; dashy.example.com http://127.0.0.1:3003; modulerouter.example.com http://10.0.0.5:8081; } server { listen 3080 ssl; server_name example.com www.example.com; root /usr/share/nginx/html; error_page 404 /404.html; } server { listen 3080 ssl; server_name gitea.example.com; location / { sub_filter '' 'x'; proxy_pass $backend; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; proxy_ssl_verify off; } } server { listen 3080 ssl; server_name login.example.com; location / { proxy_pass $backend; } } server { listen 3080 ssl; server_name modulerouter.example.com dify.example.com; location / { postpone_output 0; proxy_buffering off; proxy_pass $backend; } } server { listen 3080 ssl; server_name dashy.example.com; auth_request /_auth; error_page 401 = @login; location @login { return 302 https://login.example.com/login; } location /search { auth_request off; proxy_pass $backend; } location ~ ^/(api|autocompleter|static)/ { auth_request off; proxy_pass $backend; } location / { proxy_pass $backend; } } server { listen 3080 ssl default_server; server_name _; root /usr/share/nginx/html; location / { return 404; } } ` func TestParseNginxConf(t *testing.T) { routes, err := parseNginxConf(sampleConf) if err != nil { t.Fatalf("解析失败: %v", err) } byHost := map[string]Route{} for _, r := range routes { byHost[r.Host] = r } if _, ok := byHost["_"]; ok { t.Fatalf("不应把默认段当成服务") } d, ok := byHost["dashy.example.com"] if !ok || !d.RequireLogin || d.Upstream != "http://127.0.0.1:3003" { t.Fatalf("dashy 解析不对: %+v", d) } if len(d.BypassPaths) == 0 { t.Fatalf("dashy 应解析出免认证路径: %+v", d.BypassPaths) } g := byHost["gitea.example.com"] if g.RequireLogin || !g.InjectBeian || !g.InsecureTLS || !g.Websocket { t.Fatalf("gitea 解析不对: %+v", g) } m := byHost["modulerouter.example.com"] if m.RequireLogin || !m.Streaming { t.Fatalf("modulerouter 应解析为免认证 + 流式: %+v", m) } if _, ok := byHost["dify.example.com"]; !ok { t.Fatalf("同一段里多个域名都要生成路由") } if l := byHost["login.example.com"]; l.Kind != "auth" || l.Upstream != "" { t.Fatalf("login 应为内置 auth: %+v", l) } if p := byHost["example.com"]; p.Kind != "portal" { t.Fatalf("静态门户段应为 portal: %+v", p) } for _, r := range routes { if r.NavEnabled { t.Fatalf("导入不应默认接管导航: %+v", r) } } } func TestParseNginxConfRejectsGarbage(t *testing.T) { if _, err := parseNginxConf(" "); err == nil { t.Fatalf("空内容应报错") } if _, err := parseNginxConf("hello world"); err == nil { t.Fatalf("没有任何 server_name 应报错") } } func TestPortalAuthStore(t *testing.T) { dir := t.TempDir() oldConf := confFile confFile = dir + "/conf.yml" defer func() { confFile = oldConf }() if err := savePortalAuthFile("jianf", "s3cret-pass"); err != nil { t.Fatalf("保存失败: %v", err) } raw, err := os.ReadFile(portalAuthPath()) if err != nil { t.Fatal(err) } if strings.Contains(string(raw), "s3cret-pass") { t.Fatalf("不得写入明文口令: %s", raw) } if !checkPortalCredentials("jianf", "s3cret-pass") { t.Fatalf("正确口令应通过") } if checkPortalCredentials("jianf", "wrong") || checkPortalCredentials("other", "s3cret-pass") { t.Fatalf("错误口令/用户名不得通过") } if err := savePortalAuthFile("admin2", "another-pass"); err != nil { t.Fatal(err) } if checkPortalCredentials("jianf", "s3cret-pass") || !checkPortalCredentials("admin2", "another-pass") { t.Fatalf("改账号后校验未按新值") } user, source := portalAuthConfigured() if user != "admin2" || !strings.Contains(source, "数据卷") { t.Fatalf("配置来源不对: %s %s", user, source) } }