cli:修正帶旗標命令被全域剖析攔下的阻斷問題(#13 審核)

- parseGlobals 改為抽取式 extractGlobals:只取走已知全域選項,非全域
  選項(--has-work/--mine/--reviewer/--repo)原樣交回子命令 flagset,
  任何順序(含與全域選項交錯)皆可解析;-- 之後停止抽取。
- runVersion 補參數檢查:version --wat 仍回 exit 2。
- workflow:commentsFor 對 number<=0 視為無留言,不打 API(對齊 gitea.py
  在 number 缺漏時跳過留言檢查,避免 issues/0/comments 404 中斷掃描)。
- 新增 internal/cli/workflow_commands_test.go:以注入假 client 的接線層
  測試補上 workflow 單元測試覆蓋不到的分派路徑(members --has-work、
  pulls --mine/--reviewer/--repo、全域選項交錯、next null/table)。
- README:註明與 gitea.py 的已知輸出差異(空清單 [] vs 無輸出;
  全域選項可出現在命令前後)。

實機交叉驗證(alex):members --has-work、pulls --mine/--reviewer、
pulls --repo、next、mine 兩者輸出一致;go vet/test 全綠。
This commit is contained in:
2026-09-10 09:41:53 +08:00
parent 5583a00bb5
commit 927855d3ca
6 changed files with 403 additions and 72 deletions
+55 -6
View File
@@ -68,9 +68,9 @@ func TestRunExitCodes(t *testing.T) {
})
}
func TestParseGlobals(t *testing.T) {
func TestExtractGlobals(t *testing.T) {
g := defaultGlobals()
rest, err := parseGlobals(&g, []string{
rest, err := extractGlobals(&g, []string{
"--url", "https://example.com",
"--token=tok",
"--config", "/tmp/c.yml",
@@ -95,16 +95,65 @@ func TestParseGlobals(t *testing.T) {
}
}
func TestParseGlobalsStopsAtCommand(t *testing.T) {
func TestExtractGlobalsAfterCommand(t *testing.T) {
// 全域選項出現在命令之後也應抽出(README:teai next --output table)。
g := defaultGlobals()
rest, err := parseGlobals(&g, []string{"version", "--output", "table"})
rest, err := extractGlobals(&g, []string{"version", "--output", "table"})
if err != nil {
t.Fatal(err)
}
// 命令之後的選項屬於命令,不屬於全域。
if len(rest) != 3 || rest[0] != "version" {
if len(rest) != 1 || rest[0] != "version" {
t.Fatalf("rest = %v, want [version]", rest)
}
if g.Output != gitea.FormatTable {
t.Fatalf("output = %v, want table", g.Output)
}
}
func TestExtractGlobalsKeepsCommandFlags(t *testing.T) {
// 非全域選項屬於子命令:原樣依序交回,不能攔成 ErrUsage。
cases := [][]string{
{"members", "--has-work"},
{"pulls", "--mine"},
{"pulls", "--reviewer"},
{"pulls", "--repo", "alterminal/teai"},
{"members", "--has-work", "--output", "table"},
{"pulls", "--mine", "--repo", "alterminal/teai", "--output", "table"},
}
for _, args := range cases {
g := defaultGlobals()
rest, err := extractGlobals(&g, args)
if err != nil {
t.Fatalf("extractGlobals(%v) 不應報錯:%v", args, err)
}
want := args
if strings.HasSuffix(strings.Join(args, " "), "--output table") {
want = args[:len(args)-2]
}
if len(rest) != len(want) {
t.Fatalf("extractGlobals(%v) rest = %v, want %v", args, rest, want)
}
for i := range want {
if rest[i] != want[i] {
t.Fatalf("extractGlobals(%v) rest = %v, want %v", args, rest, want)
}
}
}
}
func TestExtractGlobalsDoubleDashStops(t *testing.T) {
g := defaultGlobals()
rest, err := extractGlobals(&g, []string{"--output", "table", "--", "--url", "x"})
if err != nil {
t.Fatal(err)
}
// -- 之後全部交回,--url 不再視為全域選項。
if len(rest) != 2 || rest[0] != "--url" || rest[1] != "x" {
t.Fatalf("rest = %v", rest)
}
if g.Output != gitea.FormatTable {
t.Fatalf("output = %v, want table", g.Output)
}
}
// errString 把字串轉成 error(測試輔助)。