From 156d95e6d1a69bfc596a0a16dcd32ddc5bd7b9eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=95=BF=E7=9C=9F?= Date: Tue, 11 Aug 2026 14:16:23 +0800 Subject: [PATCH] fix(chat): complete catalog leaf contracts --- internal/app/schema_shortcut_contract_test.go | 78 +++++++++++++++++++ internal/cli/schema_constraints_runtime.go | 6 ++ internal/helpers/chat.go | 26 +++---- 3 files changed, 97 insertions(+), 13 deletions(-) diff --git a/internal/app/schema_shortcut_contract_test.go b/internal/app/schema_shortcut_contract_test.go index a1916d8c..dd53938e 100644 --- a/internal/app/schema_shortcut_contract_test.go +++ b/internal/app/schema_shortcut_contract_test.go @@ -137,6 +137,7 @@ func TestDeliveryShortcutProgressiveQueriesReturnCompleteContracts(t *testing.T) 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( @@ -163,6 +164,83 @@ func assertSchemaSummarySafety( } } +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", true) + } + + 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"}}, + }) +} + +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 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) { leaf := executeShortcutSchemaQuery(t, "--cli-path", "doc +update") if got, want := schemaContractString(leaf["confirmation"]), "user_required"; got != want { diff --git a/internal/cli/schema_constraints_runtime.go b/internal/cli/schema_constraints_runtime.go index 686e7b76..ed9df09e 100644 --- a/internal/cli/schema_constraints_runtime.go +++ b/internal/cli/schema_constraints_runtime.go @@ -38,6 +38,11 @@ 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") registerRequireOneOf("chat.update_text_emotion", "conversation-id", "group", "id", "chat") @@ -49,6 +54,7 @@ func init() { 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") registerRequireOneOf("chat.update_notification_off", "conversation-id", "id", "chat") diff --git a/internal/helpers/chat.go b/internal/helpers/chat.go index a92a5235..794facd0 100644 --- a/internal/helpers/chat.go +++ b/internal/helpers/chat.go @@ -1361,11 +1361,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)}, }, }, @@ -1432,10 +1432,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"}, }, }, }) @@ -7846,7 +7846,7 @@ status 可选值: }, Parameters: []contract.ParamDecl{ {Name: "chat", Property: "openConversationId", Required: boolPtr(false)}, - {Name: "conversation-id", Property: "openConversationId", Required: boolPtr(false)}, + {Name: "conversation-id", Property: "openConversationId", Required: boolPtr(true)}, {Name: "id", Property: "openConversationId", Required: boolPtr(false)}, }, }, @@ -7906,7 +7906,7 @@ status 可选值: }, Parameters: []contract.ParamDecl{ {Name: "chat", Property: "openConversationId", Required: boolPtr(false)}, - {Name: "conversation-id", Property: "openConversationId", Required: boolPtr(false)}, + {Name: "conversation-id", Property: "openConversationId", Required: boolPtr(true)}, {Name: "id", Property: "openConversationId", Required: boolPtr(false)}, }, }, @@ -8078,7 +8078,7 @@ status 可选值: }, Parameters: []contract.ParamDecl{ {Name: "chat", Property: "openConversationId", Required: boolPtr(false)}, - {Name: "conversation-id", Property: "openConversationId", Required: boolPtr(false)}, + {Name: "conversation-id", Property: "openConversationId", Required: boolPtr(true)}, {Name: "id", Property: "openConversationId", Required: boolPtr(false)}, }, }, @@ -8146,7 +8146,7 @@ status 可选值: }, Parameters: []contract.ParamDecl{ {Name: "chat", Property: "openConversationId", Required: boolPtr(false)}, - {Name: "conversation-id", Property: "openConversationId", Required: boolPtr(false)}, + {Name: "conversation-id", Property: "openConversationId", Required: boolPtr(true)}, {Name: "id", Property: "openConversationId", Required: boolPtr(false)}, {Name: "message-id", Property: "openMessageId", Required: boolPtr(true)}, }, @@ -8434,7 +8434,7 @@ status 可选值: }, Parameters: []contract.ParamDecl{ {Name: "chat", Property: "openConversationId", Required: boolPtr(false)}, - {Name: "conversation-id", Property: "openConversationId", Required: boolPtr(false)}, + {Name: "conversation-id", Property: "openConversationId", Required: boolPtr(true)}, {Name: "id", Property: "openConversationId", Required: boolPtr(false)}, }, }, @@ -8494,7 +8494,7 @@ status 可选值: }, Parameters: []contract.ParamDecl{ {Name: "chat", Property: "openConversationId", Required: boolPtr(false)}, - {Name: "conversation-id", Property: "openConversationId", Required: boolPtr(false)}, + {Name: "conversation-id", Property: "openConversationId", Required: boolPtr(true)}, {Name: "id", Property: "openConversationId", Required: boolPtr(false)}, {Name: "off", Property: "mute", Required: boolPtr(false), InterfaceType: "boolean"}, }, @@ -8555,7 +8555,7 @@ status 可选值: }, Parameters: []contract.ParamDecl{ {Name: "chat", Property: "openConversationId", Required: boolPtr(false)}, - {Name: "conversation-id", Property: "openConversationId", Required: boolPtr(false)}, + {Name: "conversation-id", Property: "openConversationId", Required: boolPtr(true)}, {Name: "id", Property: "openConversationId", Required: boolPtr(false)}, {Name: "off", Property: "mute", Required: boolPtr(false), InterfaceType: "boolean"}, },