Files
dingtalk-workspace-cli/internal/cli/schema_runtime_registry_contract_test.go
玉澜 e135440b98 refactor(cli): retire legacy-overlay schema stack and gate test fixtures
Drop the legacy-metadata overlay path (runtimeToolSpecAllowingLegacy,
assembleSchemaRegistryFromBoundAllowingLegacy, metadata-based agent
selection, dry-run seams) so assembly flows exclusively through
ContractFinal; gate MCP fixture machinery behind a build flag so it can
never enter production assembly. Wire output verified identical before
and after; policy tripwire reports 26 products, 847 tools.
2026-08-04 08:36:22 +08:00

349 lines
12 KiB
Go

// Copyright 2026 Alibaba Group
// Licensed under the Apache License, Version 2.0 (the "License");
package cli
import (
"strings"
"testing"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/corecmd/contract"
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/corecmd/contractfinal"
"github.com/spf13/cobra"
)
func TestValidateSchemaRegistryAgainstCommandRegistryChecksFullIdentity(t *testing.T) {
tool := ToolSpec{Identity: contract.ToolIdentitySpec{
ProductID: "sample",
SourceProductID: "implementation_a",
Name: "run",
CanonicalPath: "sample.run",
Path: "sample.run",
CLIPath: "sample run",
PrimaryCLIPath: "sample run",
Source: "contract_identity",
}}
registry, err := SchemaRegistryFromRuntime("test", []ProductSpec{{ID: "sample", Tools: []ToolSpec{tool}}})
if err != nil {
t.Fatal(err)
}
base := CommandSpec{
CanonicalPath: "sample.run",
SourceProductID: "implementation_a",
PrimaryCLIPath: "sample run",
Visibility: SchemaVisibilityPublic,
Source: "contract_identity",
}
for name, test := range map[string]struct {
mutate func(*CommandSpec)
want string
}{
"source product": {
mutate: func(spec *CommandSpec) { spec.SourceProductID = "implementation_b" },
want: "source product",
},
"identity source": {
mutate: func(spec *CommandSpec) { spec.Source = "stale_identity_source" },
want: "identity source",
},
} {
t.Run(name, func(t *testing.T) {
expected := cloneCommandSpec(base)
test.mutate(&expected)
effective, err := newEffectiveCommandRegistry([]CommandSpec{expected})
if err != nil {
t.Fatal(err)
}
err = validateSchemaRegistryAgainstCommandRegistry(registry, effective)
if err == nil || !strings.Contains(err.Error(), test.want) {
t.Fatalf("validation error = %v, want %q", err, test.want)
}
})
}
}
func TestValidateSchemaRegistryAgainstCommandRegistryRejectsAliasViewAsCanonical(t *testing.T) {
baseTool := ToolSpec{Identity: contract.ToolIdentitySpec{
ProductID: "sample",
Name: "run",
CanonicalPath: "sample.run",
Path: "sample.run",
CLIPath: "sample run",
PrimaryCLIPath: "sample run",
Aliases: []string{"sample execute"},
Source: "contract_identity",
}}
effective, err := newEffectiveCommandRegistry([]CommandSpec{{
CanonicalPath: "sample.run",
PrimaryCLIPath: "sample run",
Aliases: []string{"sample execute"},
Visibility: SchemaVisibilityPublic,
Source: "contract_identity",
}})
if err != nil {
t.Fatal(err)
}
for name, test := range map[string]struct {
mutate func(*contract.ToolIdentitySpec)
want string
}{
"alternate cli path": {
mutate: func(identity *contract.ToolIdentitySpec) { identity.CLIPath = "sample execute" },
want: "must equal primary_cli_path",
},
"alias marker": {
mutate: func(identity *contract.ToolIdentitySpec) { identity.IsAlias = true },
want: "must have is_alias=false",
},
} {
t.Run(name, func(t *testing.T) {
tool := baseTool
test.mutate(&tool.Identity)
registry := SchemaRegistry{Products: []ProductSpec{{ID: "sample", Tools: []ToolSpec{tool}}}}
err := validateSchemaRegistryAgainstCommandRegistry(registry, effective)
if err == nil || !strings.Contains(err.Error(), test.want) {
t.Fatalf("validation error = %v, want %q", err, test.want)
}
})
}
}
func TestAssembleSchemaRegistryFailClosedMissingContractFinal(t *testing.T) {
root := &cobra.Command{Use: "dws"}
leaf := &cobra.Command{Use: "run", Short: "Run", Run: func(*cobra.Command, []string) {}}
AttachRuntimeSchema(leaf, "sample", "run", "test")
product := &cobra.Command{Use: "sample"}
product.AddCommand(leaf)
root.AddCommand(product)
t.Cleanup(func() { contract.ClearProductDeclForTest("sample") })
contract.RegisterProductDecl(contract.ProductDecl{
ID: "sample",
Selection: contract.ProductSelectionDecl{
AgentSummary: "Sample product",
UseWhen: []string{"sample routing"},
AvoidWhen: []string{"not sample"},
},
})
_, err := schemaRegistryForTest(root)
if err == nil || !strings.Contains(err.Error(), "missing RuntimeContractFinal") {
t.Fatalf("production assembly error = %v, want missing RuntimeContractFinal", err)
}
}
func TestCrossPlatformCoverageAssembleSchemaRegistryFailClosedMissingProductDecl(t *testing.T) {
root := &cobra.Command{Use: "dws"}
leaf := &cobra.Command{Use: "run", Short: "Run", Run: func(*cobra.Command, []string) {}}
AttachRuntimeSchema(leaf, "orphan", "run", "test")
contractfinal.RegisterRuntimeContractFinal(leaf, contract.ContractFinalPayload{
Identity: &contract.ToolIdentitySpec{
ProductID: "orphan", Name: "run", CanonicalPath: "orphan.run",
CLIPath: "orphan run", PrimaryCLIPath: "orphan run",
},
Title: "Orphan run",
Description: "Has ContractFinal but no ProductDecl",
Selection: &contract.SelectionSpec{AgentSummary: "orphan leaf"},
})
t.Cleanup(func() {
contractfinal.ClearRuntimeContractFinalForTest(leaf)
contract.ClearProductDeclForTest("orphan")
})
product := &cobra.Command{Use: "orphan"}
product.AddCommand(leaf)
root.AddCommand(product)
_, err := schemaRegistryForTest(root)
if err == nil || !strings.Contains(err.Error(), "missing ProductDecl") {
t.Fatalf("production assembly error = %v, want missing ProductDecl", err)
}
}
func TestAssembleSchemaRegistryRequiresContractFinalAndProductDecl(t *testing.T) {
root := &cobra.Command{Use: "dws"}
leaf := &cobra.Command{Use: "run", Short: "Run sample", Long: "Run the sample tool", Run: func(*cobra.Command, []string) {}}
AttachRuntimeSchema(leaf, "sample", "run", "test")
contractfinal.RegisterRuntimeContractFinal(leaf, contract.ContractFinalPayload{
Identity: &contract.ToolIdentitySpec{
ProductID: "sample", Name: "run", CanonicalPath: "sample.run",
CLIPath: "sample run", PrimaryCLIPath: "sample run",
},
Title: "Sample run",
Description: "Declared sample tool",
Safety: &contract.SafetySpec{
Effect: "read", Risk: "low", Confirmation: "none", Idempotency: "idempotent",
},
Interface: &contract.InterfaceSpec{
Mode: "local", Availability: "available", Reason: "test local leaf",
},
Selection: &contract.SelectionSpec{
AgentSummary: "Run a sample tool",
UseWhen: []string{"need sample run"},
AvoidWhen: []string{"need other product"},
},
})
t.Cleanup(func() {
contractfinal.ClearRuntimeContractFinalForTest(leaf)
contract.ClearProductDeclForTest("sample")
})
contract.RegisterProductDecl(contract.ProductDecl{
ID: "sample",
Selection: contract.ProductSelectionDecl{
AgentSummary: "Sample product",
UseWhen: []string{"sample routing"},
AvoidWhen: []string{"not sample"},
},
})
product := &cobra.Command{Use: "sample"}
product.AddCommand(leaf)
root.AddCommand(product)
registry, err := schemaRegistryForTest(root)
if err != nil {
t.Fatalf("declared production assembly: %v", err)
}
if len(registry.Products) != 1 || len(registry.Products[0].Tools) != 1 {
t.Fatalf("registry = %#v", registry.Products)
}
tool := registry.Products[0].Tools[0]
if tool.MetadataSource != "corecmd.contract" {
t.Fatalf("metadata_source = %q, want corecmd.contract", tool.MetadataSource)
}
if registry.Products[0].Selection.AgentSummary != "Sample product" {
t.Fatalf("product selection = %#v", registry.Products[0].Selection)
}
}
func TestAssembleContractFinalDescriptionPrefersCobraLong(t *testing.T) {
tool := assembleContractFinalTextTool(t, "Run sample", "Cobra Long description wins", "Declared description")
if tool.Description != "Cobra Long description wins" {
t.Fatalf("description = %q, want Cobra Long", tool.Description)
}
prov := tool.FieldProvenance["description"]
if prov.Precedence != "cobra_help" {
t.Fatalf("description precedence = %q, want cobra_help", prov.Precedence)
}
if prov.Resolution != "cobra_help_preferred" {
t.Fatalf("description resolution = %q, want cobra_help_preferred", prov.Resolution)
}
if prov.Source != "cobra_help" {
t.Fatalf("description source = %q, want cobra_help", prov.Source)
}
// Title stays declared-first even when Short differs.
if tool.Title != "Declared title" {
t.Fatalf("title = %q, want declared title", tool.Title)
}
titleProv := tool.FieldProvenance["title"]
if titleProv.Precedence != "contract_final" {
t.Fatalf("title precedence = %q, want contract_final", titleProv.Precedence)
}
}
func TestAssembleContractFinalDescriptionUsesDeclaredWithoutLong(t *testing.T) {
tool := assembleContractFinalTextTool(t, "Run sample", "", "Declared description without Long")
if tool.Description != "Declared description without Long" {
t.Fatalf("description = %q, want declared ContractDecl description", tool.Description)
}
prov := tool.FieldProvenance["description"]
if prov.Precedence != "contract_final" {
t.Fatalf("description precedence = %q, want contract_final", prov.Precedence)
}
if prov.Resolution != "contract_pass_through" {
t.Fatalf("description resolution = %q, want contract_pass_through", prov.Resolution)
}
if prov.Source != "corecmd.contract" {
t.Fatalf("description source = %q, want corecmd.contract", prov.Source)
}
}
// Short-only leaves must not leak Cobra Short into delivered description.
// Description compares only against Long; Short stays a title fallback candidate.
func TestAssembleContractFinalDescriptionIgnoresShortWhenDeclared(t *testing.T) {
const (
short = "Short must not become description"
declared = "Declared Contract.Description for short-only leaf"
)
tool := assembleContractFinalTextTool(t, short, "", declared)
if tool.Description != declared {
t.Fatalf("description = %q, want declared %q", tool.Description, declared)
}
if tool.Description == short {
t.Fatalf("description must not equal Short %q", short)
}
prov := tool.FieldProvenance["description"]
if prov.Precedence == "cobra_help" || prov.Source == "cobra_help" {
t.Fatalf("description provenance = %#v, must not be cobra_help when Long is empty", prov)
}
if prov.Precedence != "contract_final" {
t.Fatalf("description precedence = %q, want contract_final", prov.Precedence)
}
if prov.Resolution != "contract_pass_through" {
t.Fatalf("description resolution = %q, want contract_pass_through", prov.Resolution)
}
if prov.Source != "corecmd.contract" {
t.Fatalf("description source = %q, want corecmd.contract", prov.Source)
}
}
// assembleContractFinalTextTool builds a one-leaf tree, registers ContractFinal +
// ProductDecl, and runs the production assembly path (schemaRegistryForTest).
func assembleContractFinalTextTool(t *testing.T, short, long, declaredDescription string) ToolSpec {
t.Helper()
root := &cobra.Command{Use: "dws"}
leaf := &cobra.Command{
Use: "run",
Short: short,
Long: long,
Run: func(*cobra.Command, []string) {},
}
AttachRuntimeSchema(leaf, "sample", "run", "test")
contractfinal.RegisterRuntimeContractFinal(leaf, contract.ContractFinalPayload{
Identity: &contract.ToolIdentitySpec{
ProductID: "sample", Name: "run", CanonicalPath: "sample.run",
CLIPath: "sample run", PrimaryCLIPath: "sample run",
},
Title: "Declared title",
Description: declaredDescription,
Safety: &contract.SafetySpec{
Effect: "read", Risk: "low", Confirmation: "none", Idempotency: "idempotent",
},
Interface: &contract.InterfaceSpec{
Mode: "local", Availability: "available",
},
Selection: &contract.SelectionSpec{
AgentSummary: "Run a sample tool",
UseWhen: []string{"need sample run"},
AvoidWhen: []string{"need other product"},
},
})
t.Cleanup(func() {
contractfinal.ClearRuntimeContractFinalForTest(leaf)
contract.ClearProductDeclForTest("sample")
})
contract.RegisterProductDecl(contract.ProductDecl{
ID: "sample",
Selection: contract.ProductSelectionDecl{
AgentSummary: "Sample product",
UseWhen: []string{"sample routing"},
AvoidWhen: []string{"not sample"},
},
})
product := &cobra.Command{Use: "sample"}
product.AddCommand(leaf)
root.AddCommand(product)
registry, err := schemaRegistryForTest(root)
if err != nil {
t.Fatalf("assemble schema registry: %v", err)
}
if len(registry.Products) != 1 || len(registry.Products[0].Tools) != 1 {
t.Fatalf("registry = %#v", registry.Products)
}
return registry.Products[0].Tools[0]
}