Skip to content
This repository was archived by the owner on Jun 8, 2025. It is now read-only.

Commit d8e1be2

Browse files
authored
add tests to topics.Match, fix # matcher (#353)
1 parent b89e1dd commit d8e1be2

2 files changed

Lines changed: 95 additions & 4 deletions

File tree

backends/topics/topics.go

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,14 @@ func Match(savedTopic, givenTopic string) bool {
77
return givenTopic == savedTopic || match(strings.Split(savedTopic, "/"), strings.Split(givenTopic, "/"))
88
}
99

10-
// TODO: I've always trusted this function does the right thing,
11-
// and it's kind of been proven by use and indirect testing of backends,
12-
// but it should really have tests of its own.
1310
func match(route []string, topic []string) bool {
1411
switch {
1512
case len(route) == 0:
1613
return len(topic) == 0
1714
case len(topic) == 0:
1815
return route[0] == "#"
1916
case route[0] == "#":
20-
return true
17+
return len(route) == 1 // '#' must be last in pattern
2118
case route[0] == "+", route[0] == topic[0]:
2219
return match(route[1:], topic[1:])
2320
}

backends/topics/topics_test.go

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package topics
2+
3+
import (
4+
"fmt"
5+
. "github.com/smartystreets/goconvey/convey"
6+
"testing"
7+
)
8+
9+
func TestTopicsMatch(t *testing.T) {
10+
Convey("Match should match mqtt topics correctly", t, func() {
11+
tests := []struct {
12+
pattern string
13+
topic string
14+
expected bool
15+
}{
16+
// Exact matches
17+
{"a/b/c", "a/b/c", true},
18+
{"a/b", "a/b", true},
19+
{"a", "a", true},
20+
21+
{"a/b", "a/x", false},
22+
{"a/b", "x/b", false},
23+
24+
// Single-level wildcard +
25+
{"a/+/c", "a/b/c", true},
26+
{"a/+/c", "a/x/c", true},
27+
{"+/+/+", "a/b/c", true},
28+
{"+/b/+", "a/b/c", true},
29+
{"a/b/+", "a/b/c", true},
30+
{"+", "a", true},
31+
32+
{"a/+/c", "a/x/x", false},
33+
34+
// Wrong segment count with +
35+
{"a/+/c", "a/c", false},
36+
{"a/+/c", "a/b/c/d", false},
37+
{"+/+", "a", false},
38+
{"+/+", "a/b/c", false},
39+
40+
// Multi-level wildcard #
41+
{"a/b/#", "a/b", true},
42+
{"a/b/#", "a/b/c", true},
43+
{"a/b/#", "a/b/c/d/e", true},
44+
{"#", "a", true},
45+
{"#", "a/b/c", true},
46+
{"#", "/", true},
47+
{"#", "//", true},
48+
49+
{"a/b/#", "a", false},
50+
{"a/b/#", "a/x/c", false},
51+
52+
// # cannot match middle segments
53+
{"a/#/c", "a/b/c", false},
54+
55+
// Pattern longer than topic
56+
{"a/b/c/d", "a/b/c", false},
57+
{"a/b/+", "a/b", false},
58+
59+
// Topic longer than pattern
60+
{"a/b", "a/b/c", false},
61+
{"a/b/c", "a/b/c/d", false},
62+
{"a/b/+", "a/b/c/d", false},
63+
64+
// Empty topic and pattern
65+
{"", "", true},
66+
{"#", "", true},
67+
{"+", "", true},
68+
69+
{"", "a", false},
70+
71+
// Trailing slashes
72+
{"a/b/", "a/b/", true},
73+
74+
{"a/b", "a/b/", false},
75+
{"a/b/", "a/b", false},
76+
77+
// Topic with empty segments
78+
{"a//c", "a//c", true},
79+
{"a/+/c", "a//c", true},
80+
}
81+
82+
for i, test := range tests {
83+
Convey(fmt.Sprintf("#%d: Match(%s, %s)", i, test.pattern, test.topic), func() {
84+
result := Match(test.pattern, test.topic)
85+
86+
if result != test.expected {
87+
fmt.Printf("\nFAILED test #%d: pattern=%q topic=%q → got=%v, expected=%v\n", i, test.topic, test.pattern, result, test.expected)
88+
}
89+
90+
So(result, ShouldEqual, test.expected)
91+
})
92+
}
93+
})
94+
}

0 commit comments

Comments
 (0)