package api import ( "net/http" "net/http/httptest" "testing" "time" "booklib/internal/config" ) func testCfg() *config.Config { return &config.Config{Addr: ":8080", JWTSecret: []byte("s3cret"), ScanInterval: time.Minute, UploadMaxMB: 200, TrustedProxies: []string{"172.16.0.0/12"}} // 与 prod 默认一致: 只有 compose 网段内代理才可信 } func TestHealthz(t *testing.T) { r := NewRouter(testCfg(), nil, nil, nil, nil, nil, nil, nil, nil, nil) req := httptest.NewRequest(http.MethodGet, "/api/healthz", nil) w := httptest.NewRecorder() r.ServeHTTP(w, req) if w.Code != http.StatusOK { t.Fatalf("healthz = %d, want 200", w.Code) } } // TestRouterContract 钉死全量路由表(Task 27):增删改任何路由都会使此测试失败, // 必须显式更新——路由即对外契约。 func TestRouterContract(t *testing.T) { r := NewRouter(testCfg(), nil, nil, nil, nil, nil, nil, nil, nil, nil) want := map[string]bool{ "GET /api/healthz": true, "POST /api/auth/login": true, "GET /api/auth/me": true, "GET /api/users": true, "POST /api/users": true, "DELETE /api/users/:id": true, "GET /api/libraries": true, "POST /api/libraries": true, "POST /api/libraries/:id/scan": true, "POST /api/libraries/:id/upload": true, "POST /api/libraries/:id/upload/init": true, "GET /api/uploads/:uid": true, "POST /api/uploads/:uid/complete": true, "PUT /api/uploads/:uid/parts/:index": true, "GET /api/books": true, "GET /api/books/:id": true, "DELETE /api/books/:id": true, "GET /api/books/:id/cover": true, "GET /api/books/:id/file": true, "GET /api/books/:id/pages": true, "GET /api/books/:id/pages/:n": true, "PUT /api/books/:id/progress": true, "GET /api/progress": true, "GET /api/books/:id/bookmarks": true, "POST /api/books/:id/bookmarks": true, "PATCH /api/bookmarks/:id": true, "DELETE /api/bookmarks/:id": true, } got := map[string]bool{} for _, rt := range r.Routes() { key := rt.Method + " " + rt.Path if got[key] { t.Errorf("duplicate route %s", key) } got[key] = true } for k := range want { if !got[k] { t.Errorf("missing route %s", k) } } for k := range got { if !want[k] { t.Errorf("unexpected route %s", k) } } }