Files
smartmate/backend/auth/jwt_handler_test.go
Losita e6941f98f2 Version: 0.7.4.dev.260323
 feat(schedulerefine): 新增 refine 子路由,优先执行复合操作,失败后降级至禁复合 ReAct 兜底

ReAct 升级
- ♻️ 将原有链路升级为真正的 ReAct 执行模式,进一步增强整体调度过程的可靠性

Refine 子路由
- 🧭 在 refine 主链路中新增 `route` 节点,整体流程调整为 `contract -> plan -> slice -> route -> react -> hard_check -> summary`
-  当 `route` 命中全局复合目标时,优先尝试一次调用 `SpreadEven` / `MinContextSwitch`,失败后最多重试 2 次
- 🔀 `route` 成功后直接跳过 `ReAct`;若执行失败,则自动切换至 `fallback` 模式
- 🛡️ 在 `fallback` 模式下增加后端硬约束:禁用 `SpreadEven` / `MinContextSwitch` / `BatchMove`,仅允许使用 `Move` / `Swap` 逐任务处理
- 🧠 在 `ReAct` 的 prompt 与上下文中新增 `COMPOSITE_TOOLS_ALLOWED`,显式告知当前是否允许使用复合工具
- 🧩 扩展状态字段以承载路由与降级状态:`CompositeRetryMax` / `DisableCompositeTools` / `CompositeRouteTried` / `CompositeRouteSucceeded`
- 👀 增加 `route` 相关阶段日志,便于排查命中、重试、收口与降级原因

修复
- 🐛 修复 JWT Token 过期时间未按 `config.yaml` 配置生效的问题

备注
- 🚧 当前 ReAct 逐步微排链路已趋于稳定,但两个复合操作函数仍未恢复可用,后续将继续排查
2026-03-23 23:14:19 +08:00

129 lines
3.6 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package auth
import (
"testing"
"time"
"github.com/LoveLosita/smartflow/backend/model"
"github.com/golang-jwt/jwt/v4"
"github.com/spf13/viper"
)
func TestParseFlexibleDuration(t *testing.T) {
t.Parallel()
testCases := []struct {
name string
raw string
want time.Duration
wantFail bool
}{
{name: "标准格式", raw: "15m", want: 15 * time.Minute},
{name: "项目分钟简写", raw: "15min", want: 15 * time.Minute},
{name: "项目天简写", raw: "7d", want: 7 * 24 * time.Hour},
{name: "非法格式", raw: "abc", wantFail: true},
}
for _, tc := range testCases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
got, err := parseFlexibleDuration(tc.raw)
if tc.wantFail {
if err == nil {
t.Fatalf("期望解析失败,但得到成功: %s", tc.raw)
}
return
}
if err != nil {
t.Fatalf("解析失败: %v", err)
}
if got != tc.want {
t.Fatalf("解析结果不符合预期got=%v want=%v", got, tc.want)
}
})
}
}
func TestGenerateTokens_UseConfigExpire(t *testing.T) {
const (
accessSecret = "unit-test-access-secret"
refreshSecret = "unit-test-refresh-secret"
accessExpire = "2h"
refreshExpire = "3d"
)
originAccessSecret := viper.GetString(accessSecretConfigKey)
originRefreshSecret := viper.GetString(refreshSecretConfigKey)
originAccessExpire := viper.GetString(accessExpireConfigKey)
originRefreshExpire := viper.GetString(refreshExpireConfigKey)
viper.Set(accessSecretConfigKey, accessSecret)
viper.Set(refreshSecretConfigKey, refreshSecret)
viper.Set(accessExpireConfigKey, accessExpire)
viper.Set(refreshExpireConfigKey, refreshExpire)
t.Cleanup(func() {
viper.Set(accessSecretConfigKey, originAccessSecret)
viper.Set(refreshSecretConfigKey, originRefreshSecret)
viper.Set(accessExpireConfigKey, originAccessExpire)
viper.Set(refreshExpireConfigKey, originRefreshExpire)
})
start := time.Now()
accessTokenString, refreshTokenString, err := GenerateTokens(9527)
if err != nil {
t.Fatalf("签发 token 失败: %v", err)
}
accessClaims := parseTokenClaimsForTest(t, accessTokenString, []byte(accessSecret))
refreshClaims := parseTokenClaimsForTest(t, refreshTokenString, []byte(refreshSecret))
if accessClaims.TokenType != "access_token" {
t.Fatalf("access token_type 不符合预期: %s", accessClaims.TokenType)
}
if refreshClaims.TokenType != "refresh_token" {
t.Fatalf("refresh token_type 不符合预期: %s", refreshClaims.TokenType)
}
if accessClaims.Jti == "" || refreshClaims.Jti == "" {
t.Fatalf("jti 不能为空")
}
if accessClaims.Jti != refreshClaims.Jti {
t.Fatalf("access/refresh 应共享同一个 jti")
}
assertExpireNear(t, accessClaims.ExpiresAt.Time, start.Add(2*time.Hour), 3*time.Second)
assertExpireNear(t, refreshClaims.ExpiresAt.Time, start.Add(3*24*time.Hour), 3*time.Second)
}
func parseTokenClaimsForTest(t *testing.T, tokenString string, key []byte) *model.MyCustomClaims {
t.Helper()
token, err := jwt.ParseWithClaims(tokenString, &model.MyCustomClaims{}, func(token *jwt.Token) (interface{}, error) {
return key, nil
})
if err != nil {
t.Fatalf("解析 token 失败: %v", err)
}
if !token.Valid {
t.Fatalf("token 无效")
}
claims, ok := token.Claims.(*model.MyCustomClaims)
if !ok {
t.Fatalf("claims 类型断言失败")
}
return claims
}
func assertExpireNear(t *testing.T, got time.Time, want time.Time, tolerance time.Duration) {
t.Helper()
delta := got.Sub(want)
if delta < 0 {
delta = -delta
}
if delta > tolerance {
t.Fatalf("exp 偏差超出容忍范围got=%s want=%s delta=%s tolerance=%s", got.Format(time.RFC3339), want.Format(time.RFC3339), delta, tolerance)
}
}