Files
dingtalk-workspace-cli/internal/cli/command_safety_test.go
玉澜andCursor 0a2e49e7e4 fix(schema): separate catalog content hash from surface registry hash
Runtime assemble was stamping Snapshot.SourceHash with the registry
surface hash, so schema --all catalog_hash diverged from the CI dump
content source_hash and failed Policy. Also remap ContractFinal
Interface.Ref onto pinned MCP metadata so interface_type stays aligned.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-02 12:26:25 +08:00

168 lines
5.6 KiB
Go

// Copyright 2026 Alibaba Group
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cli
import (
"fmt"
"strings"
"testing"
)
func TestSafetyForCLIPathKnownCommand(t *testing.T) {
// dev app delete 是 destructive + high risk + user_required。
s, ok := SafetyForCLIPath("dev app delete")
if !ok {
t.Fatal("SafetyForCLIPath(\"dev app delete\") returned ok=false; want true")
}
if s.Effect != "destructive" {
t.Errorf("effect = %q, want destructive", s.Effect)
}
if s.Risk != "high" {
t.Errorf("risk = %q, want high", s.Risk)
}
if s.Confirmation != "user_required" {
t.Errorf("confirmation = %q, want user_required", s.Confirmation)
}
if !s.ShouldRender() {
t.Error("ShouldRender() = false for destructive/high/user_required; want true")
}
}
func TestCrossPlatformCoverageSafetyForCLIPathRegisteredFactory(t *testing.T) {
if !SchemaSourceRootRegistered() {
t.Fatal("package-cli TestMain must register a Schema source root")
}
s, ok := SafetyForCLIPath("dev app delete")
if !ok || s.Effect != "destructive" {
t.Fatalf("SafetyForCLIPath = %#v ok=%v", s, ok)
}
if _, ok := SafetyForCLIPath("nonexistent fake command"); ok {
t.Fatal("unknown path must return ok=false")
}
}
func TestSafetyForCLIPathUnknownSkips(t *testing.T) {
// 不存在的命令 → ok=false。
_, ok := SafetyForCLIPath("nonexistent fake command")
if ok {
t.Fatal("SafetyForCLIPath for nonexistent command returned ok=true; want false")
}
}
func TestSafetyForCLIPathReadOnlyLowRiskSkips(t *testing.T) {
// 找一个 read/low 风险命令,ShouldRender 应 false。
// dev app list 是 read,默认 low risk。
s, ok := SafetyForCLIPath("dev app list")
if !ok {
t.Skip("dev app list not in catalog; skipping")
}
if s.ShouldRender() {
t.Errorf("ShouldRender() = true for effect=%s risk=%s; want false (read/low)", s.Effect, s.Risk)
}
}
func TestSafetyForCLIPathTrimsWhitespace(t *testing.T) {
// 带首尾空格的 cli_path 也能查到。
s1, _ := SafetyForCLIPath("dev app delete")
s2, ok2 := SafetyForCLIPath(" dev app delete ")
if !ok2 {
t.Fatal("trimmed lookup failed; whitespace not trimmed")
}
if s1 != s2 {
t.Errorf("whitespace-trimmed result differs: %+v vs %+v", s1, s2)
}
}
func TestResolveMetaFailsClosedOnUnusableMetaIndex(t *testing.T) {
// Guard is unit-tested without poisoning the process-wide sync.Once used
// by the real embedded index. Decode failure must panic, not look like a
// missing CLI path (which would silently drop help Safety).
if _, err := decodeSchemaMetaIndexLookup([]byte(`{not-json`)); err == nil {
t.Fatal("decodeSchemaMetaIndexLookup(corrupt) error = nil, want fail-closed decode error")
}
defer func() {
r := recover()
if r == nil {
t.Fatal("panicIfMetaIndexUnusable(err) did not panic")
}
msg, _ := r.(string)
if msg == "" || !strings.Contains(msg, "schema CommandMeta index is unusable") {
t.Fatalf("panic = %#v, want unusable CommandMeta index message", r)
}
}()
panicIfMetaIndexUnusable(fmt.Errorf("decode schema meta index: unexpected EOF"))
}
func TestResolveMetaComplete(t *testing.T) {
// dev app delete: destructive + high + user_required + 有 selection。
m, ok := ResolveMeta("dev app delete")
if !ok {
t.Fatal("ResolveMeta(\"dev app delete\") returned ok=false")
}
if m.Identity.CLIPath != "dev app delete" {
t.Errorf("CLIPath = %q", m.Identity.CLIPath)
}
if m.Safety.Effect != "destructive" {
t.Errorf("Effect = %q, want destructive", m.Safety.Effect)
}
if len(m.Selection.UseWhen) == 0 && m.Selection.AgentSummary == "" {
t.Error("Selection is empty; expected use_when or agent_summary")
}
t.Logf("dev app delete: canonical=%s safety=%+v selection(use_when=%d avoid_when=%d examples=%d)",
m.Identity.Canonical, m.Safety, len(m.Selection.UseWhen), len(m.Selection.AvoidWhen), len(m.Selection.Examples))
}
func TestResolveMetaUnknownSkips(t *testing.T) {
_, ok := ResolveMeta("nonexistent fake command")
if ok {
t.Fatal("ResolveMeta for unknown command returned ok=true")
}
}
func TestResolveMetaCopiesAliases(t *testing.T) {
// report inbox list 在 Catalog 中声明了兼容别名 "report list"。
m, ok := ResolveMeta("report inbox list")
if !ok {
t.Fatal("ResolveMeta(\"report inbox list\") returned ok=false")
}
found := false
for _, alias := range m.Identity.Aliases {
if alias == "report list" {
found = true
break
}
}
if !found {
t.Fatalf("Identity.Aliases = %v, want to contain \"report list\"", m.Identity.Aliases)
}
}
func TestResolveMetaAliasLookup(t *testing.T) {
// 兼容别名路径必须解析到与主 cli_path 同一份元数据。
primary, ok := ResolveMeta("report inbox list")
if !ok {
t.Fatal("primary path lookup failed")
}
aliased, ok := ResolveMeta("report list")
if !ok {
t.Fatal("ResolveMeta(\"report list\") alias lookup returned ok=false")
}
if aliased.Identity.CLIPath != primary.Identity.CLIPath || aliased.Identity.Canonical != primary.Identity.Canonical {
t.Fatalf("alias metadata = %+v, want same identity as primary %+v", aliased.Identity, primary.Identity)
}
if aliased.Safety != primary.Safety {
t.Fatalf("alias safety = %+v, want %+v", aliased.Safety, primary.Safety)
}
}