Files
superwork/vscode/cli/internal/state/schema_test.go
T
cruldra def5f4e88c ✨ feat(spx): issue marker 支持第三种类型 prototype
- --type 三选一 spec|plan|prototype,marker 行写 <!-- spx:prototype=路径 -->,
  与 spec/plan 共用同一套增量更新逻辑;类型→state 字段的映射收成一张表
- 同步写 state JSON 的 prototypeFile(pattern 要求 dir/.../x.json)
- 顺手补上 b62e58d 漏掉的 prototypeSessionId / prototypeProfilePath:
  schema 与 knownStateFields 都没有它们,带原型会话的工单一 merge
  就会被 additionalProperties 拒掉

Claude-Session: https://claude.ai/code/session_01Kgmb1LvrPmFX3NoUDB4zZC
2026-09-15 00:08:31 +08:00

109 lines
3.6 KiB
Go

package state
import (
"bytes"
"os"
"path/filepath"
"runtime"
"testing"
)
// TestSchemaMirrorInSync makes sure the embedded copy under internal/state/
// matches the canonical schemas/state-json.schema.json at the repo root.
// The two-file layout exists so plain `go build` keeps working (embed needs
// the file inside the package tree) while non-Go tooling can still consume
// the root schemas/ directory. Drift between the two would be silent without
// this check.
func TestSchemaMirrorInSync(t *testing.T) {
_, thisFile, _, ok := runtime.Caller(0)
if !ok {
t.Fatal("无法定位测试文件路径")
}
pkgDir := filepath.Dir(thisFile)
canonical := filepath.Join(pkgDir, "..", "..", "..", "schemas", "state-json.schema.json")
canonicalBytes, err := os.ReadFile(canonical)
if err != nil {
t.Fatalf("读取根目录 schema 失败: %v", err)
}
if !bytes.Equal(canonicalBytes, schemaBytes) {
t.Fatalf("internal/state/schema.json 与 schemas/state-json.schema.json 内容不一致;请 `make sync-schema` 同步")
}
}
func TestValidateAcceptsEmpty(t *testing.T) {
if err := Validate([]byte(`{}`)); err != nil {
t.Fatalf("空对象应当通过校验,得到: %v", err)
}
}
func TestValidateRejectsBadColumn(t *testing.T) {
err := Validate([]byte(`{"column":"todoo"}`))
if err == nil {
t.Fatal("非法 column 应当被拒绝")
}
}
func TestValidateRejectsUnknownField(t *testing.T) {
err := Validate([]byte(`{"weirdField":1}`))
if err == nil {
t.Fatal("未声明字段应当被 additionalProperties:false 拒绝")
}
}
func TestValidateRejectsBadSpecFile(t *testing.T) {
err := Validate([]byte(`{"specFile":"..."}`))
if err == nil {
t.Fatal("占位 `...` 应当被 pattern 拒绝")
}
}
func TestValidateRejectsBadPrDiffFile(t *testing.T) {
err := Validate([]byte(`{"prDiffFile":"docs/superpowers/plans/foo.md"}`))
if err == nil {
t.Fatal("非 docs/pr-diff/*.md 路径应当被 prDiffFile pattern 拒绝")
}
}
func TestValidateAcceptsFullState(t *testing.T) {
full := `{
"column":"in-progress",
"sessionId":"abc",
"implementSessionId":"impl-1",
"reviewSessionId":"rev-1",
"testSessionId":"test-1",
"prototypeSessionId":"proto-1",
"profilePath":"/home/x/.claude/settings.json",
"brainstormProfilePath":"/home/x/.claude/profiles/brainstorm.json",
"testProfilePath":"/home/x/.claude/profiles/test.json",
"prototypeProfilePath":"/home/x/.claude/profiles/prototype.json",
"specFile":"docs/superpowers/specs/foo.md",
"planFile":"docs/superpowers/plans/foo.md",
"prototypeFile":"docs/prototype/login/figma-plugin/manifest.json",
"prDiffFile":"docs/pr-diff/pr-42-issue-7.md",
"pr":"42",
"prMerged":true,
"prMergedAt":"2026-07-16T01:00:00Z",
"branch":"feature/abc",
"worktreePath":"/home/x/Sources/worktree/proj/slug",
"implementStatus":"done",
"color":"terminal.ansiBlue",
"autoReview":true
}`
if err := Validate([]byte(full)); err != nil {
t.Fatalf("完整合法 state 应当通过校验,得到: %v", err)
}
}
func TestValidateRejectsBadPrototypeFile(t *testing.T) {
err := Validate([]byte(`{"prototypeFile":"docs/prototype/login/figma-plugin/manifest.md"}`))
if err == nil {
t.Fatal("非 .json 路径应当被 prototypeFile pattern 拒绝")
}
}
func TestValidateAcceptsBrainstormProfilePathOnly(t *testing.T) {
if err := Validate([]byte(`{"brainstormProfilePath":"/home/x/.claude/profiles/brainstorm.json"}`)); err != nil {
t.Fatalf("仅含 brainstormProfilePath 应当通过校验,得到: %v", err)
}
}