Files
magistrala/pkg/messaging/fluxmq/topic_test.go
T
Dušan Borovčanin 49488738df
Continuous Delivery / lint-and-build (push) Has been cancelled
Deploy GitHub Pages / swagger-ui (push) Has been cancelled
CI Pipeline / Lint Proto (push) Has been cancelled
CI Pipeline / Detect Changes (push) Has been cancelled
Continuous Delivery / Build and Push Docker Images (push) Has been cancelled
CI Pipeline / lint-and-build (push) Has been cancelled
CI Pipeline / Test ${{ matrix.module }} (push) Has been cancelled
CI Pipeline / Upload Coverage (push) Has been cancelled
NOISSUE - Fix queue subscriptions (#3522)
Signed-off-by: dusan <borovcanindusan1@gmail.com>
2026-05-27 00:50:36 +02:00

265 lines
6.1 KiB
Go

// Copyright (c) Abstract Machines
// SPDX-License-Identifier: Apache-2.0
package fluxmq
import (
"strings"
"testing"
)
func TestStreamFilter(t *testing.T) {
cases := []struct {
name string
prefix string
topic string
want string
}{
{
name: "all messages with prefix",
prefix: "m",
topic: "m/#",
want: "#",
},
{
name: "all messages without explicit prefix",
prefix: "writers",
topic: "#",
want: "#",
},
{
name: "specific topic filter",
prefix: "writers",
topic: "writers/domain/c/channel/+",
want: "domain/c/channel/+",
},
{
name: "topic without prefix",
prefix: "alarms",
topic: "domain/c/channel/#",
want: "domain/c/channel/#",
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
got := streamFilter(tc.prefix, tc.topic)
if got != tc.want {
t.Fatalf("stream filter mismatch: got %q, want %q", got, tc.want)
}
})
}
}
func TestQueueFilter(t *testing.T) {
got := queueFilter("writers", "writers/#")
want := "$queue/writers/#"
if got != want {
t.Fatalf("queue filter mismatch: got %q, want %q", got, want)
}
}
func TestTopicFilter(t *testing.T) {
cases := []struct {
name string
prefix string
topic string
want string
}{
{
name: "all messages with prefix",
prefix: "m",
topic: "m/#",
want: "m/#",
},
{
name: "wildcard topic",
prefix: "writers",
topic: "#",
want: "writers/#",
},
{
name: "specific topic",
prefix: "m",
topic: "m/domain/c/channel/subtopic",
want: "m/domain/c/channel/subtopic",
},
{
name: "single-level wildcard",
prefix: "m",
topic: "m/domain/c/+/subtopic",
want: "m/domain/c/+/subtopic",
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
got := topicFilter(tc.prefix, tc.topic)
if got != tc.want {
t.Fatalf("topic filter mismatch: got %q, want %q", got, tc.want)
}
})
}
}
func TestParseMQTTTopic(t *testing.T) {
cases := []struct {
name string
prefix string
topic string
domain string
channel string
subtopic string
shouldErr bool
}{
{
name: "default prefix with subtopic path",
prefix: "m",
topic: "m/domain/c/channel/sub/topic",
domain: "domain",
channel: "channel",
subtopic: "sub/topic",
},
{
name: "alternate prefix without subtopic",
prefix: "writers",
topic: "writers/domain/c/channel",
domain: "domain",
channel: "channel",
subtopic: "",
},
{
name: "leading slash is ignored",
prefix: "alarms",
topic: "/alarms/domain/c/channel/critical/high",
domain: "domain",
channel: "channel",
subtopic: "critical/high",
},
{
name: "mismatched prefix",
prefix: "writers",
topic: "m/domain/c/channel",
shouldErr: true,
},
{
name: "invalid shape",
prefix: "m",
topic: "m/domain/channel",
shouldErr: true,
},
{
name: "empty subtopic segment",
prefix: "m",
topic: "m/domain/c/channel/sub//topic",
shouldErr: true,
},
{
name: "dot topic is invalid",
prefix: "m",
topic: "m.domain.c.channel.sub.topic",
shouldErr: true,
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
domain, channel, subtopic, err := parseMQTTTopic(tc.prefix, tc.topic)
if tc.shouldErr {
if err == nil {
t.Fatal("expected parse error, got nil")
}
return
}
if err != nil {
t.Fatalf("unexpected parse error: %v", err)
}
if domain != tc.domain || channel != tc.channel || subtopic != tc.subtopic {
t.Fatalf("parsed topic mismatch: got domain=%q channel=%q subtopic=%q", domain, channel, subtopic)
}
})
}
}
func TestParseMQTTTopicFromStreamRoutingKey(t *testing.T) {
// Stream queue routing keys have the format "$queue/<prefix>/<domain>/c/<channel>[/<subtopic>]".
// After stripping "$queue/", the remainder is a valid MQTT-style topic for parseMQTTTopic.
cases := []struct {
name string
routingKey string
prefix string
domain string
channel string
subtopic string
}{
{
name: "writers queue with subtopic",
routingKey: "$queue/writers/domain/c/channel/temp",
prefix: "writers",
domain: "domain",
channel: "channel",
subtopic: "temp",
},
{
name: "main queue without subtopic",
routingKey: "$queue/m/domain/c/channel",
prefix: "m",
domain: "domain",
channel: "channel",
subtopic: "",
},
{
name: "alarms queue with nested subtopic",
routingKey: "$queue/alarms/dom/c/ch/critical/high",
prefix: "alarms",
domain: "dom",
channel: "ch",
subtopic: "critical/high",
},
}
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
mqttTopic := strings.TrimPrefix(tc.routingKey, "$queue/")
domain, channel, subtopic, err := parseMQTTTopic(tc.prefix, mqttTopic)
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if domain != tc.domain || channel != tc.channel || subtopic != tc.subtopic {
t.Fatalf("got domain=%q channel=%q subtopic=%q", domain, channel, subtopic)
}
})
}
}
func TestStringHeader(t *testing.T) {
headers := map[string]any{
"external_id": "pub-1",
"number": 42,
"bytes": []byte("bin"),
}
if got := stringHeader(headers, "external_id"); got != "pub-1" {
t.Fatalf("expected pub-1, got %q", got)
}
if got := stringHeader(headers, "bytes"); got != "bin" {
t.Fatalf("expected bin, got %q", got)
}
if got := stringHeader(headers, "number"); got != "" {
t.Fatalf("expected empty for non-string, got %q", got)
}
if got := stringHeader(headers, "missing"); got != "" {
t.Fatalf("expected empty for missing key, got %q", got)
}
if got := stringHeader(nil, "any"); got != "" {
t.Fatalf("expected empty for nil headers, got %q", got)
}
}
func TestFormatConsumerName(t *testing.T) {
got := formatConsumerName("m/domain/c/channel/#", "re/service 1")
want := "m_domain_c_channel__-re_service_1"
if got != want {
t.Fatalf("consumer name mismatch: got %q, want %q", got, want)
}
}