2026-09-23 09:40:59 +08:00
|
|
|
package api
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
|
|
|
|
"context"
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"net/http"
|
|
|
|
|
"net/http/httptest"
|
|
|
|
|
"path/filepath"
|
|
|
|
|
"testing"
|
|
|
|
|
"time"
|
|
|
|
|
|
2026-10-01 16:54:35 +08:00
|
|
|
"github.com/havline/havline/internal/auth"
|
|
|
|
|
"github.com/havline/havline/internal/backup"
|
|
|
|
|
"github.com/havline/havline/internal/certificate"
|
|
|
|
|
"github.com/havline/havline/internal/config"
|
|
|
|
|
"github.com/havline/havline/internal/db"
|
|
|
|
|
"github.com/havline/havline/internal/discovery"
|
|
|
|
|
"github.com/havline/havline/internal/proxy"
|
|
|
|
|
"github.com/havline/havline/internal/service"
|
|
|
|
|
"github.com/havline/havline/internal/settings"
|
2026-09-23 09:40:59 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func TestAuthSetupAndLoginFlow(t *testing.T) {
|
2026-10-01 16:54:35 +08:00
|
|
|
conn, err := db.Open(filepath.Join(t.TempDir(), "havline.db"))
|
2026-09-23 09:40:59 +08:00
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
tmpDir := t.TempDir()
|
|
|
|
|
authSvc := auth.New(conn)
|
|
|
|
|
proxySvc := service.NewProxyService(config.Config{DataDir: tmpDir, NginxPIDFile: tmpDir + "/nginx.pid"}, conn, proxy.NewStore(conn), certificate.NewStore(conn), settings.NewStore(conn), nil)
|
|
|
|
|
handler := NewRouter(Deps{
|
|
|
|
|
Config: config.Config{DataDir: tmpDir, NginxPIDFile: tmpDir + "/nginx.pid"},
|
|
|
|
|
Auth: authSvc,
|
|
|
|
|
Proxy: proxySvc,
|
|
|
|
|
Backup: backup.New(tmpDir),
|
|
|
|
|
Discovery: discovery.New(),
|
|
|
|
|
StartedAt: time.Now().UTC().Format(time.RFC3339),
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
statusReq := httptest.NewRequest(http.MethodGet, "/api/auth/status", nil)
|
|
|
|
|
statusRec := httptest.NewRecorder()
|
|
|
|
|
handler.ServeHTTP(statusRec, statusReq)
|
|
|
|
|
if statusRec.Code != http.StatusOK {
|
|
|
|
|
t.Fatalf("status code: %d", statusRec.Code)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if err := authSvc.Setup(context.Background(), "admin", "password123"); err != nil {
|
|
|
|
|
t.Fatalf("setup admin: %v", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
loginBody, _ := json.Marshal(map[string]string{
|
|
|
|
|
"username": "admin",
|
|
|
|
|
"password": "password123",
|
|
|
|
|
})
|
|
|
|
|
loginReq := httptest.NewRequest(http.MethodPost, "/api/auth/login", bytes.NewReader(loginBody))
|
|
|
|
|
loginRec := httptest.NewRecorder()
|
|
|
|
|
handler.ServeHTTP(loginRec, loginReq)
|
|
|
|
|
if loginRec.Code != http.StatusOK {
|
|
|
|
|
t.Fatalf("login status: %d body=%s", loginRec.Code, loginRec.Body.String())
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
cookie := loginRec.Result().Cookies()[0]
|
2026-10-01 16:54:35 +08:00
|
|
|
if cookie.Name != "havline_session" || cookie.HttpOnly == false {
|
2026-09-23 09:40:59 +08:00
|
|
|
t.Fatal("expected httponly session cookie")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
authStatusReq := httptest.NewRequest(http.MethodGet, "/api/auth/status", nil)
|
|
|
|
|
authStatusReq.AddCookie(cookie)
|
|
|
|
|
authStatusRec := httptest.NewRecorder()
|
|
|
|
|
handler.ServeHTTP(authStatusRec, authStatusReq)
|
|
|
|
|
if authStatusRec.Code != http.StatusOK {
|
|
|
|
|
t.Fatalf("auth status code: %d", authStatusRec.Code)
|
|
|
|
|
}
|
|
|
|
|
var authStatus authStatusResponse
|
|
|
|
|
if err := json.Unmarshal(authStatusRec.Body.Bytes(), &authStatus); err != nil {
|
|
|
|
|
t.Fatalf("decode auth status: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if !authStatus.Initialized || !authStatus.Authenticated {
|
|
|
|
|
t.Fatalf("expected authenticated status, got %+v", authStatus)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
proxiesReq := httptest.NewRequest(http.MethodGet, "/api/proxies", nil)
|
|
|
|
|
proxiesReq.AddCookie(cookie)
|
|
|
|
|
proxiesRec := httptest.NewRecorder()
|
|
|
|
|
handler.ServeHTTP(proxiesRec, proxiesReq)
|
|
|
|
|
if proxiesRec.Code != http.StatusOK {
|
|
|
|
|
t.Fatalf("proxies status: %d", proxiesRec.Code)
|
|
|
|
|
}
|
|
|
|
|
}
|