最后一步:把 main.go 里所有指向桥接小写名的引用(store / config / blocking / bot / runtimesettings / mqttforward / web 等)改写成直接调用 internal/* 子包的导出 API,从而拆掉所有 *_bridge.go。 变更点 - main.go 顶部 import 区改为引用 11 个 internal/ 子包:auth、blocking、 bot、config、mqttforward、runtimesettings、store、web 等。 - meshtasticFilterHook 字段类型改为 *storepkg.WriteQueue / *blockingpkg.Cache / *rspkg.Cache / *mqttforwardpkg.Stats;不再依赖 main 包内别名。 - mqttClientInfoFromClient 返回 storepkg.MQTTClientInfo(之前为 main 包别名)。 - run() 里 newSessionManager → auth.NewManager;newRouter → webpkg.NewRouter; serveHTTPUnixSocket → webpkg.ServeUnixSocket;mqttRuntimeStatus → webpkg.MQTTRuntimeStatus;botSendTextRequest → botpkg.SendTextRequest; newBlockingCache / newRuntimeSettingsCache / newMQTTForwardManager 都改为 对应包的 New / NewManager。 - main_test.go 里的 BlockingViolationForRecord 测试改用 testutil.OpenStore + blockingpkg.New,全部通过 store.Store 的公开 API 构造数据,不再需要 根目录 test_helpers_test.go。 删除根目录文件 - config.go、auth.go(已删)、blocking_bridge.go、bot_bridge.go、 help_bridge.go、llmadmin_bridge.go、mapsource_bridge.go、 mqttforward_bridge.go、runtime_settings_bridge.go、sign_bridge.go、 store_bridge.go、web_bridge.go、test_helpers_test.go。 最终结果 - 根目录只剩 main.go (~370 行) 和 main_test.go (~110 行)。 - internal/ 下 13 个独立子包:auth / blocking / bot / config / help / llmadmin / mapsource / mqttforward / runtimesettings / sign / store / store/testutil / web / webutil。 - go build ./... / go test ./... 全部通过;测试数量与重构前一致; go build . 可以产出可执行二进制(约 50 MB)。 Co-Authored-By: Claude <noreply@anthropic.com>
114 lines
4.1 KiB
Go
114 lines
4.1 KiB
Go
package main
|
|
|
|
import (
|
|
"testing"
|
|
|
|
mqtt "github.com/mochi-mqtt/server/v2"
|
|
|
|
blockingpkg "meshtastic_mqtt_server/internal/blocking"
|
|
storepkg "meshtastic_mqtt_server/internal/store"
|
|
"meshtastic_mqtt_server/internal/store/testutil"
|
|
)
|
|
|
|
func TestMQTTClientInfoFromClientNil(t *testing.T) {
|
|
info := mqttClientInfoFromClient(nil)
|
|
if info != (storepkg.MQTTClientInfo{}) {
|
|
t.Fatalf("info = %#v, want zero value", info)
|
|
}
|
|
}
|
|
|
|
func TestMQTTClientInfoFromClientIPv4(t *testing.T) {
|
|
info := mqttClientInfoFromClient(&mqtt.Client{
|
|
ID: "client-1",
|
|
Properties: mqtt.ClientProperties{Username: []byte("user-1")},
|
|
Net: mqtt.ClientConnection{Listener: "tcp", Remote: "127.0.0.1:1234"},
|
|
})
|
|
|
|
if info.ClientID != "client-1" || info.Username != "user-1" || info.Listener != "tcp" {
|
|
t.Fatalf("client fields = %#v", info)
|
|
}
|
|
if info.RemoteAddr != "127.0.0.1:1234" || info.RemoteHost != "127.0.0.1" || info.RemotePort != "1234" {
|
|
t.Fatalf("remote fields = %#v", info)
|
|
}
|
|
}
|
|
|
|
func TestMQTTClientInfoFromClientIPv6(t *testing.T) {
|
|
info := mqttClientInfoFromClient(&mqtt.Client{Net: mqtt.ClientConnection{Remote: "[::1]:1234"}})
|
|
if info.RemoteHost != "::1" || info.RemotePort != "1234" {
|
|
t.Fatalf("remote fields = %#v, want host ::1 and port 1234", info)
|
|
}
|
|
}
|
|
|
|
func TestMQTTClientInfoFromClientUnsplitRemote(t *testing.T) {
|
|
info := mqttClientInfoFromClient(&mqtt.Client{Net: mqtt.ClientConnection{Remote: "localhost"}})
|
|
if info.RemoteHost != "localhost" || info.RemotePort != "" {
|
|
t.Fatalf("remote fields = %#v, want host localhost and empty port", info)
|
|
}
|
|
}
|
|
|
|
// blockingViolationForRecord 的测试用真实 *Store + blocking.Cache 走完整路径,
|
|
// 不依赖 cache 的未导出字段。
|
|
|
|
func TestBlockingViolationForRecordNode(t *testing.T) {
|
|
st := testutil.OpenStore(t)
|
|
defer st.Close()
|
|
nodeNum := int64(305419896)
|
|
if _, err := st.CreateNodeBlocking("!12345678", &nodeNum, "blocked", true); err != nil {
|
|
t.Fatalf("CreateNodeBlocking() error = %v", err)
|
|
}
|
|
cache, err := blockingpkg.New(st)
|
|
if err != nil {
|
|
t.Fatalf("blocking.New() error = %v", err)
|
|
}
|
|
record := map[string]any{"type": "position", "from": "!12345678", "from_num": uint32(305419896)}
|
|
violation := blockingViolationForRecord(cache, record)
|
|
if violation == nil || violation["blocking_type"] != "node" {
|
|
t.Fatalf("blockingViolationForRecord() = %#v, want node violation", violation)
|
|
}
|
|
}
|
|
|
|
func TestBlockingViolationForRecordForbiddenWordFields(t *testing.T) {
|
|
st := testutil.OpenStore(t)
|
|
defer st.Close()
|
|
if _, err := st.CreateForbiddenWordBlocking("spam", "contains", false, "blocked", true); err != nil {
|
|
t.Fatalf("CreateForbiddenWordBlocking() error = %v", err)
|
|
}
|
|
cache, err := blockingpkg.New(st)
|
|
if err != nil {
|
|
t.Fatalf("blocking.New() error = %v", err)
|
|
}
|
|
|
|
for _, tc := range []struct {
|
|
name string
|
|
record map[string]any
|
|
field string
|
|
}{
|
|
{name: "text", record: map[string]any{"type": "text_message", "from": "!1", "text": "has SPAM"}, field: "text"},
|
|
{name: "nodeinfo", record: map[string]any{"type": "nodeinfo", "from": "!1", "long_name": "has SPAM"}, field: "long_name"},
|
|
{name: "map_report", record: map[string]any{"type": "map_report", "from": "!1", "long_name": "has SPAM"}, field: "long_name"},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
violation := blockingViolationForRecord(cache, tc.record)
|
|
if violation == nil || violation["blocking_type"] != "forbidden_word" || violation["blocking_field"] != tc.field || violation["matched_word"] != "spam" {
|
|
t.Fatalf("blockingViolationForRecord() = %#v, want forbidden word on %s", violation, tc.field)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestBlockingViolationForRecordAllowed(t *testing.T) {
|
|
st := testutil.OpenStore(t)
|
|
defer st.Close()
|
|
if _, err := st.CreateForbiddenWordBlocking("spam", "contains", false, "blocked", true); err != nil {
|
|
t.Fatalf("CreateForbiddenWordBlocking() error = %v", err)
|
|
}
|
|
cache, err := blockingpkg.New(st)
|
|
if err != nil {
|
|
t.Fatalf("blocking.New() error = %v", err)
|
|
}
|
|
record := map[string]any{"type": "text_message", "from": "!1", "text": "hello"}
|
|
if violation := blockingViolationForRecord(cache, record); violation != nil {
|
|
t.Fatalf("blockingViolationForRecord() = %#v, want nil", violation)
|
|
}
|
|
}
|