AI 托管账号支持筛选未托管

筛选只有「仅已托管」一个选项,想找还没开启的账号只能翻页。补上
managed=0:未托管既包括从未绑定的账号,也包括绑定后被关闭的账号,
运营要开启的正是这两类,所以放在一起。

测试覆盖三种状态,并断言两个筛选相加等于全部、没有重复计数。

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Your Name
2026-09-04 11:34:39 +08:00
co-authored by Claude Opus 5
parent 151834caac
commit b42743c882
2 changed files with 67 additions and 1 deletions
+7 -1
View File
@@ -33,8 +33,14 @@ func (a *App) adminAIAgents(w http.ResponseWriter, r *http.Request) {
fail(w, http.StatusBadRequest, 20001, err.Error())
return
}
if strings.TrimSpace(r.URL.Query().Get("managed")) == "1" {
// "0" is not the absence of a filter here: an account is unmanaged both when
// it has no binding at all and when its binding is switched off, and the
// operator looking for accounts to turn on wants to see both.
switch strings.TrimSpace(r.URL.Query().Get("managed")) {
case "1":
where += " AND g.user_id IS NOT NULL AND g.enabled=1"
case "0":
where += " AND (g.user_id IS NULL OR g.enabled=0)"
}
// Same keyword shape as the user list: nickname or 星遇号.
if keyword := strings.TrimSpace(r.URL.Query().Get("keyword")); keyword != "" {
@@ -246,3 +246,63 @@ func TestAIReplyRetriesAfterReasoningExhaustsBudgetMySQL(t *testing.T) {
t.Fatalf("calls = %d, want 2", calls)
}
}
// The managed filter has three states, and "0" is a real one: an account is
// unmanaged both when it was never bound and when its binding is switched off.
func TestAdminAIAgentsManagedFilterMySQL(t *testing.T) {
db := isolatedIMDatabase(t)
a := &App{db: db, hub: NewHub(), config: Config{Environment: "development", JWTSecret: "isolated-im-regression-secret-only"}}
for _, statement := range []string{
`UPDATE users SET is_test=1,test_batch='regression' WHERE id IN (1,2,3)`,
`INSERT INTO ai_agents(user_id,model_id,enabled) VALUES(1,0,1),(2,0,0)`,
} {
if _, err := db.Exec(statement); err != nil {
t.Fatal(err)
}
}
list := func(query string) []int64 {
t.Helper()
var payload struct {
Items []struct {
UserID int64 `json:"userId"`
} `json:"items"`
Total int `json:"total"`
}
if err := json.Unmarshal(imTestCall(t, a.adminAIAgents, 1, "GET", "/admin/v1/ai/agents"+query, "", 200), &payload); err != nil {
t.Fatal(err)
}
ids := []int64{}
for _, item := range payload.Items {
ids = append(ids, item.UserID)
}
if payload.Total != len(ids) {
t.Fatalf("total=%d 与条目数 %d 不一致", payload.Total, len(ids))
}
return ids
}
if got := list("?managed=1"); len(got) != 1 || got[0] != 1 {
t.Fatalf("仅已托管 = %v, want [1]", got)
}
// Account 2 is bound but switched off, account 3 was never bound.
got := list("?managed=0")
if len(got) != 2 {
t.Fatalf("仅未托管 = %v, want 两个账号", got)
}
for _, want := range []int64{2, 3} {
found := false
for _, id := range got {
found = found || id == want
}
if !found {
t.Fatalf("仅未托管 = %v, 缺少 %d", got, want)
}
}
if all := list(""); len(all) != 3 {
t.Fatalf("全部账号 = %v, want 3 个", all)
}
// The two halves together are the whole list, with nothing counted twice.
if len(list("?managed=1"))+len(list("?managed=0")) != len(list("")) {
t.Fatal("两个筛选相加必须等于全部")
}
}