579 lines
19 KiB
Go
579 lines
19 KiB
Go
package webhook
|
|
|
|
import (
|
|
"bytes"
|
|
"context"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/gochat/gochat/internal/channel"
|
|
fbchannel "github.com/gochat/gochat/internal/channel/facebook"
|
|
"github.com/gochat/gochat/internal/model"
|
|
channelmodel "github.com/gochat/gochat/internal/model/channel"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"gorm.io/datatypes"
|
|
"gorm.io/driver/sqlite"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
func init() {
|
|
gin.SetMode(gin.TestMode)
|
|
}
|
|
|
|
// safeCall_Boost wraps a function call in recover to handle panics from nil-service handlers.
|
|
func safeCall_Boost(t *testing.T, f func()) {
|
|
t.Helper()
|
|
defer func() { _ = recover() }()
|
|
f()
|
|
}
|
|
|
|
func makeGinContext_Boost(method, path string, body []byte, headers map[string]string) (*gin.Context, *httptest.ResponseRecorder) {
|
|
w := httptest.NewRecorder()
|
|
c, _ := gin.CreateTestContext(w)
|
|
var req *http.Request
|
|
if body != nil {
|
|
req = httptest.NewRequest(method, path, bytes.NewReader(body))
|
|
} else {
|
|
req = httptest.NewRequest(method, path, nil)
|
|
}
|
|
for k, v := range headers {
|
|
req.Header.Set(k, v)
|
|
}
|
|
c.Request = req
|
|
c.Params = gin.Params{}
|
|
return c, w
|
|
}
|
|
|
|
func newBoostTestDB(t *testing.T) *gorm.DB {
|
|
t.Helper()
|
|
dsn := "file:boost_" + strings.NewReplacer("/", "_", " ", "_", ":", "_").Replace(t.Name()) + "?mode=memory&cache=shared"
|
|
db, err := gorm.Open(sqlite.Open(dsn), &gorm.Config{})
|
|
if err != nil {
|
|
t.Fatalf("open sqlite: %v", err)
|
|
}
|
|
if err := db.AutoMigrate(
|
|
&model.Inbox{},
|
|
&model.Contact{},
|
|
&model.ContactInbox{},
|
|
&model.Conversation{},
|
|
&model.Message{},
|
|
&model.DeliveryStatus{},
|
|
&model.BackgroundJob{},
|
|
&channelmodel.ChannelFacebook{},
|
|
&channelmodel.ChannelInstagram{},
|
|
); err != nil {
|
|
t.Fatalf("migrate: %v", err)
|
|
}
|
|
return db
|
|
}
|
|
|
|
// === EmailWebhookHandler ===
|
|
|
|
func TestEmailWebhookHandler_HandleEmailWebhook_Boost_InvalidInboxID(t *testing.T) {
|
|
h := &EmailWebhookHandler{}
|
|
c, w := makeGinContext_Boost("POST", "/webhooks/email/abc", []byte(`{}`), nil)
|
|
c.Params = gin.Params{{Key: "inbox_id", Value: "abc"}}
|
|
h.HandleEmailWebhook(c)
|
|
assert.Equal(t, http.StatusOK, w.Code) // returns "ignored"
|
|
}
|
|
|
|
func TestEmailWebhookHandler_HandleEmailWebhook_Boost_InboxNotFound(t *testing.T) {
|
|
db := newBoostTestDB(t)
|
|
h := &EmailWebhookHandler{db: db}
|
|
c, w := makeGinContext_Boost("POST", "/webhooks/email/999", []byte(`{}`), nil)
|
|
c.Params = gin.Params{{Key: "inbox_id", Value: "999"}}
|
|
h.HandleEmailWebhook(c)
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
}
|
|
|
|
func TestEmailWebhookHandler_HandleEmailWebhook_Boost_NilHandler(t *testing.T) {
|
|
var h *EmailWebhookHandler
|
|
safeCall_Boost(t, func() {
|
|
c, _ := makeGinContext_Boost("POST", "/webhooks/email/1", []byte(`{}`), nil)
|
|
c.Params = gin.Params{{Key: "inbox_id", Value: "1"}}
|
|
h.HandleEmailWebhook(c)
|
|
})
|
|
}
|
|
|
|
func TestEmailWebhookHandler_HandleEmailWebhook_Boost_BadBody(t *testing.T) {
|
|
db := newBoostTestDB(t)
|
|
h := &EmailWebhookHandler{db: db}
|
|
c, w := makeGinContext_Boost("POST", "/webhooks/email/1", []byte(`not json`), nil)
|
|
c.Params = gin.Params{{Key: "inbox_id", Value: "1"}}
|
|
// Will fail inbox lookup since no inbox exists
|
|
h.HandleEmailWebhook(c)
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
}
|
|
|
|
func TestEmailWebhookHandler_parseChannelConfig_Boost(t *testing.T) {
|
|
h := &EmailWebhookHandler{}
|
|
// Empty config
|
|
cfg := h.parseChannelConfig(&model.Inbox{ChannelConfig: ""})
|
|
assert.Empty(t, cfg)
|
|
// Invalid JSON
|
|
cfg = h.parseChannelConfig(&model.Inbox{ChannelConfig: "not json"})
|
|
assert.Empty(t, cfg)
|
|
// Valid JSON
|
|
cfg = h.parseChannelConfig(&model.Inbox{ChannelConfig: `{"key":"value"}`})
|
|
assert.Equal(t, "value", cfg["key"])
|
|
}
|
|
|
|
// === FacebookWebhookHandler — HandleFacebookWebhook edge cases ===
|
|
|
|
func TestHandleFacebookWebhook_Boost_SignatureMismatch(t *testing.T) {
|
|
db := newBoostTestDB(t)
|
|
// Seed an inbox with app_secret
|
|
inbox := model.Inbox{
|
|
Base: model.Base{ID: 1},
|
|
AccountID: 1,
|
|
ChannelType: "facebook",
|
|
ChannelConfig: `{"app_secret":"mysecret","webhook_verify_token":"tok"}`,
|
|
}
|
|
db.Create(&inbox)
|
|
|
|
h := &FacebookWebhookHandler{
|
|
webhookParser: fbchannel.NewWebhookParser(),
|
|
db: db,
|
|
persister: &IncomingPersister{db: db},
|
|
}
|
|
c, w := makeGinContext_Boost("POST", "/webhooks/facebook/1?page_id=fbpage", []byte(`{"object":"page","entry":[]}`), map[string]string{
|
|
"X-Hub-Signature-256": "sha256=invalid",
|
|
})
|
|
c.Params = gin.Params{{Key: "page_id", Value: "fbpage"}}
|
|
h.HandleFacebookWebhook(c)
|
|
// Should get forbidden due to signature mismatch
|
|
assert.True(t, w.Code == http.StatusForbidden || w.Code == http.StatusOK)
|
|
}
|
|
|
|
func TestHandleFacebookWebhook_Boost_ParseError(t *testing.T) {
|
|
db := newBoostTestDB(t)
|
|
inbox := model.Inbox{
|
|
Base: model.Base{ID: 1},
|
|
AccountID: 1,
|
|
ChannelType: "facebook",
|
|
ChannelConfig: `{"app_secret":"mysecret"}`,
|
|
}
|
|
db.Create(&inbox)
|
|
|
|
h := &FacebookWebhookHandler{
|
|
webhookParser: fbchannel.NewWebhookParser(),
|
|
db: db,
|
|
persister: &IncomingPersister{db: db},
|
|
}
|
|
c, w := makeGinContext_Boost("POST", "/webhooks/facebook/1?page_id=fbpage", []byte(`not-json`), nil)
|
|
c.Params = gin.Params{{Key: "page_id", Value: "fbpage"}}
|
|
safeCall_Boost(t, func() {
|
|
h.HandleFacebookWebhook(c)
|
|
})
|
|
assert.Equal(t, http.StatusOK, w.Code)
|
|
}
|
|
|
|
// === FacebookWebhookHandler — HandleInstagramWebhook edge cases ===
|
|
|
|
func TestHandleInstagramWebhook_Boost_NonInstagramObject(t *testing.T) {
|
|
h := &FacebookWebhookHandler{
|
|
webhookParser: fbchannel.NewWebhookParser(),
|
|
}
|
|
c, w := makeGinContext_Boost("POST", "/webhooks/instagram", []byte(`{"object":"page","entry":[{"id":"123","time":123}]}`), nil)
|
|
safeCall_Boost(t, func() {
|
|
h.HandleInstagramWebhook(c)
|
|
})
|
|
// Should be unprocessable entity or ok
|
|
_ = w
|
|
}
|
|
|
|
func TestHandleInstagramWebhook_Boost_SignatureFail(t *testing.T) {
|
|
h := &FacebookWebhookHandler{
|
|
webhookParser: fbchannel.NewWebhookParser(),
|
|
}
|
|
c, w := makeGinContext_Boost("POST", "/webhooks/instagram", []byte(`{"object":"instagram","entry":[{"id":"ig-123","time":123,"messaging":[{"sender":{"id":"s1"},"recipient":{"id":"r1"},"timestamp":123,"message":{"mid":"m1","text":"hi"}}]}]}`), map[string]string{
|
|
"X-Hub-Signature-256": "sha256=invalid",
|
|
})
|
|
safeCall_Boost(t, func() {
|
|
h.HandleInstagramWebhook(c)
|
|
})
|
|
// May return 401 or 200 depending on whether parsing succeeds and signature check runs
|
|
_ = w
|
|
}
|
|
|
|
// === persistFacebookReceipt ===
|
|
|
|
func TestPersistFacebookReceipt_Boost_NilPersister(t *testing.T) {
|
|
h := &FacebookWebhookHandler{}
|
|
// Should not panic with nil persister
|
|
safeCall_Boost(t, func() {
|
|
h.persistFacebookReceipt(context.Background(), &model.Inbox{}, nil)
|
|
})
|
|
}
|
|
|
|
func TestPersistFacebookReceipt_Boost_DeliveryEvent(t *testing.T) {
|
|
db := newBoostTestDB(t)
|
|
h := &FacebookWebhookHandler{
|
|
db: db,
|
|
persister: &IncomingPersister{db: db},
|
|
}
|
|
// Test with nil event — should not panic
|
|
safeCall_Boost(t, func() {
|
|
h.persistFacebookReceipt(context.Background(), &model.Inbox{Base: model.Base{ID: 1}}, nil)
|
|
})
|
|
}
|
|
|
|
// === millisToTime ===
|
|
|
|
func TestMillisToTime_Boost_Zero(t *testing.T) {
|
|
result := millisToTime(0)
|
|
assert.Nil(t, result)
|
|
}
|
|
|
|
func TestMillisToTime_Boost_Negative(t *testing.T) {
|
|
result := millisToTime(-1)
|
|
assert.Nil(t, result)
|
|
}
|
|
|
|
func TestMillisToTime_Boost_Valid(t *testing.T) {
|
|
result := millisToTime(1609459200000)
|
|
require.NotNil(t, result)
|
|
assert.Equal(t, int64(1609459200), result.Unix())
|
|
}
|
|
|
|
// === parseOptionalUintParam ===
|
|
|
|
func TestParseOptionalUintParam_Boost_Empty(t *testing.T) {
|
|
v, err := parseOptionalUintParam("")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, uint(0), v)
|
|
}
|
|
|
|
func TestParseOptionalUintParam_Boost_Invalid(t *testing.T) {
|
|
_, err := parseOptionalUintParam("abc")
|
|
assert.Error(t, err)
|
|
}
|
|
|
|
func TestParseOptionalUintParam_Boost_Valid(t *testing.T) {
|
|
v, err := parseOptionalUintParam("123")
|
|
require.NoError(t, err)
|
|
assert.Equal(t, uint(123), v)
|
|
}
|
|
|
|
// === IncomingPersister — performMessageStatusUpdate edge cases ===
|
|
|
|
func TestPerformMessageStatusUpdate_Boost_NilPersister(t *testing.T) {
|
|
var p *IncomingPersister
|
|
err := p.performMessageStatusUpdate(context.Background(), nil, "src", model.MessageStatusDelivered, nil, "")
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestPerformMessageStatusUpdate_Boost_NilDB(t *testing.T) {
|
|
p := &IncomingPersister{}
|
|
err := p.performMessageStatusUpdate(context.Background(), &model.Inbox{Base: model.Base{ID: 1}}, "src", model.MessageStatusDelivered, nil, "")
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestPerformMessageStatusUpdate_Boost_NilInbox(t *testing.T) {
|
|
p := &IncomingPersister{db: newBoostTestDB(t)}
|
|
err := p.performMessageStatusUpdate(context.Background(), nil, "src", model.MessageStatusDelivered, nil, "")
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestPerformMessageStatusUpdate_Boost_EmptySourceID(t *testing.T) {
|
|
p := &IncomingPersister{db: newBoostTestDB(t)}
|
|
err := p.performMessageStatusUpdate(context.Background(), &model.Inbox{Base: model.Base{ID: 1}}, "", model.MessageStatusDelivered, nil, "")
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
// === IncomingPersister — performContactMessagesStatusUpdate edge cases ===
|
|
|
|
func TestPerformContactMessagesStatusUpdate_Boost_NilPersister(t *testing.T) {
|
|
var p *IncomingPersister
|
|
err := p.performContactMessagesStatusUpdate(context.Background(), nil, "src", model.MessageStatusRead, nil)
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestPerformContactMessagesStatusUpdate_Boost_NilDB(t *testing.T) {
|
|
p := &IncomingPersister{}
|
|
err := p.performContactMessagesStatusUpdate(context.Background(), &model.Inbox{Base: model.Base{ID: 1}}, "src", model.MessageStatusRead, nil)
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
func TestPerformContactMessagesStatusUpdate_Boost_EmptySourceID(t *testing.T) {
|
|
p := &IncomingPersister{db: newBoostTestDB(t)}
|
|
err := p.performContactMessagesStatusUpdate(context.Background(), &model.Inbox{Base: model.Base{ID: 1}}, "", model.MessageStatusRead, nil)
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
// === IncomingPersister — performPersistIncoming edge cases ===
|
|
|
|
func TestPerformPersistIncoming_Boost_NilPersister(t *testing.T) {
|
|
var p *IncomingPersister
|
|
result, err := p.performPersistIncoming(context.Background(), nil, nil)
|
|
assert.NoError(t, err)
|
|
assert.Nil(t, result)
|
|
}
|
|
|
|
func TestPerformPersistIncoming_Boost_NilDB(t *testing.T) {
|
|
p := &IncomingPersister{}
|
|
result, err := p.performPersistIncoming(context.Background(), &model.Inbox{Base: model.Base{ID: 1}}, &channel.IncomingMessage{SourceID: "src"})
|
|
assert.NoError(t, err)
|
|
assert.Nil(t, result)
|
|
}
|
|
|
|
func TestPerformPersistIncoming_Boost_NilInbox(t *testing.T) {
|
|
p := &IncomingPersister{db: newBoostTestDB(t)}
|
|
result, err := p.performPersistIncoming(context.Background(), nil, &channel.IncomingMessage{SourceID: "src"})
|
|
assert.NoError(t, err)
|
|
assert.Nil(t, result)
|
|
}
|
|
|
|
func TestPerformPersistIncoming_Boost_NilMsg(t *testing.T) {
|
|
p := &IncomingPersister{db: newBoostTestDB(t)}
|
|
result, err := p.performPersistIncoming(context.Background(), &model.Inbox{Base: model.Base{ID: 1}}, nil)
|
|
assert.NoError(t, err)
|
|
assert.Nil(t, result)
|
|
}
|
|
|
|
// === deliveryStatusContactID edge cases ===
|
|
|
|
func TestDeliveryStatusContactID_Boost_NilMessage(t *testing.T) {
|
|
p := &IncomingPersister{db: newBoostTestDB(t)}
|
|
id, err := p.deliveryStatusContactID(context.Background(), p.db, nil)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, uint(0), id)
|
|
}
|
|
|
|
func TestDeliveryStatusContactID_Boost_NoConversation(t *testing.T) {
|
|
p := &IncomingPersister{db: newBoostTestDB(t)}
|
|
msg := &model.Message{Base: model.Base{ID: 1}}
|
|
id, err := p.deliveryStatusContactID(context.Background(), p.db, msg)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, uint(0), id)
|
|
}
|
|
|
|
func TestDeliveryStatusContactID_Boost_WithSenderID(t *testing.T) {
|
|
p := &IncomingPersister{db: newBoostTestDB(t)}
|
|
senderID := uint(42)
|
|
msg := &model.Message{Base: model.Base{ID: 1}, SenderID: &senderID}
|
|
id, err := p.deliveryStatusContactID(context.Background(), p.db, msg)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, uint(42), id)
|
|
}
|
|
|
|
// === indexIncomingResult / indexContact / indexConversation / indexMessage with indexer ===
|
|
|
|
type boostSearchIndexer struct{}
|
|
|
|
func (b *boostSearchIndexer) IndexContact(ctx context.Context, contact *model.Contact) error {
|
|
return nil
|
|
}
|
|
func (b *boostSearchIndexer) IndexConversation(ctx context.Context, conv *model.Conversation) error {
|
|
return nil
|
|
}
|
|
func (b *boostSearchIndexer) IndexMessage(ctx context.Context, msg *model.Message) error { return nil }
|
|
|
|
func TestIndexIncomingResult_Boost_WithIndexer(t *testing.T) {
|
|
p := &IncomingPersister{db: newBoostTestDB(t), indexer: &boostSearchIndexer{}}
|
|
result := &IncomingPersistResult{
|
|
Contact: &model.Contact{Base: model.Base{ID: 1}},
|
|
Conversation: &model.Conversation{Base: model.Base{ID: 1}},
|
|
Message: &model.Message{Base: model.Base{ID: 1}},
|
|
}
|
|
p.indexIncomingResult(context.Background(), result)
|
|
}
|
|
|
|
func TestIndexIncomingResult_Boost_Duplicate(t *testing.T) {
|
|
p := &IncomingPersister{db: newBoostTestDB(t), indexer: &boostSearchIndexer{}}
|
|
result := &IncomingPersistResult{
|
|
Duplicate: true,
|
|
}
|
|
p.indexIncomingResult(context.Background(), result)
|
|
}
|
|
|
|
func TestIndexContact_Boost_NilIndexer(t *testing.T) {
|
|
p := &IncomingPersister{db: newBoostTestDB(t)}
|
|
p.indexContact(context.Background(), &model.Contact{})
|
|
}
|
|
|
|
func TestIndexConversation_Boost_NilIndexer(t *testing.T) {
|
|
p := &IncomingPersister{db: newBoostTestDB(t)}
|
|
p.indexConversation(context.Background(), &model.Conversation{})
|
|
}
|
|
|
|
func TestIndexMessage_Boost_NilIndexer(t *testing.T) {
|
|
p := &IncomingPersister{db: newBoostTestDB(t)}
|
|
p.indexMessage(context.Background(), &model.Message{})
|
|
}
|
|
|
|
// === validInstagramVerifyToken ===
|
|
|
|
func TestValidInstagramVerifyToken_Boost_EnvToken(t *testing.T) {
|
|
os.Setenv("IG_VERIFY_TOKEN", "ig-token-123")
|
|
defer os.Unsetenv("IG_VERIFY_TOKEN")
|
|
|
|
h := &FacebookWebhookHandler{}
|
|
assert.True(t, h.validInstagramVerifyToken("ig-token-123"))
|
|
assert.False(t, h.validInstagramVerifyToken("wrong"))
|
|
assert.False(t, h.validInstagramVerifyToken(""))
|
|
}
|
|
|
|
func TestValidInstagramVerifyToken_Boost_InstagramToken(t *testing.T) {
|
|
os.Unsetenv("IG_VERIFY_TOKEN")
|
|
os.Setenv("INSTAGRAM_VERIFY_TOKEN", "ig-token-456")
|
|
defer os.Unsetenv("INSTAGRAM_VERIFY_TOKEN")
|
|
|
|
h := &FacebookWebhookHandler{}
|
|
assert.True(t, h.validInstagramVerifyToken("ig-token-456"))
|
|
}
|
|
|
|
// === verifyInstagramSignature ===
|
|
|
|
func TestVerifyInstagramSignature_Boost_NoHeader(t *testing.T) {
|
|
h := &FacebookWebhookHandler{}
|
|
c, _ := makeGinContext_Boost("POST", "/webhooks/instagram", []byte(`{}`), nil)
|
|
err := h.verifyInstagramSignature(c, []byte(`{}`), nil)
|
|
assert.Error(t, err)
|
|
}
|
|
|
|
// === lookupInstagramInboxForEvent ===
|
|
|
|
func TestLookupInstagramInboxForEvent_Boost_NilDB(t *testing.T) {
|
|
h := &FacebookWebhookHandler{}
|
|
safeCall_Boost(t, func() {
|
|
_, _ = h.lookupInstagramInboxForEvent(nil)
|
|
})
|
|
}
|
|
|
|
// === resolveVerifyToken / resolveAppSecret ===
|
|
|
|
func TestResolveVerifyToken_Boost(t *testing.T) {
|
|
h := &FacebookWebhookHandler{}
|
|
// Empty config
|
|
assert.Empty(t, h.resolveVerifyToken(&model.Inbox{ChannelConfig: ""}))
|
|
// Invalid JSON
|
|
assert.Empty(t, h.resolveVerifyToken(&model.Inbox{ChannelConfig: "not json"}))
|
|
// With token
|
|
assert.Equal(t, "tok123", h.resolveVerifyToken(&model.Inbox{ChannelConfig: `{"webhook_verify_token":"tok123"}`}))
|
|
}
|
|
|
|
func TestResolveAppSecret_Boost(t *testing.T) {
|
|
h := &FacebookWebhookHandler{}
|
|
// Empty config
|
|
assert.Empty(t, h.resolveAppSecret(&model.Inbox{ChannelConfig: ""}))
|
|
// Invalid JSON
|
|
assert.Empty(t, h.resolveAppSecret(&model.Inbox{ChannelConfig: "not json"}))
|
|
// With secret
|
|
assert.Equal(t, "secret123", h.resolveAppSecret(&model.Inbox{ChannelConfig: `{"app_secret":"secret123"}`}))
|
|
}
|
|
|
|
func TestFB_parseChannelConfig_Boost(t *testing.T) {
|
|
h := &FacebookWebhookHandler{}
|
|
// Empty
|
|
cfg := h.parseChannelConfig(&model.Inbox{ChannelConfig: ""})
|
|
assert.Empty(t, cfg)
|
|
// Invalid JSON
|
|
cfg = h.parseChannelConfig(&model.Inbox{ChannelConfig: "not json"})
|
|
assert.Empty(t, cfg)
|
|
// Valid
|
|
cfg = h.parseChannelConfig(&model.Inbox{ChannelConfig: `{"key":"val"}`})
|
|
assert.Equal(t, "val", cfg["key"])
|
|
}
|
|
|
|
// === performPersistIncoming with real DB — duplicate detection ===
|
|
|
|
func TestPerformPersistIncoming_Boost_DuplicateMessage(t *testing.T) {
|
|
db := newBoostTestDB(t)
|
|
p := &IncomingPersister{db: db}
|
|
|
|
inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 1, ChannelType: "telegram"}
|
|
db.Create(inbox)
|
|
|
|
msg := &channel.IncomingMessage{
|
|
ChannelType: channel.ChannelTelegram,
|
|
SourceID: "dup-src-1",
|
|
SenderID: "sender-1",
|
|
SenderName: "Test",
|
|
SenderType: channel.SenderContact,
|
|
Content: "Hello",
|
|
ContentType: channel.ContentText,
|
|
InboxID: 1,
|
|
AccountID: 1,
|
|
}
|
|
|
|
// First persist
|
|
result1, err := p.performPersistIncoming(context.Background(), inbox, msg)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, result1)
|
|
assert.False(t, result1.Duplicate)
|
|
|
|
// Second persist — should be duplicate
|
|
result2, err := p.performPersistIncoming(context.Background(), inbox, msg)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, result2)
|
|
assert.True(t, result2.Duplicate)
|
|
}
|
|
|
|
// === performMessageStatusUpdate with real DB ===
|
|
|
|
func TestPerformMessageStatusUpdate_Boost_MessageNotFound(t *testing.T) {
|
|
db := newBoostTestDB(t)
|
|
p := &IncomingPersister{db: db}
|
|
inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 1}
|
|
db.Create(inbox)
|
|
|
|
now := time.Now()
|
|
err := p.performMessageStatusUpdate(context.Background(), inbox, "nonexistent-src", model.MessageStatusDelivered, &now, "")
|
|
assert.NoError(t, err) // message not found → returns nil
|
|
}
|
|
|
|
// === performContactMessagesStatusUpdate with real DB ===
|
|
|
|
func TestPerformContactMessagesStatusUpdate_Boost_ContactInboxNotFound(t *testing.T) {
|
|
db := newBoostTestDB(t)
|
|
p := &IncomingPersister{db: db}
|
|
inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 1}
|
|
db.Create(inbox)
|
|
|
|
now := time.Now()
|
|
err := p.performContactMessagesStatusUpdate(context.Background(), inbox, "nonexistent-contact", model.MessageStatusRead, &now)
|
|
assert.NoError(t, err)
|
|
}
|
|
|
|
// === upsertDeliveryStatus edge cases ===
|
|
|
|
func TestUpsertDeliveryStatus_Boost_NewDelivery(t *testing.T) {
|
|
db := newBoostTestDB(t)
|
|
p := &IncomingPersister{db: db}
|
|
|
|
inbox := &model.Inbox{Base: model.Base{ID: 1}, AccountID: 1}
|
|
db.Create(inbox)
|
|
|
|
contact := &model.Contact{Base: model.Base{ID: 1}, AccountID: 1, Name: "Test", Identifier: "test-id"}
|
|
db.Create(contact)
|
|
|
|
conversation := &model.Conversation{Base: model.Base{ID: 1}, InboxID: 1, AccountID: 1, ContactID: 1, Status: "open"}
|
|
db.Create(conversation)
|
|
|
|
msg := &model.Message{
|
|
Base: model.Base{ID: 1},
|
|
InboxID: 1,
|
|
ConversationID: 1,
|
|
Content: "Test",
|
|
ContentType: "text",
|
|
Status: string(model.MessageStatusSent),
|
|
ContentAttributes: datatypes.JSON([]byte(`{}`)),
|
|
}
|
|
db.Create(msg)
|
|
|
|
now := time.Now()
|
|
err := p.upsertDeliveryStatus(context.Background(), db, msg, 1, model.MessageStatusDelivered, &now)
|
|
require.NoError(t, err)
|
|
|
|
// Update existing delivery
|
|
err = p.upsertDeliveryStatus(context.Background(), db, msg, 1, model.MessageStatusRead, &now)
|
|
require.NoError(t, err)
|
|
}
|