Files
dingtalk-workspace-cli/internal/shortcut/smart/resolve_test.go
DennisandClaude Opus 4.8 d41214988a fix(shortcut): keep external contacts in name resolution; alias resource-url msg-id
Two independent shortcut correctness fixes surfaced by the audit:

- Name→ID resolution (chat +dm / +broadcast / … via the shared resolver) dropped
  every search_contact_by_key_word row with an empty userId. External /
  cross-org contacts arrive with only an openDingTalkId, so they were silently
  discarded — making resolution report a real person as missing, or collapse to
  the wrong single match when an in-org namesake existed. Keep any row with at
  least one usable identity (userId or openDingTalkId) and fall the display name
  back through nick/showName/flowerName/staffName/userName.

- chat +messages-resource-url required --message-id with no alias, so an agent
  copying the message list's openMessageId/msgId output field hit "unknown
  flag". Accept --msg-id / --open-message-id as aliases (declared via an
  at-least-one constraint since a shortcut's Required check only sees the
  primary flag name), mirroring the earlier chat message download-media fix.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-28 20:11:26 +08:00

68 lines
2.4 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 smart
import "testing"
func TestExtractUsers(t *testing.T) {
data := map[string]any{
"result": []any{
// in-org contact: full identity
map[string]any{"userId": "024083", "name": "朱鸿", "openDingTalkId": "DHUGO"},
// external / cross-org contact: no userId, only openDingTalkId +
// display name under a non-"name" key — must be kept, not dropped.
map[string]any{"openDingtalkId": "DEXT", "nick": "外部小王"},
// unusable row (no identity at all) is skipped
map[string]any{"name": "无ID的人"},
// non-map entry is skipped
"garbage",
},
}
users := extractUsers(data)
if len(users) != 2 {
t.Fatalf("extractUsers kept %d, want 2: %#v", len(users), users)
}
if users[0].userID != "024083" || users[0].openDingTalkID != "DHUGO" || users[0].name != "朱鸿" {
t.Errorf("in-org user = %#v", users[0])
}
// external contact kept via openDingTalkId, name resolved from "nick"
if users[1].userID != "" || users[1].openDingTalkID != "DEXT" || users[1].name != "外部小王" {
t.Errorf("external user = %#v", users[1])
}
}
func TestExtractUsersNoResult(t *testing.T) {
if got := extractUsers(map[string]any{}); got != nil {
t.Errorf("no result should be nil, got %#v", got)
}
}
func TestUsersWithUserIDExcludesOpenIDOnlyContacts(t *testing.T) {
users := []contactUser{
{userID: "user-1", openDingTalkID: "open-1", name: "内部用户"},
{openDingTalkID: "open-external", name: "外部联系人"},
}
got := usersWithUserID(users)
if len(got) != 1 || got[0].userID != "user-1" {
t.Fatalf("usersWithUserID() = %#v, want only the organization user", got)
}
}
func TestUserLabelsFallsBackToOpenDingTalkID(t *testing.T) {
got := userLabels([]contactUser{{openDingTalkID: "open-external", name: "外部联系人"}})
if len(got) != 1 || got[0] != "外部联系人(open-external)" {
t.Fatalf("userLabels() = %#v", got)
}
}