173 lines
5.3 KiB
Go
173 lines
5.3 KiB
Go
package eventbus
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"reflect"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/segmentio/kafka-go"
|
|
|
|
"lingniu-vehicle-ingest/go/vehicle-gateway/internal/envelope"
|
|
)
|
|
|
|
func TestKafkaSinkRoutesRawTopics(t *testing.T) {
|
|
writer := &recordingWriter{}
|
|
sink := newKafkaSinkWithWriter(writer, KafkaConfig{})
|
|
|
|
env := envelope.FrameEnvelope{Protocol: envelope.ProtocolJT808, Phone: "13307795425", MessageID: "0x0200"}
|
|
if err := sink.PublishRaw(context.Background(), env); err != nil {
|
|
t.Fatalf("PublishRaw() error = %v", err)
|
|
}
|
|
if len(writer.messages) != 1 {
|
|
t.Fatalf("messages = %d", len(writer.messages))
|
|
}
|
|
msg := writer.messages[0]
|
|
if got, want := msg.Topic, "vehicle.raw.go.jt808.v1"; got != want {
|
|
t.Fatalf("topic = %q, want %q", got, want)
|
|
}
|
|
if string(msg.Key) != "JT808:13307795425" {
|
|
t.Fatalf("key = %q", string(msg.Key))
|
|
}
|
|
var decoded envelope.FrameEnvelope
|
|
if err := json.Unmarshal(msg.Value, &decoded); err != nil {
|
|
t.Fatalf("json.Unmarshal() error = %v", err)
|
|
}
|
|
if decoded.EventID == "" || decoded.ParseStatus != envelope.ParseOK {
|
|
t.Fatalf("unexpected payload defaults: %#v", decoded)
|
|
}
|
|
}
|
|
|
|
func TestKafkaSinkRoutesUnifiedTopic(t *testing.T) {
|
|
writer := &recordingWriter{}
|
|
sink := newKafkaSinkWithWriter(writer, KafkaConfig{UnifiedTopic: "custom.unified"})
|
|
|
|
env := envelope.FrameEnvelope{Protocol: envelope.ProtocolGB32960, VIN: "LNBVIN00000000001", MessageID: "0x02"}
|
|
if err := sink.PublishUnified(context.Background(), env); err != nil {
|
|
t.Fatalf("PublishUnified() error = %v", err)
|
|
}
|
|
if writer.messages[0].Topic != "custom.unified" {
|
|
t.Fatalf("topic = %q", writer.messages[0].Topic)
|
|
}
|
|
if string(writer.messages[0].Key) != "LNBVIN00000000001" {
|
|
t.Fatalf("key = %q", string(writer.messages[0].Key))
|
|
}
|
|
}
|
|
|
|
func TestKafkaSinkRejectsUnknownProtocol(t *testing.T) {
|
|
writer := &recordingWriter{}
|
|
sink := newKafkaSinkWithWriter(writer, KafkaConfig{})
|
|
|
|
err := sink.PublishRaw(context.Background(), envelope.FrameEnvelope{Protocol: envelope.Protocol("UNKNOWN")})
|
|
if err == nil {
|
|
t.Fatal("expected unknown protocol error")
|
|
}
|
|
}
|
|
|
|
func TestKafkaSinkPublishesDurableRecordsInSingleWriterCall(t *testing.T) {
|
|
writer := &recordingWriter{}
|
|
sink := newKafkaSinkWithWriter(writer, KafkaConfig{UnifiedTopic: "custom.unified"})
|
|
env := envelope.FrameEnvelope{Protocol: envelope.ProtocolJT808, Phone: "13307795425", MessageID: "0x0200"}
|
|
|
|
err := sink.PublishRecords(context.Background(), []durableRecord{
|
|
{Kind: "raw", Envelope: env},
|
|
{Kind: "unified", Envelope: env},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("PublishRecords() error = %v", err)
|
|
}
|
|
if writer.writeCalls != 1 {
|
|
t.Fatalf("WriteMessages calls = %d, want 1", writer.writeCalls)
|
|
}
|
|
if len(writer.messages) != 2 {
|
|
t.Fatalf("messages = %d, want 2", len(writer.messages))
|
|
}
|
|
if writer.messages[0].Topic != "vehicle.raw.go.jt808.v1" || writer.messages[1].Topic != "custom.unified" {
|
|
t.Fatalf("topics = %q, %q", writer.messages[0].Topic, writer.messages[1].Topic)
|
|
}
|
|
}
|
|
|
|
func TestNewKafkaSinkUsesProductionDeliveryGuarantees(t *testing.T) {
|
|
sink, err := NewKafkaSink(KafkaConfig{Brokers: []string{"127.0.0.1:9092"}})
|
|
if err != nil {
|
|
t.Fatalf("NewKafkaSink() error = %v", err)
|
|
}
|
|
defer sink.Close()
|
|
|
|
writer, ok := sink.writer.(*kafka.Writer)
|
|
if !ok {
|
|
t.Fatalf("writer type = %s", reflect.TypeOf(sink.writer))
|
|
}
|
|
if writer.RequiredAcks != kafka.RequireAll {
|
|
t.Fatalf("RequiredAcks = %v, want %v", writer.RequiredAcks, kafka.RequireAll)
|
|
}
|
|
if writer.Async {
|
|
t.Fatal("writer must publish synchronously so Kafka errors propagate")
|
|
}
|
|
if writer.AllowAutoTopicCreation {
|
|
t.Fatal("writer must not auto-create production topics")
|
|
}
|
|
if _, ok := writer.Balancer.(*kafka.Hash); !ok {
|
|
t.Fatalf("balancer type = %s, want *kafka.Hash", reflect.TypeOf(writer.Balancer))
|
|
}
|
|
}
|
|
|
|
func TestValidateKafkaConfigRejectsRawFieldsTopicOverlap(t *testing.T) {
|
|
err := ValidateKafkaConfig(KafkaConfig{
|
|
RawTopics: map[envelope.Protocol]string{
|
|
envelope.ProtocolJT808: "vehicle.raw.go.jt808.v1",
|
|
},
|
|
FieldsTopics: map[envelope.Protocol]string{
|
|
envelope.ProtocolJT808: "vehicle.raw.go.jt808.v1",
|
|
},
|
|
})
|
|
if err == nil {
|
|
t.Fatal("ValidateKafkaConfig() error = nil, want overlap rejection")
|
|
}
|
|
if !strings.Contains(err.Error(), "fields kafka topic") {
|
|
t.Fatalf("error = %q, want fields topic family hint", err)
|
|
}
|
|
}
|
|
|
|
func TestValidateKafkaConfigRejectsKnownProtocolTopicMismatch(t *testing.T) {
|
|
err := ValidateKafkaConfig(KafkaConfig{
|
|
RawTopics: map[envelope.Protocol]string{
|
|
envelope.ProtocolJT808: "vehicle.raw.go.gb32960.v1",
|
|
},
|
|
})
|
|
if err == nil {
|
|
t.Fatal("ValidateKafkaConfig() error = nil, want raw protocol mismatch")
|
|
}
|
|
if !strings.Contains(err.Error(), "must match protocol") {
|
|
t.Fatalf("error = %q, want protocol mismatch hint", err)
|
|
}
|
|
|
|
err = ValidateKafkaConfig(KafkaConfig{
|
|
FieldsTopics: map[envelope.Protocol]string{
|
|
envelope.ProtocolYutongMQTT: "vehicle.fields.go.jt808.v1",
|
|
},
|
|
})
|
|
if err == nil {
|
|
t.Fatal("ValidateKafkaConfig() error = nil, want fields protocol mismatch")
|
|
}
|
|
if !strings.Contains(err.Error(), "must match protocol") {
|
|
t.Fatalf("error = %q, want protocol mismatch hint", err)
|
|
}
|
|
}
|
|
|
|
type recordingWriter struct {
|
|
messages []kafka.Message
|
|
writeCalls int
|
|
}
|
|
|
|
func (w *recordingWriter) WriteMessages(_ context.Context, messages ...kafka.Message) error {
|
|
w.writeCalls++
|
|
w.messages = append(w.messages, messages...)
|
|
return nil
|
|
}
|
|
|
|
func (w *recordingWriter) Close() error {
|
|
return nil
|
|
}
|