213 lines
8.1 KiB
Go
213 lines
8.1 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
|
||
//
|
||
// Unless required by applicable law or agreed to in writing, software
|
||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||
// See the License for the specific language governing permissions and
|
||
// limitations under the License.
|
||
|
||
package helpers
|
||
|
||
import (
|
||
"reflect"
|
||
"sort"
|
||
"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"
|
||
)
|
||
|
||
// collectDevLeafCommands 递归收集命令树中所有叶子命令(无子命令者)。
|
||
func collectDevLeafCommands(cmd *cobra.Command, out *[]*cobra.Command) {
|
||
if cmd == nil {
|
||
return
|
||
}
|
||
if len(cmd.Commands()) == 0 {
|
||
*out = append(*out, cmd)
|
||
return
|
||
}
|
||
for _, sub := range cmd.Commands() {
|
||
collectDevLeafCommands(sub, out)
|
||
}
|
||
}
|
||
|
||
func TestDevRepresentativeResultContractsReachContractFinal(t *testing.T) {
|
||
root := newDevAppTestRoot(&captureRunner{})
|
||
var leaves []*cobra.Command
|
||
collectDevLeafCommands(root, &leaves)
|
||
finals := make(map[string]contract.ContractFinalPayload)
|
||
for _, leaf := range leaves {
|
||
final, ok := contractfinal.RuntimeContractFinal(leaf)
|
||
if ok && final.Identity != nil {
|
||
finals[final.Identity.CanonicalPath] = final
|
||
}
|
||
}
|
||
|
||
paginationCheck := func(t *testing.T, final contract.ContractFinalPayload) {
|
||
t.Helper()
|
||
if final.Pagination == nil || final.Pagination.Kind != contract.PaginationKindCursor ||
|
||
final.Pagination.CursorParameter != "cursor" || final.Pagination.MetaPath != contract.PaginationMetaPath {
|
||
t.Fatalf("paginated list contract = %#v", final)
|
||
}
|
||
}
|
||
tests := []struct {
|
||
canonical string
|
||
outcomes []contract.ResultOutcome
|
||
check func(*testing.T, contract.ContractFinalPayload)
|
||
}{
|
||
{
|
||
canonical: "dev.list_dev_app",
|
||
outcomes: []contract.ResultOutcome{contract.ResultOutcomeSuccess, contract.ResultOutcomeFailure},
|
||
check: paginationCheck,
|
||
},
|
||
{canonical: "dev.list_dev_app_permissions", outcomes: []contract.ResultOutcome{contract.ResultOutcomeSuccess, contract.ResultOutcomeFailure}, check: paginationCheck},
|
||
{canonical: "dev.list_dev_app_events", outcomes: []contract.ResultOutcome{contract.ResultOutcomeSuccess, contract.ResultOutcomeFailure}, check: paginationCheck},
|
||
{canonical: "dev.list_dev_app_versions", outcomes: []contract.ResultOutcome{contract.ResultOutcomeSuccess, contract.ResultOutcomeFailure}, check: paginationCheck},
|
||
{canonical: "dev.get_dev_app", outcomes: []contract.ResultOutcome{contract.ResultOutcomeSuccess, contract.ResultOutcomeFailure}},
|
||
{
|
||
canonical: "dev.get_dev_app_credentials",
|
||
outcomes: []contract.ResultOutcome{contract.ResultOutcomeSuccess, contract.ResultOutcomeFailure},
|
||
check: func(t *testing.T, final contract.ContractFinalPayload) {
|
||
result := final.Result
|
||
if want := []string{"appSecret", "clientSecret"}; !reflect.DeepEqual(result.SensitivePaths, want) {
|
||
t.Fatalf("sensitive paths = %#v, want %#v", result.SensitivePaths, want)
|
||
}
|
||
},
|
||
},
|
||
{canonical: "dev.get_dev_app_version_status", outcomes: []contract.ResultOutcome{contract.ResultOutcomeSuccess, contract.ResultOutcomePending, contract.ResultOutcomeFailure}},
|
||
{canonical: "dev.connect_status", outcomes: []contract.ResultOutcome{contract.ResultOutcomeSuccess, contract.ResultOutcomeFailure}},
|
||
{canonical: "dev.connect_list", outcomes: []contract.ResultOutcome{contract.ResultOutcomeSuccess, contract.ResultOutcomeFailure}},
|
||
}
|
||
for _, test := range tests {
|
||
t.Run(test.canonical, func(t *testing.T) {
|
||
final, ok := finals[test.canonical]
|
||
if !ok || final.Result == nil {
|
||
t.Fatalf("representative leaf %s has no final Result", test.canonical)
|
||
}
|
||
if !reflect.DeepEqual(final.Result.Outcomes, test.outcomes) {
|
||
t.Fatalf("outcomes = %#v, want %#v", final.Result.Outcomes, test.outcomes)
|
||
}
|
||
if len(final.Result.DataSchema) == 0 {
|
||
t.Fatal("data_schema is empty")
|
||
}
|
||
if test.check != nil {
|
||
test.check(t, final)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
// TestDevLeafDeclarationCompleteness 是队列 B190 的 dev 叶子声明完整性自查:
|
||
// 每一个**注册了 ContractFinal** 的 dev 叶子都必须满足 Identity+Safety 最小集
|
||
// (无半壳:有注册但字段缺失)。对未注册 ContractFinal 的叶子(裸 Cobra 迁移态
|
||
// 或 NewLeafCommand 漏声明)不硬失败,而是汇总为 findings 清单打印,供人工判断
|
||
// 是否属「空壳入册」。
|
||
func TestDevLeafDeclarationCompleteness(t *testing.T) {
|
||
root := newDevAppTestRoot(&captureRunner{})
|
||
var leaves []*cobra.Command
|
||
collectDevLeafCommands(root, &leaves)
|
||
|
||
if len(leaves) == 0 {
|
||
t.Fatalf("no dev leaves collected under test root")
|
||
}
|
||
t.Logf("dev domain leaves under test: %d", len(leaves))
|
||
|
||
var noFinal []string
|
||
for _, leaf := range leaves {
|
||
path := leaf.CommandPath()
|
||
final, ok := contractfinal.RuntimeContractFinal(leaf)
|
||
if !ok {
|
||
noFinal = append(noFinal, path)
|
||
continue
|
||
}
|
||
t.Run(path, func(t *testing.T) {
|
||
if final.Identity == nil {
|
||
t.Fatalf("leaf %q ContractFinal.Identity is nil", path)
|
||
}
|
||
if final.Identity.ProductID == "" {
|
||
t.Fatalf("leaf %q Identity.ProductID empty", path)
|
||
}
|
||
if final.Identity.Name == "" {
|
||
t.Fatalf("leaf %q Identity.Name empty", path)
|
||
}
|
||
if final.Identity.CanonicalPath == "" {
|
||
t.Fatalf("leaf %q Identity.CanonicalPath empty", path)
|
||
}
|
||
if final.Safety == nil {
|
||
t.Fatalf("leaf %q ContractFinal.Safety is nil (Safety minimal set missing)", path)
|
||
}
|
||
if final.Description == "" {
|
||
t.Fatalf("leaf %q ContractFinal.Description empty (no description declared)", path)
|
||
}
|
||
})
|
||
}
|
||
|
||
if len(noFinal) > 0 {
|
||
sort.Strings(noFinal)
|
||
t.Logf("B190 findings: %d dev leaves without ContractFinal (may be intentional bare-Cobra migration or empty-shell NewLeafCommand):", len(noFinal))
|
||
for _, p := range noFinal {
|
||
t.Logf(" - %s", p)
|
||
}
|
||
}
|
||
}
|
||
|
||
// TestDevLeafDeclarationNoEmptyShellNewLeaf 是队列 B190 的核心断言:NewLeafCommand
|
||
// 完全托管的叶子必须注册 ContractFinal——声明了 LeafSpec 却无 ContractFinal 就是
|
||
// 「空壳入册」。本测试通过 Verify 一个已知的既存漏洞(version check-approval 用
|
||
// NewLeafCommand 但 LeafSpec 未声明 Contract)来固化当前现状,并断言这类「声明了
|
||
// Shell 却无最终契约」的问题在受控已知集中,而非蔓延到其它托管叶子。
|
||
func TestDevLeafDeclarationNoEmptyShellNewLeaf(t *testing.T) {
|
||
root := newDevAppTestRoot(&captureRunner{})
|
||
var leaves []*cobra.Command
|
||
collectDevLeafCommands(root, &leaves)
|
||
|
||
knownEmptyShell := map[string]bool{}
|
||
|
||
var unexpected []string
|
||
for _, leaf := range leaves {
|
||
path := leaf.CommandPath()
|
||
if _, ok := contractfinal.RuntimeContractFinal(leaf); ok {
|
||
continue // 正常托管叶子已有 ContractFinal。
|
||
}
|
||
// 无 ContractFinal:裸 Cobra 迁移态(connect 系列)或已知空壳。
|
||
if knownEmptyShell[path] {
|
||
continue
|
||
}
|
||
// 裸 Cobra 命令(不经 NewLeafCommand)不要求 ContractFinal,排除。
|
||
if isBareDevCobraLeaf(leaf) {
|
||
continue
|
||
}
|
||
unexpected = append(unexpected, path)
|
||
}
|
||
if len(unexpected) > 0 {
|
||
t.Fatalf("unexpected empty-shell NewLeafCommand leaves without ContractFinal: %v", unexpected)
|
||
}
|
||
}
|
||
|
||
// isBareDevCobraLeaf 判断某 dev 叶子是否为裸 Cobra 命令(不经 NewLeafCommand,
|
||
// 不注册 ContractFinal)。通过多次构造命令并检查其是否有本地 flags 来间接识别
|
||
// 不可靠,改用命令名白名单:connect 本地运维子命令为已知裸 Cobra 迁移态。
|
||
func isBareDevCobraLeaf(cmd *cobra.Command) bool {
|
||
switch cmd.Name() {
|
||
case "list", "status", "restart", "stop":
|
||
// 仅当命令位于 dev connect 子树下才算裸 Cobra(避免误伤 dev app 的
|
||
// version list 等托管叶子)。
|
||
if strings.Contains(cmd.CommandPath(), "dev connect") {
|
||
return true
|
||
}
|
||
}
|
||
return false
|
||
}
|
||
|
||
var (
|
||
_ = contractfinal.HasRuntimeContractFinal
|
||
_ = sort.Strings
|
||
)
|