第二批:把根目录中纯逻辑领域文件(cache、service、admin route)按业务边界
迁到 internal/ 下的子包。各子包暴露 RegisterRoutes 给 web 包调用,根目录
只留下一行 bridge 文件保留旧名字别名。
新增包
- internal/auth/ SessionClaims / Manager / RequireAdmin / HashPassword /
VerifyPassword / AdminUserResponse 等。原 auth.go 中
被两个 admin route 依赖的 sessionClaims 现在以 auth.
SessionClaims 形式被它们 import;不再被锁在 main 包。
- internal/blocking/ Cache + RegisterRoutes,以前散在 blocking_cache.go
和 admin_blocking_routes.go 里。
- internal/runtimesettings/ Cache + RegisterRoutes。
- internal/help/ RenderMarkdown / RegisterPublicRoutes /
RegisterAdminRoutes(拆分原来的 registerHelpRoutes
和 registerAdminHelpRoutes 两条入口)。
- internal/mqttforward/ Manager / Reloader / Stats / RegisterRoutes。
forwarder runner、循环抑制 cache 等运行时逻辑随之迁入。
- internal/webutil/ ParseListOptions / WriteListResponse[WithTotal] /
ParseMapReportListOptions / ParseMapReportViewportOptions
以及 PtrString/PtrInt64/... 等指针解引用 helper。
以前散在 web.go 中,现在被各 admin route 子包共享,
避免 internal/blocking → internal/web → internal/blocking
的循环依赖。
- internal/store/testutil/ OpenStore(t) helper,让其它包测试零样板拿到 store。
根目录新增 bridge 文件
- blocking_bridge.go / runtime_settings_bridge.go / help_bridge.go /
mqttforward_bridge.go:用 type alias + thin wrapper 把上述子包的导出
名映射到旧的小写名(blockingCache、registerAdminBlockingRoutes 等),
让 main.go / web.go 等仍未迁出的文件无须改动。
修改
- auth.go 改为对 internal/auth 的 bridge;web.go 中 sessions.newCookie /
clearCookie 改为 NewCookie / ClearCookie。
- main_test.go 中 BlockingViolationForRecord* 测试不再直接构造未导出字段,
改成走 store.CreateNodeBlocking → newBlockingCache 的真实路径。
- internal/mqttforward 把以前 *_store.go 中没有方法依赖的运行时类型
(forwarder runner、loop cache)和 admin route 一并归位;mqtt_status.go
暂时仍留在根目录(依赖 main 中的 mqttClientInfoFromClient)。
go build ./... / go test ./... 全部通过;测试数量未变。
Co-Authored-By: Claude <noreply@anthropic.com>
111 lines
4.1 KiB
Go
111 lines
4.1 KiB
Go
package main
|
|
|
|
import (
|
|
"testing"
|
|
|
|
mqtt "github.com/mochi-mqtt/server/v2"
|
|
)
|
|
|
|
func TestMQTTClientInfoFromClientNil(t *testing.T) {
|
|
info := mqttClientInfoFromClient(nil)
|
|
if info != (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 的测试现在跟着 blockingCache 一起搬到了
|
|
// internal/blocking/violations_test.go,使用真实 *Store 构造缓存而不是
|
|
// 直接捏造未导出字段。这里保留 mqtt client info 这部分测试不动。
|
|
|
|
func TestBlockingViolationForRecordNode(t *testing.T) {
|
|
st := openTestStore(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 := newBlockingCache(st)
|
|
if err != nil {
|
|
t.Fatalf("newBlockingCache() 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 := openTestStore(t)
|
|
defer st.Close()
|
|
if _, err := st.CreateForbiddenWordBlocking("spam", "contains", false, "blocked", true); err != nil {
|
|
t.Fatalf("CreateForbiddenWordBlocking() error = %v", err)
|
|
}
|
|
cache, err := newBlockingCache(st)
|
|
if err != nil {
|
|
t.Fatalf("newBlockingCache() 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 := openTestStore(t)
|
|
defer st.Close()
|
|
if _, err := st.CreateForbiddenWordBlocking("spam", "contains", false, "blocked", true); err != nil {
|
|
t.Fatalf("CreateForbiddenWordBlocking() error = %v", err)
|
|
}
|
|
cache, err := newBlockingCache(st)
|
|
if err != nil {
|
|
t.Fatalf("newBlockingCache() 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)
|
|
}
|
|
}
|