Files
dingtalk-workspace-cli/scripts/policy/skill-command-check/main_test.go
T
SCzhengand修雨 809d026b7e ci: add CLI compatibility and PR quality gates (#602)
* ci(interface): 添加接口完整性和CLI烟雾测试检查

- 在Makefile中新增interface-integrity、update-interface-baseline、cli-smoke和mock-mcp-smoke目标
- 实现接口基线脚本以比较CLI公共命令树与基线文件
- 新增脚本确保二进制文件可渲染所有顶层命令的帮助信息
- 添加mock-mcp-smoke测试验证HTTP和stdio MCP请求/响应传输
- 在GitHub Actions CI工作流中加入interface-integrity、cli-smoke和mock-mcp-smoke检查
- 新增AI行为检查工作流,限制AI生成PR的改动范围和禁改保护文件
- 文档中补充PR质量门禁要求及接口变更流程说明

* feat(policy): 增加多项兼容性和完整性检查

- Makefile中新增reset-interface-baseline、schema-compatibility、update-schema-baseline、skill-command-integrity等目标
- CI流程新增schema-compatibility和skill-command-integrity步骤
- 文档中详细说明接口兼容性基线更新和重置流程及schema基线注意事项
- 实现interface-baseline工具支持兼容性重置和合并,多项接口兼容性检查逻辑完善
- 新增schema-compat工具用于标准化schema列表及兼容性检查与合并
- 新增skill-command-integrity检查确保技能中引用命令存在
- 为interface-baseline和schema-compat添加单元测试覆盖基本兼容性规则

* test(skill-command-check): 增加命令解析和解析结果测试用例

- 补充对 parseReference 函数的边界情况和跳过条件的测试
- 新增 resolveCommandReference 函数测试,覆盖有效、无效及跳过场景
- 增加 isPlaceholder 函数的测试,验证占位符识别准确性

refactor(skill-command-check): 优化命令路径解析和验证逻辑

- 使用 resolveCommandReference 统一处理命令解析结果,清晰区分有效、无效和跳过情况
- 新增 commandResolution 类型及常量,提升代码可读性和扩展性
- 调整 parseReference 增加对 shell 组合符 “ & ” 的跳过处理

docs(mono): 更新最佳实践和产品文档命令示例

- 优化《best_practices/07-minutes.md》中行动项与摘要拉取示例命令,提升准确性
- 修改产品文档中 ALIDOC 表格数据和多维表格记录相关命令,命令路径更规范统一
- 同步多端技能文档,确保命令示例一致且正确

* ci: check interface against PR merge-base

* feat(policy): enforce CLI contract compatibility

* ci: enforce PR coverage thresholds

* ci: add fail-closed CI gate

* ci: adapt schema compatibility gate to runtime catalog

* fix(ci): close schema compatibility gate gaps

* test(auth): skip POSIX mode assertion on Windows

* test(auth): scope POSIX file-backend checks

* fix(ci): enforce native platform coverage

* test(ci): make skill paths portable

---------

Co-authored-by: 修雨 <huyizhou.hyz@alibaba-inc.com>
2026-07-15 14:53:34 +08:00

172 lines
6.1 KiB
Go

// Copyright 2026 Alibaba Group
// Licensed under the Apache License, Version 2.0 (the "License");
package main
import (
"bytes"
"os"
"path/filepath"
"strings"
"testing"
"github.com/spf13/cobra"
)
func TestRunSkillCommandCheck(t *testing.T) {
root := testCommandRoot()
directory := t.TempDir()
skills := filepath.Join(directory, "skills")
if err := os.MkdirAll(skills, 0o755); err != nil {
t.Fatal(err)
}
path := filepath.Join(skills, "SKILL.md")
body := "Use `dws auth login`.\nUse `dws auth login` again.\nUse `dws auth bogus-command`.\n"
if err := os.WriteFile(path, []byte(body), 0o600); err != nil {
t.Fatal(err)
}
var stdout, stderr bytes.Buffer
if code := run(directory, root, &stdout, &stderr); code != 1 {
t.Fatalf("invalid command code=%d stderr=%s", code, stderr.String())
}
if !strings.Contains(stderr.String(), "SKILL.md:3") || !strings.Contains(stderr.String(), "command path does not exist") {
t.Fatalf("unexpected failure %q", stderr.String())
}
body = "Use `dws auth login`.\n占位写法 `dws <cmd> --help`.\n禁止使用 `dws auth bogus-command`.\n"
if err := os.WriteFile(path, []byte(body), 0o600); err != nil {
t.Fatal(err)
}
stdout.Reset()
stderr.Reset()
if code := run(directory, testCommandRoot(), &stdout, &stderr); code != 0 {
t.Fatalf("valid commands code=%d stderr=%s", code, stderr.String())
}
if !strings.Contains(stdout.String(), "ok (1 executable command paths)") {
t.Fatalf("unexpected success output %q", stdout.String())
}
stderr.Reset()
if code := run(filepath.Join(directory, "missing"), testCommandRoot(), &stdout, &stderr); code != 2 {
t.Fatalf("missing skills code=%d, want 2", code)
}
}
func TestExtractReferences(t *testing.T) {
directory := t.TempDir()
markdown := filepath.Join(directory, "sample.md")
body := "Inline `dws auth login`.\n```sh\n$ dws schema \"dev app create\"\nnot a command\n```\n❌ `dws bogus`\n"
if err := os.WriteFile(markdown, []byte(body), 0o600); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(directory, "ignored.txt"), []byte("dws bogus"), 0o600); err != nil {
t.Fatal(err)
}
refs, err := extractReferences(directory)
if err != nil {
t.Fatal(err)
}
if len(refs) != 2 || refs[0].Line != 1 || refs[1].Line != 3 {
t.Fatalf("references=%v", refs)
}
}
func TestShellFieldsAndFailureFormatting(t *testing.T) {
got := shellFields(`dws schema "dev app create" 'single value' escaped\ value`)
want := []string{"dws", "schema", "dev app create", "single value", "escaped value"}
if strings.Join(got, "|") != strings.Join(want, "|") {
t.Fatalf("shellFields()=%v, want %v", got, want)
}
root := t.TempDir()
ref := commandRef{File: filepath.Join(root, "skills", "SKILL.md"), Line: 7, Text: "dws bogus"}
wantFailure := filepath.Join("skills", "SKILL.md") + ":7: `dws bogus`: missing"
if got := formatFailure(root, ref, "missing"); got != wantFailure {
t.Fatalf("formatFailure()=%q, want %q", got, wantFailure)
}
if !isAntiPatternLine("这是错误命令 dws bogus") || isAntiPatternLine("dws auth login") {
t.Fatal("anti-pattern marker detection is incorrect")
}
}
func testCommandRoot() *cobra.Command {
root := &cobra.Command{Use: "dws", Args: cobra.NoArgs}
auth := &cobra.Command{Use: "auth", Args: cobra.NoArgs}
auth.AddCommand(&cobra.Command{Use: "login", Aliases: []string{"signin"}})
root.AddCommand(auth, &cobra.Command{Use: "schema [path]", Args: cobra.MaximumNArgs(1)})
return root
}
func TestParseReferenceExtractsCommandPath(t *testing.T) {
path, flags, skip := parseReference(`dws doc read --node <DOC_ID> --format json`)
if skip || path != "dws doc read" {
t.Fatalf("parseReference() = %q, skip=%v", path, skip)
}
if len(flags) != 2 || flags[0] != "format" || flags[1] != "node" {
t.Fatalf("flags = %v", flags)
}
}
func TestParseReferenceSkipsCombinedDocumentationNotation(t *testing.T) {
if _, _, skip := parseReference(`dws doc create/update --content-format jsonml`); !skip {
t.Fatal("combined create/update notation should not be treated as an executable command")
}
}
func TestParseReferenceSkipsShellComposition(t *testing.T) {
if _, _, skip := parseReference(`dws A & dws B & wait`); !skip {
t.Fatal("shell composition should not be treated as an executable command")
}
}
func TestResolveCommandReference(t *testing.T) {
root := &cobra.Command{Use: "dws", Args: cobra.NoArgs}
auth := &cobra.Command{Use: "auth", Args: cobra.NoArgs}
auth.AddCommand(&cobra.Command{Use: "login", Aliases: []string{"signin"}})
sheet := &cobra.Command{Use: "sheet", Args: cobra.NoArgs}
sheet.AddCommand(&cobra.Command{Use: "read"})
root.AddCommand(
auth,
sheet,
&cobra.Command{Use: "schema [path]", Args: cobra.MaximumNArgs(1)},
&cobra.Command{Use: "plugin-info <name>", Args: cobra.ExactArgs(1)},
)
tests := []struct {
name string
path string
want commandResolution
}{
{name: "existing leaf", path: "dws auth login", want: resolutionValid},
{name: "existing group", path: "dws auth", want: resolutionValid},
{name: "top-level alias", path: "dws auth signin", want: resolutionValid},
{name: "missing top-level command", path: "dws bogus-command", want: resolutionInvalid},
{name: "missing nested command", path: "dws auth bogus-command", want: resolutionInvalid},
{name: "top-level placeholder", path: "dws <cmd>", want: resolutionSkip},
{name: "nested placeholder", path: "dws sheet <command>", want: resolutionSkip},
{name: "quoted positional argument", path: "dws schema dev app create", want: resolutionValid},
{name: "leaf positional argument", path: "dws plugin-info example", want: resolutionValid},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
if got := resolveCommandReference(root, test.path); got != test.want {
t.Fatalf("resolveCommandReference(%q) = %d, want %d", test.path, got, test.want)
}
})
}
}
func TestIsPlaceholder(t *testing.T) {
for _, token := range []string{"<cmd>", "<子命令>", "[optional]"} {
if !isPlaceholder(token) {
t.Errorf("isPlaceholder(%q) = false, want true", token)
}
}
for _, token := range []string{"cmd", "<cmd", "cmd>", "[optional"} {
if isPlaceholder(token) {
t.Errorf("isPlaceholder(%q) = true, want false", token)
}
}
}