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.
186 lines
6.4 KiB
Go
186 lines
6.4 KiB
Go
// Copyright 2026 Alibaba Group
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
package cli_test
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/app"
|
|
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/cli"
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
// Exercise the complete production source-to-snapshot path from an external
|
|
// test package. Keeping this here (rather than in a generator package) means
|
|
// Go's normal per-package coverage accounting attributes the exercised Schema
|
|
// assembly code to internal/cli.
|
|
func TestCrossPlatformCoverageProductionSchemaSourcePipeline(t *testing.T) {
|
|
root := app.NewSchemaSourceRootCommand()
|
|
resolved, err := cli.ResolveSchemaBuild(root)
|
|
if err != nil {
|
|
t.Fatalf("ResolveSchemaBuild() error = %v", err)
|
|
}
|
|
if resolved.CommandCount() == 0 || resolved.RegistryHash() == "" {
|
|
t.Fatalf("resolved build is empty: commands=%d hash=%q", resolved.CommandCount(), resolved.RegistryHash())
|
|
}
|
|
snapshot, err := cli.BuildSchemaCatalogSnapshot(resolved, cli.SchemaCatalogBuildOptions{
|
|
RegistryHash: resolved.RegistryHash(),
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("BuildSchemaCatalogSnapshot() error = %v", err)
|
|
}
|
|
if len(snapshot.Tools) == 0 {
|
|
t.Fatal("production Schema snapshot contains no tools")
|
|
}
|
|
registry, err := cli.AssembleSchemaRegistry(app.NewSchemaSourceRootCommand())
|
|
if err != nil {
|
|
t.Fatalf("AssembleSchemaRegistry() error = %v", err)
|
|
}
|
|
if len(registry.Products) == 0 {
|
|
t.Fatal("assembled production Schema registry contains no products")
|
|
}
|
|
if err := cli.ValidateRuntimeSchemaCompleteness(app.NewSchemaSourceRootCommand()); err != nil {
|
|
t.Fatalf("ValidateRuntimeSchemaCompleteness() error = %v", err)
|
|
}
|
|
root = app.NewSchemaSourceRootCommand()
|
|
effective, err := cli.BuildEffectiveCommandRegistry(root)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
bound, err := cli.BindEffectiveCommandRegistry(root, effective)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := cli.ValidateAgentSelectionContract(bound); err != nil {
|
|
t.Fatalf("ValidateAgentSelectionContract() error = %v", err)
|
|
}
|
|
if _, _, err := cli.BuildAgentSelectionEvalFixture(bound); err != nil {
|
|
t.Fatalf("BuildAgentSelectionEvalFixture() error = %v", err)
|
|
}
|
|
if _, err := cli.ValidateAgentExampleDelivery(bound, registry); err != nil {
|
|
t.Fatalf("ValidateAgentExampleDelivery() error = %v", err)
|
|
}
|
|
if err := cli.ValidateSchemaParameterBindingDelivery(bound, registry); err != nil {
|
|
t.Fatalf("ValidateSchemaParameterBindingDelivery() error = %v", err)
|
|
}
|
|
exclusions, err := cli.ReviewedRuntimeSchemaExclusions()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
report := cli.RuntimeSchemaCompleteness(root, exclusions)
|
|
if len(report.Missing)+len(report.InvalidExclusions)+len(report.StaleExclusions)+len(report.DeliveryErrors) != 0 {
|
|
t.Fatalf("RuntimeSchemaCompleteness() = %#v", report)
|
|
}
|
|
if capabilities, err := cli.ReviewedDryRunCapabilities(); err != nil || len(capabilities) == 0 {
|
|
t.Fatalf("ReviewedDryRunCapabilities() = %d, %v", len(capabilities), err)
|
|
}
|
|
// Phase 2: active binding tuples are empty; property delivery is ParamDecl.
|
|
if bindings, err := cli.LoadSchemaParameterBindings(); err != nil || len(bindings) != 0 {
|
|
t.Fatalf("LoadSchemaParameterBindings() = %d, %v; want empty active map after Phase 2", len(bindings), err)
|
|
}
|
|
if counts := cli.RuntimeSchemaMetadataLoadCounts(); counts.AgentMetadata == 0 {
|
|
t.Fatalf("RuntimeSchemaMetadataLoadCounts() = %#v", counts)
|
|
}
|
|
}
|
|
|
|
// TestCrossPlatformCoverageAssembleSchemaCatalogFromRootAndMaterialize covers
|
|
// the production RegisterSchemaSourceRoot → assembleSchemaCatalogFromRoot
|
|
// success path, content source_hash / catalog_hash, lazy materialize, and the
|
|
// ParamDecl-declared interface_type remaps that used to come from the retired
|
|
// MCP pin.
|
|
func TestCrossPlatformCoverageAssembleSchemaCatalogFromRootAndMaterialize(t *testing.T) {
|
|
cli.InstallProductionSchemaAssemblyForTest(func() *cobra.Command {
|
|
return app.NewSchemaSourceRootCommand()
|
|
})
|
|
t.Cleanup(cli.RestorePackageCLISchemaDeliveryForTest)
|
|
|
|
all, err := cli.DeliverySchemaAllPayloadForTest()
|
|
if err != nil {
|
|
t.Fatalf("DeliverySchemaAllPayloadForTest() error = %v", err)
|
|
}
|
|
catalogHash, _ := all["catalog_hash"].(string)
|
|
surfaceHash, _ := all["surface_hash"].(string)
|
|
if catalogHash == "" || surfaceHash == "" {
|
|
t.Fatalf("missing hashes: catalog_hash=%q surface_hash=%q", catalogHash, surfaceHash)
|
|
}
|
|
if catalogHash == surfaceHash {
|
|
t.Fatalf("catalog_hash must be content hash, not registry/surface hash (%s)", catalogHash)
|
|
}
|
|
resolved, err := cli.ResolveSchemaBuild(app.NewSchemaSourceRootCommand())
|
|
if err != nil {
|
|
t.Fatalf("ResolveSchemaBuild() error = %v", err)
|
|
}
|
|
if surfaceHash != resolved.RegistryHash() {
|
|
t.Fatalf("surface_hash=%q, want EffectiveCommandRegistry hash %q", surfaceHash, resolved.RegistryHash())
|
|
}
|
|
|
|
wantTypes := map[string]map[string]string{
|
|
"chat.reply_personal_message": {
|
|
"ai-tag": "string",
|
|
},
|
|
"dev.search_open_platform_docs_rag": {
|
|
"page": "number",
|
|
"size": "number",
|
|
},
|
|
"minutes.list_accessible_minutes": {
|
|
"start": "number",
|
|
"end": "number",
|
|
},
|
|
"minutes.list_shared_minutes": {
|
|
"start": "number",
|
|
"end": "number",
|
|
},
|
|
}
|
|
tools := map[string]map[string]any{}
|
|
for _, tool := range schemaAnyMaps(all["products"]) {
|
|
for _, leaf := range schemaAnyMaps(tool["tools"]) {
|
|
canonical, _ := leaf["canonical_path"].(string)
|
|
if canonical != "" {
|
|
tools[canonical] = leaf
|
|
}
|
|
}
|
|
}
|
|
if len(tools) == 0 {
|
|
t.Fatalf("schema --all contained no tools; top-level keys=%v", mapKeys(all))
|
|
}
|
|
for canonical, flags := range wantTypes {
|
|
tool := tools[canonical]
|
|
if tool == nil {
|
|
t.Fatalf("missing tool %s in schema --all", canonical)
|
|
}
|
|
params, _ := tool["parameters"].(map[string]any)
|
|
for flag, want := range flags {
|
|
param, _ := params[flag].(map[string]any)
|
|
if got, _ := param["interface_type"].(string); got != want {
|
|
t.Fatalf("%s --%s interface_type = %#v, want %q", canonical, flag, param["interface_type"], want)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func schemaAnyMaps(value any) []map[string]any {
|
|
switch items := value.(type) {
|
|
case []map[string]any:
|
|
return items
|
|
case []any:
|
|
out := make([]map[string]any, 0, len(items))
|
|
for _, item := range items {
|
|
if m, ok := item.(map[string]any); ok {
|
|
out = append(out, m)
|
|
}
|
|
}
|
|
return out
|
|
default:
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func mapKeys(value map[string]any) []string {
|
|
keys := make([]string, 0, len(value))
|
|
for key := range value {
|
|
keys = append(keys, key)
|
|
}
|
|
return keys
|
|
}
|