* 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>
647 lines
20 KiB
Go
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
|
|
}
|