mirror of
https://github.com/larksuite/cli.git
synced 2026-08-03 08:32:46 +08:00
feat(im): merge chat-members-add batch results into a ledger
This commit is contained in:
@@ -4,10 +4,14 @@
|
||||
package im
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"github.com/larksuite/cli/errs"
|
||||
"github.com/larksuite/cli/internal/validate"
|
||||
"github.com/larksuite/cli/shortcuts/common"
|
||||
larkcore "github.com/larksuite/oapi-sdk-go/v3/core"
|
||||
)
|
||||
|
||||
const imChatMembersAddPathFmt = "/open-apis/im/v1/chats/%s/members"
|
||||
@@ -70,3 +74,85 @@ func validateChatMembersAdd(runtime *common.RuntimeContext) error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// chatMembersAddResult is the merged ledger across the users-call and the
|
||||
// bots-call.
|
||||
type chatMembersAddResult struct {
|
||||
succeeded []string
|
||||
invalid []string
|
||||
notExisted []string
|
||||
pendingApproval []string
|
||||
callErrors []map[string]interface{}
|
||||
}
|
||||
|
||||
func newChatMembersAddResult() *chatMembersAddResult {
|
||||
return &chatMembersAddResult{
|
||||
succeeded: []string{},
|
||||
invalid: []string{},
|
||||
notExisted: []string{},
|
||||
pendingApproval: []string{},
|
||||
callErrors: []map[string]interface{}{},
|
||||
}
|
||||
}
|
||||
|
||||
// addChatMembersBatch issues one chat.members.create call for a single
|
||||
// member_id_type and folds the outcome into res. A full-call error (e.g.
|
||||
// missing scope, chat-wide bot cap exceeded) is recorded as a call_errors
|
||||
// entry carrying the affected id_list, rather than aborting the other call.
|
||||
func addChatMembersBatch(runtime *common.RuntimeContext, chatID, memberType, memberIDType string, ids []string, res *chatMembersAddResult) {
|
||||
path := fmt.Sprintf(imChatMembersAddPathFmt, validate.EncodePathSegment(chatID))
|
||||
data, err := runtime.DoAPIJSONTyped(http.MethodPost, path,
|
||||
larkcore.QueryParams{
|
||||
"member_id_type": []string{memberIDType},
|
||||
"succeed_type": []string{"1"},
|
||||
},
|
||||
map[string]interface{}{"id_list": ids},
|
||||
)
|
||||
if err != nil {
|
||||
res.callErrors = append(res.callErrors, map[string]interface{}{
|
||||
"member_type": memberType,
|
||||
"id_list": ids,
|
||||
"error": err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
invalid := stringsFromAny(data["invalid_id_list"])
|
||||
notExisted := stringsFromAny(data["not_existed_id_list"])
|
||||
pending := stringsFromAny(data["pending_approval_id_list"])
|
||||
res.invalid = append(res.invalid, invalid...)
|
||||
res.notExisted = append(res.notExisted, notExisted...)
|
||||
res.pendingApproval = append(res.pendingApproval, pending...)
|
||||
|
||||
failed := make(map[string]struct{}, len(invalid)+len(notExisted)+len(pending))
|
||||
for _, id := range invalid {
|
||||
failed[id] = struct{}{}
|
||||
}
|
||||
for _, id := range notExisted {
|
||||
failed[id] = struct{}{}
|
||||
}
|
||||
for _, id := range pending {
|
||||
failed[id] = struct{}{}
|
||||
}
|
||||
for _, id := range ids {
|
||||
if _, isFailed := failed[id]; !isFailed {
|
||||
res.succeeded = append(res.succeeded, id)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// stringsFromAny converts a JSON-decoded []interface{} of strings to []string,
|
||||
// skipping any non-string entries defensively.
|
||||
func stringsFromAny(v interface{}) []string {
|
||||
arr, ok := v.([]interface{})
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
out := make([]string, 0, len(arr))
|
||||
for _, item := range arr {
|
||||
if s, ok := item.(string); ok {
|
||||
out = append(out, s)
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
@@ -152,3 +152,69 @@ func newChatMembersAddTestRuntime(t *testing.T, rtRoundTripper http.RoundTripper
|
||||
runtime.Cmd = cmd
|
||||
return runtime
|
||||
}
|
||||
|
||||
func TestAddChatMembersBatch_AllSucceed(t *testing.T) {
|
||||
rt := newUserShortcutRuntime(t, shortcutRoundTripFunc(func(req *http.Request) (*http.Response, error) {
|
||||
return shortcutJSONResponse(200, map[string]interface{}{"code": 0, "data": map[string]interface{}{}}), nil
|
||||
}))
|
||||
res := newChatMembersAddResult()
|
||||
addChatMembersBatch(rt, "oc_x", "user", "open_id", []string{"ou_a", "ou_b"}, res)
|
||||
|
||||
if !equalStringSlices(res.succeeded, []string{"ou_a", "ou_b"}) {
|
||||
t.Errorf("succeeded = %v, want [ou_a ou_b]", res.succeeded)
|
||||
}
|
||||
if len(res.invalid) != 0 || len(res.notExisted) != 0 || len(res.pendingApproval) != 0 || len(res.callErrors) != 0 {
|
||||
t.Errorf("expected no failures, got %+v", res)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddChatMembersBatch_PartialFailure(t *testing.T) {
|
||||
rt := newUserShortcutRuntime(t, shortcutRoundTripFunc(func(req *http.Request) (*http.Response, error) {
|
||||
return shortcutJSONResponse(200, map[string]interface{}{
|
||||
"code": 0,
|
||||
"data": map[string]interface{}{
|
||||
"invalid_id_list": []interface{}{"ou_c"},
|
||||
"not_existed_id_list": []interface{}{"ou_d"},
|
||||
"pending_approval_id_list": []interface{}{"ou_e"},
|
||||
},
|
||||
}), nil
|
||||
}))
|
||||
res := newChatMembersAddResult()
|
||||
addChatMembersBatch(rt, "oc_x", "user", "open_id", []string{"ou_a", "ou_b", "ou_c", "ou_d", "ou_e"}, res)
|
||||
|
||||
if !equalStringSlices(res.succeeded, []string{"ou_a", "ou_b"}) {
|
||||
t.Errorf("succeeded = %v, want [ou_a ou_b]", res.succeeded)
|
||||
}
|
||||
if !equalStringSlices(res.invalid, []string{"ou_c"}) {
|
||||
t.Errorf("invalid = %v, want [ou_c]", res.invalid)
|
||||
}
|
||||
if !equalStringSlices(res.notExisted, []string{"ou_d"}) {
|
||||
t.Errorf("notExisted = %v, want [ou_d]", res.notExisted)
|
||||
}
|
||||
if !equalStringSlices(res.pendingApproval, []string{"ou_e"}) {
|
||||
t.Errorf("pendingApproval = %v, want [ou_e]", res.pendingApproval)
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddChatMembersBatch_CallLevelFailure(t *testing.T) {
|
||||
rt := newUserShortcutRuntime(t, shortcutRoundTripFunc(func(req *http.Request) (*http.Response, error) {
|
||||
return shortcutJSONResponse(400, map[string]interface{}{"code": 123, "msg": "bot count exceeds chat limit"}), nil
|
||||
}))
|
||||
res := newChatMembersAddResult()
|
||||
addChatMembersBatch(rt, "oc_x", "bot", "app_id", []string{"cli_y"}, res)
|
||||
|
||||
if len(res.succeeded) != 0 {
|
||||
t.Errorf("succeeded = %v, want empty (call failed)", res.succeeded)
|
||||
}
|
||||
if len(res.callErrors) != 1 {
|
||||
t.Fatalf("callErrors = %v, want 1 entry", res.callErrors)
|
||||
}
|
||||
ce := res.callErrors[0]
|
||||
if ce["member_type"] != "bot" {
|
||||
t.Errorf("call_errors[0].member_type = %v, want bot", ce["member_type"])
|
||||
}
|
||||
ids, _ := ce["id_list"].([]string)
|
||||
if !equalStringSlices(ids, []string{"cli_y"}) {
|
||||
t.Errorf("call_errors[0].id_list = %v, want [cli_y]", ids)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user