forked from Gitlink/gitlink-cli
260 lines
10 KiB
Go
260 lines
10 KiB
Go
package workflow
|
|
|
|
import (
|
|
"testing"
|
|
)
|
|
|
|
func init() {
|
|
// Register all workflows for tests (defs init doesn't run in test context
|
|
// because importing defs would create an import cycle).
|
|
Register(&WorkflowDef{
|
|
Name: "community-ops",
|
|
Category: "运营",
|
|
Description: "社区运营自动化",
|
|
Trigger: TriggerDef{Type: "poll", On: "issue.created", Interval: "5m"},
|
|
Steps: []StepDef{
|
|
{Type: StepTypeCommand, Name: "open-issues", Purpose: "获取开放 Issue", Target: "issue +list --state open --limit 50"},
|
|
{Type: StepTypeCommand, Name: "labels", Purpose: "获取标签库", Target: "label +list"},
|
|
{Type: StepTypeCommand, Name: "members", Purpose: "获取成员列表", Target: "member +list"},
|
|
{Type: StepTypeSkill, Name: "triage", Purpose: "AI 分析并执行分拣", Target: "gitlink-triage", DependsOn: []string{"open-issues", "labels", "members"}, RunWhen: RunAlways},
|
|
{Type: StepTypeCommand, Name: "repo-info", Purpose: "获取项目基础信息", Target: "repo +info"},
|
|
{Type: StepTypeCommand, Name: "merged-prs", Purpose: "获取已合并 PR", Target: "pr +list --state merged --limit 50"},
|
|
{Type: StepTypeCommand, Name: "commits", Purpose: "获取提交历史", Target: "commit +list --limit 50"},
|
|
{Type: StepTypeSkill, Name: "health-report", Purpose: "AI 生成周报", Target: "gitlink-health", DependsOn: []string{"repo-info", "merged-prs", "commits", "open-issues"}, RunWhen: RunWeekly},
|
|
{Type: StepTypeCommand, Name: "releases", Purpose: "获取版本发布记录", Target: "release +list"},
|
|
{Type: StepTypeSkill, Name: "changelog", Purpose: "生成 Release Notes", Target: "gitlink-changelog", DependsOn: []string{"commits", "releases", "merged-prs"}, RunWhen: RunOnChange},
|
|
},
|
|
})
|
|
Register(&WorkflowDef{
|
|
Name: "code-quality",
|
|
Category: "质量",
|
|
Description: "代码质量看门人",
|
|
Trigger: TriggerDef{Type: "poll", On: "pr.opened", Interval: "5m"},
|
|
Steps: []StepDef{
|
|
{Type: StepTypeCommand, Name: "open-prs", Purpose: "获取开放 PR 列表", Target: "pr +list --state open --limit 20"},
|
|
{Type: StepTypeCommand, Name: "ci-builds", Purpose: "获取 CI 构建状态", Target: "ci +builds --limit 10"},
|
|
{Type: StepTypeCommand, Name: "repo-info", Purpose: "获取仓库保护规则配置", Target: "repo +info"},
|
|
{Type: StepTypeCommand, Name: "commits", Purpose: "获取最近提交", Target: "commit +list --limit 30"},
|
|
{Type: StepTypeCommand, Name: "branches", Purpose: "获取分支列表", Target: "branch +list"},
|
|
{Type: StepTypeSkill, Name: "review", Purpose: "AI 审查 PR 代码质量", Target: "gitlink-review", DependsOn: []string{"open-prs"}},
|
|
{Type: StepTypeSkill, Name: "ci-diagnosis", Purpose: "AI 诊断 CI 构建失败", Target: "gitlink-ci", DependsOn: []string{"ci-builds", "commits"}},
|
|
},
|
|
})
|
|
Register(&WorkflowDef{
|
|
Name: "project-init",
|
|
Category: "初始化",
|
|
Description: "项目一键初始化",
|
|
Trigger: TriggerDef{Type: "manual", On: "manual"},
|
|
Steps: []StepDef{
|
|
{Type: StepTypeSkill, Name: "init-scaffold", Purpose: "创建仓库并初始化脚手架", Target: "gitlink-init-scaffold"},
|
|
{Type: StepTypeCommand, Name: "repo-info", Purpose: "确认仓库已创建", Target: "repo +info"},
|
|
{Type: StepTypeCommand, Name: "existing-files", Purpose: "验证文件", Target: "file +list"},
|
|
{Type: StepTypeCommand, Name: "labels", Purpose: "验证标签库", Target: "label +list"},
|
|
{Type: StepTypeSkill, Name: "license-check", Purpose: "检查许可证合规", Target: "gitlink-license", DependsOn: []string{"existing-files"}},
|
|
{Type: StepTypeCommand, Name: "milestones", Purpose: "验证里程碑", Target: "milestone +list"},
|
|
{Type: StepTypeCommand, Name: "existing-issues", Purpose: "验证初始 Issue", Target: "issue +list --state all --limit 10"},
|
|
{Type: StepTypeCommand, Name: "branches", Purpose: "检查分支结构", Target: "branch +list"},
|
|
{Type: StepTypeSkill, Name: "repo-audit", Purpose: "综合审计仓库健康度", Target: "gitlink-repo", DependsOn: []string{"repo-info", "existing-files", "labels", "milestones", "branches"}},
|
|
},
|
|
})
|
|
Register(&WorkflowDef{
|
|
Name: "multi-repo",
|
|
Category: "协同",
|
|
Description: "多仓库协同",
|
|
Trigger: TriggerDef{Type: "cron", On: "0 9 * * 1", Interval: "24h"},
|
|
Steps: []StepDef{
|
|
{Type: StepTypeCommand, Name: "multi-repo-snapshot", Purpose: "采集多个仓库状态", Target: "workflow-internal:multi-repo-snapshot"},
|
|
{Type: StepTypeSkill, Name: "multi-repo-coordination", Purpose: "生成统一报告", Target: "gitlink-multi-repo", DependsOn: []string{"multi-repo-snapshot"}},
|
|
},
|
|
})
|
|
Register(&WorkflowDef{
|
|
Name: "contributor-growth",
|
|
Category: "成长",
|
|
Description: "贡献者成长体系",
|
|
Trigger: TriggerDef{Type: "cron", On: "0 9 * * 1", Interval: "24h"},
|
|
Steps: []StepDef{
|
|
{Type: StepTypeCommand, Name: "commits", Purpose: "提交历史", Target: "commit +list --limit 100"},
|
|
{Type: StepTypeCommand, Name: "open-issues", Purpose: "开放 Issue", Target: "issue +list --state open --limit 50"},
|
|
{Type: StepTypeCommand, Name: "closed-issues", Purpose: "已关闭 Issue", Target: "issue +list --state closed --limit 50"},
|
|
{Type: StepTypeCommand, Name: "merged-prs", Purpose: "已合并 PR", Target: "pr +list --state merged --limit 50"},
|
|
{Type: StepTypeCommand, Name: "members", Purpose: "项目成员列表", Target: "member +list"},
|
|
{Type: StepTypeCommand, Name: "repo-info", Purpose: "项目基础数据", Target: "repo +info"},
|
|
{Type: StepTypeSkill, Name: "contributor-ranking", Purpose: "AI 分析贡献者排行", Target: "gitlink-contributor-ranking", DependsOn: []string{"commits", "open-issues", "closed-issues", "merged-prs", "members"}},
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestRegistry(t *testing.T) {
|
|
if len(registry) != 5 {
|
|
t.Fatalf("expected 5 workflows, got %d", len(registry))
|
|
}
|
|
|
|
for _, name := range []string{"community-ops", "code-quality", "project-init", "multi-repo", "contributor-growth"} {
|
|
wf := Get(name)
|
|
if wf == nil {
|
|
t.Fatalf("workflow %q not found", name)
|
|
}
|
|
if len(wf.Steps) == 0 {
|
|
t.Fatalf("workflow %q has no steps", name)
|
|
}
|
|
if wf.Trigger.Type == "" {
|
|
t.Fatalf("workflow %q has no trigger.type", name)
|
|
}
|
|
}
|
|
|
|
all := All()
|
|
if len(all) != 5 {
|
|
t.Fatalf("All() returned %d workflows, expected 5", len(all))
|
|
}
|
|
}
|
|
|
|
func TestGetNonexistent(t *testing.T) {
|
|
if Get("nonexistent") != nil {
|
|
t.Fatal("expected nil for nonexistent workflow")
|
|
}
|
|
}
|
|
|
|
func TestResolvePath(t *testing.T) {
|
|
cases := []struct {
|
|
template, owner, repo, expected string
|
|
}{
|
|
{"{v1}/issues", "chroe", "gitlink-cli", "/v1/chroe/gitlink-cli/issues"},
|
|
{"{base}/pulls", "chroe", "gitlink-cli", "/chroe/gitlink-cli/pulls"},
|
|
{"{base}", "org", "proj", "/org/proj"},
|
|
{"{v1}/issues?state=open", "x", "y", "/v1/x/y/issues?state=open"},
|
|
}
|
|
for _, tc := range cases {
|
|
got := ResolvePath(tc.template, tc.owner, tc.repo)
|
|
if got != tc.expected {
|
|
t.Fatalf("ResolvePath(%q, %s, %s) = %q, want %q", tc.template, tc.owner, tc.repo, got, tc.expected)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestTriggers(t *testing.T) {
|
|
expected := map[string]struct {
|
|
on string
|
|
typ string
|
|
}{
|
|
"community-ops": {"issue.created", "poll"},
|
|
"code-quality": {"pr.opened", "poll"},
|
|
"project-init": {"manual", "manual"},
|
|
"multi-repo": {"0 9 * * 1", "cron"},
|
|
"contributor-growth": {"0 9 * * 1", "cron"},
|
|
}
|
|
for name, want := range expected {
|
|
wf := Get(name)
|
|
if wf.Trigger.On != want.on {
|
|
t.Fatalf("%s: trigger.on = %q, want %q", name, wf.Trigger.On, want.on)
|
|
}
|
|
if wf.Trigger.Type != want.typ {
|
|
t.Fatalf("%s: trigger.type = %q, want %q", name, wf.Trigger.Type, want.typ)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestStepTypes(t *testing.T) {
|
|
wf := Get("community-ops")
|
|
if wf == nil {
|
|
t.Fatal("community-ops not found")
|
|
}
|
|
|
|
typeCounts := map[StepType]int{}
|
|
for _, s := range wf.Steps {
|
|
typeCounts[s.Type]++
|
|
}
|
|
if typeCounts[StepTypeCommand] < 1 {
|
|
t.Fatal("community-ops should have at least one command step")
|
|
}
|
|
if typeCounts[StepTypeSkill] < 1 {
|
|
t.Fatal("community-ops should have at least one skill step")
|
|
}
|
|
}
|
|
|
|
func TestSkillStepDependsOn(t *testing.T) {
|
|
wf := Get("community-ops")
|
|
if wf == nil {
|
|
t.Fatal("community-ops not found")
|
|
}
|
|
|
|
var triage *StepDef
|
|
for i := range wf.Steps {
|
|
if wf.Steps[i].Name == "triage" {
|
|
triage = &wf.Steps[i]
|
|
break
|
|
}
|
|
}
|
|
if triage == nil {
|
|
t.Fatal("triage step not found")
|
|
}
|
|
if len(triage.DependsOn) != 3 {
|
|
t.Fatalf("triage step should have 3 dependencies, got %d: %v", len(triage.DependsOn), triage.DependsOn)
|
|
}
|
|
expectedDeps := map[string]bool{"open-issues": false, "labels": false, "members": false}
|
|
for _, dep := range triage.DependsOn {
|
|
if _, ok := expectedDeps[dep]; !ok {
|
|
t.Fatalf("unexpected dependency: %s", dep)
|
|
}
|
|
expectedDeps[dep] = true
|
|
}
|
|
}
|
|
|
|
func TestMultiRepoWorkflowShape(t *testing.T) {
|
|
wf := Get("multi-repo")
|
|
if wf == nil {
|
|
t.Fatal("multi-repo not found")
|
|
}
|
|
if len(wf.Steps) != 2 {
|
|
t.Fatalf("multi-repo should have 2 steps, got %d", len(wf.Steps))
|
|
}
|
|
if wf.Steps[0].Name != "multi-repo-snapshot" || wf.Steps[0].Target != "workflow-internal:multi-repo-snapshot" {
|
|
t.Fatalf("unexpected snapshot step: %+v", wf.Steps[0])
|
|
}
|
|
if wf.Steps[1].Target != "gitlink-multi-repo" {
|
|
t.Fatalf("multi-repo skill target = %q, want gitlink-multi-repo", wf.Steps[1].Target)
|
|
}
|
|
}
|
|
|
|
func TestParseCommandTarget(t *testing.T) {
|
|
cases := []struct {
|
|
input string
|
|
expected []string
|
|
}{
|
|
{"issue +list --state open", []string{"issue", "+list", "--state", "open"}},
|
|
{"repo +info", []string{"repo", "+info"}},
|
|
{"pr +list --state merged --limit 50", []string{"pr", "+list", "--state", "merged", "--limit", "50"}},
|
|
{"issue +list --state open --limit 50", []string{"issue", "+list", "--state", "open", "--limit", "50"}},
|
|
}
|
|
for _, tc := range cases {
|
|
got := ParseCommandTarget(tc.input)
|
|
if len(got) != len(tc.expected) {
|
|
t.Fatalf("ParseCommandTarget(%q): len=%d, want len=%d (got=%v)", tc.input, len(got), len(tc.expected), got)
|
|
}
|
|
for i := range got {
|
|
if got[i] != tc.expected[i] {
|
|
t.Fatalf("ParseCommandTarget(%q)[%d] = %q, want %q", tc.input, i, got[i], tc.expected[i])
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCodeQualityHasReviewStep(t *testing.T) {
|
|
wf := Get("code-quality")
|
|
if wf == nil {
|
|
t.Fatal("code-quality not found")
|
|
}
|
|
if len(wf.Steps) < 7 {
|
|
t.Fatalf("code-quality should have at least 7 steps (including review), got %d", len(wf.Steps))
|
|
}
|
|
found := false
|
|
for _, s := range wf.Steps {
|
|
if s.Target == "gitlink-review" {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
t.Fatal("code-quality missing gitlink-review skill step")
|
|
}
|
|
}
|