569 lines
24 KiB
Go
569 lines
24 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 (
|
||
"context"
|
||
"reflect"
|
||
"strings"
|
||
"testing"
|
||
|
||
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/executor"
|
||
"github.com/DingTalk-Real-AI/dingtalk-workspace-cli/internal/output"
|
||
"github.com/spf13/cobra"
|
||
)
|
||
|
||
// fakeDevAppRunner 捕获 runDevAppTool 派发的 invocation,用于断言 LeafSpec.Call
|
||
// 装配出的 toolArgs 与手写版逐字等价。这是 catalog drift 不覆盖的运行时维度。
|
||
type fakeDevAppRunner struct {
|
||
got executor.Invocation
|
||
}
|
||
|
||
func prepareDevAppLeafTestCommand(cmd *cobra.Command) *cobra.Command {
|
||
ctx, _ := output.WithResultStore(context.Background())
|
||
cmd.SetContext(ctx)
|
||
return cmd
|
||
}
|
||
|
||
func (f *fakeDevAppRunner) Run(_ context.Context, inv executor.Invocation) (executor.Result, error) {
|
||
f.got = inv
|
||
// 空 Response:normalizeDevAppServiceResult 无 "content" 时早返回,安全。
|
||
return executor.Result{Invocation: inv, Response: map[string]any{}}, nil
|
||
}
|
||
|
||
// TestDevAppCredentialsGetLeafDispatchesTrimmedArgs 验证迁移到 LeafSpec 后:
|
||
// toolArgs 键/值/trim 与手写版等价,且 PostMount 设上了 schema 注解与 NoArgs。
|
||
func TestDevAppCredentialsGetLeafDispatchesTrimmedArgs(t *testing.T) {
|
||
r := &fakeDevAppRunner{}
|
||
cmd := prepareDevAppLeafTestCommand(newDevAppCredentialsGetCommand(r))
|
||
// 含首尾空白:验证 LeafFlag.Trim 等价手写 devAppStringFlag 的 TrimSpace。
|
||
if err := cmd.Flags().Set("unified-app-id", " APP-123 "); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if err := cmd.RunE(cmd, nil); err != nil {
|
||
t.Fatalf("RunE() error = %v", err)
|
||
}
|
||
if r.got.Tool != devAppCredentialsGetTool {
|
||
t.Fatalf("tool = %q, want %q", r.got.Tool, devAppCredentialsGetTool)
|
||
}
|
||
if v, _ := r.got.Params["unifiedAppId"].(string); v != "APP-123" {
|
||
t.Fatalf("unifiedAppId = %q, want trimmed \"APP-123\"", v)
|
||
}
|
||
if cmd.Annotations["mcp-tool"] != devAppCredentialsGetTool {
|
||
t.Fatalf("mcp-tool annotation = %q (PostMount 未生效)", cmd.Annotations["mcp-tool"])
|
||
}
|
||
if cmd.Args == nil {
|
||
t.Fatal("Args == nil, want NoArgs via PostMount")
|
||
}
|
||
}
|
||
|
||
// TestDevAppLifecycleLeafWriteGuardAndArgs 验证框架确认门拦截/放行 + toolArgs 装配。
|
||
func TestDevAppLifecycleLeafWriteGuardAndArgs(t *testing.T) {
|
||
r := &fakeDevAppRunner{}
|
||
cmd := prepareDevAppLeafTestCommand(newDevAppEnableCommand(r))
|
||
if err := cmd.Flags().Set("unified-app-id", "APP-9"); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
// 无 --yes / --dry-run:SafetySpec 的 user_required 确认必须拦下。
|
||
if err := cmd.RunE(cmd, nil); err == nil {
|
||
t.Fatal("RunE() without --yes: want write-guard error, got nil")
|
||
}
|
||
// devAppYes 在 cmd.Flags 里找 "yes";注册并置位,守卫放行。
|
||
cmd.Flags().Bool("yes", false, "")
|
||
if err := cmd.Flags().Set("yes", "true"); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if err := cmd.RunE(cmd, nil); err != nil {
|
||
t.Fatalf("RunE() with --yes: error = %v", err)
|
||
}
|
||
if r.got.Tool != devAppEnableTool {
|
||
t.Fatalf("tool = %q, want %q", r.got.Tool, devAppEnableTool)
|
||
}
|
||
if v, _ := r.got.Params["unifiedAppId"].(string); v != "APP-9" {
|
||
t.Fatalf("unifiedAppId = %q, want \"APP-9\"", v)
|
||
}
|
||
}
|
||
|
||
// TestDevAppCredentialsGetMissingRequired 验证自定义必填报错(apperrors)逐字保留。
|
||
func TestDevAppCredentialsGetMissingRequired(t *testing.T) {
|
||
r := &fakeDevAppRunner{}
|
||
cmd := newDevAppCredentialsGetCommand(r)
|
||
err := cmd.RunE(cmd, nil)
|
||
if err == nil {
|
||
t.Fatal("want required error for missing --unified-app-id")
|
||
}
|
||
if !strings.Contains(err.Error(), "--unified-app-id 为必填") {
|
||
t.Fatalf("err = %v, want 含 \"--unified-app-id 为必填\"", err)
|
||
}
|
||
}
|
||
|
||
// TestDevAppVersionLeafs 验证 version get/status/check-approval 的 toolArgs
|
||
// 装配:双必填 trim + check-approval 经 Call 注入 precheckOnly=true 常量。
|
||
func TestDevAppVersionLeafs(t *testing.T) {
|
||
cases := []struct {
|
||
name string
|
||
build func(*fakeDevAppRunner) *cobra.Command
|
||
tool string
|
||
precheckOnly any
|
||
}{
|
||
{"get", func(r *fakeDevAppRunner) *cobra.Command { return newDevAppVersionGetCommand(r) }, devAppVersionDetailTool, nil},
|
||
{"status", func(r *fakeDevAppRunner) *cobra.Command { return newDevAppVersionStatusCommand(r) }, devAppVersionStatusTool, nil},
|
||
{"check-approval", func(r *fakeDevAppRunner) *cobra.Command { return newDevAppVersionCheckApprovalCommand(r) }, devAppVersionPublishTool, true},
|
||
}
|
||
for _, c := range cases {
|
||
t.Run(c.name, func(t *testing.T) {
|
||
r := &fakeDevAppRunner{}
|
||
cmd := prepareDevAppLeafTestCommand(c.build(r))
|
||
if err := cmd.Flags().Set("unified-app-id", " APP-1 "); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if err := cmd.Flags().Set("version-id", " v9 "); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if err := cmd.RunE(cmd, nil); err != nil {
|
||
t.Fatalf("RunE() error = %v", err)
|
||
}
|
||
if r.got.Tool != c.tool {
|
||
t.Fatalf("tool = %q, want %q", r.got.Tool, c.tool)
|
||
}
|
||
if v, _ := r.got.Params["unifiedAppId"].(string); v != "APP-1" {
|
||
t.Fatalf("unifiedAppId = %q, want trimmed \"APP-1\"", v)
|
||
}
|
||
if v, _ := r.got.Params["versionId"].(string); v != "v9" {
|
||
t.Fatalf("versionId = %q, want trimmed \"v9\"", v)
|
||
}
|
||
if c.precheckOnly != nil {
|
||
if v, _ := r.got.Params["precheckOnly"].(bool); v != c.precheckOnly {
|
||
t.Fatalf("precheckOnly = %v, want %v", r.got.Params["precheckOnly"], c.precheckOnly)
|
||
}
|
||
} else if _, present := r.got.Params["precheckOnly"]; present {
|
||
t.Fatalf("precheckOnly present = %v, want absent", r.got.Params["precheckOnly"])
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
// TestDevAppMemberListLeaf 验证 member list 的 unified-id 装配。
|
||
func TestDevAppMemberListLeaf(t *testing.T) {
|
||
r := &fakeDevAppRunner{}
|
||
cmd := prepareDevAppLeafTestCommand(newDevAppMemberListCommand(r))
|
||
if err := cmd.Flags().Set("unified-app-id", "APP-7"); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if err := cmd.RunE(cmd, nil); err != nil {
|
||
t.Fatalf("RunE() error = %v", err)
|
||
}
|
||
if r.got.Tool != devAppMemberListTool {
|
||
t.Fatalf("tool = %q, want %q", r.got.Tool, devAppMemberListTool)
|
||
}
|
||
if v, _ := r.got.Params["unifiedAppId"].(string); v != "APP-7" {
|
||
t.Fatalf("unifiedAppId = %q, want \"APP-7\"", v)
|
||
}
|
||
}
|
||
|
||
// TestDevAppPermissionListLeaf 验证 ToUpper transform + auth-status 默认 + 命令别名。
|
||
func TestDevAppPermissionListLeaf(t *testing.T) {
|
||
r := &fakeDevAppRunner{}
|
||
cmd := prepareDevAppLeafTestCommand(newDevAppPermissionListCommand(r))
|
||
// 命令别名 "search" 经 PostMount 设回。
|
||
if !containsString(cmd.Aliases, "search") {
|
||
t.Fatalf("cmd.Aliases = %v, want contain search", cmd.Aliases)
|
||
}
|
||
_ = cmd.Flags().Set("unified-app-id", "APP-P")
|
||
_ = cmd.Flags().Set("scope-type", "app")
|
||
if err := cmd.RunE(cmd, nil); err != nil {
|
||
t.Fatalf("RunE() error = %v", err)
|
||
}
|
||
// auth-status 默认 ALL → ToUpper ALL。
|
||
if v, _ := r.got.Params["authStatus"].(string); v != "ALL" {
|
||
t.Fatalf("authStatus = %v, want ALL (default ToUpper)", r.got.Params["authStatus"])
|
||
}
|
||
// scope-type 小写 app → ToUpper APP。
|
||
if v, _ := r.got.Params["scopeType"].(string); v != "APP" {
|
||
t.Fatalf("scopeType = %v, want APP (ToUpper)", r.got.Params["scopeType"])
|
||
}
|
||
if _, present := r.got.Params["pageSize"]; !present {
|
||
t.Fatal("pageSize missing, want cursor injection")
|
||
}
|
||
}
|
||
|
||
// TestDevAppVersionPublishLeaf 验证 precheckOnly=false 常量 + confirmed-sensitive Changed() 语义。
|
||
func TestDevAppVersionPublishLeaf(t *testing.T) {
|
||
r := &fakeDevAppRunner{}
|
||
cmd := prepareDevAppLeafTestCommand(newDevAppVersionPublishCommand(r))
|
||
cmd.Flags().Bool("yes", false, "")
|
||
_ = cmd.Flags().Set("yes", "true")
|
||
_ = cmd.Flags().Set("unified-app-id", "APP-V")
|
||
_ = cmd.Flags().Set("version-id", "v1")
|
||
// confirmed-sensitive 未显式设:不入参。
|
||
if err := cmd.RunE(cmd, nil); err != nil {
|
||
t.Fatalf("RunE() error = %v", err)
|
||
}
|
||
if v, _ := r.got.Params["precheckOnly"].(bool); v != false {
|
||
t.Fatalf("precheckOnly = %v, want false", r.got.Params["precheckOnly"])
|
||
}
|
||
if _, present := r.got.Params["confirmedSensitive"]; present {
|
||
t.Fatalf("confirmedSensitive present = %v, want omitted (not Changed)", r.got.Params["confirmedSensitive"])
|
||
}
|
||
// 显式设 confirmed-sensitive=true:入参。
|
||
r2 := &fakeDevAppRunner{}
|
||
cmd2 := prepareDevAppLeafTestCommand(newDevAppVersionPublishCommand(r2))
|
||
cmd2.Flags().Bool("yes", false, "")
|
||
_ = cmd2.Flags().Set("yes", "true")
|
||
_ = cmd2.Flags().Set("unified-app-id", "APP-V")
|
||
_ = cmd2.Flags().Set("version-id", "v1")
|
||
_ = cmd2.Flags().Set("confirmed-sensitive", "true")
|
||
_ = cmd2.RunE(cmd2, nil)
|
||
if v, _ := r2.got.Params["confirmedSensitive"].(bool); v != true {
|
||
t.Fatalf("confirmedSensitive = %v, want true", r2.got.Params["confirmedSensitive"])
|
||
}
|
||
}
|
||
|
||
// TestDevAppGetLeafDualKey 验证二选一定位键。
|
||
func TestDevAppGetLeafDualKey(t *testing.T) {
|
||
// 都不传:报错(框架统一约束措辞)。
|
||
r := &fakeDevAppRunner{}
|
||
cmd := prepareDevAppLeafTestCommand(newDevAppGetCommand(r))
|
||
if err := cmd.RunE(cmd, nil); err == nil ||
|
||
!strings.Contains(err.Error(), "请传入 --unified-app-id 或 --app-key") {
|
||
t.Fatalf("err = %v, want 二选一报错", err)
|
||
}
|
||
// 只 app-key:params 含 appKey,无 unifiedAppId。
|
||
_ = cmd.Flags().Set("app-key", "dingABC")
|
||
if err := cmd.RunE(cmd, nil); err != nil {
|
||
t.Fatalf("RunE() error = %v", err)
|
||
}
|
||
if v, _ := r.got.Params["appKey"].(string); v != "dingABC" {
|
||
t.Fatalf("appKey = %v, want dingABC", r.got.Params["appKey"])
|
||
}
|
||
if _, present := r.got.Params["unifiedAppId"]; present {
|
||
t.Fatalf("unifiedAppId present, want omitted")
|
||
}
|
||
}
|
||
|
||
// TestDevAppListLeaf 是 showcase:退役 devAppPutString/devAppPutInt/devAppFlagOrFallback。
|
||
// 验证 name/keyword 别名回退、LeafInt(!=0 入参)、cursor 注入、OmitEmpty。
|
||
func TestDevAppListLeaf(t *testing.T) {
|
||
// keyword 别名回退:只设 keyword,name 应取 keyword 值。
|
||
r := &fakeDevAppRunner{}
|
||
cmd := prepareDevAppLeafTestCommand(newDevAppListCommand(r))
|
||
_ = cmd.Flags().Set("keyword", "DemoApp")
|
||
_ = cmd.Flags().Set("app-group-id", "5")
|
||
_ = cmd.Flags().Set("develop-type", "0") // 0:LeafInt 不入参
|
||
if err := cmd.RunE(cmd, nil); err != nil {
|
||
t.Fatalf("RunE() error = %v", err)
|
||
}
|
||
if v, _ := r.got.Params["name"].(string); v != "DemoApp" {
|
||
t.Fatalf("name = %q, want keyword-fallback \"DemoApp\"", v)
|
||
}
|
||
if v, _ := r.got.Params["appGroupId"].(int); v != 5 {
|
||
t.Fatalf("appGroupId = %v, want int(5)", r.got.Params["appGroupId"])
|
||
}
|
||
if _, present := r.got.Params["developType"]; present {
|
||
t.Fatalf("developType present = %v, want omitted (LeafInt 0)", r.got.Params["developType"])
|
||
}
|
||
if _, present := r.got.Params["pageSize"]; !present {
|
||
t.Fatal("pageSize missing, want cursor injection")
|
||
}
|
||
if _, present := r.got.Params["creator"]; present {
|
||
t.Fatalf("creator present, want omitted (OmitEmpty)")
|
||
}
|
||
// name 优先于 keyword。
|
||
r2 := &fakeDevAppRunner{}
|
||
cmd2 := prepareDevAppLeafTestCommand(newDevAppListCommand(r2))
|
||
_ = cmd2.Flags().Set("name", "Primary")
|
||
_ = cmd2.Flags().Set("keyword", "Fallback")
|
||
_ = cmd2.RunE(cmd2, nil)
|
||
if v, _ := r2.got.Params["name"].(string); v != "Primary" {
|
||
t.Fatalf("name = %q, want Primary (name beats keyword)", v)
|
||
}
|
||
}
|
||
|
||
// TestDevAppEventListLeaf 验证 cursor/pageSize 经 devAppCallCursor 工具注入。
|
||
func TestDevAppEventListLeaf(t *testing.T) {
|
||
r := &fakeDevAppRunner{}
|
||
cmd := prepareDevAppLeafTestCommand(newDevAppEventListCommand(r))
|
||
_ = cmd.Flags().Set("unified-app-id", "APP-E")
|
||
if err := cmd.RunE(cmd, nil); err != nil {
|
||
t.Fatalf("RunE() error = %v", err)
|
||
}
|
||
if v, _ := r.got.Params["unifiedAppId"].(string); v != "APP-E" {
|
||
t.Fatalf("unifiedAppId = %q, want APP-E", v)
|
||
}
|
||
// page-size 默认 20,由 registerDevAppCursorFlags + CallCursor 注入。
|
||
if v, present := r.got.Params["pageSize"]; !present || v != 20 {
|
||
t.Fatalf("pageSize = %v, want 20 from cursor tool", v)
|
||
}
|
||
// cursor 默认空:不入参。
|
||
if _, present := r.got.Params["cursor"]; present {
|
||
t.Fatalf("cursor present = %v, want omitted when empty", r.got.Params["cursor"])
|
||
}
|
||
// keyword 未设:OmitEmpty 省略。
|
||
if _, present := r.got.Params["keyword"]; present {
|
||
t.Fatalf("keyword present, want omitted (OmitEmpty)")
|
||
}
|
||
}
|
||
|
||
// TestDevAppUpdateLeaf 验证「至少一项」校验 + OmitEmpty 装配。
|
||
func TestDevAppUpdateLeaf(t *testing.T) {
|
||
r := &fakeDevAppRunner{}
|
||
cmd := prepareDevAppLeafTestCommand(newDevAppUpdateCommand(r))
|
||
cmd.Flags().Bool("yes", false, "")
|
||
_ = cmd.Flags().Set("yes", "true")
|
||
_ = cmd.Flags().Set("unified-app-id", "APP-U")
|
||
// 全空:至少一项拦(框架统一约束措辞)。
|
||
if err := cmd.RunE(cmd, nil); err == nil ||
|
||
!strings.Contains(err.Error(), "至少提供一项待更新字段:--name、--desc 或 --icon-media-id") {
|
||
t.Fatalf("err = %v, want 至少提供一项待更新字段:--name、--desc 或 --icon-media-id", err)
|
||
}
|
||
_ = cmd.Flags().Set("desc", " 新描述 ")
|
||
if err := cmd.RunE(cmd, nil); err != nil {
|
||
t.Fatalf("RunE() error = %v", err)
|
||
}
|
||
if v, _ := r.got.Params["desc"].(string); v != "新描述" {
|
||
t.Fatalf("desc = %q, want trimmed", v)
|
||
}
|
||
if _, present := r.got.Params["name"]; present {
|
||
t.Fatalf("name present, want omitted (OmitEmpty)")
|
||
}
|
||
}
|
||
|
||
// TestDevAppMemberAddLeaf 验证 userIds []string 注入 + memberType 装配 + 写守卫/必填顺序。
|
||
func TestDevAppMemberAddLeaf(t *testing.T) {
|
||
r := &fakeDevAppRunner{}
|
||
cmd := prepareDevAppLeafTestCommand(newDevAppMemberAddCommand(r))
|
||
_ = cmd.Flags().Set("unified-app-id", "APP-M")
|
||
// 无 --yes:写守卫拦。
|
||
if err := cmd.RunE(cmd, nil); err == nil {
|
||
t.Fatal("want write-guard error without --yes")
|
||
}
|
||
cmd.Flags().Bool("yes", false, "")
|
||
_ = cmd.Flags().Set("yes", "true")
|
||
// 缺 user-ids/member-type:必填拦。
|
||
if err := cmd.RunE(cmd, nil); err == nil ||
|
||
!strings.Contains(err.Error(), "--user-ids 为必填") {
|
||
t.Fatalf("err = %v, want --user-ids 为必填", err)
|
||
}
|
||
_ = cmd.Flags().Set("user-ids", "u1, u2")
|
||
if err := cmd.RunE(cmd, nil); err == nil ||
|
||
!strings.Contains(err.Error(), "--member-type 为必填") {
|
||
t.Fatalf("err = %v, want --member-type 为必填", err)
|
||
}
|
||
// 齐全:userIds []string + memberType 装配。
|
||
_ = cmd.Flags().Set("member-type", "DEVELOPER")
|
||
if err := cmd.RunE(cmd, nil); err != nil {
|
||
t.Fatalf("RunE() error = %v", err)
|
||
}
|
||
users, ok := r.got.Params["userIds"].([]string)
|
||
if !ok || len(users) != 2 || users[0] != "u1" || users[1] != "u2" {
|
||
t.Fatalf("userIds = %v, want [u1 u2]", r.got.Params["userIds"])
|
||
}
|
||
if v, _ := r.got.Params["memberType"].(string); v != "DEVELOPER" {
|
||
t.Fatalf("memberType = %v, want DEVELOPER", r.got.Params["memberType"])
|
||
}
|
||
}
|
||
|
||
// TestDevAppSecurityConfigLeaf 验证 3 个列表 flag 的 Call 注入 + 「至少一项」校验。
|
||
func TestDevAppSecurityConfigLeaf(t *testing.T) {
|
||
r := &fakeDevAppRunner{}
|
||
cmd := prepareDevAppLeafTestCommand(newDevAppSecurityConfigCommand(r))
|
||
cmd.Flags().Bool("yes", false, "")
|
||
_ = cmd.Flags().Set("yes", "true")
|
||
_ = cmd.Flags().Set("unified-app-id", "APP-S")
|
||
// 全空:至少一项拦。
|
||
if err := cmd.RunE(cmd, nil); err == nil ||
|
||
!strings.Contains(err.Error(), "至少提供一项安全配置:--ip-whitelist、--redirect-urls 或 --sso-urls") {
|
||
t.Fatalf("err = %v, want 至少提供一项安全配置:--ip-whitelist、--redirect-urls 或 --sso-urls", err)
|
||
}
|
||
// 设 ip-whitelist:注入 []string,其余省略。
|
||
_ = cmd.Flags().Set("ip-whitelist", "10.0.0.1; 10.0.0.2")
|
||
if err := cmd.RunE(cmd, nil); err != nil {
|
||
t.Fatalf("RunE() error = %v", err)
|
||
}
|
||
ips, ok := r.got.Params["ipWhitelist"].([]string)
|
||
if !ok || len(ips) != 2 || ips[0] != "10.0.0.1" || ips[1] != "10.0.0.2" {
|
||
t.Fatalf("ipWhitelist = %v, want [10.0.0.1 10.0.0.2]", r.got.Params["ipWhitelist"])
|
||
}
|
||
if _, present := r.got.Params["redirectUrls"]; present {
|
||
t.Fatalf("redirectUrls present, want omitted")
|
||
}
|
||
}
|
||
|
||
// TestDevAppPermissionAddLeaf 验证 scope-values 经 Call 注入为 []string + 写守卫/必填。
|
||
func TestDevAppPermissionAddLeaf(t *testing.T) {
|
||
r := &fakeDevAppRunner{}
|
||
cmd := prepareDevAppLeafTestCommand(newDevAppPermissionAddCommand(r))
|
||
// scope-values 在 PostMount 注册。
|
||
if err := cmd.Flags().Set("unified-app-id", "APP-3"); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
// 无 --yes 且无 scope-values:写守卫先拦。
|
||
if err := cmd.RunE(cmd, nil); err == nil {
|
||
t.Fatal("want write-guard error without --yes")
|
||
}
|
||
cmd.Flags().Bool("yes", false, "")
|
||
_ = cmd.Flags().Set("yes", "true")
|
||
// 有 --yes 但无 scope-values:必填校验拦。
|
||
if err := cmd.RunE(cmd, nil); err == nil ||
|
||
!strings.Contains(err.Error(), "--scope-values 为必填") {
|
||
t.Fatalf("err = %v, want --scope-values 为必填", err)
|
||
}
|
||
// 齐全:scopeValues 注入为 []string。
|
||
_ = cmd.Flags().Set("scope-values", "Contact.User.mobile, qyapi_robot_sendmsg")
|
||
if err := cmd.RunE(cmd, nil); err != nil {
|
||
t.Fatalf("RunE() error = %v", err)
|
||
}
|
||
scopes, ok := r.got.Params["scopeValues"].([]string)
|
||
if !ok || len(scopes) != 2 || scopes[0] != "Contact.User.mobile" || scopes[1] != "qyapi_robot_sendmsg" {
|
||
t.Fatalf("scopeValues = %v, want [Contact.User.mobile qyapi_robot_sendmsg]", r.got.Params["scopeValues"])
|
||
}
|
||
if v, _ := r.got.Params["unifiedAppId"].(string); v != "APP-3" {
|
||
t.Fatalf("unifiedAppId = %q, want APP-3", v)
|
||
}
|
||
}
|
||
|
||
// TestDevAppWebappConfigLeaf 验证 OmitEmpty 装配 + 「至少一项」跨 flag 校验。
|
||
func TestDevAppWebappConfigLeaf(t *testing.T) {
|
||
r := &fakeDevAppRunner{}
|
||
cmd := prepareDevAppLeafTestCommand(newDevAppWebappConfigCommand(r))
|
||
cmd.Flags().Bool("yes", false, "")
|
||
_ = cmd.Flags().Set("yes", "true")
|
||
|
||
// 全空:写守卫放行后,「至少一项」必须拦。
|
||
if err := cmd.Flags().Set("unified-app-id", "APP-1"); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
err := cmd.RunE(cmd, nil)
|
||
if err == nil || !strings.Contains(err.Error(), "至少提供一项网页应用配置:--h5-page-type、--homepage-url、--pc-homepage-url 或 --omp-url") {
|
||
t.Fatalf("err = %v, want 至少提供一项网页应用配置:--h5-page-type、--homepage-url、--pc-homepage-url 或 --omp-url", err)
|
||
}
|
||
|
||
// 只设一项:该项入参,其余 OmitEmpty 省略。
|
||
if err := cmd.Flags().Set("homepage-url", " https://a.example.invalid "); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
r = &fakeDevAppRunner{}
|
||
cmd2 := prepareDevAppLeafTestCommand(newDevAppWebappConfigCommand(r))
|
||
cmd2.Flags().Bool("yes", false, "")
|
||
_ = cmd2.Flags().Set("yes", "true")
|
||
_ = cmd2.Flags().Set("unified-app-id", "APP-1")
|
||
_ = cmd2.Flags().Set("homepage-url", " https://a.example.invalid ")
|
||
if err := cmd2.RunE(cmd2, nil); err != nil {
|
||
t.Fatalf("RunE() error = %v", err)
|
||
}
|
||
if v, _ := r.got.Params["homepageUrl"].(string); v != "https://a.example.invalid" {
|
||
t.Fatalf("homepageUrl = %q, want trimmed", v)
|
||
}
|
||
if _, present := r.got.Params["h5PageType"]; present {
|
||
t.Fatalf("h5PageType present = %v, want omitted (OmitEmpty)", r.got.Params["h5PageType"])
|
||
}
|
||
if _, present := r.got.Params["ompUrl"]; present {
|
||
t.Fatalf("ompUrl present = %v, want omitted (OmitEmpty)", r.got.Params["ompUrl"])
|
||
}
|
||
}
|
||
|
||
// TestDevAppLeafToolArgsTable 表驱动补齐剩余迁移命令的 toolArgs 断言。
|
||
// 覆盖此前无断言的:robot enable/disable、permission remove、event sub/unsub、
|
||
// member remove、version create、app create、robot config-get、lifecycle disable。
|
||
func TestDevAppLeafToolArgsTable(t *testing.T) {
|
||
cases := []struct {
|
||
name string
|
||
build func(executor.Runner) *cobra.Command
|
||
flags map[string]string
|
||
needsYes bool
|
||
wantTool string
|
||
wantParam map[string]any
|
||
wantAbsent []string // 应缺席的 params 键(OmitEmpty 校验)
|
||
}{
|
||
{"robot enable", newDevAppRobotEnableCommand, map[string]string{"unified-app-id": "APP-R"}, true, devAppRobotEnableTool, map[string]any{"unifiedAppId": "APP-R"}, nil},
|
||
{"robot disable", newDevAppRobotOfflineCommand, map[string]string{"unified-app-id": "APP-R"}, true, devAppRobotOfflineTool, map[string]any{"unifiedAppId": "APP-R"}, nil},
|
||
{"permission remove", newDevAppPermissionRemoveCommand, map[string]string{"unified-app-id": "APP-P", "scope-values": "a.b, c.d"}, true, devAppPermissionRmTool, map[string]any{"unifiedAppId": "APP-P", "scopeValues": []string{"a.b", "c.d"}}, nil},
|
||
{"event subscribe", newDevAppEventSubscribeCommand, map[string]string{"unified-app-id": "APP-E", "event-codes": "x, y"}, true, devAppEventSubscribeTool, map[string]any{"unifiedAppId": "APP-E", "eventCodes": []string{"x", "y"}}, nil},
|
||
{"event unsubscribe", newDevAppEventUnsubscribeCommand, map[string]string{"unified-app-id": "APP-E", "event-codes": "x, y"}, true, devAppEventUnsubscribeTool, map[string]any{"unifiedAppId": "APP-E", "eventCodes": []string{"x", "y"}}, nil},
|
||
{"member remove", newDevAppMemberRemoveCommand, map[string]string{"unified-app-id": "APP-M", "user-ids": "u1,u2", "member-type": "DEVELOPER"}, true, devAppMemberRemoveTool, map[string]any{"unifiedAppId": "APP-M", "userIds": []string{"u1", "u2"}, "memberType": "DEVELOPER"}, nil},
|
||
{"version create", newDevAppVersionCreateCommand, map[string]string{"unified-app-id": "APP-V", "desc": "新增机器人"}, true, devAppVersionCreateTool, map[string]any{"unifiedAppId": "APP-V", "desc": "新增机器人"}, []string{"version"}},
|
||
{"app create", newDevAppCreateCommand, map[string]string{"name": "DemoApp"}, true, devAppCreateTool, map[string]any{"name": "DemoApp"}, []string{"desc", "iconMediaId"}},
|
||
{"robot config get", newDevAppRobotConfigGetCommand, map[string]string{"unified-app-id": "APP-C"}, false, devAppRobotConfigGetTool, map[string]any{"unifiedAppId": "APP-C"}, nil},
|
||
{"lifecycle disable", newDevAppDisableCommand, map[string]string{"unified-app-id": "APP-D"}, true, devAppDisableTool, map[string]any{"unifiedAppId": "APP-D"}, nil},
|
||
}
|
||
for _, c := range cases {
|
||
t.Run(c.name, func(t *testing.T) {
|
||
r := &fakeDevAppRunner{}
|
||
cmd := prepareDevAppLeafTestCommand(c.build(r))
|
||
if c.needsYes {
|
||
cmd.Flags().Bool("yes", false, "")
|
||
_ = cmd.Flags().Set("yes", "true")
|
||
}
|
||
for k, v := range c.flags {
|
||
if err := cmd.Flags().Set(k, v); err != nil {
|
||
t.Fatalf("set %s: %v", k, err)
|
||
}
|
||
}
|
||
if err := cmd.RunE(cmd, nil); err != nil {
|
||
t.Fatalf("RunE() error = %v", err)
|
||
}
|
||
if r.got.Tool != c.wantTool {
|
||
t.Fatalf("tool = %q, want %q", r.got.Tool, c.wantTool)
|
||
}
|
||
for k, want := range c.wantParam {
|
||
if got := r.got.Params[k]; !reflect.DeepEqual(got, want) {
|
||
t.Fatalf("param %s = %#v (%T), want %#v (%T)", k, got, got, want, want)
|
||
}
|
||
}
|
||
for _, k := range c.wantAbsent {
|
||
if _, present := r.got.Params[k]; present {
|
||
t.Fatalf("param %s present = %#v, want absent (OmitEmpty)", k, r.got.Params[k])
|
||
}
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
// TestDevAppMemberRemoveValidateChain 覆盖 member remove Validate 钩子的逐级
|
||
// 必填校验:unified-app-id → user-ids → member-type。
|
||
func TestDevAppMemberRemoveValidateChain(t *testing.T) {
|
||
newCmd := func() *cobra.Command {
|
||
cmd := newDevAppMemberRemoveCommand(&fakeDevAppRunner{})
|
||
cmd.Flags().Bool("yes", false, "")
|
||
if err := cmd.Flags().Set("yes", "true"); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
return cmd
|
||
}
|
||
|
||
cmd := newCmd()
|
||
if err := cmd.RunE(cmd, nil); err == nil || !strings.Contains(err.Error(), "unified-app-id") {
|
||
t.Fatalf("missing unified-app-id err = %v", err)
|
||
}
|
||
|
||
cmd = newCmd()
|
||
if err := cmd.Flags().Set("unified-app-id", "APP-1"); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if err := cmd.RunE(cmd, nil); err == nil || !strings.Contains(err.Error(), "user-ids") {
|
||
t.Fatalf("missing user-ids err = %v", err)
|
||
}
|
||
|
||
cmd = newCmd()
|
||
if err := cmd.Flags().Set("unified-app-id", "APP-1"); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if err := cmd.Flags().Set("user-ids", "u1"); err != nil {
|
||
t.Fatal(err)
|
||
}
|
||
if err := cmd.RunE(cmd, nil); err == nil || !strings.Contains(err.Error(), "member-type") {
|
||
t.Fatalf("missing member-type err = %v", err)
|
||
}
|
||
}
|