add: mqtt 客户端测试 + 文档更新
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
"""
|
||||
MQTTSubscribeClient 单元测试
|
||||
|
||||
覆盖:主题通配符编译、路由构建、消息分发、请求-响应(reply_topic)与不回复约定。
|
||||
"""
|
||||
import json
|
||||
|
||||
from chongming_mqtt.mqtt_subscribe import MQTTSubscribeClient, _compile_topic
|
||||
|
||||
from fakes import attach, run
|
||||
|
||||
|
||||
# ---------- _compile_topic 通配符匹配 ----------
|
||||
|
||||
def test_compile_topic_exact():
|
||||
p = _compile_topic("/edge_app_controller")
|
||||
assert p.match("/edge_app_controller")
|
||||
assert not p.match("/edge_app_controller/other")
|
||||
assert not p.match("/other")
|
||||
|
||||
|
||||
def test_compile_topic_plus_wildcard():
|
||||
"""+ 只匹配单级"""
|
||||
p = _compile_topic("/dev/+/cmd")
|
||||
assert p.match("/dev/box1/cmd")
|
||||
assert not p.match("/dev/box1/sub/cmd")
|
||||
|
||||
|
||||
def test_compile_topic_hash_wildcard():
|
||||
"""# 匹配多级且须在末尾"""
|
||||
p = _compile_topic("/dev/#")
|
||||
assert p.match("/dev/a/b/c")
|
||||
assert p.match("/dev/x")
|
||||
assert not p.match("/other/a")
|
||||
|
||||
|
||||
# ---------- 路由构建 ----------
|
||||
|
||||
def test_routes_skip_invalid():
|
||||
"""无效(空 topic / 无 handler)订阅项应被跳过"""
|
||||
client = MQTTSubscribeClient("mqtt://h:1883", routes=[
|
||||
{"topic": "", "handler": lambda d, t: None},
|
||||
{"topic": "/t", "handler": lambda d, t: None},
|
||||
{"topic": "/t2"}, # 无 handler
|
||||
])
|
||||
assert len(client._routes) == 1
|
||||
|
||||
|
||||
# ---------- 消息分发与回复 ----------
|
||||
|
||||
async def _echo_handler(payload, topic):
|
||||
return {"echo": payload.get("v")}
|
||||
|
||||
|
||||
def test_dispatch_invokes_handler_and_replies():
|
||||
"""收到消息 → 调用 handler → 结果发布到 reply_topic"""
|
||||
client = MQTTSubscribeClient("mqtt://h:1883", routes=[
|
||||
{"topic": "/req", "handler": _echo_handler, "reply_topic": "/resp"},
|
||||
])
|
||||
fake = attach(client)
|
||||
run(client._dispatch("/req", b'{"v": 7}'))
|
||||
assert len(fake.published) == 1
|
||||
topic, payload, _ = fake.published[0]
|
||||
assert topic == "/resp"
|
||||
assert json.loads(payload) == {"echo": 7}
|
||||
|
||||
|
||||
def test_dispatch_no_reply_when_handler_returns_none():
|
||||
"""handler 返回 None 时不发布回复(静默命令)"""
|
||||
async def silent(payload, topic):
|
||||
return None
|
||||
|
||||
client = MQTTSubscribeClient("mqtt://h:1883", routes=[
|
||||
{"topic": "/req", "handler": silent, "reply_topic": "/resp"},
|
||||
])
|
||||
fake = attach(client)
|
||||
run(client._dispatch("/req", b'{"v": 1}'))
|
||||
assert fake.published == []
|
||||
|
||||
|
||||
def test_dispatch_no_reply_without_reply_topic():
|
||||
"""未配置 reply_topic 时不发布"""
|
||||
client = MQTTSubscribeClient("mqtt://h:1883", routes=[
|
||||
{"topic": "/req", "handler": _echo_handler},
|
||||
])
|
||||
fake = attach(client)
|
||||
run(client._dispatch("/req", b'{"v": 1}'))
|
||||
assert fake.published == []
|
||||
|
||||
|
||||
def test_dispatch_ignores_non_json():
|
||||
"""非 JSON 消息被忽略,不调用 handler"""
|
||||
calls = []
|
||||
|
||||
async def h(payload, topic):
|
||||
calls.append(payload)
|
||||
|
||||
client = MQTTSubscribeClient("mqtt://h:1883", routes=[
|
||||
{"topic": "/req", "handler": h, "reply_topic": "/resp"},
|
||||
])
|
||||
fake = attach(client)
|
||||
run(client._dispatch("/req", b"not-json"))
|
||||
assert calls == []
|
||||
assert fake.published == []
|
||||
|
||||
|
||||
def test_dispatch_unmatched_topic_ignored():
|
||||
"""不匹配任何路由主题的消息被忽略"""
|
||||
client = MQTTSubscribeClient("mqtt://h:1883", routes=[
|
||||
{"topic": "/req", "handler": _echo_handler, "reply_topic": "/resp"},
|
||||
])
|
||||
fake = attach(client)
|
||||
run(client._dispatch("/other/topic", b'{"v": 1}'))
|
||||
assert fake.published == []
|
||||
|
||||
|
||||
def test_reply_topic_placeholder_fill():
|
||||
"""reply_topic 的 {1} 占位符填充订阅主题通配符捕获值"""
|
||||
client = MQTTSubscribeClient("mqtt://h:1883", routes=[
|
||||
{"topic": "/dev/+/cmd", "handler": _echo_handler, "reply_topic": "/resp/{1}"},
|
||||
])
|
||||
fake = attach(client)
|
||||
run(client._dispatch("/dev/box9/cmd", b'{"v": 1}'))
|
||||
assert fake.published[0][0] == "/resp/box9"
|
||||
Reference in New Issue
Block a user