Files
dingtalk-workspace-cli/internal/app/schema_help_flag_completeness_test.go
玉澜 2e51dcf35d docs(schema): tell the truth about single-source identity and wire policy
- schema_cobra_binding: the Identity-vs-spec check is now a defensive
  self-consistency assertion (the spec is collected from the same
  ContractFinal.Identity), not a cross-source pin; name the real drift
  anchors (native annotation cross-check, collector uniqueness
  self-validation, homology tool-count tripwire, surface/catalog hash
  baselines) and relabel the mismatch diagnostic as collected vs declared.
- schema-compat: state explicitly that accepting interface_type clearing
  is a deliberate wire-visible policy decision taken with the MCP pin
  retirement (missing = unknown; re-population requires ParamDecl).
- schema_command_registry: drop retired bindings audit / MCP pin from the
  peer reviewed-inputs comment; note they must not reappear.
- canonical: schema help no longer claims commands must enter a reviewed
  registry; identity is collected from ContractFinal.Identity.
- homology: the tool-count tripwire error now says where to bump it after
  review.
- Sweep stale 'reviewed registry' wording in corecmd and the help-flag
  completeness gate comment; AGENTS.md interface-facts section matches.
2026-08-03 19:22:53 +08:00

305 lines
9.9 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.
package app
import (
"bytes"
"encoding/json"
"fmt"
"sort"
"strings"
"testing"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/cli"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
// TestFinalSchemaParametersMatchExecutableHelpFlags is the fast, in-process
// Help <-> Schema parameter completeness gate. It deliberately starts from the
// collected command identity and its exact Cobra bindings, then compares
// every public primary leaf with the final delivered ToolSpec projection. The
// binder has already proved that compatibility aliases of a leaf share the
// same executable contract as their primary, so aliases do not create a
// second parameter source here.
func TestFinalSchemaParametersMatchExecutableHelpFlags(t *testing.T) {
root := NewRootCommand()
bound := boundSchemaCommandsForHelpFlagTest(t, root)
snapshot := fullSchemaSnapshotForTest(t)
assertSchemaParametersMatchExecutableHelpFlags(t, bound, snapshot.Tools, "source-built final Schema")
}
// TestDeliverySchemaParametersMatchExecutableHelpFlags runs the same exact-set
// gate against the artifact that ships in the binary. Going through the real
// schema --all command is intentional: a stale generated Catalog must fail
// even when a fresh source-built snapshot would agree with Cobra Help.
func TestDeliverySchemaParametersMatchExecutableHelpFlags(t *testing.T) {
root := NewRootCommand()
bound := boundSchemaCommandsForHelpFlagTest(t, root)
tools := deliverySchemaAllToolsForHelpFlagTest(t, root)
assertSchemaParametersMatchExecutableHelpFlags(t, bound, tools, "delivery schema --all")
}
func boundSchemaCommandsForHelpFlagTest(t testing.TB, root *cobra.Command) cli.BoundCommandRegistry {
t.Helper()
effective, err := cli.BuildEffectiveCommandRegistry(root)
if err != nil {
t.Fatalf("build EffectiveCommandRegistry: %v", err)
}
bound, err := cli.BindEffectiveCommandRegistry(root, effective)
if err != nil {
t.Fatalf("bind EffectiveCommandRegistry to live Cobra tree: %v", err)
}
return bound
}
func deliverySchemaAllToolsForHelpFlagTest(t testing.TB, root *cobra.Command) map[string]map[string]any {
t.Helper()
var stdout, stderr bytes.Buffer
root.SetOut(&stdout)
root.SetErr(&stderr)
root.SetArgs([]string{"schema", "--all", "--format", "json"})
if err := root.Execute(); err != nil {
t.Fatalf("execute delivery schema --all: %v; stderr=%s", err, stderr.String())
}
var payload struct {
Products []struct {
Tools []map[string]any `json:"tools"`
} `json:"products"`
}
if err := json.Unmarshal(stdout.Bytes(), &payload); err != nil {
t.Fatalf("decode delivery schema --all: %v", err)
}
tools := make(map[string]map[string]any)
for _, product := range payload.Products {
for _, tool := range product.Tools {
canonical := strings.TrimSpace(schemaContractString(tool["canonical_path"]))
if canonical == "" {
t.Fatal("delivery schema --all contains an empty canonical path")
}
if _, exists := tools[canonical]; exists {
t.Fatalf("delivery schema --all contains duplicate canonical %q", canonical)
}
tools[canonical] = tool
}
}
if len(tools) == 0 {
t.Fatal("delivery schema --all contains no tools")
}
return tools
}
func assertSchemaParametersMatchExecutableHelpFlags(
t testing.TB,
bound cli.BoundCommandRegistry,
tools map[string]map[string]any,
source string,
) {
t.Helper()
problems := schemaHelpFlagCompletenessProblems(bound, tools)
checked := 0
for _, command := range bound.Commands {
if command.Visibility == cli.SchemaVisibilityPublic {
checked++
}
}
if len(problems) > 0 {
t.Fatalf("%s parameter surface differs from executable Cobra Help:\n%s", source, strings.Join(problems, "\n"))
}
t.Logf("validated Help-visible flags against %s parameters for %d public tools", source, checked)
}
func TestSchemaHelpFlagCompletenessRejectsStaleCatalogFlags(t *testing.T) {
leaf := &cobra.Command{Use: "run", Run: func(*cobra.Command, []string) {}}
leaf.Flags().String("fresh", "", "new executable input")
bound := cli.BoundCommandRegistry{Commands: []cli.BoundCommandSpec{{
CommandSpec: cli.CommandSpec{
CanonicalPath: "sample.run",
PrimaryCLIPath: "sample run",
Visibility: cli.SchemaVisibilityPublic,
},
PrimaryCommand: leaf,
}}}
tools := map[string]map[string]any{
"sample.run": {
"parameters": map[string]any{
"stale": map[string]any{"type": "string"},
},
},
}
problems := schemaHelpFlagCompletenessProblems(bound, tools)
joined := strings.Join(problems, "\n")
if !strings.Contains(joined, `missing_in_schema=["fresh"]`) ||
!strings.Contains(joined, `extra_in_schema=["stale"]`) {
t.Fatalf("stale Catalog flag drift was not reported: %s", joined)
}
}
func schemaHelpFlagCompletenessProblems(bound cli.BoundCommandRegistry, tools map[string]map[string]any) []string {
var problems []string
public := make(map[string]bool)
checked := 0
for _, command := range bound.Commands {
if command.Visibility != cli.SchemaVisibilityPublic {
continue
}
public[command.CanonicalPath] = true
checked++
tool, ok := tools[command.CanonicalPath]
if !ok {
problems = append(problems, fmt.Sprintf(
"canonical=%q path=%q missing final Schema tool",
command.CanonicalPath,
command.PrimaryCLIPath,
))
continue
}
if problem := schemaHelpFlagCompletenessProblem(
command.CanonicalPath,
command.PrimaryCLIPath,
command.PrimaryCommand,
tool,
); problem != "" {
problems = append(problems, problem)
}
}
for canonical := range tools {
if !public[canonical] {
problems = append(problems, fmt.Sprintf("canonical=%q is an unexpected final Schema tool", canonical))
}
}
if checked != len(tools) {
problems = append(problems, fmt.Sprintf(
"public BoundCommand count=%d final Schema tool count=%d",
checked,
len(tools),
))
}
sort.Strings(problems)
return problems
}
func TestSchemaHelpFlagCompletenessRejectsAncestorPersistentLeak(t *testing.T) {
root := &cobra.Command{Use: "dws"}
root.PersistentFlags().String("format", "json", "output format")
product := &cobra.Command{Use: "product"}
product.PersistentFlags().String("leaked", "", "product-scoped option")
leaf := &cobra.Command{Use: "run", Run: func(*cobra.Command, []string) {}}
leaf.Flags().String("declared", "", "declared option")
leaf.Flags().String("json", "", "Base JSON object payload for this tool invocation")
leaf.Flags().String("hidden", "", "internal option")
_ = leaf.Flags().MarkHidden("hidden")
root.AddCommand(product)
product.AddCommand(leaf)
tool := map[string]any{
"parameters": map[string]any{
"declared": map[string]any{"type": "string"},
},
}
problem := schemaHelpFlagCompletenessProblem("product.run", "product run", leaf, tool)
if !strings.Contains(problem, `missing_in_schema=["leaked"]`) {
t.Fatalf("ancestor persistent leak was not reported: %s", problem)
}
if strings.Contains(problem, "format") || strings.Contains(problem, "json") || strings.Contains(problem, "hidden") {
t.Fatalf("root controls and reviewed non-Schema flags must be excluded: %s", problem)
}
}
func schemaHelpFlagCompletenessProblem(canonical, path string, command *cobra.Command, tool map[string]any) string {
helpFlags := schemaHelpVisibleFlagNames(command)
schemaFlags := make(map[string]bool)
for name := range schemaContractMap(tool["parameters"]) {
schemaFlags[name] = true
}
missing := schemaFlagNameDifference(helpFlags, schemaFlags)
extra := schemaFlagNameDifference(schemaFlags, helpFlags)
if len(missing) == 0 && len(extra) == 0 {
return ""
}
return fmt.Sprintf(
"canonical=%q path=%q missing_in_schema=%s extra_in_schema=%s",
canonical,
path,
schemaQuotedFlagNames(missing),
schemaQuotedFlagNames(extra),
)
}
// schemaHelpVisibleFlagNames models Cobra's leaf Help surface without rendering
// text. Local flags and ancestor persistent flags are executable tool inputs.
// Only the reviewed root execution controls are omitted; an unexpected new
// root persistent flag must fail this gate instead of being silently treated as
// process scaffolding.
func schemaHelpVisibleFlagNames(command *cobra.Command) map[string]bool {
visible := make(map[string]bool)
if command == nil {
return visible
}
visit := func(flag *pflag.Flag, rootPersistent bool) {
if flag == nil || flag.Hidden || flag.Name == "help" || schemaGenericPayloadEscapeHatch(flag) {
return
}
if rootPersistent && schemaRootExecutionControl(flag.Name) {
return
}
visible[flag.Name] = true
}
command.LocalNonPersistentFlags().VisitAll(func(flag *pflag.Flag) { visit(flag, false) })
command.PersistentFlags().VisitAll(func(flag *pflag.Flag) { visit(flag, false) })
root := command.Root()
for parent := command.Parent(); parent != nil; parent = parent.Parent() {
isRoot := parent == root
parent.PersistentFlags().VisitAll(func(flag *pflag.Flag) { visit(flag, isRoot) })
}
return visible
}
func schemaRootExecutionControl(name string) bool {
switch name {
case "client-id", "client-secret", "debug", "dry-run", "fields", "format", "jq", "mock",
"output", "profile", "timeout", "token", "verbose", "yes":
return true
default:
return false
}
}
func schemaGenericPayloadEscapeHatch(flag *pflag.Flag) bool {
if flag == nil {
return false
}
switch flag.Name {
case "json":
return strings.TrimSpace(flag.Usage) == "Base JSON object payload for this tool invocation"
case "params":
return strings.TrimSpace(flag.Usage) == "Additional JSON object payload merged after --json"
default:
return false
}
}
func schemaFlagNameDifference(left, right map[string]bool) []string {
result := make([]string, 0)
for name := range left {
if !right[name] {
result = append(result, name)
}
}
sort.Strings(result)
return result
}
func schemaQuotedFlagNames(names []string) string {
quoted := make([]string, 0, len(names))
for _, name := range names {
quoted = append(quoted, fmt.Sprintf("%q", name))
}
return "[" + strings.Join(quoted, ",") + "]"
}