From 966890e29a903acb15517b9f52f27d98279ba855 Mon Sep 17 00:00:00 2001 From: Joyd Date: Mon, 6 May 2024 11:29:11 +0200 Subject: [PATCH] feat: Add migration endpoint (#5) Co-authored-by: Joyd --- .golangci.yml | 4 + pkg/message/message.go | 4 + pkg/message/migrate_messages.go | 94 +++++++++++++++++++ pkg/message/migrate_messages_test.go | 132 +++++++++++++++++++++++++++ pkg/message/mock_gen.go | 79 ++++++++++++++++ 5 files changed, 313 insertions(+) create mode 100644 pkg/message/migrate_messages.go create mode 100644 pkg/message/migrate_messages_test.go diff --git a/.golangci.yml b/.golangci.yml index f222eba..dbb8c1d 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -155,3 +155,7 @@ issues: text: "got 'user_ids' want 'user_i_ds'" linters: - tagliatelle + - path: 'pkg/message/migrate_messages.go' + text: "got 'mentioned_user_ids' want 'mention_user_i_ds'" + linters: + - tagliatelle diff --git a/pkg/message/message.go b/pkg/message/message.go index 3b2d9c5..ac7cb4b 100644 --- a/pkg/message/message.go +++ b/pkg/message/message.go @@ -17,6 +17,10 @@ type Message interface { // ListMessages retrieves a list of messages in a channel. // See https://sendbird.com/docs/chat/platform-api/v3/message/messaging-basics/list-messages ListMessages(ctx context.Context, channelType ChannelType, channelURL string, listMessagesRequest ListMessagesRequest) (*ListMessagesResponse, error) + + // MigrateMessages migrates messages to a channel. + // See https://sendbird.com/docs/chat/platform-api/v3/message/migration/migrate-messages + MigrateMessages(ctx context.Context, channelURL string, migrateMessagesRequest MigrateMessagesRequest) error } type message struct { diff --git a/pkg/message/migrate_messages.go b/pkg/message/migrate_messages.go new file mode 100644 index 0000000..0bd612f --- /dev/null +++ b/pkg/message/migrate_messages.go @@ -0,0 +1,94 @@ +package message + +import ( + "context" + "errors" + "fmt" +) + +type TextMessage struct { + // UserID specifies the user ID of the sender - required. + UserID string `json:"user_id"` + + // MessageType specifies the type of the message - required. + MessageType MessageType `json:"message_type"` + + // Message specifies the content of the message - required. + Message string `json:"message,omitempty"` + + // Timestamp specifies the time when the message was sent in Unix milliseconds format - required. + Timestamp int64 `json:"timestamp,omitempty"` + + // CustomType specifies a custom message type used for message grouping. The + // length is limited to 128 characters - optional. + CustomType string `json:"custom_type,omitempty"` + + // MentionUserIDs specifies an array of IDs of the users to mention in the + // message. This property is used only when mention_type is users - optional. + MentionUserIDs []string `json:"mentioned_user_ids,omitempty"` + + // Data specifies additional message information. This property serves as a + // container for a long text of any type of characters which can also be a + // JSON-formatted string like {"font-size": "24px"} - optional. + Data string `json:"data,omitempty"` + + // DedupID specifies a unique ID for the message created by another system. + // In general, this property is used to prevent the same message data from + // getting inserted when migrating messages from another system to the + // Sendbird server. If specified, the server performs a duplicate check using + // the property value - optional. + DedupID string `json:"dedup_id,omitempty"` +} + +// MigrateMessagesRequest is the request to migrate messages to a channel. +type MigrateMessagesRequest struct { + // Messages specifies an array of messages to migrate - required. + Messages []TextMessage `json:"messages"` + + // UpdateReadTS determines whether to update the read receipt time for all channel members when message.timestamp + // of the latest migrated message is prior to their read receipt time + UpdateReadTS bool `json:"update_read_ts,omitempty"` +} + +func (smr *MigrateMessagesRequest) Validate() error { + if len(smr.Messages) == 0 { + return errors.New("messages cannot be empty") + } + + for _, message := range smr.Messages { + if message.UserID == "" { + return errors.New("user_id cannot be empty") + } + + if message.MessageType == "" { + return errors.New("message_type cannot be empty") + } + + if message.Message == "" { + return errors.New("message cannot be empty") + } + + if message.Timestamp == 0 { + return errors.New("timestamp cannot be empty") + } + } + + return nil +} + +// MigrateMessages migrates messages to a channel. +// See https://sendbird.com/docs/chat/platform-api/v3/message/migration/migrate-messages +func (m *message) MigrateMessages(ctx context.Context, channelURL string, migrateMessagesRequest MigrateMessagesRequest) error { + if err := migrateMessagesRequest.Validate(); err != nil { + return fmt.Errorf("failed to validate migrate messages request: %w", err) + } + + path := "/migration/" + channelURL + + _, err := m.client.Post(ctx, path, migrateMessagesRequest, nil) + if err != nil { + return fmt.Errorf("failed to migrate messages: %w", err) + } + + return nil +} diff --git a/pkg/message/migrate_messages_test.go b/pkg/message/migrate_messages_test.go new file mode 100644 index 0000000..0d909d9 --- /dev/null +++ b/pkg/message/migrate_messages_test.go @@ -0,0 +1,132 @@ +package message + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/tomMoulard/sendbird-go/pkg/client" +) + +func TestValidateMMR(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + request MigrateMessagesRequest + assertErr assert.ErrorAssertionFunc + }{ + { + name: "invalid request - empty messages", + request: MigrateMessagesRequest{ + Messages: []TextMessage{}, + }, + assertErr: assert.Error, + }, + { + name: "invalid request - missing user_id", + request: MigrateMessagesRequest{ + Messages: []TextMessage{ + { + MessageType: MessageTypeText, + Message: "Hello, World!", + Timestamp: 1609459200000, + }, + }, + }, + assertErr: assert.Error, + }, + { + name: "invalid request - missing message_type", + request: MigrateMessagesRequest{ + Messages: []TextMessage{ + { + UserID: "42", + Message: "Hello, World!", + Timestamp: 1609459200000, + }, + }, + }, + assertErr: assert.Error, + }, + { + name: "invalid request - missing message", + request: MigrateMessagesRequest{ + Messages: []TextMessage{ + { + UserID: "42", + MessageType: MessageTypeText, + Timestamp: 1609459200000, + }, + }, + }, + assertErr: assert.Error, + }, + { + name: "invalid request - missing timestamp", + request: MigrateMessagesRequest{ + Messages: []TextMessage{ + { + UserID: "42", + MessageType: MessageTypeText, + Message: "Hello, World!", + }, + }, + }, + assertErr: assert.Error, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + err := test.request.Validate() + test.assertErr(t, err) + }) + } +} + +func TestMigrateMessages(t *testing.T) { + t.Parallel() + + validRequest := MigrateMessagesRequest{ + Messages: []TextMessage{ + { + UserID: "42", + MessageType: MessageTypeText, + Message: "Hello, World!", + Timestamp: 1609459200000, + CustomType: "greeting", + Data: `{ "emotion": "happy" }`, + DedupID: "unique123", + }, + }, + } + + tests := []struct { + name string + request MigrateMessagesRequest + assertErr assert.ErrorAssertionFunc + }{ + { + name: "valid request", + request: validRequest, + assertErr: assert.NoError, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + t.Parallel() + + client := client.NewClientMock(t). + OnPost("/migration/channel_url", test.request, nil).TypedReturns(nil, nil).Once(). + Parent + message := NewMessage(client) + + err := message.MigrateMessages(context.Background(), "channel_url", test.request) + test.assertErr(t, err) + }) + } +} diff --git a/pkg/message/mock_gen.go b/pkg/message/mock_gen.go index ebf8ca0..91211ac 100644 --- a/pkg/message/mock_gen.go +++ b/pkg/message/mock_gen.go @@ -228,3 +228,82 @@ func (_c *messageSendMessageCall) OnListMessagesRaw(channelType interface{}, cha func (_c *messageSendMessageCall) OnSendMessageRaw(channelType interface{}, channelURL interface{}, sendMessageRequest interface{}) *messageSendMessageCall { return _c.Parent.OnSendMessageRaw(channelType, channelURL, sendMessageRequest) } + + +// MigrateMessages mocks the migrateMessages method +func (_m *messageMock) MigrateMessages(_ context.Context, channelURL string, migrateMessagesRequest MigrateMessagesRequest) error { + _ret := _m.Called(channelURL, migrateMessagesRequest) + + if _rf, ok := _ret.Get(0).(func(string, MigrateMessagesRequest) error); ok { + return _rf(channelURL, migrateMessagesRequest) + } + + return _ret.Error(0) +} + +// OnMigrateMessages prepares the mock and sets expectations for the MigrateMessages method +func (_m *messageMock) OnMigrateMessages(channelURL string, migrateMessagesRequest MigrateMessagesRequest) *messageMigrateMessagesCall { + return &messageMigrateMessagesCall{Call: _m.Mock.On("MigrateMessages", channelURL, migrateMessagesRequest), Parent: _m} +} + +// OnMigrateMessagesRaw prepares the mock and sets expectations for the MigrateMessages method +func (_m *messageMock) OnMigrateMessagesRaw(channelURL interface{}, migrateMessagesRequest interface{}) *messageMigrateMessagesCall { + return &messageMigrateMessagesCall{Call: _m.Mock.On("MigrateMessages", channelURL, migrateMessagesRequest), Parent: _m} +} + +type messageMigrateMessagesCall struct { + *mock.Call + Parent *messageMock +} + +// Once specifies that the mock should only return the result once +func (_c *messageMigrateMessagesCall) Once() *messageMigrateMessagesCall { + _c.Call = _c.Call.Once() + return _c +} + +// Twice specifies that the mock should only return the result twice +func (_c *messageMigrateMessagesCall) Twice() *messageMigrateMessagesCall { + _c.Call = _c.Call.Twice() + return _c +} + +// Times specifies the number of times the mock should return the result +func (_c *messageMigrateMessagesCall) Times(i int) *messageMigrateMessagesCall { + _c.Call = _c.Call.Times(i) + return _c +} + +// WaitUntil waits until the channel receives a time notification +func (_c *messageMigrateMessagesCall) WaitUntil(w <-chan time.Time) *messageMigrateMessagesCall { + _c.Call = _c.Call.WaitUntil(w) + return _c +} + +// After waits for a duration before returning the result +func (_c *messageMigrateMessagesCall) After(d time.Duration) *messageMigrateMessagesCall { + _c.Call = _c.Call.After(d) + return _c +} + +// Run custom function during the call +func (_c *messageMigrateMessagesCall) Run(fn func(args mock.Arguments)) *messageMigrateMessagesCall { + _c.Call = _c.Call.Run(fn) + return _c +} + +// Return specifies the return arguments for the mock +func (_c *messageMigrateMessagesCall) Returns(err error) *messageMigrateMessagesCall { + _c.Call = _c.Call.Return(err) + return _c +} + +// OnMigrateMessages sets the expectations for the MigrateMessages method +func (_c *messageMigrateMessagesCall) OnMigrateMessages(channelURL string, migrateMessagesRequest MigrateMessagesRequest) *messageMigrateMessagesCall { + return _c.Parent.OnMigrateMessages(channelURL, migrateMessagesRequest) +} + +// OnMigrateMessagesRaw sets the expectations for the MigrateMessages method +func (_c *messageMigrateMessagesCall) OnMigrateMessagesRaw(channelURL interface{}, migrateMessagesRequest interface{}) *messageMigrateMessagesCall { + return _c.Parent.OnMigrateMessagesRaw(channelURL, migrateMessagesRequest) +} \ No newline at end of file