feat: teai role
This commit is contained in:
@@ -9,8 +9,17 @@ import (
|
||||
"sort"
|
||||
)
|
||||
|
||||
// AgentsTeamName 是工作流團隊的名稱(AGENTS.md:調度成員由 Agents 團隊決定)。
|
||||
const AgentsTeamName = "Agents"
|
||||
const (
|
||||
// AgentsTeamName 是工作流團隊的名稱(AGENTS.md:調度成員由 Agents 團隊決定)。
|
||||
AgentsTeamName = "Agents"
|
||||
// SupervisorTeamName 是管理者團隊的名稱;成員角色為 supervisor。
|
||||
SupervisorTeamName = "Supervisor"
|
||||
|
||||
// RoleSupervisor 是 Supervisor 團隊成員的角色值。
|
||||
RoleSupervisor = "supervisor"
|
||||
// RoleWorker 是 Agents 團隊成員的角色值。
|
||||
RoleWorker = "worker"
|
||||
)
|
||||
|
||||
// Client 抽象 teai 需要的 Gitea API 讀取操作(僅唯讀)。
|
||||
// #5 的 internal/gitea HTTP 用戶端實作此介面;workflow 不依賴 HTTP 細節。
|
||||
@@ -49,7 +58,7 @@ type PullItem struct {
|
||||
Pull PullRequest
|
||||
}
|
||||
|
||||
// Scanner 在 Client 之上組合出各命令的資料(whoami/orgs/members/mine/pulls/next)。
|
||||
// Scanner 在 Client 之上組合出各命令的資料(whoami/role/orgs/members/mine/pulls/next)。
|
||||
type Scanner struct {
|
||||
Client Client
|
||||
}
|
||||
@@ -59,6 +68,63 @@ func (s *Scanner) Whoami() (string, error) {
|
||||
return s.Client.CurrentUser()
|
||||
}
|
||||
|
||||
// Role 依目前帳號所屬團隊回傳角色:Supervisor 團隊為 supervisor,
|
||||
// Agents 團隊為 worker。同時屬於兩者時以 supervisor 為準。
|
||||
// 兩者都不屬於時回空字串(CLI 輸出 null)。
|
||||
func (s *Scanner) Role() (string, error) {
|
||||
username, err := s.Whoami()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
orgs, err := s.Orgs()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
inAgents := false
|
||||
for _, org := range orgs {
|
||||
teams, err := s.Client.ListTeams(org)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
for _, team := range teams {
|
||||
switch team.Name {
|
||||
case SupervisorTeamName, AgentsTeamName:
|
||||
default:
|
||||
continue
|
||||
}
|
||||
ok, err := s.usernameInTeam(team.ID, username)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if team.Name == SupervisorTeamName {
|
||||
return RoleSupervisor, nil
|
||||
}
|
||||
inAgents = true
|
||||
}
|
||||
}
|
||||
if inAgents {
|
||||
return RoleWorker, nil
|
||||
}
|
||||
return "", nil
|
||||
}
|
||||
|
||||
// usernameInTeam 檢查帳號是否為該團隊成員。
|
||||
func (s *Scanner) usernameInTeam(teamID int64, username string) (bool, error) {
|
||||
members, err := s.Client.ListTeamMembers(teamID)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
for _, m := range members {
|
||||
if m == username {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
// Orgs 回傳目前帳號所屬的組織(去重、排序)。
|
||||
func (s *Scanner) Orgs() ([]string, error) {
|
||||
names, err := s.Client.ListOrgs()
|
||||
|
||||
@@ -288,6 +288,91 @@ func TestErrorPropagation(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoleWorker(t *testing.T) {
|
||||
s, _ := newTestScanner()
|
||||
got, err := s.Role()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got != RoleWorker {
|
||||
t.Errorf("Agents 團隊成員應為 worker,得到 %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoleSupervisor(t *testing.T) {
|
||||
s, fc := newTestScanner()
|
||||
fc.teams["alterminal"] = []Team{
|
||||
{ID: 1, Name: "Agents"},
|
||||
{ID: 2, Name: "Supervisor"},
|
||||
}
|
||||
fc.teamMembers[2] = []string{"alex"}
|
||||
got, err := s.Role()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got != RoleSupervisor {
|
||||
t.Errorf("Supervisor 團隊成員應為 supervisor,得到 %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoleSupervisorTakesPrecedence(t *testing.T) {
|
||||
s, fc := newTestScanner()
|
||||
// alex 同時在 Agents(newTestScanner 預設)與 Supervisor。
|
||||
fc.teams["alterminal"] = []Team{
|
||||
{ID: 1, Name: "Agents"},
|
||||
{ID: 2, Name: "Supervisor"},
|
||||
}
|
||||
fc.teamMembers[2] = []string{"alex", "ceo"}
|
||||
got, err := s.Role()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got != RoleSupervisor {
|
||||
t.Errorf("同時屬於兩者時應為 supervisor,得到 %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoleNone(t *testing.T) {
|
||||
s, fc := newTestScanner()
|
||||
fc.currentUser = "outsider"
|
||||
got, err := s.Role()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got != "" {
|
||||
t.Errorf("不在 Supervisor/Agents 應回空字串,得到 %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoleWorkerWhenSupervisorExists(t *testing.T) {
|
||||
s, fc := newTestScanner()
|
||||
fc.teams["alterminal"] = []Team{
|
||||
{ID: 1, Name: "Agents"},
|
||||
{ID: 2, Name: "Supervisor"},
|
||||
}
|
||||
fc.teamMembers[2] = []string{"ceo"}
|
||||
got, err := s.Role()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got != RoleWorker {
|
||||
t.Errorf("只在 Agents、不在 Supervisor 應為 worker,得到 %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRoleErrorPropagation(t *testing.T) {
|
||||
s, fc := newTestScanner()
|
||||
fc.failOn["teams"] = errors.New("boom")
|
||||
if _, err := s.Role(); err == nil {
|
||||
t.Errorf("ListTeams 錯誤應向上傳播")
|
||||
}
|
||||
delete(fc.failOn, "teams")
|
||||
fc.failOn["members"] = errors.New("boom")
|
||||
if _, err := s.Role(); err == nil {
|
||||
t.Errorf("ListTeamMembers 錯誤應向上傳播")
|
||||
}
|
||||
}
|
||||
|
||||
func TestPullsDefault(t *testing.T) {
|
||||
s, fc := newTestScanner()
|
||||
fc.pulls["alterminal/teai"] = []PullRequest{
|
||||
|
||||
Reference in New Issue
Block a user