Files
dingtalk-workspace-cli/internal/app/schema_oa_attachment_contract_test.go
昭逸 721a40b05e fix(oa): declare attachment result contracts
- add success and failure outcomes for three attachment commands
- define business data schemas and mark downloadUri as sensitive
- migrate attachment commands to unified result output
- verify compact and full Schema result projections
- cover success, malformed response, and tool error paths
to #666
2026-08-18 10:41:21 +08:00

167 lines
6.2 KiB
Go

// Copyright 2026 Alibaba Group
// Licensed under the Apache License, Version 2.0 (the "License");
package app
import (
"bytes"
"encoding/json"
"reflect"
"strings"
"testing"
)
func TestCrossPlatformCoverageOAAttachmentDeliveredSchemaMatchesExecutableHelp(t *testing.T) {
tests := []struct {
cliPath string
canonical string
rpc string
description string
effect string
resultType string
resultFields map[string]string
sensitivePaths []string
}{
{
cliPath: "oa approval attachment download-url",
canonical: "oa.get_attachment_download_url",
rpc: "get_attachment_download_url",
description: "获取审批附件下载授权并生成临时下载链接",
effect: "read",
resultType: "object",
resultFields: map[string]string{
"spaceId": "integer", "agentId": "integer", "downloadUri": "string",
"class": "string", "fileId": "string",
},
sensitivePaths: []string{"downloadUri"},
},
{
cliPath: "oa approval attachment authorize-download",
canonical: "oa.auth_download_file",
rpc: "auth_download_file",
description: "批量授权当前用户下载指定的审批钉盘文件",
effect: "write",
resultType: "boolean",
},
{
cliPath: "oa approval attachment authorize-preview",
canonical: "oa.auth_preview_attachment",
rpc: "auth_preview_attachment",
description: "批量授权当前用户预览审批单中的附件",
effect: "write",
resultType: "object",
resultFields: map[string]string{
"spaceId": "integer", "agentId": "integer", "class": "string",
},
},
}
for _, test := range tests {
t.Run(test.rpc, func(t *testing.T) {
root := NewRootCommand()
command := exactCommandForTest(root, test.cliPath)
if command == nil {
t.Fatalf("executable command %q is missing", test.cliPath)
}
var stdout, stderr bytes.Buffer
root.SetOut(&stdout)
root.SetErr(&stderr)
root.SetArgs([]string{"schema", test.cliPath, "--format", "json"})
if err := root.Execute(); err != nil {
t.Fatalf("execute delivery schema leaf: %v; stderr=%s", err, stderr.String())
}
var tool map[string]any
if err := json.Unmarshal(stdout.Bytes(), &tool); err != nil {
t.Fatalf("decode delivery schema leaf: %v", err)
}
if got := schemaContractString(tool["canonical_path"]); got != test.canonical {
t.Fatalf("canonical_path = %q, want %q", got, test.canonical)
}
if got := schemaContractString(tool["primary_cli_path"]); got != test.cliPath {
t.Fatalf("primary_cli_path = %q, want %q", got, test.cliPath)
}
if got := schemaContractString(tool["description"]); !strings.HasPrefix(got, test.description) {
t.Fatalf("description = %q, want prefix %q", got, test.description)
}
if got := schemaContractString(tool["interface_mode"]); got != "mcp" {
t.Fatalf("interface_mode = %q, want mcp", got)
}
if got := schemaContractString(tool["availability"]); got != "available" {
t.Fatalf("availability = %q, want available", got)
}
interfaceRef := schemaInterfaceObject(tool["interface_ref"])
if got := schemaContractString(interfaceRef["product_id"]); got != "oa" {
t.Fatalf("interface_ref.product_id = %q, want oa", got)
}
if got := schemaContractString(interfaceRef["rpc_name"]); got != test.rpc {
t.Fatalf("interface_ref.rpc_name = %q, want %q", got, test.rpc)
}
if got := schemaContractString(tool["effect"]); got != test.effect {
t.Fatalf("effect = %q, want %q", got, test.effect)
}
if got := schemaContractString(tool["risk"]); got != "low" {
t.Fatalf("risk = %q, want low", got)
}
if got := schemaContractString(tool["confirmation"]); got != "not_required" {
t.Fatalf("confirmation = %q, want not_required", got)
}
if got := schemaContractString(tool["idempotency"]); got != "idempotent" {
t.Fatalf("idempotency = %q, want idempotent", got)
}
fullResult := oaAttachmentResultContract(t, tool, test.resultType, test.resultFields, test.sensitivePaths)
stdout.Reset()
stderr.Reset()
root.SetArgs([]string{"schema", test.cliPath, "--compact", "--format", "json"})
if err := root.Execute(); err != nil {
t.Fatalf("execute compact delivery schema leaf: %v; stderr=%s", err, stderr.String())
}
var compactTool map[string]any
if err := json.Unmarshal(stdout.Bytes(), &compactTool); err != nil {
t.Fatalf("decode compact delivery schema leaf: %v", err)
}
compactResult, ok := compactTool["result"].(map[string]any)
if !ok {
t.Fatalf("compact result = %#v, want object", compactTool["result"])
}
if !reflect.DeepEqual(compactResult, fullResult) {
t.Fatalf("compact/full result projection differs\ncompact: %#v\nfull: %#v", compactResult, fullResult)
}
if problem := schemaHelpFlagCompletenessProblem(test.canonical, test.cliPath, command, tool); problem != "" {
t.Fatal(problem)
}
})
}
}
func oaAttachmentResultContract(t *testing.T, tool map[string]any, resultType string, fields map[string]string, sensitivePaths []string) map[string]any {
t.Helper()
result, ok := tool["result"].(map[string]any)
if !ok {
t.Fatalf("full result = %#v, want object", tool["result"])
}
if got, want := schemaContractStringSlice(result["outcomes"]), []string{"success", "failure"}; !reflect.DeepEqual(got, want) {
t.Fatalf("result.outcomes = %#v, want %#v", got, want)
}
if got := schemaContractStringSlice(result["sensitive_paths"]); !reflect.DeepEqual(got, sensitivePaths) {
t.Fatalf("result.sensitive_paths = %#v, want %#v", got, sensitivePaths)
}
dataSchema, ok := result["data_schema"].(map[string]any)
if !ok || schemaContractString(dataSchema["type"]) != resultType {
t.Fatalf("result.data_schema = %#v, want type %q", result["data_schema"], resultType)
}
properties, _ := dataSchema["properties"].(map[string]any)
if len(properties) != len(fields) {
t.Fatalf("result.data_schema.properties = %#v, want fields %#v", properties, fields)
}
for name, fieldType := range fields {
property, ok := properties[name].(map[string]any)
if !ok || schemaContractString(property["type"]) != fieldType {
t.Fatalf("result.data_schema.properties.%s = %#v, want type %q", name, properties[name], fieldType)
}
}
return result
}