✨ 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 逐步微排链路已趋于稳定,但两个复合操作函数仍未恢复可用,后续将继续排查
129 lines
3.6 KiB
Go
129 lines
3.6 KiB
Go
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)
|
||
}
|
||
}
|