Files
Agent-System-Backend/internal/skills/skills_test.go
T
weichenandCursor 1afe164798 Pin published company default renderer and drop candidate wording
Why: agent-document-renderer 31fbe54 publishes company.default@1.0.0 with
review status approved and publicationAllowed true (owner decision
2026-10-16: A4, ~2.5cm margins, SimSun + Times New Roman 12pt body, SimHei
headings, no logo). Backend still hard-coded Candidate=true and every prompt,
skill and tool text called the default a pending-human-visual-review
candidate.

Evidence: go vet ./... exit 0; gofmt -l on changed packages empty;
go test -count=1 ./internal/docgen/... ./internal/prompts/...
./internal/skills/... ./internal/tools/... ok; go test -count=1 ./... exit 0
(42 packages ok); go build ./... and CGO_ENABLED=0 go build ./cmd/agent-system
ok; git diff --cached --check clean. New tests failed before the change.

Impact: bump deps/agent-document-renderer 0d1c512 -> 31fbe54 and regenerate
the renderer identity. The manifest profile receipt now reports the
renderer's own review status and derives Candidate from the snapshot state
(false for the published default). Prompts (base.deliverables v12,
tool.render_document v2, task.report v4, task.doc-minutes v4), docx-author
and house-style skills, the render_document tool description and comparison
exception details describe the default as published (approved by owner
2026-10-16) while still limiting it to ordinary uncontrolled workspace
working/preview Word. Routing is unchanged: project, controlled,
special-template, review and approved outputs still go to RDM, with no
Backend default fallback and no legacy Word builder fallback.

Refs: unified renderer plan Tasks 7/10
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-10-16 05:07:19 +00:00

242 lines
7.3 KiB
Go

package skills
import (
"encoding/json"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"testing"
)
func TestSeedIdempotentAndNoOverwrite(t *testing.T) {
dir := t.TempDir()
res, err := Seed(dir)
if err != nil || res.Written < 4 {
t.Fatalf("first seed want several files, got %+v err=%v", res, err)
}
for _, name := range []string{"docx-author", "xlsx-author", "pptx-author", "house-style"} {
data, err := os.ReadFile(filepath.Join(dir, name, "SKILL.md"))
if err != nil || len(data) == 0 {
t.Fatalf("skill %s missing after seed: %v", name, err)
}
}
if _, err := os.ReadFile(filepath.Join(dir, "house-style", "housestyle", "__init__.py")); err != nil {
t.Fatalf("package init was not seeded: %v", err)
}
again, err := Seed(dir)
if err != nil || again.Written != 0 || again.Upgraded != 0 {
t.Fatalf("second seed must be a no-op, got %+v err=%v", again, err)
}
custom := filepath.Join(dir, "docx-author", "SKILL.md")
if err := os.WriteFile(custom, []byte("用户改过的配方"), 0o644); err != nil {
t.Fatal(err)
}
drifted, err := Seed(dir)
if err != nil {
t.Fatal(err)
}
if drifted.Written != 0 || drifted.Upgraded != 0 {
t.Fatalf("user-edited skill must not be overwritten, %+v", drifted)
}
data, _ := os.ReadFile(custom)
if string(data) != "用户改过的配方" {
t.Fatal("user edit must survive seeding")
}
found := false
for _, p := range drifted.Drifted {
if p == "docx-author/SKILL.md" {
found = true
}
}
if !found {
t.Fatalf("drift not reported: %+v", drifted)
}
quiet, err := Seed(dir)
if err != nil || len(quiet.Drifted) != 0 {
t.Fatalf("unchanged user edit should not be reported again, %+v err=%v", quiet, err)
}
}
func TestSeedUpgradesPreviousManaged(t *testing.T) {
dir := t.TempDir()
if _, err := Seed(dir); err != nil {
t.Fatal(err)
}
rel := "docx-author/SKILL.md"
dst := filepath.Join(dir, filepath.FromSlash(rel))
old := []byte("previous builtin body")
if err := os.WriteFile(dst, old, 0o644); err != nil {
t.Fatal(err)
}
manPath := filepath.Join(dir, manifestName)
raw, err := os.ReadFile(manPath)
if err != nil {
t.Fatal(err)
}
var man manifestFile
if err := json.Unmarshal(raw, &man); err != nil {
t.Fatal(err)
}
man.Files[rel] = manifestEntry{SHA256: hashBytes(old), Managed: true}
encoded, err := json.MarshalIndent(man, "", " ")
if err != nil {
t.Fatal(err)
}
if err := os.WriteFile(manPath, encoded, 0o644); err != nil {
t.Fatal(err)
}
res, err := Seed(dir)
if err != nil {
t.Fatal(err)
}
if res.Upgraded < 1 {
t.Fatalf("previous builtin must upgrade, %+v", res)
}
got, _ := os.ReadFile(dst)
if string(got) == string(old) {
t.Fatal("upgraded file still has the previous body")
}
}
func TestSeedUpgradesLegacyBuiltin(t *testing.T) {
dir := t.TempDir()
_, thisFile, _, ok := runtime.Caller(0)
if !ok {
t.Fatal("caller")
}
old, err := os.ReadFile(filepath.Join(filepath.Dir(thisFile), "testdata", "legacy-docx-author.md"))
if err != nil {
t.Fatal(err)
}
rel := "docx-author/SKILL.md"
if hashBytes(old) != legacyBuiltinHashes[rel] {
t.Fatalf("testdata hash drifted: %s", hashBytes(old))
}
dst := filepath.Join(dir, "docx-author", "SKILL.md")
if err := os.MkdirAll(filepath.Dir(dst), 0o755); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(dst, old, 0o644); err != nil {
t.Fatal(err)
}
res, err := Seed(dir)
if err != nil {
t.Fatal(err)
}
if res.Upgraded < 1 {
t.Fatalf("legacy builtin must upgrade, %+v", res)
}
got, _ := os.ReadFile(dst)
if hashBytes(got) == hashBytes(old) {
t.Fatal("legacy file was not replaced")
}
}
func TestNames(t *testing.T) {
names := Names()
want := []string{"canvas-design", "copy-editing", "copywriting", "docx-author", "frontend-design", "house-style", "pptx-author", "xlsx-author"}
if len(names) != len(want) {
t.Fatalf("want %d sorted builtin skills, got %d: %v", len(want), len(names), names)
}
for i := range want {
if names[i] != want[i] {
t.Fatalf("builtin skill order drifted at %d: want %v, got %v", i, want, names)
}
}
}
// 第三方 vendor 的技能必须带 UPSTREAM.md 溯源(来源、上游固定 SHA、许可证、
// 适配说明),保证可追溯升级与许可合规;缺失即测试失败。
func TestVendoredSkillsCarryProvenance(t *testing.T) {
vendored := []string{"canvas-design", "copy-editing", "copywriting", "frontend-design"}
for _, name := range vendored {
data, err := recipes.ReadFile("recipes/" + name + "/UPSTREAM.md")
if err != nil {
t.Fatalf("vendored skill %s missing UPSTREAM.md: %v", name, err)
}
s := string(data)
for _, marker := range []string{"https://github.com/", "上游固定版本", "许可证"} {
if !strings.Contains(s, marker) {
t.Fatalf("vendored skill %s UPSTREAM.md missing %q", name, marker)
}
}
}
if _, err := recipes.ReadFile("recipes/frontend-design/LICENSE.txt"); err != nil {
t.Fatalf("frontend-design upstream license missing: %v", err)
}
if _, err := recipes.ReadFile("recipes/copywriting/UPSTREAM-LICENSE"); err != nil {
t.Fatalf("copywriting upstream license missing: %v", err)
}
}
func TestEmbedIncludesPackageInit(t *testing.T) {
if _, err := recipes.ReadFile("recipes/house-style/housestyle/__init__.py"); err != nil {
t.Fatal(err)
}
}
func TestHouseStyleRoutesProductionWordThroughUnifiedRenderer(t *testing.T) {
raw, err := recipes.ReadFile("recipes/house-style/SKILL.md")
if err != nil {
t.Fatal(err)
}
body := string(raw)
for _, required := range []string{"render_document", "已发布", "2026-10-16", "approved", "RDM", "禁止 Backend 默认回退"} {
if !strings.Contains(body, required) {
t.Errorf("house-style skill missing %q", required)
}
}
for _, stale := range []string{"pending-human-visual-review", "默认候选"} {
if strings.Contains(body, stale) {
t.Errorf("house-style still describes the organization default as unpublished: %q", stale)
}
}
for _, forbidden := range []string{"AGENT_SYSTEM_LEGACY_WORD_COMPARISON", "docx_kit", "new_document(template=", "dx.save("} {
if strings.Contains(body, forbidden) {
t.Fatalf("house-style still advertises Word bypass %q", forbidden)
}
}
}
func TestDocxAuthorDescribesPublishedDefaultOnlyForWorkspaceWord(t *testing.T) {
raw, err := recipes.ReadFile("recipes/docx-author/SKILL.md")
if err != nil {
t.Fatal(err)
}
body := string(raw)
for _, required := range []string{"render_document", "已发布", "2026-10-16", "approved", "RDM", "禁止默认回退"} {
if !strings.Contains(body, required) {
t.Errorf("docx-author skill missing %q", required)
}
}
for _, stale := range []string{"pending-human-visual-review", "默认候选"} {
if strings.Contains(body, stale) {
t.Errorf("docx-author still describes the organization default as unpublished: %q", stale)
}
}
}
func TestHouseStylePython(t *testing.T) {
if _, err := exec.LookPath("python3"); err != nil {
t.Skip("python3 not available")
}
probe := exec.Command("python3", "-c", "import docx, pptx, openpyxl")
if err := probe.Run(); err != nil {
t.Skip("python-docx, python-pptx or openpyxl missing")
}
_, thisFile, _, ok := runtime.Caller(0)
if !ok {
t.Fatal("caller")
}
dir := filepath.Join(filepath.Dir(thisFile), "recipes", "house-style")
cmd := exec.Command("python3", "-m", "unittest", "tests.test_housestyle")
cmd.Dir = dir
cmd.Env = append(os.Environ(), "PYTHONPATH="+dir)
out, err := cmd.CombinedOutput()
if err != nil {
t.Fatalf("housestyle tests: %v\n%s", err, out)
}
}