Files

102 lines
2.8 KiB
Go
Raw Permalink Normal View History

package api
import (
"context"
"io"
"log/slog"
"net/http"
"net/http/httptest"
"path/filepath"
"regexp"
"strings"
"testing"
"github.com/fonu/fonu/internal/auth"
"github.com/fonu/fonu/internal/certificate"
"github.com/fonu/fonu/internal/cloudflared"
"github.com/fonu/fonu/internal/config"
"github.com/fonu/fonu/internal/db"
"github.com/fonu/fonu/internal/frp"
"github.com/fonu/fonu/internal/secret"
"github.com/fonu/fonu/internal/settings"
)
var routeParamPattern = regexp.MustCompile(`\{[^}]+\}`)
// TestProtectedRoutesRequireAuth 遍历路由表:受保护路由匿名必须 401,公开路由不能被鉴权误拦。
func TestProtectedRoutesRequireAuth(t *testing.T) {
dir := t.TempDir()
conn, err := db.Open(filepath.Join(dir, "fonu.db"))
if err != nil {
t.Fatalf("open db: %v", err)
}
defer conn.Close()
if err := db.Migrate(context.Background(), conn, filepath.Join("..", "..", "migrations")); err != nil {
t.Fatalf("migrate: %v", err)
}
authSvc := auth.New(conn)
box, err := secret.NewBox("router-auth-test-key")
if err != nil {
t.Fatal(err)
}
cfg := config.Config{DataDir: dir, NginxPIDFile: filepath.Join(dir, "nginx.pid")}
logger := slog.New(slog.NewTextHandler(io.Discard, nil))
frpSvc := frp.NewService(cfg, frp.NewStore(conn), box, logger, certificate.NewStore(conn))
cfSvc := cloudflared.NewService(cfg, cloudflared.NewStore(conn), box, logger, nil)
handler, specs := newRouterWithSpecs(Deps{
Config: cfg,
Auth: authSvc,
FRPMulti: frpSvc,
Cloudflare: cfSvc,
Settings: settings.NewStore(conn),
Logger: logger,
})
protected, public := 0, 0
for _, spec := range specs {
req := httptest.NewRequest(routeMethod(spec.Pattern), sampleRoutePath(spec.Pattern), nil)
code := callRouteSafely(handler, req)
if spec.Public {
public++
if code == http.StatusUnauthorized {
t.Fatalf("公开路由 %s 不应被鉴权拦截", spec.Pattern)
}
continue
}
protected++
if code != http.StatusUnauthorized {
t.Fatalf("受保护路由 %s 匿名请求应返回 401,实际 %d", spec.Pattern, code)
}
}
if protected < 100 {
t.Fatalf("受保护路由覆盖不足:%d 条", protected)
}
if public < 4 {
t.Fatalf("公开路由覆盖不足:%d 条", public)
}
}
func routeMethod(pattern string) string {
method, _, _ := strings.Cut(pattern, " ")
return method
}
func sampleRoutePath(pattern string) string {
_, path, _ := strings.Cut(pattern, " ")
return routeParamPattern.ReplaceAllString(path, "1")
}
// callRouteSafely 在依赖缺失导致 handler panic 时返回 0,避免公开路由的构造依赖影响鉴权断言。
func callRouteSafely(handler http.Handler, req *http.Request) (code int) {
defer func() {
if recover() != nil {
code = 0
}
}()
rec := httptest.NewRecorder()
handler.ServeHTTP(rec, req)
return rec.Code
}