滑动续期、网络错误归类、API Key 激活乐观锁与生效提示

This commit is contained in:
2026-08-11 11:45:03 +08:00
parent 23b2820101
commit e63af49831
21 changed files with 679 additions and 15 deletions
+54 -2
View File
@@ -8,9 +8,11 @@ import (
"net/http/httptest"
"strings"
"testing"
"time"
"github.com/gin-gonic/gin"
"github.com/glebarez/sqlite"
"github.com/golang-jwt/jwt/v5"
"gorm.io/gorm"
"gorm.io/gorm/logger"
@@ -122,6 +124,30 @@ func doRequest(t *testing.T, r *gin.Engine, method, path, token, body string) *h
return w
}
type apiTestClaims struct {
jwt.RegisteredClaims
Ver uint `json:"ver"`
}
func shortAPIToken(t *testing.T, token string) string {
t.Helper()
claims := &apiTestClaims{}
_, err := jwt.ParseWithClaims(token, claims, func(*jwt.Token) (any, error) {
return []byte("test-secret"), nil
})
if err != nil {
t.Fatalf("parse login token: %v", err)
}
now := time.Now()
claims.IssuedAt = jwt.NewNumericDate(now)
claims.ExpiresAt = jwt.NewNumericDate(now.Add(time.Hour))
short, err := jwt.NewWithClaims(jwt.SigningMethodHS256, claims).SignedString([]byte("test-secret"))
if err != nil {
t.Fatalf("sign short token: %v", err)
}
return short
}
func TestLoginEndpoint(t *testing.T) {
r, _, _ := newTestRouter(t)
tests := []struct {
@@ -171,6 +197,26 @@ func TestSecuredRoutesRequireToken(t *testing.T) {
}
}
func TestReadRequestRenewsSession(t *testing.T) {
r, auth, _ := newTestRouter(t)
token, _, err := auth.Login(context.Background(), "admin", "pass123", "",
service.SessionMeta{ClientIP: "127.0.0.1"})
if err != nil {
t.Fatalf("login: %v", err)
}
w := doRequest(t, r, http.MethodGet, "/api/v1/auth/credentials", shortAPIToken(t, token), "")
if w.Code != http.StatusOK {
t.Fatalf("status = %d, want 200, body %s", w.Code, w.Body.String())
}
renewed := w.Header().Get("X-Renewed-Token")
if renewed == "" {
t.Fatal("X-Renewed-Token 为空, want 只读请求续期")
}
if _, err := auth.ParseToken(context.Background(), renewed); err != nil {
t.Errorf("renewed token invalid: %v", err)
}
}
func TestSystemLogsEndpoint(t *testing.T) {
r, auth, logs := newTestRouter(t)
token, _, err := auth.Login(context.Background(), "admin", "pass123", "", service.SessionMeta{ClientIP: "127.0.0.1"})
@@ -245,11 +291,17 @@ func TestRevokeSessionsEndpoint(t *testing.T) {
if err := json.Unmarshal(login.Body.Bytes(), &sess); err != nil || sess.Token == "" {
t.Fatalf("login: %s", login.Body.String())
}
w := doRequest(t, r, http.MethodPost, "/api/v1/auth/revoke-sessions", sess.Token, "")
short := shortAPIToken(t, sess.Token)
w := doRequest(t, r, http.MethodPost, "/api/v1/auth/revoke-sessions", short, "")
if w.Code != http.StatusOK || !strings.Contains(w.Body.String(), "token") {
t.Fatalf("revoke = %d %s", w.Code, w.Body.String())
}
w = doRequest(t, r, http.MethodGet, "/api/v1/auth/credentials", sess.Token, "")
for _, name := range []string{"X-Renewed-Token", "X-Renewed-Expires-At"} {
if got := w.Header().Get(name); got != "" {
t.Errorf("写请求 %s = %q, want empty", name, got)
}
}
w = doRequest(t, r, http.MethodGet, "/api/v1/auth/credentials", short, "")
if w.Code != http.StatusUnauthorized {
t.Errorf("撤销后旧 token 访问 = %d, want 401", w.Code)
}