0.10.1: 安全与业务逻辑加固、新品牌与部署加固
三轮审查修复(60+ 项),相对远端 main(b536672)的关键变更:
- 安全: 数据面 SSRF 拨号防护(防 DNS rebinding)/上游凭据剥离/登录防枚举
与锁定态统一/可信代理(X-Forwarded-For)限流加固/会话版本失效机制/
撤销即时传播/弱密钥拒绝启动/脱敏字节级重写(保签名契约)
- 业务逻辑: 裸 body 上传 panic/bootstrap 审计管线卡死/定价通配符优先级/
全局工具可见性/调度器停机补跑/TOTP 挑战令牌消费顺序/熔断探针语义/
>4MB 响应 token 计量/管理员重置密码作废会话 等
- 前端: 新 logo(语枢 AI 网关主题)/Provider 凭据异常警示/删除入口/
后端错误消息透传/localStorage 敏感数据收敛
- 部署: CREDENTIAL_MASTER_KEY 持久化与弱值拒绝/Provider DELETE 接口/
nginx 安全头/worker 内存限制
- 新增迁移 000029(key_hash 索引)/000030(usage_daily 币种维度)
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
package agentnode
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func routeTestNode(id, status string, enabled bool, capabilities map[string]any) Node {
|
||||
raw, _ := json.Marshal(capabilities)
|
||||
return Node{ID: id, Code: id, Status: status, Enabled: enabled, Capabilities: raw, LastHeartbeatAt: func() *time.Time { now := time.Now(); return &now }()}
|
||||
}
|
||||
|
||||
func TestSelectRouteCandidatesFiltersCapabilitiesAndStatus(t *testing.T) {
|
||||
nodes := []Node{
|
||||
routeTestNode("online-capable", "online", true, map[string]any{"tool_exec": true, "region": "cn"}),
|
||||
routeTestNode("online-disabled-capability", "online", true, map[string]any{"tool_exec": false}),
|
||||
routeTestNode("online-missing-capability", "online", true, map[string]any{"region": "cn"}),
|
||||
routeTestNode("offline-capable", "offline", true, map[string]any{"tool_exec": true}),
|
||||
routeTestNode("disabled-capable", "online", false, map[string]any{"tool_exec": true}),
|
||||
}
|
||||
selected := selectRouteCandidates(nodes, "request-1", []string{"tool_exec"})
|
||||
if len(selected) != 1 || selected[0].ID != "online-capable" {
|
||||
t.Fatalf("unexpected candidates: %#v", selected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestSelectRouteCandidatesIsDeterministic(t *testing.T) {
|
||||
nodes := []Node{
|
||||
routeTestNode("node-a", "online", true, nil),
|
||||
routeTestNode("node-b", "online", true, nil),
|
||||
routeTestNode("node-c", "online", true, nil),
|
||||
}
|
||||
first := selectRouteCandidates(nodes, "request-42", nil)
|
||||
second := selectRouteCandidates([]Node{nodes[2], nodes[0], nodes[1]}, "request-42", nil)
|
||||
if len(first) != len(second) || len(first) == 0 {
|
||||
t.Fatalf("candidate lengths differ: %d %d", len(first), len(second))
|
||||
}
|
||||
for index := range first {
|
||||
if first[index].ID != second[index].ID {
|
||||
t.Fatalf("selection order is not stable: first=%v second=%v", first, second)
|
||||
}
|
||||
}
|
||||
if first[0].ID == first[1].ID {
|
||||
t.Fatal("candidate order contains duplicates")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNormalizeRoutePreviewInput(t *testing.T) {
|
||||
input, err := normalizeRoutePreviewInput(RoutePreviewInput{
|
||||
PoolType: " PUBLIC ", PoolCode: "shared", RequestKey: " request-1 ",
|
||||
RequiredCapabilities: []string{"tool_exec", " tool_exec ", ""},
|
||||
})
|
||||
if err != nil || input.PoolType != "public" || input.RequestKey != "request-1" || len(input.RequiredCapabilities) != 1 {
|
||||
t.Fatalf("normalized input=%+v err=%v", input, err)
|
||||
}
|
||||
if _, err := normalizeRoutePreviewInput(RoutePreviewInput{PoolType: "public", PoolCode: "shared"}); err == nil {
|
||||
t.Fatal("empty request key must be rejected")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user