Files
dingtalk-workspace-cli/internal/cli/schema_full_pipeline_external_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

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
}