package auth import ( "net/http" "net/http/httptest" "strings" "testing" "time" "github.com/gin-gonic/gin" "hyapp-admin-server/internal/config" authcore "hyapp-admin-server/internal/service" ) func TestRefreshUsesRootCookieAndClearsLegacyCookiePath(t *testing.T) { gin.SetMode(gin.TestMode) store := newFakeAuthStore(t) handler := testAuthHandler(store) login, err := handler.service.Login(LoginInput{Username: "admin", Password: "secret", IP: "127.0.0.1", UserAgent: "browser"}) if err != nil { t.Fatalf("login: %v", err) } router := gin.New() router.POST("/api/v1/auth/refresh", handler.Refresh) req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/refresh", nil) req.AddCookie(&http.Cookie{Name: refreshCookieName, Value: login.RefreshToken}) rec := httptest.NewRecorder() router.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("refresh status = %d body=%s", rec.Code, rec.Body.String()) } setCookies := rec.Result().Cookies() if !hasCookieWithPath(setCookies, refreshCookieName, refreshCookiePath) { t.Fatalf("refresh must set root refresh cookie: %+v", setCookies) } if !hasExpiredCookieWithPath(setCookies, refreshCookieName, legacyRefreshCookiePath) { t.Fatalf("refresh must clear legacy path cookie: %+v", setCookies) } } func TestRefreshTriesDuplicateRefreshCookiesUntilOneWorks(t *testing.T) { gin.SetMode(gin.TestMode) store := newFakeAuthStore(t) handler := testAuthHandler(store) first, err := handler.service.Login(LoginInput{Username: "admin", Password: "secret", IP: "127.0.0.1", UserAgent: "browser"}) if err != nil { t.Fatalf("login: %v", err) } refreshed, err := handler.service.Refresh(first.RefreshToken, LoginInput{IP: "127.0.0.1", UserAgent: "browser"}) if err != nil { t.Fatalf("prime refresh: %v", err) } router := gin.New() router.POST("/api/v1/auth/refresh", handler.Refresh) req := httptest.NewRequest(http.MethodPost, "/api/v1/auth/refresh", nil) req.Header.Set("Cookie", refreshCookieName+"="+first.RefreshToken+"; "+refreshCookieName+"="+refreshed.RefreshToken) rec := httptest.NewRecorder() router.ServeHTTP(rec, req) if rec.Code != http.StatusOK { t.Fatalf("refresh status = %d body=%s", rec.Code, rec.Body.String()) } if strings.Contains(rec.Body.String(), "刷新会话已失效") { t.Fatalf("refresh should skip stale duplicate cookie: %s", rec.Body.String()) } } func testAuthHandler(store *fakeAuthStore) *Handler { cfg := config.Config{RefreshTokenTTL: 7 * 24 * time.Hour, RefreshCookieSameSite: "lax"} return &Handler{ service: NewService(store, authcore.NewAuthService("test-secret", time.Hour), cfg), cfg: cfg, } } func hasCookieWithPath(cookies []*http.Cookie, name string, path string) bool { for _, cookie := range cookies { if cookie.Name == name && cookie.Path == path && cookie.MaxAge >= 0 && cookie.Value != "" { return true } } return false } func hasExpiredCookieWithPath(cookies []*http.Cookie, name string, path string) bool { for _, cookie := range cookies { if cookie.Name == name && cookie.Path == path && cookie.MaxAge < 0 { return true } } return false }