Refactor Kafka publishing without DB outbox
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
@@ -9,6 +11,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/segmentio/kafka-go"
|
||||
"github.com/tinode/chat/server/auth"
|
||||
"github.com/tinode/chat/server/logs"
|
||||
"github.com/tinode/chat/server/store"
|
||||
@@ -901,6 +904,196 @@ func TestHandleBroadcastDataFiltersInvalidMentions(t *testing.T) {
|
||||
verifyResponseCodes(helper.results[0], []int{http.StatusAccepted}, t)
|
||||
}
|
||||
|
||||
func TestSaveMessageMaybeKafkaPublishesWithoutDBPersist(t *testing.T) {
|
||||
ctrl := gomock.NewController(t)
|
||||
defer ctrl.Finish()
|
||||
|
||||
oldKafkaBus := kafkaBus
|
||||
oldMessages := store.Messages
|
||||
oldPublisher := publishKafkaMessagePersist
|
||||
defer func() {
|
||||
kafkaBus = oldKafkaBus
|
||||
store.Messages = oldMessages
|
||||
publishKafkaMessagePersist = oldPublisher
|
||||
}()
|
||||
|
||||
kafkaBus = &kafkaRuntime{}
|
||||
store.Messages = mock_store.NewMockMessagesPersistenceInterface(ctrl)
|
||||
var publishedTopic string
|
||||
var publishedKey string
|
||||
var publishedEnvelope kafkaEnvelope
|
||||
publishKafkaMessagePersist = func(_ context.Context, topicName, key string, envelope kafkaEnvelope) error {
|
||||
publishedTopic = topicName
|
||||
publishedKey = key
|
||||
publishedEnvelope = envelope
|
||||
return nil
|
||||
}
|
||||
|
||||
msg := &types.Message{
|
||||
TenantID: 1,
|
||||
Topic: "grp-test",
|
||||
SeqId: 1,
|
||||
From: types.Uid(1).String(),
|
||||
Head: types.KVMap{"x-im-client-mid": "client-1"},
|
||||
Content: "hello",
|
||||
}
|
||||
|
||||
got, err := saveMessageMaybeKafka(context.Background(), 1, msg, []string{"file1"}, true)
|
||||
if err != nil {
|
||||
t.Fatalf("saveMessageMaybeKafka returned error: %v", err)
|
||||
}
|
||||
if got == nil || got.SeqId != 1 || !got.MarkedReadBySender {
|
||||
t.Fatalf("unexpected save result: %#v", got)
|
||||
}
|
||||
if msg.ClientMsgID != "client-1" {
|
||||
t.Fatalf("client message id not normalized: %q", msg.ClientMsgID)
|
||||
}
|
||||
if publishedTopic != "message_persist" {
|
||||
t.Fatalf("unexpected kafka topic: %q", publishedTopic)
|
||||
}
|
||||
if publishedKey != "grp-test" {
|
||||
t.Fatalf("unexpected kafka key: %q", publishedKey)
|
||||
}
|
||||
if publishedEnvelope.EventType != "message.persist.group" {
|
||||
t.Fatalf("unexpected event type: %q", publishedEnvelope.EventType)
|
||||
}
|
||||
if publishedEnvelope.TenantID != 1 || publishedEnvelope.TenantCode != "" {
|
||||
t.Fatalf("unexpected tenant routing fields: tenant_id=%d tenant_code=%q", publishedEnvelope.TenantID, publishedEnvelope.TenantCode)
|
||||
}
|
||||
if publishedEnvelope.IdempotencyKey != "client-1" {
|
||||
t.Fatalf("unexpected idempotency key: %q", publishedEnvelope.IdempotencyKey)
|
||||
}
|
||||
}
|
||||
|
||||
func TestShouldTryHotMessageFallback(t *testing.T) {
|
||||
if !shouldTryHotMessageFallback(types.TopicCatGrp, 159, &types.QueryOpt{Since: 159, Limit: 1}) {
|
||||
t.Fatal("expected fallback for latest group seq query")
|
||||
}
|
||||
if shouldTryHotMessageFallback(types.TopicCatP2P, 159, &types.QueryOpt{Since: 159, Limit: 1}) {
|
||||
t.Fatal("did not expect fallback for p2p topic")
|
||||
}
|
||||
if shouldTryHotMessageFallback(types.TopicCatGrp, 159, &types.QueryOpt{Since: 1, Limit: 1}) {
|
||||
t.Fatal("did not expect fallback for old history query")
|
||||
}
|
||||
if shouldTryHotMessageFallback(types.TopicCatGrp, 159, &types.QueryOpt{Since: 159, Limit: 50}) {
|
||||
t.Fatal("did not expect fallback for broad query")
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilterHotMessagesForUserSkipsDeleted(t *testing.T) {
|
||||
uid := types.Uid(1)
|
||||
now := time.Now()
|
||||
messages := []types.Message{
|
||||
{SeqId: 1},
|
||||
{SeqId: 2, DeletedFor: []types.SoftDelete{{User: uid.String()}}},
|
||||
{SeqId: 3, DeletedFor: []types.SoftDelete{{User: uid.UserId()}}},
|
||||
{SeqId: 4, DeletedAt: &now},
|
||||
{SeqId: 5, DelId: 10},
|
||||
}
|
||||
filtered := filterHotMessagesForUser(messages, uid)
|
||||
if len(filtered) != 1 || filtered[0].SeqId != 1 {
|
||||
t.Fatalf("unexpected filtered messages: %#v", filtered)
|
||||
}
|
||||
}
|
||||
|
||||
func TestMarkKafkaDeadLetterPublishesBeforeDBWrite(t *testing.T) {
|
||||
oldKafkaBus := kafkaBus
|
||||
oldPublisher := publishKafkaDeadLetter
|
||||
oldInfrastructure := store.HTTPInfrastructure
|
||||
defer func() {
|
||||
kafkaBus = oldKafkaBus
|
||||
publishKafkaDeadLetter = oldPublisher
|
||||
store.HTTPInfrastructure = oldInfrastructure
|
||||
}()
|
||||
|
||||
kafkaBus = &kafkaRuntime{config: kafkaConfig{ConsumerGroup: "im-server"}}
|
||||
infrastructure := &testHTTPInfrastructureStore{}
|
||||
store.HTTPInfrastructure = infrastructure
|
||||
var publishedEnvelope kafkaEnvelope
|
||||
publishKafkaDeadLetter = func(_ context.Context, topicName, key string, envelope kafkaEnvelope) error {
|
||||
if topicName != "dead_letter" || key != "event-1" {
|
||||
t.Fatalf("unexpected dead letter publish target topic=%q key=%q", topicName, key)
|
||||
}
|
||||
publishedEnvelope = envelope
|
||||
return nil
|
||||
}
|
||||
|
||||
original := kafkaEnvelope{
|
||||
EventID: "event-1",
|
||||
TenantID: 1,
|
||||
EventType: "message.persist.group",
|
||||
BizType: "message",
|
||||
BizID: "grp-test:1",
|
||||
}
|
||||
consume := &types.KafkaConsumeRecord{
|
||||
EventID: "event-1",
|
||||
ConsumerGroup: "im-server",
|
||||
SourceTopic: "im.message.persist",
|
||||
RetryCount: 3,
|
||||
}
|
||||
if err := markKafkaDeadLetter(context.Background(), 1, original, consume, "DB_FAILED", "boom"); err != nil {
|
||||
t.Fatalf("markKafkaDeadLetter returned error: %v", err)
|
||||
}
|
||||
if len(infrastructure.deadLetters) != 0 {
|
||||
t.Fatalf("dead letter was written before dead_letter topic consumption: %#v", infrastructure.deadLetters)
|
||||
}
|
||||
if publishedEnvelope.EventID == "" || publishedEnvelope.TenantID != 1 {
|
||||
t.Fatalf("dead letter event was not published correctly: %#v", publishedEnvelope)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleKafkaDeadLetterWritesDB(t *testing.T) {
|
||||
oldKafkaBus := kafkaBus
|
||||
oldInfrastructure := store.HTTPInfrastructure
|
||||
defer func() {
|
||||
kafkaBus = oldKafkaBus
|
||||
store.HTTPInfrastructure = oldInfrastructure
|
||||
}()
|
||||
|
||||
kafkaBus = nil
|
||||
infrastructure := &testHTTPInfrastructureStore{}
|
||||
store.HTTPInfrastructure = infrastructure
|
||||
original := kafkaEnvelope{
|
||||
EventID: "event-1",
|
||||
TenantID: 1,
|
||||
EventType: "message.persist.group",
|
||||
BizType: "message",
|
||||
BizID: "grp-test:1",
|
||||
}
|
||||
encodedOriginal, _ := json.Marshal(original)
|
||||
dead := types.KafkaDeadLetter{
|
||||
DeadID: "dead-1",
|
||||
EventID: "event-1",
|
||||
SourceTopic: "im.message.persist",
|
||||
ConsumerGroup: "im-server",
|
||||
EventType: "message.persist.group",
|
||||
BizType: "message",
|
||||
BizID: "grp-test:1",
|
||||
Envelope: encodedOriginal,
|
||||
RetryCount: 3,
|
||||
LastReason: "DB_FAILED",
|
||||
LastError: "boom",
|
||||
Status: types.KafkaDeadStatusDead,
|
||||
}
|
||||
payload, _ := json.Marshal(dead)
|
||||
envelope := kafkaEnvelope{
|
||||
EventID: "dead-1",
|
||||
TenantID: 1,
|
||||
EventType: "kafka.dead_letter.created",
|
||||
ParentEventID: "event-1",
|
||||
Payload: payload,
|
||||
}
|
||||
if err := handleKafkaDeadLetter(context.Background(), envelope, kafka.Message{}); err != nil {
|
||||
t.Fatalf("handleKafkaDeadLetter returned error: %v", err)
|
||||
}
|
||||
if len(infrastructure.deadLetters) != 1 {
|
||||
t.Fatalf("expected one dead letter write, got %d", len(infrastructure.deadLetters))
|
||||
}
|
||||
if infrastructure.deadLetters[0].DeadID != "dead-1" || infrastructure.deadLetters[0].EventID != "event-1" {
|
||||
t.Fatalf("unexpected dead letter write: %#v", infrastructure.deadLetters[0])
|
||||
}
|
||||
}
|
||||
|
||||
func TestHandleBroadcastDataInactiveTopic(t *testing.T) {
|
||||
numUsers := 2
|
||||
helper := TopicTestHelper{}
|
||||
|
||||
Reference in New Issue
Block a user