feat(search): index provider webhooks

This commit is contained in:
2026-06-07 15:23:29 +08:00
parent 28e7205410
commit 8eabadc3fc
10 changed files with 183 additions and 13 deletions
@@ -35,6 +35,27 @@ type recordingListener struct {
events []*channel.ChannelEvent
}
type recordingIncomingSearchIndexer struct {
contacts []model.Contact
conversations []model.Conversation
messages []model.Message
}
func (r *recordingIncomingSearchIndexer) IndexContact(ctx context.Context, contact *model.Contact) error {
r.contacts = append(r.contacts, *contact)
return nil
}
func (r *recordingIncomingSearchIndexer) IndexConversation(ctx context.Context, conversation *model.Conversation) error {
r.conversations = append(r.conversations, *conversation)
return nil
}
func (r *recordingIncomingSearchIndexer) IndexMessage(ctx context.Context, message *model.Message) error {
r.messages = append(r.messages, *message)
return nil
}
type webhookAutomationDBProvider struct {
db *gorm.DB
}
@@ -175,8 +196,9 @@ func TestIncomingPersisterQueuesMessageStatusUpdateWithWorker(t *testing.T) {
dispatcher := channel.NewDispatcher()
listener := &recordingListener{}
dispatcher.Register(listener)
indexer := &recordingIncomingSearchIndexer{}
wp := worker.NewWorkerPoolWithOptions(db, worker.WithQueues("low"))
persister := NewIncomingPersister(db, dispatcher)
persister := NewIncomingPersister(db, dispatcher).SetSearchIndexer(indexer)
msg := &channel.IncomingMessage{
ChannelType: channel.ChannelTelegram,
@@ -232,6 +254,9 @@ func TestIncomingPersisterQueuesMessageStatusUpdateWithWorker(t *testing.T) {
if !listenerSaw(listener, channel.EventMessageStatusUpdated) {
t.Fatalf("expected message.status_updated event, got %#v", listener.events)
}
if len(indexer.messages) != 2 || indexer.messages[1].ID != updated.ID || indexer.messages[1].Status != string(model.MessageStatusDelivered) {
t.Fatalf("expected delivered message to be indexed, got %#v", indexer.messages)
}
}
func TestIncomingPersisterStatusJobDoesNotDowngradeRead(t *testing.T) {
@@ -250,7 +275,8 @@ func TestIncomingPersisterStatusJobDoesNotDowngradeRead(t *testing.T) {
t.Fatalf("create message: %v", err)
}
wp := worker.NewWorkerPoolWithOptions(db, worker.WithQueues("low"))
persister := NewIncomingPersister(db).SetWorkerPool(wp)
indexer := &recordingIncomingSearchIndexer{}
persister := NewIncomingPersister(db).SetSearchIndexer(indexer).SetWorkerPool(wp)
if err := persister.UpdateMessageStatus(t.Context(), &inbox, "provider-read-1", model.MessageStatusDelivered, nil); err != nil {
t.Fatalf("enqueue downgrade: %v", err)
@@ -330,7 +356,8 @@ func TestIncomingPersisterQueuesContactMessagesStatusUpdateWithWorker(t *testing
alreadyRead := createWebhookStatusMessage(t, db, inbox, conversation, "read-before", model.MessageStatusRead, cutoff.Add(-20*time.Second))
afterCutoff := createWebhookStatusMessage(t, db, inbox, conversation, "sent-after", model.MessageStatusSent, cutoff.Add(time.Minute))
wp := worker.NewWorkerPoolWithOptions(db, worker.WithQueues("low"))
persister := NewIncomingPersister(db).SetWorkerPool(wp)
indexer := &recordingIncomingSearchIndexer{}
persister := NewIncomingPersister(db).SetSearchIndexer(indexer).SetWorkerPool(wp)
if err := persister.UpdateContactConversationMessagesStatus(t.Context(), &inbox, "meta-user-1", model.MessageStatusRead, &cutoff); err != nil {
t.Fatalf("enqueue contact read: %v", err)
@@ -355,6 +382,9 @@ func TestIncomingPersisterQueuesContactMessagesStatusUpdateWithWorker(t *testing
if deliveryCount != 3 {
t.Fatalf("expected 3 read delivery statuses, got %d", deliveryCount)
}
if len(indexer.messages) != 3 {
t.Fatalf("expected 3 updated messages to be indexed, got %#v", indexer.messages)
}
}
func TestIncomingPersisterCreatesConversationMessageAndDedupes(t *testing.T) {
@@ -363,7 +393,8 @@ func TestIncomingPersisterCreatesConversationMessageAndDedupes(t *testing.T) {
dispatcher := channel.NewDispatcher()
listener := &recordingListener{}
dispatcher.Register(listener)
persister := NewIncomingPersister(db, dispatcher)
indexer := &recordingIncomingSearchIndexer{}
persister := NewIncomingPersister(db, dispatcher).SetSearchIndexer(indexer)
msg := &channel.IncomingMessage{
ChannelType: channel.ChannelTelegram,
@@ -387,6 +418,9 @@ func TestIncomingPersisterCreatesConversationMessageAndDedupes(t *testing.T) {
if result.Message.Content != "hello" || result.Message.SourceID != "tg-msg-1" {
t.Fatalf("unexpected message: %#v", result.Message)
}
if len(indexer.contacts) != 1 || len(indexer.conversations) != 1 || len(indexer.messages) != 1 {
t.Fatalf("expected first incoming result indexed, contacts=%#v conversations=%#v messages=%#v", indexer.contacts, indexer.conversations, indexer.messages)
}
duplicate, err := persister.PersistIncoming(t.Context(), &inbox, msg)
if err != nil {
@@ -395,6 +429,9 @@ func TestIncomingPersisterCreatesConversationMessageAndDedupes(t *testing.T) {
if duplicate == nil || !duplicate.Duplicate {
t.Fatalf("expected duplicate result, got %#v", duplicate)
}
if len(indexer.messages) != 1 {
t.Fatalf("expected duplicate to skip indexing, got %#v", indexer.messages)
}
msg.SourceID = "tg-msg-2"
msg.Content = "second"
@@ -405,6 +442,9 @@ func TestIncomingPersisterCreatesConversationMessageAndDedupes(t *testing.T) {
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)
}
if len(indexer.messages) != 2 || indexer.messages[1].ID != second.Message.ID {
t.Fatalf("expected second incoming message indexed, got %#v", indexer.messages)
}
var messageCount int64
if err := db.Model(&model.Message{}).Where("inbox_id = ?", inbox.ID).Count(&messageCount).Error; err != nil {
@@ -426,8 +466,9 @@ func TestIncomingPersisterQueuesIncomingMessageWithWorker(t *testing.T) {
dispatcher := channel.NewDispatcher()
listener := &recordingListener{}
dispatcher.Register(listener)
indexer := &recordingIncomingSearchIndexer{}
wp := worker.NewWorkerPool(db)
persister := NewIncomingPersister(db, dispatcher).SetWorkerPool(wp)
persister := NewIncomingPersister(db, dispatcher).SetSearchIndexer(indexer).SetWorkerPool(wp)
msg := &channel.IncomingMessage{
ChannelType: channel.ChannelTelegram,
@@ -464,6 +505,9 @@ func TestIncomingPersisterQueuesIncomingMessageWithWorker(t *testing.T) {
t.Fatalf("process incoming job processed=%v err=%v", processed, err)
}
assertPersistedMessage(t, db, inbox.ID, "tg-inbound-job-1", "persist me later")
if len(indexer.messages) != 1 || indexer.messages[0].SourceID != "tg-inbound-job-1" {
t.Fatalf("expected durable incoming message to be indexed, got %#v", indexer.messages)
}
for _, eventType := range []channel.EventType{channel.EventContactCreated, channel.EventConversationCreated, channel.EventConversationOpened, channel.EventMessageCreated, channel.EventMessageIncoming} {
if !listenerSaw(listener, eventType) {
t.Fatalf("expected event %s, got %#v", eventType, listener.events)