Files
dingtalk-workspace-cli/internal/event/personal/client_test.go
aa6abc5ed6 feat(event): add personal event subscriptions (#589)
* dws event

* fix consume fail

* test: add stream ticket injection probe

* feat: add portal ticket stream mode

* user event

* fix: allow portal ticket normal without app secret

* event

* user event

* eventType filter

* refactor(event): 优化IPC端点路径处理和改进相关测试

- 用dwsevent.IPCEndpoint替代原先根据GOOS判断的路径逻辑
- 新增event包实现Unix socket路径长度限制及长路径fallback机制
- 添加endpoint_test.go覆盖路径短长及唯一性的单元测试
- 修改busctl模块使用统一的IPC端点获取方法,避免重复实现
- transport_unix.go新增checkSocketPath函数检查路径长度,防止EINVAL错误
- 在监听和连接Unix socket时加入路径限制检查,提升错误明晰度
- 去除多个文件中无用的runtime导入,简化代码依赖

* opt

* event skill

* default value

* install script event

* fix: remove subscribe id event fanout filter

* fix(personal): 修正指定发送人消息描述错误

* more im event

* filter subId

* fix: align personal event schema with stream payload

* fix: avoid duplicate app helper name

* feat: simplify personal event schemas

* feat: simplify event schema output

* docs: refine dingtalk event skill references

* feat: align personal event consume flags

* fix event stop and status visibility

* hide app event public entrypoints

* hide incomplete personal sender event

* remove external event reference comments

* chore(event): prepare official release

* fix(event): harden personal stream lifecycle

---------

Co-authored-by: 玉澜 <yulan.wqy@alibaba-inc.com>
Co-authored-by: zhengyubai <zhengyubai618@gmail.com>
2026-07-10 17:54:43 +08:00

647 lines
20 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 personal
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"log/slog"
"net/http"
"net/http/httptest"
"strconv"
"strings"
"testing"
)
func TestClientCreateSubscriptionDWSRequestAndArrayResponse(t *testing.T) {
var gotPath string
var gotReq dwsCreateSubscriptionRequest
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
t.Fatalf("method = %s", r.Method)
}
gotPath = r.URL.Path
if got := r.Header.Get("Authorization"); got != "Bearer token-1" {
t.Fatalf("Authorization = %q", got)
}
if got := r.Header.Get("x-user-access-token"); got != "token-1" {
t.Fatalf("x-user-access-token = %q", got)
}
if got := r.Header.Get("X-DWS-Client-Id"); got != "client-1" {
t.Fatalf("X-DWS-Client-Id = %q", got)
}
if got := r.Header.Get("X-DWS-Source-Id"); got != "open" {
t.Fatalf("X-DWS-Source-Id = %q", got)
}
if got := r.Header.Get("X-DWS-Corp-Id"); got != "corp-1" {
t.Fatalf("X-DWS-Corp-Id = %q", got)
}
if err := json.NewDecoder(r.Body).Decode(&gotReq); err != nil {
t.Fatalf("Decode body: %v", err)
}
_ = json.NewEncoder(w).Encode(map[string]any{
"success": true,
"result": []string{"sub-1"},
})
}))
defer srv.Close()
c := NewClient(srv.URL, Identity{
AccessToken: "token-1",
CorpID: "corp-1",
UserID: "user-1",
ClientID: "client-1",
SourceID: "open",
})
sub, err := c.CreateSubscription(t.Context(), CreateSubscriptionRequest{
EventKey: EventSingleChat,
RuleType: "singleChat",
Name: "test-o2o",
RuleParam: map[string]any{
"targetUid": "507971",
"targetUidType": "staffId",
},
Filter: map[string]any{"field": "payload.body.content", "op": "contains", "value": "P0"},
IdempotencyKey: "idem-1",
})
if err != nil {
t.Fatalf("CreateSubscription() error = %v", err)
}
if gotPath != "/subscription/user" {
t.Fatalf("path = %q, want /subscription/user", gotPath)
}
if gotReq.ClientID != "client-1" || gotReq.SourceID != "open" || gotReq.EventKey != EventSingleChat {
t.Fatalf("request identity/event = %#v", gotReq)
}
if gotReq.DeliveryPref != "realtime" {
t.Fatalf("deliveryPref = %q, want realtime", gotReq.DeliveryPref)
}
var filterRule map[string]any
if err := json.Unmarshal([]byte(gotReq.FilterRule), &filterRule); err != nil {
t.Fatalf("filterRule is not JSON: %q: %v", gotReq.FilterRule, err)
}
if filterRule["targetUid"] != "507971" || filterRule["targetUidType"] != "staffId" {
t.Fatalf("filterRule = %#v", filterRule)
}
if gotReq.Ext["ruleType"] != "singleChat" || gotReq.Ext["name"] != "test-o2o" || gotReq.Ext["idempotencyKey"] != "idem-1" {
t.Fatalf("ext = %#v", gotReq.Ext)
}
if sub.SubscribeID != "sub-1" {
t.Fatalf("subscribe_id = %q", sub.SubscribeID)
}
if sub.EventKey != EventSingleChat || sub.RuleType != "singleChat" || sub.Status != "active" || sub.SourceID != "open" {
t.Fatalf("subscription = %#v", sub)
}
}
func TestClientCreateSubscriptionObjectResponses(t *testing.T) {
cases := []map[string]any{
{"subId": "sub-camel", "eventKey": EventMention, "sourceId": "open", "status": 1},
{"subscribe_id": "sub-snake", "event_key": EventMention, "source_id": "open", "status": "active"},
}
for _, result := range cases {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_ = json.NewEncoder(w).Encode(map[string]any{
"success": true,
"result": result,
})
}))
c := NewClient(srv.URL, Identity{AccessToken: "token-1", ClientID: "client-1", SourceID: "open"})
sub, err := c.CreateSubscription(t.Context(), CreateSubscriptionRequest{
EventKey: EventMention,
RuleType: "at",
RuleParam: map[string]any{},
})
srv.Close()
if err != nil {
t.Fatalf("CreateSubscription() error = %v", err)
}
if sub.SubscribeID == "" || !strings.HasPrefix(sub.SubscribeID, "sub-") {
t.Fatalf("subscription = %#v", sub)
}
}
}
func TestClientDebugLogCreateSubscriptionRequestResponse(t *testing.T) {
logs := captureClientDebugLogs(t)
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_ = json.NewEncoder(w).Encode(map[string]any{
"success": true,
"requestId": "req-ok",
"result": []string{"sub-1"},
})
}))
defer srv.Close()
c := NewClient(srv.URL, Identity{AccessToken: "secret-token", ClientID: "client-1", SourceID: "pre_open_source"})
if _, err := c.CreateSubscription(t.Context(), CreateSubscriptionRequest{
EventKey: EventSingleChat,
RuleType: "singleChat",
RuleParam: map[string]any{
"targetUid": "507971",
"targetUidType": "staffId",
},
IdempotencyKey: "idem-1",
}); err != nil {
t.Fatalf("CreateSubscription() error = %v", err)
}
out := logs.String()
for _, want := range []string{
"personal event control request",
"/subscription/user",
"client-1",
"pre_open_source",
EventSingleChat,
"filterRule",
"targetUid",
"507971",
"sub-1",
"req-ok",
} {
if !strings.Contains(out, want) {
t.Fatalf("debug log missing %q: %s", want, out)
}
}
if strings.Contains(out, "secret-token") {
t.Fatalf("debug log leaked access token: %s", out)
}
}
func TestClientBusinessErrorHTTP200(t *testing.T) {
logs := captureClientDebugLogs(t)
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_ = json.NewEncoder(w).Encode(map[string]any{
"success": false,
"requestId": "req-1",
"errorCode": "INVALID_PARAM",
"errorMsg": "clientId is empty",
})
}))
defer srv.Close()
c := NewClient(srv.URL, Identity{AccessToken: "token-1", ClientID: "client-1", SourceID: "open"})
_, err := c.CreateSubscription(t.Context(), CreateSubscriptionRequest{
EventKey: EventMention,
RuleType: "at",
RuleParam: map[string]any{},
})
if err == nil || !strings.Contains(err.Error(), "INVALID_PARAM") || !strings.Contains(err.Error(), "clientId is empty") {
t.Fatalf("error = %v, want INVALID_PARAM business error", err)
}
var apiErr *APIError
if !errors.As(err, &apiErr) {
t.Fatalf("error type = %T, want *APIError", err)
}
if apiErr.Details["method"] != http.MethodPost || apiErr.Details["path"] != "/subscription/user" ||
apiErr.Details["http_status"] != http.StatusOK || apiErr.Details["request_id"] != "req-1" {
t.Fatalf("details = %#v", apiErr.Details)
}
out := logs.String()
for _, want := range []string{"/subscription/user", "INVALID_PARAM", "clientId is empty", "req-1", "request", "response"} {
if !strings.Contains(out, want) {
t.Fatalf("debug log missing %q: %s", want, out)
}
}
}
func TestClientOmitsCorpHeaderWhenUnknown(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if got := r.Header.Get("X-DWS-Corp-Id"); got != "" {
t.Fatalf("X-DWS-Corp-Id = %q, want empty", got)
}
if got := r.Header.Get("Authorization"); got != "Bearer token-1" {
t.Fatalf("Authorization = %q", got)
}
_ = json.NewEncoder(w).Encode(map[string]any{
"success": true,
"result": []string{"sub-1"},
})
}))
defer srv.Close()
c := NewClient(srv.URL, Identity{AccessToken: "token-1", ClientID: "client-1", SourceID: "open"})
if _, err := c.CreateSubscription(t.Context(), CreateSubscriptionRequest{
EventKey: EventMention,
RuleType: "at",
RuleParam: map[string]any{},
}); err != nil {
t.Fatalf("CreateSubscription() error = %v", err)
}
}
func TestIdentityKeyUsesLocalSubjectFallback(t *testing.T) {
withCorpUser := Identity{CorpID: "corp-1", UserID: "user-1", ClientID: "client-1", SourceID: "open"}
if got := withCorpUser.Key(); got != "corp_user\x00corp-1\x00user-1\x00client-1\x00open" {
t.Fatalf("corp/user key = %q", got)
}
fallback := Identity{LocalSubject: "refresh:abc", ClientID: "client-1", SourceID: "open"}
if got := fallback.Key(); got != "local_subject\x00refresh:abc\x00client-1\x00open" {
t.Fatalf("fallback key = %q", got)
}
}
func TestClientDeleteSubscriptionTreatsNotFoundAsSuccess(t *testing.T) {
var gotBody map[string]string
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodPost {
t.Fatalf("method = %s", r.Method)
}
if r.URL.Path != "/subscription/cancel" {
t.Fatalf("path = %q", r.URL.Path)
}
if err := json.NewDecoder(r.Body).Decode(&gotBody); err != nil {
t.Fatalf("decode body: %v", err)
}
_ = json.NewEncoder(w).Encode(map[string]any{
"success": false,
"error": map[string]any{"code": "PERSONAL_EVENT_NOT_FOUND", "message": "not found"},
})
}))
defer srv.Close()
c := NewClient(srv.URL, Identity{AccessToken: "token", ClientID: "client", SourceID: "open"})
if err := c.DeleteSubscription(t.Context(), "sub-404"); err != nil {
t.Fatalf("DeleteSubscription() error = %v", err)
}
if gotBody["subId"] != "sub-404" {
t.Fatalf("cancel body = %#v", gotBody)
}
}
func TestClientDeleteSubscriptionBusinessError(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_ = json.NewEncoder(w).Encode(map[string]any{
"success": false,
"requestId": "req-cancel",
"errorCode": "INVALID_STATE",
"errorMsg": "subscription cannot be cancelled",
})
}))
defer srv.Close()
c := NewClient(srv.URL, Identity{AccessToken: "token-1", ClientID: "client-1", SourceID: "open"})
err := c.DeleteSubscription(t.Context(), "sub-1")
if err == nil || !strings.Contains(err.Error(), "INVALID_STATE") {
t.Fatalf("DeleteSubscription() error = %v, want INVALID_STATE", err)
}
var apiErr *APIError
if !errors.As(err, &apiErr) {
t.Fatalf("error type = %T, want *APIError", err)
}
if apiErr.Details["path"] != "/subscription/cancel" || apiErr.Details["request_id"] != "req-cancel" {
t.Fatalf("details = %#v", apiErr.Details)
}
}
func TestClientDebugLogListAndDeleteSubscription(t *testing.T) {
logs := captureClientDebugLogs(t)
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/event/sublist":
_ = json.NewEncoder(w).Encode(map[string]any{
"success": true,
"result": map[string]any{"items": []map[string]any{}},
})
case "/subscription/cancel":
_ = json.NewEncoder(w).Encode(map[string]any{"success": true})
default:
t.Fatalf("unexpected path %q", r.URL.Path)
}
}))
defer srv.Close()
c := NewClient(srv.URL, Identity{AccessToken: "token", ClientID: "client", SourceID: "open"})
if _, err := c.ListSubscriptions(t.Context(), ListOptions{Status: "active"}); err != nil {
t.Fatalf("ListSubscriptions() error = %v", err)
}
if err := c.DeleteSubscription(t.Context(), "sub-1"); err != nil {
t.Fatalf("DeleteSubscription() error = %v", err)
}
out := logs.String()
for _, want := range []string{"/event/sublist", "clientId=client", "sourceId=open", "/subscription/cancel", "sub-1"} {
if !strings.Contains(out, want) {
t.Fatalf("debug log missing %q: %s", want, out)
}
}
}
func TestClientDebugLogRedactsSensitivePayloadFields(t *testing.T) {
logs := captureClientDebugLogs(t)
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_ = json.NewEncoder(w).Encode(map[string]any{
"success": true,
"requestId": "req-secret",
"result": map[string]any{
"access_token": "resp-access-token",
"client_secret": "resp-client-secret",
"ticket": "resp-ticket",
"Authorization": "Bearer resp-auth",
"safe": "ok",
},
})
}))
defer srv.Close()
c := NewClient(srv.URL, Identity{AccessToken: "header-token-secret", ClientID: "client", SourceID: "open"})
err := c.do(t.Context(), http.MethodPost, "/subscription/user", nil, map[string]any{
"access_token": "req-access-token",
"client_secret": "req-client-secret",
"ticket": "req-ticket",
"Authorization": "Bearer req-auth",
"safe": "ok",
}, nil)
if err != nil {
t.Fatalf("do() error = %v", err)
}
out := logs.String()
for _, leaked := range []string{
"header-token-secret",
"req-access-token",
"req-client-secret",
"req-ticket",
"Bearer req-auth",
"resp-access-token",
"resp-client-secret",
"resp-ticket",
"Bearer resp-auth",
} {
if strings.Contains(out, leaked) {
t.Fatalf("debug log leaked %q: %s", leaked, out)
}
}
for _, want := range []string{"<redacted>", "safe", "ok", "req-secret"} {
if !strings.Contains(out, want) {
t.Fatalf("debug log missing %q: %s", want, out)
}
}
}
func TestClientListSubscriptionsDWSSublist(t *testing.T) {
var gotQuery string
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/event/sublist" {
t.Fatalf("path = %q", r.URL.Path)
}
gotQuery = r.URL.RawQuery
_ = json.NewEncoder(w).Encode(map[string]any{
"success": true,
"result": map[string]any{
"total": 2,
"pageNo": 1,
"pageSize": 20,
"items": []map[string]any{
{
"subId": "sub-1",
"eventKey": EventSingleChat,
"sourceId": "open",
"deliveryPref": "realtime",
"status": 1,
"gmtCreate": "2026-06-29T10:00:00Z",
},
{
"subId": "sub-2",
"eventKey": EventMention,
"sourceId": "open",
"status": 3,
},
},
},
})
}))
defer srv.Close()
c := NewClient(srv.URL, Identity{AccessToken: "token", ClientID: "client", SourceID: "open"})
subs, err := c.ListSubscriptions(t.Context(), ListOptions{Status: "active", EventKey: EventSingleChat})
if err != nil {
t.Fatalf("ListSubscriptions() error = %v", err)
}
if !strings.Contains(gotQuery, "clientId=client") || !strings.Contains(gotQuery, "sourceId=open") ||
!strings.Contains(gotQuery, "pageNo=1") || !strings.Contains(gotQuery, "pageSize=100") {
t.Fatalf("query = %q", gotQuery)
}
if len(subs) != 1 || subs[0].SubscribeID != "sub-1" || subs[0].Status != "active" || subs[0].CreatedAt == "" {
t.Fatalf("subs = %#v", subs)
}
}
func TestClientGetSubscriptionFiltersSublist(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
_ = json.NewEncoder(w).Encode(map[string]any{
"success": true,
"result": map[string]any{
"items": []map[string]any{
{"subId": "sub-1", "eventKey": EventMention, "sourceId": "open", "status": 1},
{"subId": "sub-2", "eventKey": EventSingleChat, "sourceId": "open", "status": 1},
},
},
})
}))
defer srv.Close()
c := NewClient(srv.URL, Identity{AccessToken: "token", ClientID: "client", SourceID: "open"})
sub, err := c.GetSubscription(t.Context(), "sub-2")
if err != nil {
t.Fatalf("GetSubscription() error = %v", err)
}
if sub.SubscribeID != "sub-2" || sub.EventKey != EventSingleChat {
t.Fatalf("subscription = %#v", sub)
}
}
func TestClientListSubscriptionsPaginatesAllResults(t *testing.T) {
var pages []int
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
pageNo, err := strconv.Atoi(r.URL.Query().Get("pageNo"))
if err != nil {
t.Fatalf("pageNo = %q", r.URL.Query().Get("pageNo"))
}
pages = append(pages, pageNo)
start := (pageNo - 1) * subscriptionListPageSize
end := start + subscriptionListPageSize
if end > 205 {
end = 205
}
_ = json.NewEncoder(w).Encode(map[string]any{
"success": true,
"result": map[string]any{
"total": 205,
"items": dwsSubscriptionTestItems(start, end),
},
})
}))
defer srv.Close()
c := NewClient(srv.URL, Identity{AccessToken: "token", ClientID: "client", SourceID: "open"})
subs, err := c.ListSubscriptions(t.Context(), ListOptions{})
if err != nil {
t.Fatalf("ListSubscriptions() error = %v", err)
}
if len(subs) != 205 || subs[204].SubscribeID != "sub-204" {
t.Fatalf("subscriptions = %d, last = %#v", len(subs), subs[len(subs)-1])
}
if fmt.Sprint(pages) != "[1 2 3]" {
t.Fatalf("pages = %v, want [1 2 3]", pages)
}
}
func TestClientGetSubscriptionFindsLaterPage(t *testing.T) {
var calls int
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
calls++
pageNo, _ := strconv.Atoi(r.URL.Query().Get("pageNo"))
items := dwsSubscriptionTestItems(0, 100)
if pageNo == 2 {
items = dwsSubscriptionTestItems(100, 101)
}
_ = json.NewEncoder(w).Encode(map[string]any{
"success": true,
"result": map[string]any{"total": 101, "items": items},
})
}))
defer srv.Close()
c := NewClient(srv.URL, Identity{AccessToken: "token", ClientID: "client", SourceID: "open"})
sub, err := c.GetSubscription(t.Context(), "sub-100")
if err != nil {
t.Fatalf("GetSubscription() error = %v", err)
}
if sub.SubscribeID != "sub-100" || calls != 2 {
t.Fatalf("subscription = %#v, calls = %d", sub, calls)
}
}
func TestClientListSubscriptionsWithoutTotalStopsOnEmptyPage(t *testing.T) {
var calls int
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
calls++
items := dwsSubscriptionTestItems(0, 100)
if r.URL.Query().Get("pageNo") == "2" {
items = nil
}
_ = json.NewEncoder(w).Encode(map[string]any{
"success": true,
"result": map[string]any{"items": items},
})
}))
defer srv.Close()
c := NewClient(srv.URL, Identity{AccessToken: "token", ClientID: "client", SourceID: "open"})
subs, err := c.ListSubscriptions(t.Context(), ListOptions{})
if err != nil {
t.Fatal(err)
}
if len(subs) != 100 || calls != 2 {
t.Fatalf("subscriptions = %d, calls = %d", len(subs), calls)
}
}
func TestClientListSubscriptionsUsesServerPageSize(t *testing.T) {
var calls int
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
calls++
pageNo, _ := strconv.Atoi(r.URL.Query().Get("pageNo"))
start := (pageNo - 1) * 20
end := start + 20
if end > 45 {
end = 45
}
_ = json.NewEncoder(w).Encode(map[string]any{
"success": true,
"result": map[string]any{
"total": 45,
"pageSize": 20,
"items": dwsSubscriptionTestItems(start, end),
},
})
}))
defer srv.Close()
c := NewClient(srv.URL, Identity{AccessToken: "token", ClientID: "client", SourceID: "open"})
subs, err := c.ListSubscriptions(t.Context(), ListOptions{})
if err != nil {
t.Fatal(err)
}
if len(subs) != 45 || calls != 3 {
t.Fatalf("subscriptions = %d, calls = %d", len(subs), calls)
}
}
func TestClientListSubscriptionsDeduplicatesSubscribeID(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
items := dwsSubscriptionTestItems(0, 100)
if r.URL.Query().Get("pageNo") == "2" {
items = append(dwsSubscriptionTestItems(99, 100), dwsSubscriptionTestItems(100, 101)...)
}
_ = json.NewEncoder(w).Encode(map[string]any{
"success": true,
"result": map[string]any{"total": 101, "items": items},
})
}))
defer srv.Close()
c := NewClient(srv.URL, Identity{AccessToken: "token", ClientID: "client", SourceID: "open"})
subs, err := c.ListSubscriptions(t.Context(), ListOptions{})
if err != nil {
t.Fatal(err)
}
if len(subs) != 101 || subs[100].SubscribeID != "sub-100" {
t.Fatalf("subscriptions = %#v", subs)
}
}
func TestClientListSubscriptionsRejectsRepeatedPage(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
_ = json.NewEncoder(w).Encode(map[string]any{
"success": true,
"result": map[string]any{
"total": 200,
"items": dwsSubscriptionTestItems(0, 100),
},
})
}))
defer srv.Close()
c := NewClient(srv.URL, Identity{AccessToken: "token", ClientID: "client", SourceID: "open"})
_, err := c.ListSubscriptions(t.Context(), ListOptions{})
if err == nil || !strings.Contains(err.Error(), "pagination made no progress") {
t.Fatalf("ListSubscriptions() error = %v", err)
}
}
func dwsSubscriptionTestItems(start, end int) []map[string]any {
items := make([]map[string]any, 0, end-start)
for i := start; i < end; i++ {
items = append(items, map[string]any{
"subId": fmt.Sprintf("sub-%d", i),
"eventKey": EventSingleChat,
"sourceId": "open",
"status": 1,
})
}
return items
}
func captureClientDebugLogs(t *testing.T) *bytes.Buffer {
t.Helper()
var buf bytes.Buffer
previous := slog.Default()
slog.SetDefault(slog.New(slog.NewJSONHandler(&buf, &slog.HandlerOptions{Level: slog.LevelDebug})))
t.Cleanup(func() {
slog.SetDefault(previous)
})
return &buf
}