feat(webhook): persist incoming provider messages
This commit is contained in:
@@ -9,9 +9,12 @@ import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/gochat/gochat/internal/channel"
|
||||
channelprovider "github.com/gochat/gochat/internal/channel/provider"
|
||||
"github.com/gochat/gochat/internal/model"
|
||||
channelmodel "github.com/gochat/gochat/internal/model/channel"
|
||||
"gorm.io/driver/sqlite"
|
||||
@@ -21,12 +24,17 @@ import (
|
||||
func newWebhookLookupTestDB(t *testing.T) *gorm.DB {
|
||||
t.Helper()
|
||||
|
||||
db, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{})
|
||||
dsn := "file:" + 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{},
|
||||
&channelmodel.ChannelTelegram{},
|
||||
&channelmodel.ChannelLINE{},
|
||||
&channelmodel.ChannelTwilioSMS{},
|
||||
@@ -39,6 +47,61 @@ func newWebhookLookupTestDB(t *testing.T) *gorm.DB {
|
||||
return db
|
||||
}
|
||||
|
||||
func TestIncomingPersisterCreatesConversationMessageAndDedupes(t *testing.T) {
|
||||
db := newWebhookLookupTestDB(t)
|
||||
inbox := seedWebhookInbox(t, db, "telegram")
|
||||
persister := NewIncomingPersister(db)
|
||||
|
||||
msg := &channel.IncomingMessage{
|
||||
ChannelType: channel.ChannelTelegram,
|
||||
SourceID: "tg-msg-1",
|
||||
SenderID: "tg-user-1",
|
||||
SenderName: "Ada Lovelace",
|
||||
SenderType: channel.SenderContact,
|
||||
Content: "hello",
|
||||
ContentType: channel.ContentText,
|
||||
InboxID: inbox.ID,
|
||||
AccountID: inbox.AccountID,
|
||||
}
|
||||
|
||||
result, err := persister.PersistIncoming(t.Context(), &inbox, msg)
|
||||
if err != nil {
|
||||
t.Fatalf("persist incoming: %v", err)
|
||||
}
|
||||
if result.Contact == nil || result.ContactInbox == nil || result.Conversation == nil || result.Message == nil {
|
||||
t.Fatalf("expected full persistence result: %#v", result)
|
||||
}
|
||||
if result.Message.Content != "hello" || result.Message.SourceID != "tg-msg-1" {
|
||||
t.Fatalf("unexpected message: %#v", result.Message)
|
||||
}
|
||||
|
||||
duplicate, err := persister.PersistIncoming(t.Context(), &inbox, msg)
|
||||
if err != nil {
|
||||
t.Fatalf("persist duplicate: %v", err)
|
||||
}
|
||||
if duplicate == nil || !duplicate.Duplicate {
|
||||
t.Fatalf("expected duplicate result, got %#v", duplicate)
|
||||
}
|
||||
|
||||
msg.SourceID = "tg-msg-2"
|
||||
msg.Content = "second"
|
||||
second, err := persister.PersistIncoming(t.Context(), &inbox, msg)
|
||||
if err != nil {
|
||||
t.Fatalf("persist second: %v", err)
|
||||
}
|
||||
if second.Contact.ID != result.Contact.ID || second.Conversation.ID != result.Conversation.ID {
|
||||
t.Fatalf("expected contact/conversation reuse: first=%#v second=%#v", result, second)
|
||||
}
|
||||
|
||||
var messageCount int64
|
||||
if err := db.Model(&model.Message{}).Where("inbox_id = ?", inbox.ID).Count(&messageCount).Error; err != nil {
|
||||
t.Fatalf("count messages: %v", err)
|
||||
}
|
||||
if messageCount != 2 {
|
||||
t.Fatalf("expected 2 persisted messages after duplicate skip, got %d", messageCount)
|
||||
}
|
||||
}
|
||||
|
||||
func shopifyHMAC(secret string, body []byte) string {
|
||||
mac := hmac.New(sha256.New, []byte(secret))
|
||||
mac.Write(body)
|
||||
@@ -90,6 +153,42 @@ func TestTelegramWebhookLookupInboxByBotToken(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestTelegramWebhookPersistsIncomingMessage(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
db := newWebhookLookupTestDB(t)
|
||||
inbox := seedWebhookInbox(t, db, "telegram")
|
||||
channelRecord := channelmodel.ChannelTelegram{
|
||||
AccountID: 1,
|
||||
InboxID: inbox.ID,
|
||||
BotToken: "123:secret-token",
|
||||
BotName: "support_bot",
|
||||
}
|
||||
if err := db.Create(&channelRecord).Error; err != nil {
|
||||
t.Fatalf("create telegram channel: %v", err)
|
||||
}
|
||||
|
||||
body := []byte(`{"update_id":1001,"message":{"message_id":2002,"from":{"id":3003,"first_name":"Ada","last_name":"Lovelace","username":"ada"},"chat":{"id":3003,"type":"private"},"date":1710000000,"text":"hello telegram"}}`)
|
||||
h := NewTelegramWebhookHandler(channelprovider.NewTelegramProvider(), nil, db)
|
||||
r := gin.New()
|
||||
r.POST("/webhooks/telegram/:bot_token", h.HandleTelegramWebhook)
|
||||
req := httptest.NewRequest(http.MethodPost, "/webhooks/telegram/123:secret-token", bytes.NewReader(body))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
w := httptest.NewRecorder()
|
||||
|
||||
r.ServeHTTP(w, req)
|
||||
|
||||
if w.Code != http.StatusOK {
|
||||
t.Fatalf("expected 200, got %d body=%s", w.Code, w.Body.String())
|
||||
}
|
||||
var message model.Message
|
||||
if err := db.Where("inbox_id = ? AND source_id = ?", inbox.ID, "2002").First(&message).Error; err != nil {
|
||||
t.Fatalf("expected telegram message persisted: %v", err)
|
||||
}
|
||||
if message.Content != "hello telegram" || message.MessageType != string(model.MessageTypeIncoming) {
|
||||
t.Fatalf("unexpected message: %#v", message)
|
||||
}
|
||||
}
|
||||
|
||||
func TestLineWebhookLookupInboxByLineChannelID(t *testing.T) {
|
||||
db := newWebhookLookupTestDB(t)
|
||||
inbox := seedWebhookInbox(t, db, "line")
|
||||
|
||||
Reference in New Issue
Block a user