224 lines
7.7 KiB
Go
224 lines
7.7 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
|
|
|
|
package cli
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/spf13/cobra"
|
|
)
|
|
|
|
func TestEmbeddedSchemaCatalogIntegrity(t *testing.T) {
|
|
loaded := embeddedSchemaCatalog()
|
|
if !embeddedSchemaCatalogAvailable() {
|
|
t.Fatal("embedded schema catalog is unavailable or failed integrity validation")
|
|
}
|
|
if got, want := len(loaded.Snapshot.Tools), 538; got != want {
|
|
t.Fatalf("embedded tools = %d, want %d", got, want)
|
|
}
|
|
if got, want := len(loaded.Products), 20; got != want {
|
|
t.Fatalf("embedded products = %d, want %d", got, want)
|
|
}
|
|
if got := schemaString(loaded.Snapshot.Catalog["source"]); got != "embedded-command-catalog" {
|
|
t.Fatalf("catalog source = %q", got)
|
|
}
|
|
}
|
|
|
|
func TestEmbeddedSchemaCatalogProgressiveQueries(t *testing.T) {
|
|
overview, err := embeddedSchemaPayload(nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
compact := compactSchemaOverviewPayload(overview)
|
|
if got, want := schemaProductToolCount(map[string]any{"tools": compact["products"]}), 20; got != want {
|
|
t.Fatalf("compact product count = %d, want %d", got, want)
|
|
}
|
|
|
|
leaf, err := embeddedSchemaPayload([]string{"calendar event create"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := schemaString(leaf["canonical_path"]); got != "calendar.create_calendar_event" {
|
|
t.Fatalf("canonical path = %q", got)
|
|
}
|
|
if len(schemaMapSlice(leaf["parameters"])) != 0 {
|
|
t.Fatal("parameters unexpectedly decoded as a list")
|
|
}
|
|
if parameters, ok := leaf["parameters"].(map[string]any); !ok || len(parameters) == 0 {
|
|
t.Fatal("calendar.create_event parameters are empty")
|
|
}
|
|
|
|
group, err := embeddedSchemaPayload([]string{"calendar.event"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if schemaProductToolCount(map[string]any{"tools": group["tools"]}) == 0 {
|
|
t.Fatal("calendar.event group is empty")
|
|
}
|
|
|
|
alias, err := embeddedSchemaPayload([]string{"aitable record list"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if alias["is_alias"] != true || schemaString(alias["cli_path"]) != "aitable record list" {
|
|
t.Fatalf("alias query did not preserve compatibility path: %#v", alias)
|
|
}
|
|
if schemaString(alias["canonical_path"]) != "aitable.query_records" {
|
|
t.Fatalf("alias canonical path = %q", schemaString(alias["canonical_path"]))
|
|
}
|
|
}
|
|
|
|
func TestEmbeddedCatalogPreservesManualSchemaHintContract(t *testing.T) {
|
|
leaf, err := embeddedSchemaPayload([]string{"chat category create-smart"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := schemaString(leaf["source"]); got != "manual-schema-hint" {
|
|
t.Fatalf("source = %q, want manual-schema-hint", got)
|
|
}
|
|
parameters := schemaMap(leaf["parameters"])
|
|
name := parameters["name"]
|
|
if name["property"] != "categoryName" || name["required"] != true {
|
|
t.Fatalf("name parameter = %#v", name)
|
|
}
|
|
for flagName, property := range map[string]string{
|
|
"keywords": "groupNameKeywords",
|
|
"members": "memberOpenDingTalkIds",
|
|
} {
|
|
parameter := parameters[flagName]
|
|
if parameter["property"] != property || parameter["interface_type"] != "array" || parameter["required"] != false {
|
|
t.Fatalf("%s parameter = %#v", flagName, parameter)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestCompatibleSchemaCommandPrefersMatchingAnnotatedAlias(t *testing.T) {
|
|
root := &cobra.Command{Use: "dws"}
|
|
chat := &cobra.Command{Use: "chat"}
|
|
message := &cobra.Command{Use: "message"}
|
|
send := &cobra.Command{Use: "send", Run: func(*cobra.Command, []string) {}}
|
|
reply := &cobra.Command{Use: "reply", Run: func(*cobra.Command, []string) {}}
|
|
AttachRuntimeSchema(send, "chat", "send_personal_message", "hardcoded:chat")
|
|
AttachRuntimeSchema(reply, "chat", "reply_personal_message", "hardcoded:chat")
|
|
message.AddCommand(send, reply)
|
|
chat.AddCommand(message)
|
|
root.AddCommand(chat)
|
|
|
|
definition := CatalogCommandDefinition{
|
|
ProductID: "chat",
|
|
ToolName: "send_personal_message",
|
|
CLIPath: "chat message reply",
|
|
Aliases: []string{"chat message send"},
|
|
}
|
|
if got := compatibleSchemaCommand(root, definition.CLIPath, definition); got != nil {
|
|
t.Fatalf("conflicting primary matched %s", got.CommandPath())
|
|
}
|
|
if got := compatibleSchemaCommand(root, definition.Aliases[0], definition); got != send {
|
|
t.Fatalf("matching alias = %v, want send", got)
|
|
}
|
|
}
|
|
|
|
func TestEmbeddedCatalogBackfillsIdentityWithoutReplayingRealFlags(t *testing.T) {
|
|
root := &cobra.Command{Use: "dws"}
|
|
aitable := &cobra.Command{Use: "aitable"}
|
|
workflow := &cobra.Command{Use: "workflow"}
|
|
list := &cobra.Command{Use: "list", Run: func(*cobra.Command, []string) {}}
|
|
list.Flags().Int("limit", 20, "optional page size")
|
|
workflow.AddCommand(list)
|
|
aitable.AddCommand(workflow)
|
|
root.AddCommand(aitable)
|
|
|
|
AnnotateEmbeddedSchemaCommands(root)
|
|
productID, toolName, _ := runtimeSchemaAnnotations(list)
|
|
if productID != "aitable" || toolName != "workflow_list" {
|
|
t.Fatalf("identity = %s.%s", productID, toolName)
|
|
}
|
|
if _, annotated := runtimeFlagRequiredState(list.Flags().Lookup("limit")); annotated {
|
|
t.Fatal("embedded Catalog replayed stale required metadata onto a real Cobra flag")
|
|
}
|
|
}
|
|
|
|
func TestStripSchemaPayloadCompactLeaf(t *testing.T) {
|
|
leaf, err := embeddedSchemaPayload([]string{"calendar event create"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
stripped := stripSchemaPayloadCompact(leaf)
|
|
|
|
// Must keep agent-essential fields.
|
|
for _, key := range []string{"cli_path", "canonical_path", "description", "effect", "risk", "confirmation", "parameters", "constraints"} {
|
|
if _, ok := stripped[key]; !ok {
|
|
t.Fatalf("compact leaf missing essential key %q", key)
|
|
}
|
|
}
|
|
|
|
// Must strip provenance / redundant fields.
|
|
for _, key := range []string{"agent_metadata_source", "agent_source_refs", "agent_summary_source", "effect_source", "metadata_source", "primary_cli_path", "parameter_count", "has_parameters", "interface_ref", "source", "title", "display"} {
|
|
if _, ok := stripped[key]; ok {
|
|
t.Fatalf("compact leaf still contains stripped key %q", key)
|
|
}
|
|
}
|
|
|
|
// Parameters must not contain interface_description / property.
|
|
if params, ok := stripped["parameters"].(map[string]any); ok {
|
|
for name, p := range params {
|
|
if pm, ok := p.(map[string]any); ok {
|
|
for _, stripped := range []string{"interface_description", "interface_type", "property"} {
|
|
if _, present := pm[stripped]; present {
|
|
t.Fatalf("compact param %q still contains %q", name, stripped)
|
|
}
|
|
}
|
|
// Must keep type and required.
|
|
if _, present := pm["type"]; !present {
|
|
t.Fatalf("compact param %q missing type", name)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestStripSchemaPayloadCompactOverview(t *testing.T) {
|
|
overview, err := embeddedSchemaPayload(nil)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
overview = compactSchemaOverviewPayload(overview)
|
|
stripped := stripSchemaPayloadCompact(overview)
|
|
|
|
// Overview must keep kind/level/count/products.
|
|
for _, key := range []string{"kind", "level", "count", "products"} {
|
|
if _, ok := stripped[key]; !ok {
|
|
t.Fatalf("compact overview missing key %q", key)
|
|
}
|
|
}
|
|
// Must strip agent_metadata / interface_metadata at top level.
|
|
for _, key := range []string{"agent_metadata", "interface_metadata", "source"} {
|
|
if _, ok := stripped[key]; ok {
|
|
t.Fatalf("compact overview still contains stripped key %q", key)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestStripSchemaPayloadCompactProduct(t *testing.T) {
|
|
product, err := embeddedSchemaPayload([]string{"calendar"})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
stripped := stripSchemaPayloadCompact(product)
|
|
|
|
if _, ok := stripped["product"]; !ok {
|
|
t.Fatal("compact product missing 'product' key")
|
|
}
|
|
prod := stripped["product"].(map[string]any)
|
|
for _, key := range []string{"agent_metadata_source", "agent_source_refs", "source"} {
|
|
if _, ok := prod[key]; ok {
|
|
t.Fatalf("compact product still contains stripped key %q", key)
|
|
}
|
|
}
|
|
}
|