open-im-server/test/webhook/callbacks_test.go
dsx137 49ec272f88
feat: afterMsgSaveDB webhook (#3788)
* feat: afterMsgSaveDB webhook

fix(config): add default webhook event filters

fix(core): handle group mentions and webhook enqueue errors

Use group IDs when filtering group-message attention callbacks, and report failures when asynchronous webhook tasks cannot be queued or posted.

feat(webhook): add standalone mock webhook server CLI

test(webhook): add mock server and test suite for all callback commands

feat(msgtransfer): trigger afterMsgSaveDB webhook when message saved to DB

feat(config): add afterMsgSaveDB webhook configuration

feat(callbackstruct): add CallbackAfterMsgSaveDB command and request structs

* refactor(core): simplify asynchronous webhook enqueueing
2026-08-21 19:36:11 +08:00

210 lines
6.9 KiB
Go

// Copyright © 2023 OpenIM. All rights reserved.
//
// 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 webhook
import (
"bytes"
"encoding/json"
"net/http"
"net/http/httptest"
"testing"
cbapi "github.com/openimsdk/open-im-server/v3/pkg/callbackstruct"
sdkws "github.com/openimsdk/protocol/sdkws"
"github.com/openimsdk/protocol/wrapperspb"
"github.com/stretchr/testify/require"
)
func runWebhookTest(t *testing.T, srv *Server, command string, reqObj any) *httptest.ResponseRecorder {
t.Helper()
body, err := json.Marshal(reqObj)
require.NoError(t, err)
req := httptest.NewRequest(http.MethodPost, "/callbackExample/"+command, bytes.NewReader(body))
req.Header.Set("Content-Type", "application/json")
w := httptest.NewRecorder()
srv.ServeHTTP(w, req)
require.Equal(t, http.StatusOK, w.Code)
return w
}
func TestAllWebhookCallbacks_UserAndStatus(t *testing.T) {
t.Parallel()
srv := NewServer()
t.Run("UserRegister", func(t *testing.T) {
w := runWebhookTest(t, srv, cbapi.CallbackBeforeUserRegisterCommand, &cbapi.CallbackBeforeUserRegisterReq{
CallbackCommand: cbapi.CallbackCommand(cbapi.CallbackBeforeUserRegisterCommand),
Users: []*sdkws.UserInfo{{UserID: "u1", Nickname: "User1"}},
})
var resp cbapi.CallbackBeforeUserRegisterResp
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
require.Len(t, resp.Users, 1)
require.Equal(t, "User1", resp.Users[0].Nickname)
runWebhookTest(t, srv, cbapi.CallbackAfterUserRegisterCommand, &cbapi.CallbackAfterUserRegisterReq{
CallbackCommand: cbapi.CallbackCommand(cbapi.CallbackAfterUserRegisterCommand),
Users: []*sdkws.UserInfo{{UserID: "u1"}},
})
})
t.Run("UpdateUserInfo", func(t *testing.T) {
newNick := "NewNick"
w := runWebhookTest(t, srv, cbapi.CallbackBeforeUpdateUserInfoCommand, &cbapi.CallbackBeforeUpdateUserInfoReq{
CallbackCommand: cbapi.CallbackCommand(cbapi.CallbackBeforeUpdateUserInfoCommand),
UserID: "u1",
Nickname: &newNick,
})
var resp cbapi.CallbackBeforeUpdateUserInfoResp
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
require.NotNil(t, resp.Nickname)
require.Equal(t, "NewNick", *resp.Nickname)
runWebhookTest(t, srv, cbapi.CallbackAfterUpdateUserInfoCommand, &cbapi.CallbackAfterUpdateUserInfoReq{
CallbackCommand: cbapi.CallbackCommand(cbapi.CallbackAfterUpdateUserInfoCommand),
UserID: "u1",
Nickname: "NewNick",
})
})
t.Run("UpdateUserInfoEx", func(t *testing.T) {
w := runWebhookTest(t, srv, cbapi.CallbackBeforeUpdateUserInfoExCommand, &cbapi.CallbackBeforeUpdateUserInfoExReq{
CallbackCommand: cbapi.CallbackCommand(cbapi.CallbackBeforeUpdateUserInfoExCommand),
UserID: "u1",
Ex: wrapperspb.String("new-ex"),
})
var resp cbapi.CallbackBeforeUpdateUserInfoExResp
require.NoError(t, json.Unmarshal(w.Body.Bytes(), &resp))
require.NotNil(t, resp.Ex)
require.Equal(t, "new-ex", resp.Ex.Value)
runWebhookTest(t, srv, cbapi.CallbackAfterUpdateUserInfoExCommand, &cbapi.CallbackAfterUpdateUserInfoExReq{
CallbackCommand: cbapi.CallbackCommand(cbapi.CallbackAfterUpdateUserInfoExCommand),
UserID: "u1",
Ex: wrapperspb.String("new-ex"),
})
})
t.Run("UserStatusGateway", func(t *testing.T) {
for _, cmd := range []string{
cbapi.CallbackAfterUserOnlineCommand,
cbapi.CallbackAfterUserOfflineCommand,
cbapi.CallbackAfterUserKickOffCommand,
} {
runWebhookTest(t, srv, cmd, &cbapi.CallbackUserOnlineReq{
UserStatusCallbackReq: cbapi.UserStatusCallbackReq{
UserStatusBaseCallback: cbapi.UserStatusBaseCallback{
CallbackCommand: cmd,
Platform: "iOS",
},
UserID: "u1",
},
})
}
})
}
func TestAllWebhookCallbacks_FriendAndGroup(t *testing.T) {
t.Parallel()
srv := NewServer()
t.Run("FriendCallbacks", func(t *testing.T) {
friendCmds := []string{
cbapi.CallbackBeforeAddFriendCommand,
cbapi.CallbackAfterAddFriendCommand,
cbapi.CallbackBeforeAddFriendAgreeCommand,
cbapi.CallbackAfterAddFriendAgreeCommand,
cbapi.CallbackAfterDeleteFriendCommand,
cbapi.CallbackBeforeImportFriendsCommand,
cbapi.CallbackAfterImportFriendsCommand,
cbapi.CallbackBeforeSetFriendRemarkCommand,
cbapi.CallbackAfterSetFriendRemarkCommand,
cbapi.CallbackBeforeAddBlackCommand,
cbapi.CallbackAfterRemoveBlackCommand,
}
for _, cmd := range friendCmds {
runWebhookTest(t, srv, cmd, &cbapi.CallbackBeforeAddFriendReq{
CallbackCommand: cbapi.CallbackCommand(cmd),
FromUserID: "u1",
ToUserID: "u2",
})
}
})
t.Run("GroupCallbacks", func(t *testing.T) {
groupCmds := []string{
cbapi.CallbackBeforeCreateGroupCommand,
cbapi.CallbackAfterCreateGroupCommand,
cbapi.CallbackBeforeMembersJoinGroupCommand,
cbapi.CallbackBeforeJoinGroupCommand,
cbapi.CallbackAfterJoinGroupCommand,
cbapi.CallbackAfterQuitGroupCommand,
cbapi.CallbackAfterKickGroupCommand,
cbapi.CallbackAfterDisMissGroupCommand,
cbapi.CallbackAfterTransferGroupOwnerCommand,
cbapi.CallbackBeforeInviteJoinGroupCommand,
cbapi.CallbackBeforeSetGroupInfoCommand,
cbapi.CallbackAfterSetGroupInfoCommand,
cbapi.CallbackBeforeSetGroupInfoExCommand,
cbapi.CallbackAfterSetGroupInfoExCommand,
cbapi.CallbackBeforeSetGroupMemberInfoCommand,
cbapi.CallbackAfterSetGroupMemberInfoCommand,
}
for _, cmd := range groupCmds {
runWebhookTest(t, srv, cmd, &cbapi.CallbackQuitGroupReq{
CallbackCommand: cbapi.CallbackCommand(cmd),
GroupID: "g1",
UserID: "u1",
})
}
})
}
func TestAllWebhookCallbacks_MsgAndPush(t *testing.T) {
t.Parallel()
srv := NewServer()
msgCmds := []string{
cbapi.CallbackBeforeSendSingleMsgCommand,
cbapi.CallbackAfterSendSingleMsgCommand,
cbapi.CallbackBeforeSendGroupMsgCommand,
cbapi.CallbackAfterSendGroupMsgCommand,
cbapi.CallbackBeforeMsgModifyCommand,
cbapi.CallbackAfterSingleMsgReadCommand,
cbapi.CallbackAfterGroupMsgReadCommand,
cbapi.CallbackAfterMsgSaveDBCommand,
cbapi.CallbackAfterRevokeMsgCommand,
cbapi.CallbackBeforeOfflinePushCommand,
cbapi.CallbackBeforeOnlinePushCommand,
cbapi.CallbackBeforeGroupOnlinePushCommand,
}
for _, cmd := range msgCmds {
cmd := cmd
t.Run(cmd, func(t *testing.T) {
t.Parallel()
runWebhookTest(t, srv, cmd, &cbapi.CallbackAfterMsgSaveDBReq{
CommonCallbackReq: cbapi.CommonCallbackReq{
SendID: "u1",
CallbackCommand: cmd,
},
RecvID: "u2",
GroupID: "g1",
})
})
}
}