Merge branch 'main' into feat/eval-devix-poll
This commit is contained in:
@@ -25,11 +25,32 @@ func TestRuntimeSchemaCompletenessCoversPublicCommandTree(t *testing.T) {
|
||||
if !containsSchemaPath(report.Covered, "chat category create-smart") {
|
||||
t.Fatal("chat category create-smart is not covered by runtime Schema")
|
||||
}
|
||||
for _, path := range missingChatCatalogCoveragePaths() {
|
||||
if !containsSchemaPath(report.Covered, path) {
|
||||
t.Fatalf("%s is not covered by runtime Schema", path)
|
||||
}
|
||||
}
|
||||
if !containsSchemaPath(report.Excluded, "agoal strategy list") {
|
||||
t.Fatal("agoal strategy list is not recorded as a reviewed exclusion")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRuntimeSchemaCompletenessDoesNotExcludeMissingChatCatalogPaths(t *testing.T) {
|
||||
exclusions, err := cli.ReviewedRuntimeSchemaExclusions()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
excluded := map[string]bool{}
|
||||
for _, exclusion := range exclusions {
|
||||
excluded[exclusion.CLIPath] = true
|
||||
}
|
||||
for _, path := range missingChatCatalogCoveragePaths() {
|
||||
if excluded[path] {
|
||||
t.Fatalf("%s must not remain in runtime Schema exclusions", path)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func containsSchemaPath(paths []string, want string) bool {
|
||||
for _, path := range paths {
|
||||
if path == want {
|
||||
@@ -38,3 +59,38 @@ func containsSchemaPath(paths []string, want string) bool {
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func missingChatCatalogCoveragePaths() []string {
|
||||
return []string{
|
||||
"chat category add-conv",
|
||||
"chat category create",
|
||||
"chat category delete",
|
||||
"chat category remove-conv",
|
||||
"chat category rename",
|
||||
"chat chmod",
|
||||
"chat clear-all-red-point",
|
||||
"chat clear-messages",
|
||||
"chat clear-red-point",
|
||||
"chat data-auth cross-org",
|
||||
"chat group audit-join-validation",
|
||||
"chat group list-all",
|
||||
"chat group list-join-validations",
|
||||
"chat group members list-by-ids",
|
||||
"chat group notice create",
|
||||
"chat group notice edit",
|
||||
"chat group notice get",
|
||||
"chat group notice list",
|
||||
"chat group share-invite",
|
||||
"chat group update-alias",
|
||||
"chat hide",
|
||||
"chat list-all-conversations",
|
||||
"chat mark-read",
|
||||
"chat mark-unread",
|
||||
"chat message list-emotion-replies",
|
||||
"chat message set-top-msg",
|
||||
"chat message unset-top-msg",
|
||||
"chat mute-at-all",
|
||||
"chat mute-red-envelope",
|
||||
"chat text translate",
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,12 +114,14 @@ func TestDeliveryShortcutProgressiveQueriesReturnCompleteContracts(t *testing.T)
|
||||
|
||||
product := executeShortcutSchemaQuery(t, "chat")
|
||||
productPayload, _ := product["product"].(map[string]any)
|
||||
if got, want := int(product["count"].(float64)), 187; got != want {
|
||||
if got, want := int(product["count"].(float64)), 217; got != want {
|
||||
t.Fatalf("schema chat count = %d, want %d", got, want)
|
||||
}
|
||||
summaries := schemaContractObjectSlice(productPayload["tools"])
|
||||
shortcutCount := 0
|
||||
summaryByCLIPath := make(map[string]map[string]any, len(summaries))
|
||||
for _, summary := range summaries {
|
||||
summaryByCLIPath[schemaContractString(summary["cli_path"])] = summary
|
||||
if strings.HasPrefix(schemaContractString(summary["canonical_path"]), "chat.shortcut_") {
|
||||
shortcutCount++
|
||||
}
|
||||
@@ -127,6 +129,135 @@ func TestDeliveryShortcutProgressiveQueriesReturnCompleteContracts(t *testing.T)
|
||||
if shortcutCount != 98 {
|
||||
t.Fatalf("schema chat shortcut summaries = %d, want 98", shortcutCount)
|
||||
}
|
||||
for _, cliPath := range missingChatCatalogCoveragePaths() {
|
||||
if summaryByCLIPath[cliPath] == nil {
|
||||
t.Fatalf("schema chat missing expected catalog tool %q", cliPath)
|
||||
}
|
||||
}
|
||||
assertSchemaSummarySafety(t, summaryByCLIPath, "chat clear-messages", "destructive", "high", "user_required")
|
||||
assertSchemaSummarySafety(t, summaryByCLIPath, "chat data-auth cross-org", "write", "high", "user_required")
|
||||
assertSchemaSummarySafety(t, summaryByCLIPath, "chat group share-invite", "write", "medium", "user_required")
|
||||
assertChatCatalogCompleteLeafContracts(t)
|
||||
}
|
||||
|
||||
func assertSchemaSummarySafety(
|
||||
t testing.TB,
|
||||
summaries map[string]map[string]any,
|
||||
cliPath string,
|
||||
effect string,
|
||||
risk string,
|
||||
confirmation string,
|
||||
) {
|
||||
t.Helper()
|
||||
summary := summaries[cliPath]
|
||||
if summary == nil {
|
||||
t.Fatalf("schema chat missing expected catalog tool %q", cliPath)
|
||||
}
|
||||
if got := schemaContractString(summary["effect"]); got != effect {
|
||||
t.Fatalf("%s effect = %q, want %q", cliPath, got, effect)
|
||||
}
|
||||
if got := schemaContractString(summary["risk"]); got != risk {
|
||||
t.Fatalf("%s risk = %q, want %q", cliPath, got, risk)
|
||||
}
|
||||
if got := schemaContractString(summary["confirmation"]); got != confirmation {
|
||||
t.Fatalf("%s confirmation = %q, want %q", cliPath, got, confirmation)
|
||||
}
|
||||
}
|
||||
|
||||
func assertChatCatalogCompleteLeafContracts(t testing.TB) {
|
||||
t.Helper()
|
||||
for _, cliPath := range []string{
|
||||
"chat clear-messages",
|
||||
"chat clear-red-point",
|
||||
"chat hide",
|
||||
"chat mark-read",
|
||||
"chat mark-unread",
|
||||
"chat mute-at-all",
|
||||
"chat mute-red-envelope",
|
||||
} {
|
||||
leaf := executeShortcutSchemaQuery(t, "--cli-path", cliPath)
|
||||
assertSchemaLeafParameterRequired(t, leaf, cliPath, "conversation-id", false)
|
||||
assertSchemaLeafConstraints(t, leaf, cliPath, map[string]any{
|
||||
"require_one_of": [][]string{{"conversation-id", "id", "chat"}},
|
||||
"mutually_exclusive": [][]string{{"conversation-id", "id", "chat"}},
|
||||
})
|
||||
}
|
||||
|
||||
markRead := executeShortcutSchemaQuery(t, "--cli-path", "chat mark-read")
|
||||
assertSchemaLeafParameterRequired(t, markRead, "chat mark-read", "message-id", true)
|
||||
|
||||
chmod := executeShortcutSchemaQuery(t, "--cli-path", "chat chmod")
|
||||
assertSchemaLeafConstraints(t, chmod, "chat chmod", map[string]any{
|
||||
"require_one_of": [][]string{{"conversation-id", "open-dingtalk-id", "user", "permParam"}},
|
||||
"mutually_exclusive": [][]string{{"conversation-id", "open-dingtalk-id", "user"}},
|
||||
})
|
||||
assertChatGrantParameterFacts(t, chmod, "chat chmod")
|
||||
|
||||
crossOrg := executeShortcutSchemaQuery(t, "--cli-path", "chat data-auth cross-org")
|
||||
assertSchemaLeafConstraints(t, crossOrg, "chat data-auth cross-org", map[string]any{
|
||||
"require_one_of": [][]string{{"target-org-id", "all"}},
|
||||
"mutually_exclusive": [][]string{{"target-org-id", "all"}},
|
||||
})
|
||||
assertChatGrantParameterFacts(t, crossOrg, "chat data-auth cross-org")
|
||||
|
||||
shareInvite := executeShortcutSchemaQuery(t, "--cli-path", "chat group share-invite")
|
||||
assertSchemaLeafConstraints(t, shareInvite, "chat group share-invite", map[string]any{
|
||||
"require_one_of": [][]string{{"target", "receiver"}},
|
||||
"mutually_exclusive": [][]string{{"target", "receiver"}},
|
||||
})
|
||||
|
||||
auditJoin := executeShortcutSchemaQuery(t, "--cli-path", "chat group audit-join-validation")
|
||||
assertSchemaLeafParameterEnum(t, auditJoin, "chat group audit-join-validation", "status", []string{"AuditApprove", "AuditDelete"})
|
||||
}
|
||||
|
||||
func assertSchemaLeafParameterRequired(t testing.TB, leaf map[string]any, cliPath, name string, want bool) {
|
||||
t.Helper()
|
||||
parameters := schemaContractMap(leaf["parameters"])
|
||||
parameter := parameters[name]
|
||||
if parameter == nil {
|
||||
t.Fatalf("%s missing --%s parameter: %#v", cliPath, name, parameters)
|
||||
}
|
||||
if got, _ := parameter["required"].(bool); got != want {
|
||||
t.Fatalf("%s --%s required = %#v, want %v", cliPath, name, parameter["required"], want)
|
||||
}
|
||||
}
|
||||
|
||||
func assertSchemaLeafParameterEnum(t testing.TB, leaf map[string]any, cliPath, name string, want []string) {
|
||||
t.Helper()
|
||||
parameters := schemaContractMap(leaf["parameters"])
|
||||
parameter := parameters[name]
|
||||
if parameter == nil {
|
||||
t.Fatalf("%s missing --%s parameter: %#v", cliPath, name, parameters)
|
||||
}
|
||||
if got := schemaContractStringSlice(parameter["enum"]); !schemaContractJSONEqual(got, want) {
|
||||
t.Fatalf("%s --%s enum = %#v, want %#v", cliPath, name, got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func assertSchemaLeafConstraints(t testing.TB, leaf map[string]any, cliPath string, want map[string]any) {
|
||||
t.Helper()
|
||||
if got := leaf["constraints"]; !schemaContractJSONEqual(got, want) {
|
||||
t.Fatalf("%s constraints = %#v, want %#v", cliPath, got, want)
|
||||
}
|
||||
}
|
||||
|
||||
func assertChatGrantParameterFacts(t testing.TB, leaf map[string]any, cliPath string) {
|
||||
t.Helper()
|
||||
parameters := schemaContractMap(leaf["parameters"])
|
||||
grantType := parameters["grant-type"]
|
||||
if grantType == nil {
|
||||
t.Fatalf("%s missing --grant-type parameter: %#v", cliPath, parameters)
|
||||
}
|
||||
wantEnum := []string{"once", "session", "timed", "permanent"}
|
||||
if got := schemaContractStringSlice(grantType["enum"]); !schemaContractJSONEqual(got, wantEnum) {
|
||||
t.Fatalf("%s --grant-type enum = %#v, want %#v", cliPath, got, wantEnum)
|
||||
}
|
||||
if got := schemaContractString(parameters["session-id"]["required_when"]); got != "grant-type is session" {
|
||||
t.Fatalf("%s --session-id required_when = %q, want grant-type is session", cliPath, got)
|
||||
}
|
||||
if got := schemaContractString(parameters["ttl"]["required_when"]); got != "grant-type is timed" {
|
||||
t.Fatalf("%s --ttl required_when = %q, want grant-type is timed", cliPath, got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeliveryDocUpdateShortcutPublishesCompleteConditionalContract(t *testing.T) {
|
||||
|
||||
@@ -91,36 +91,6 @@ var reviewedRuntimeSchemaExclusionGroups = []runtimeSchemaExclusionGroup{
|
||||
"calendar acl add",
|
||||
"calendar acl delete",
|
||||
"calendar book update",
|
||||
"chat category add-conv",
|
||||
"chat category create",
|
||||
"chat category delete",
|
||||
"chat category remove-conv",
|
||||
"chat category rename",
|
||||
"chat chmod",
|
||||
"chat clear-all-red-point",
|
||||
"chat clear-messages",
|
||||
"chat clear-red-point",
|
||||
"chat data-auth cross-org",
|
||||
"chat group audit-join-validation",
|
||||
"chat group list-all",
|
||||
"chat group list-join-validations",
|
||||
"chat group members list-by-ids",
|
||||
"chat group notice create",
|
||||
"chat group notice edit",
|
||||
"chat group notice get",
|
||||
"chat group notice list",
|
||||
"chat group share-invite",
|
||||
"chat group update-alias",
|
||||
"chat hide",
|
||||
"chat list-all-conversations",
|
||||
"chat mark-read",
|
||||
"chat mark-unread",
|
||||
"chat message list-emotion-replies",
|
||||
"chat message set-top-msg",
|
||||
"chat message unset-top-msg",
|
||||
"chat mute-at-all",
|
||||
"chat mute-red-envelope",
|
||||
"chat text translate",
|
||||
"contact label get",
|
||||
"contact label list",
|
||||
"contact label list-members",
|
||||
|
||||
@@ -38,20 +38,33 @@ func init() {
|
||||
registerRequireTogether("calendar.update_calendar_event", "recurrence-type", "recurrence-interval", "recurrence-range-type")
|
||||
registerExclusiveOneOf("chat.search_messages_by_sender", "sender-user-id", "sender-open-dingtalk-id")
|
||||
registerExclusiveOneOf("chat.create_and_send_card", "group", "receiver")
|
||||
RegisterRuntimeSchemaConstraints("chat.chat_permission_grant", RuntimeSchemaConstraints{
|
||||
MutuallyExclusive: [][]string{{"conversation-id", "open-dingtalk-id", "user"}},
|
||||
RequireOneOf: [][]string{{"conversation-id", "open-dingtalk-id", "user", "permParam"}},
|
||||
})
|
||||
registerExclusiveOneOf("chat.chat_permission_grant_cross_org_data", "target-org-id", "all")
|
||||
registerRequireOneOf("chat.add_emoji_reaction", "conversation-id", "group", "id", "chat")
|
||||
registerRequireOneOf("chat.add_text_emotion", "conversation-id", "group", "id", "chat")
|
||||
registerExclusiveOneOf("chat.clear_conversation_messages", "conversation-id", "id", "chat")
|
||||
registerExclusiveOneOf("chat.clear_conversation_red_point", "conversation-id", "id", "chat")
|
||||
registerRequireOneOf("chat.update_text_emotion", "conversation-id", "group", "id", "chat")
|
||||
registerExclusiveOneOf("chat.get_conversation_info", "group", "user", "open-dingtalk-id")
|
||||
registerExclusiveOneOf("chat.hide_conversation", "conversation-id", "id", "chat")
|
||||
registerExclusiveOneOf("chat.list_conversation_message_v2", "group", "user", "open-dingtalk-id")
|
||||
registerExclusiveOneOf("chat.list_individual_chat_message", "user", "open-dingtalk-id")
|
||||
registerExclusiveOneOf("chat.mark_conversation_unread", "conversation-id", "id", "chat")
|
||||
registerExclusiveOneOf("chat.mark_message_read", "conversation-id", "id", "chat")
|
||||
registerRequireOneOf("chat.remove_emoji_reaction", "conversation-id", "group", "id", "chat")
|
||||
registerRequireOneOf("chat.remove_text_emotion", "conversation-id", "group", "id", "chat")
|
||||
registerRequireOneOf("chat.send_personal_message", "text", "content", "msg-type")
|
||||
registerExclusiveOneOf("chat.send_robot_message", "group", "users")
|
||||
registerRequireOneOf("chat.set_group_member_mute_list", "users", "user")
|
||||
registerExclusiveOneOf("chat.share_group_invite_url", "target", "receiver")
|
||||
registerExclusiveOneOf("chat.transfer_group_owner", "new-owner", "user")
|
||||
registerRequireOneOf("chat.update_conv_member_roles", "users", "user")
|
||||
registerExclusiveOneOf("chat.update_at_all_notification_off", "conversation-id", "id", "chat")
|
||||
registerRequireOneOf("chat.update_notification_off", "conversation-id", "id", "chat")
|
||||
registerExclusiveOneOf("chat.update_red_env_notification_off", "conversation-id", "id", "chat")
|
||||
registerRequireTogether("contact.query_dismission_employee_list", "start", "end")
|
||||
registerRequireOneOf("dev.connect_status", "robot-client-id", "unified-app-id")
|
||||
registerRequireOneOf("dev.connect_stop", "robot-client-id", "unified-app-id")
|
||||
|
||||
+53
-35
@@ -1492,6 +1492,36 @@ func buildChatCrossOrgDataAuthArgs(cmd *cobra.Command) (map[string]any, error) {
|
||||
return toolArgs, nil
|
||||
}
|
||||
|
||||
func buildChatGroupShareInviteArgs(cmd *cobra.Command) (map[string]any, error) {
|
||||
if err := validateRequiredFlags(cmd, "source"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
target, _ := cmd.Flags().GetString("target")
|
||||
receiver, _ := cmd.Flags().GetString("receiver")
|
||||
if target == "" && receiver == "" {
|
||||
return nil, fmt.Errorf("--target or --receiver is required")
|
||||
}
|
||||
if target != "" && receiver != "" {
|
||||
return nil, fmt.Errorf("--target and --receiver are mutually exclusive")
|
||||
}
|
||||
toolArgs := map[string]any{
|
||||
"sourceOpenConversationId": mustGetFlag(cmd, "source"),
|
||||
}
|
||||
if target != "" {
|
||||
toolArgs["targetOpenConversationId"] = target
|
||||
}
|
||||
if receiver != "" {
|
||||
toolArgs["receiverOpenDingTalkId"] = receiver
|
||||
}
|
||||
if v, _ := cmd.Flags().GetInt64("expires-seconds"); v > 0 || cmd.Flags().Changed("expires-seconds") {
|
||||
toolArgs["expiresSeconds"] = v
|
||||
}
|
||||
if v, _ := cmd.Flags().GetString("uuid"); v != "" {
|
||||
toolArgs["uuid"] = v
|
||||
}
|
||||
return toolArgs, nil
|
||||
}
|
||||
|
||||
func appendChatChmodParams(cmd *cobra.Command, toolArgs map[string]any) error {
|
||||
conversationID, _ := cmd.Flags().GetString("conversation-id")
|
||||
openDingTalkID, _ := cmd.Flags().GetString("open-dingtalk-id")
|
||||
@@ -1974,11 +2004,11 @@ func newChatCommand() *cobra.Command {
|
||||
Parameters: []contract.ParamDecl{
|
||||
{Name: "agentCode", Property: "agentCode", Required: boolPtr(false)},
|
||||
{Name: "conversation-id", Property: "grantParams.openCid", Required: boolPtr(false)},
|
||||
{Name: "grant-type", Property: "grantType", Required: boolPtr(false)},
|
||||
{Name: "grant-type", Property: "grantType", Required: boolPtr(false), Enum: []string{"once", "session", "timed", "permanent"}},
|
||||
{Name: "open-dingtalk-id", Property: "grantParams.openDingTalkId", Required: boolPtr(false)},
|
||||
{Name: "permParam", Property: "grantParams", Required: boolPtr(false)},
|
||||
{Name: "session-id", Property: "sessionId", Required: boolPtr(false)},
|
||||
{Name: "ttl", Property: "ttl", Required: boolPtr(false)},
|
||||
{Name: "session-id", Property: "sessionId", Required: boolPtr(false), RequiredWhen: "grant-type is session"},
|
||||
{Name: "ttl", Property: "ttl", Required: boolPtr(false), RequiredWhen: "grant-type is timed"},
|
||||
{Name: "user", Property: "grantParams.userId", Required: boolPtr(false)},
|
||||
},
|
||||
},
|
||||
@@ -2017,10 +2047,15 @@ func newChatCommand() *cobra.Command {
|
||||
chatDataAuthCrossOrgCmd.Flags().String("grant-type", "timed", "授权策略: once|session|timed|permanent")
|
||||
chatDataAuthCrossOrgCmd.Flags().String("ttl", "24h", "timed 授权有效期,如 1h/4h/24h/7d")
|
||||
chatDataAuthCrossOrgCmd.Flags().String("session-id", "", "session 授权的会话标识")
|
||||
chatDataAuthCrossOrgCmd.Flags().BoolP("yes", "y", false, "确认执行跨组织 chat 数据授权")
|
||||
DeclareLeafMetadata(chatDataAuthCrossOrgCmd, LeafSpec{
|
||||
Safety: contract.SafetySpec{
|
||||
Effect: "write", Risk: "medium",
|
||||
Confirmation: "not_required", Idempotency: "unknown",
|
||||
Effect: "write", Risk: "high",
|
||||
Confirmation: "user_required", Idempotency: "unknown",
|
||||
},
|
||||
Validate: func(cmd *cobra.Command, args []string) error {
|
||||
_, err := buildChatCrossOrgDataAuthArgs(cmd)
|
||||
return err
|
||||
},
|
||||
Contract: LeafContract{
|
||||
Identity: contract.ToolIdentitySpec{
|
||||
@@ -2045,10 +2080,10 @@ func newChatCommand() *cobra.Command {
|
||||
Parameters: []contract.ParamDecl{
|
||||
{Name: "agentCode", Property: "agentCode", Required: boolPtr(false)},
|
||||
{Name: "all", Property: "grantParams.targetOrgId", Required: boolPtr(false)},
|
||||
{Name: "grant-type", Property: "grantType", Required: boolPtr(false)},
|
||||
{Name: "session-id", Property: "sessionId", Required: boolPtr(false)},
|
||||
{Name: "grant-type", Property: "grantType", Required: boolPtr(false), Enum: []string{"once", "session", "timed", "permanent"}},
|
||||
{Name: "session-id", Property: "sessionId", Required: boolPtr(false), RequiredWhen: "grant-type is session"},
|
||||
{Name: "target-org-id", Property: "grantParams.targetOrgId", Required: boolPtr(false)},
|
||||
{Name: "ttl", Property: "ttl", Required: boolPtr(false)},
|
||||
{Name: "ttl", Property: "ttl", Required: boolPtr(false), RequiredWhen: "grant-type is timed"},
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -8227,12 +8262,16 @@ status 可选值:
|
||||
if err != nil {
|
||||
return fmt.Errorf("--record-id must be a valid integer: %w", err)
|
||||
}
|
||||
status := mustGetFlag(cmd, "status")
|
||||
if status != "AuditApprove" && status != "AuditDelete" {
|
||||
return fmt.Errorf("unsupported audit status %q, must be one of: AuditApprove, AuditDelete", status)
|
||||
}
|
||||
toolArgs := map[string]any{
|
||||
"openConversationId": mustGetFlag(cmd, "group"),
|
||||
"applyRecordId": recordID,
|
||||
"applicantUid": mustGetFlag(cmd, "applicant"),
|
||||
"inviterUid": mustGetFlag(cmd, "inviter"),
|
||||
"status": mustGetFlag(cmd, "status"),
|
||||
"status": status,
|
||||
}
|
||||
if v, _ := cmd.Flags().GetString("description"); v != "" {
|
||||
toolArgs["auditDescription"] = v
|
||||
@@ -8282,7 +8321,7 @@ status 可选值:
|
||||
{Name: "group", Property: "openConversationId", Required: boolPtr(true)},
|
||||
{Name: "inviter", Property: "inviterUid", Required: boolPtr(true)},
|
||||
{Name: "record-id", Property: "applyRecordId", Required: boolPtr(true), InterfaceType: "integer"},
|
||||
{Name: "status", Property: "status", Required: boolPtr(true), Enum: []string{"AuditApprove", "AuditDelete", "AuditIgnore", "AuditRefuse", "AuditBlock"}},
|
||||
{Name: "status", Property: "status", Required: boolPtr(true), Enum: []string{"AuditApprove", "AuditDelete"}},
|
||||
},
|
||||
},
|
||||
})
|
||||
@@ -9386,32 +9425,10 @@ status 可选值:
|
||||
dws chat group share-invite --source <openConversationId> --target <openConversationId> --expires-seconds 86400
|
||||
# 查询群 ID: dws chat search --query "群名"`,
|
||||
RunE: func(cmd *cobra.Command, args []string) error {
|
||||
if err := validateRequiredFlags(cmd, "source"); err != nil {
|
||||
toolArgs, err := buildChatGroupShareInviteArgs(cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
target, _ := cmd.Flags().GetString("target")
|
||||
receiver, _ := cmd.Flags().GetString("receiver")
|
||||
if target == "" && receiver == "" {
|
||||
return fmt.Errorf("--target or --receiver is required")
|
||||
}
|
||||
if target != "" && receiver != "" {
|
||||
return fmt.Errorf("--target and --receiver are mutually exclusive")
|
||||
}
|
||||
toolArgs := map[string]any{
|
||||
"sourceOpenConversationId": mustGetFlag(cmd, "source"),
|
||||
}
|
||||
if target != "" {
|
||||
toolArgs["targetOpenConversationId"] = target
|
||||
}
|
||||
if receiver != "" {
|
||||
toolArgs["receiverOpenDingTalkId"] = receiver
|
||||
}
|
||||
if v, _ := cmd.Flags().GetInt64("expires-seconds"); v > 0 || cmd.Flags().Changed("expires-seconds") {
|
||||
toolArgs["expiresSeconds"] = v
|
||||
}
|
||||
if v, _ := cmd.Flags().GetString("uuid"); v != "" {
|
||||
toolArgs["uuid"] = v
|
||||
}
|
||||
return callMCPToolOnServer("im", "share_group_invite_url", toolArgs)
|
||||
},
|
||||
}
|
||||
@@ -9421,10 +9438,11 @@ status 可选值:
|
||||
chatGroupShareInviteCmd.Flags().String("receiver", "", "接收分享消息的单聊用户 openDingTalkId(与 --target 二选一)")
|
||||
chatGroupShareInviteCmd.Flags().Int64("expires-seconds", 0, "链接有效期(秒),0 表示永久有效,不传使用服务端默认值")
|
||||
chatGroupShareInviteCmd.Flags().String("uuid", "", "消息幂等键(可选)")
|
||||
chatGroupShareInviteCmd.Flags().BoolP("yes", "y", false, "确认分享群邀请链接")
|
||||
DeclareLeafMetadata(chatGroupShareInviteCmd, LeafSpec{
|
||||
Safety: contract.SafetySpec{
|
||||
Effect: "write", Risk: "medium",
|
||||
Confirmation: "not_required", Idempotency: "unknown",
|
||||
Confirmation: "user_required", Idempotency: "unknown",
|
||||
},
|
||||
Contract: LeafContract{
|
||||
Identity: contract.ToolIdentitySpec{
|
||||
|
||||
@@ -746,6 +746,22 @@ func TestCrossPlatformCoverageChatAuditUsesUserIDs(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCrossPlatformCoverageChatAuditRejectsUnsupportedStatus(t *testing.T) {
|
||||
caller := &chatChangedContractCaller{}
|
||||
err := executeChatChangedContract(t, caller,
|
||||
"group", "audit-join-validation",
|
||||
"--group", "cid-1", "--record-id", "123", "--applicant", "user-a", "--inviter", "user-b", "--status", "AuditRefuse")
|
||||
if err == nil {
|
||||
t.Fatal("expected unsupported audit status error")
|
||||
}
|
||||
if !strings.Contains(err.Error(), `unsupported audit status "AuditRefuse"`) {
|
||||
t.Fatalf("error = %v, want unsupported status", err)
|
||||
}
|
||||
if len(caller.calls) != 0 {
|
||||
t.Fatalf("unsupported status must not call MCP: %#v", caller.calls)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCrossPlatformCoverageChatSendResolvesUserBeforeDispatch(t *testing.T) {
|
||||
caller := &chatChangedContractCaller{resolveUsers: true}
|
||||
err := executeChatChangedContract(t, caller, "message", "send", "--user", "123", "--text", "hello")
|
||||
|
||||
@@ -168,6 +168,77 @@ func TestCrossPlatformCoverageWukongWeeklyChatCategoryQueries(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestCrossPlatformCoverageChatDataAuthCrossOrgRequiresConfirmation(t *testing.T) {
|
||||
caller := &wukongWeeklySyncCaller{}
|
||||
_, _, err := executeWukongWeeklySyncCommand(t, "chat", caller, newChatCommand,
|
||||
"data-auth", "cross-org", "--target-org-id", "439446171")
|
||||
requireWukongWeeklySyncConfirmation(t, err)
|
||||
requireWukongWeeklySyncNoCalls(t, caller)
|
||||
|
||||
caller = &wukongWeeklySyncCaller{}
|
||||
_, _, err = executeWukongWeeklySyncCommand(t, "chat", caller, newChatCommand,
|
||||
"data-auth", "cross-org", "--target-org-id", "439446171", "--yes")
|
||||
if err != nil {
|
||||
t.Fatalf("confirmed cross-org data auth returned error: %v", err)
|
||||
}
|
||||
requireWukongWeeklySyncCall(t, caller, wukongWeeklySyncCall{
|
||||
server: "im",
|
||||
tool: "chat_permission_grant",
|
||||
args: map[string]any{
|
||||
"agentCode": "wukong",
|
||||
"grantCategory": "data",
|
||||
"grantParams": `{"targetOrgId":"439446171"}`,
|
||||
"grantType": "timed",
|
||||
"scope": "chat.data:cross-org",
|
||||
"ttl": "24h",
|
||||
},
|
||||
})
|
||||
if _, ok := caller.calls[0].args["yes"]; ok {
|
||||
t.Fatalf("confirmed payload leaked yes flag: %#v", caller.calls[0].args)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCrossPlatformCoverageChatGroupShareInviteRequiresConfirmation(t *testing.T) {
|
||||
caller := &wukongWeeklySyncCaller{}
|
||||
_, _, err := executeWukongWeeklySyncCommand(t, "chat", caller, newChatCommand,
|
||||
"group", "share-invite", "--target", "cid-target")
|
||||
if err == nil || !strings.Contains(err.Error(), "source") {
|
||||
t.Fatalf("missing source error = %v, want source", err)
|
||||
}
|
||||
requireWukongWeeklySyncNoCalls(t, caller)
|
||||
|
||||
caller = &wukongWeeklySyncCaller{}
|
||||
_, _, err = executeWukongWeeklySyncCommand(t, "chat", caller, newChatCommand,
|
||||
"group", "share-invite", "--source", "cid-source", "--target", "cid-target")
|
||||
requireWukongWeeklySyncConfirmation(t, err)
|
||||
requireWukongWeeklySyncNoCalls(t, caller)
|
||||
|
||||
caller = &wukongWeeklySyncCaller{}
|
||||
_, _, err = executeWukongWeeklySyncCommand(t, "chat", caller, newChatCommand,
|
||||
"group", "share-invite",
|
||||
"--source", "cid-source",
|
||||
"--target", "cid-target",
|
||||
"--expires-seconds", "86400",
|
||||
"--uuid", "invite-uuid",
|
||||
"--yes")
|
||||
if err != nil {
|
||||
t.Fatalf("confirmed share-invite returned error: %v", err)
|
||||
}
|
||||
requireWukongWeeklySyncCall(t, caller, wukongWeeklySyncCall{
|
||||
server: "im",
|
||||
tool: "share_group_invite_url",
|
||||
args: map[string]any{
|
||||
"expiresSeconds": int64(86400),
|
||||
"sourceOpenConversationId": "cid-source",
|
||||
"targetOpenConversationId": "cid-target",
|
||||
"uuid": "invite-uuid",
|
||||
},
|
||||
})
|
||||
if _, ok := caller.calls[0].args["yes"]; ok {
|
||||
t.Fatalf("confirmed payload leaked yes flag: %#v", caller.calls[0].args)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCrossPlatformCoverageWukongWeeklyChatMessageEditValidation(t *testing.T) {
|
||||
root := newChatCommand()
|
||||
edit, _, findErr := root.Find([]string{"message", "edit"})
|
||||
|
||||
Reference in New Issue
Block a user