Files
iotlcm/server/internal/model/sync_scope_test.go
T
MiFaZhan 59eb34976a chore: 初始开源发布
iotlcm-admin-svr:管理后台服务。

- Go module:iotlcm → git.5iot.com/iotlcm-public/iotlcm-admin-svr(729 文件)
- 凭据与地址脱敏:JWT signingKey(含代码里的 MustGet 默认值)、
  MySQL DSN、Redis 口令、告警邮件发件人邮箱全部改为占位;
  私网 IP → 文档地址段;库名 xiujie_admin → iotlcm
- 真实手机号/邮箱 → 合法 11 位编造号 / example.com
- 移除内部资产:CI 触发器、过时 DDL(sql/db_iotlcm.sql)、
  私有依赖 go get 行、明文 registry 登录口令
- 启动 banner 统一 IOTLCM 方块字 + 社区惯例署名
- Dockerfile 改用公共基础镜像
- 拼错的 RADEME.md → README.md,README 重写
- 需要真实数据库的用例改为可用 IOTLCM_TEST_WITH_DB=1 显式启用
- 补 Apache-2.0 LICENSE,LF 行尾 + .gitattributes

数据库 schema 不随本仓发布(生产 DDL 已过时),README 已说明。
2026-10-23 14:48:30 +08:00

142 lines
4.6 KiB
Go

package model
import (
"strings"
"testing"
)
func TestNormalizeScopeNilAndEmptyMeanFullSync(t *testing.T) {
for name, scope := range map[string]*SyncScope{
"nil": nil,
"no channels": {},
"empty slice": {Channels: []string{}},
} {
got, err := NormalizeScope(scope)
if err != nil {
t.Fatalf("%s: unexpected error: %v", name, err)
}
if got != nil {
t.Fatalf("%s: expected nil scope for full sync, got %+v", name, got)
}
if got.ChannelCount() != 0 || !got.Allows("anything") {
t.Fatalf("%s: nil scope must allow every channel", name)
}
}
}
func TestNormalizeScopeDeduplicatesAndSorts(t *testing.T) {
got, err := NormalizeScope(&SyncScope{Channels: []string{"217", "173", "217", "189"}})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
want := []string{"173", "189", "217"}
if len(got.Channels) != len(want) {
t.Fatalf("channels = %v, want %v", got.Channels, want)
}
for i := range want {
if got.Channels[i] != want[i] {
t.Fatalf("channels = %v, want %v (sorted)", got.Channels, want)
}
}
// Sorting keeps ScopeDigest stable for requests listing the same channels
// in a different order.
reordered, err := NormalizeScope(&SyncScope{Channels: []string{"189", "217", "173"}})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if strings.Join(reordered.Channels, ",") != strings.Join(got.Channels, ",") {
t.Fatalf("order-dependent normalization: %v vs %v", reordered.Channels, got.Channels)
}
}
func TestNormalizeScopeTrimsWhitespace(t *testing.T) {
got, err := NormalizeScope(&SyncScope{Channels: []string{" 217 ", "173"}})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got.Allows("217") != true {
t.Fatalf("whitespace-padded chanKey was not trimmed: %+v", got)
}
}
func TestNormalizeScopeRejectsInvalidInput(t *testing.T) {
cases := map[string]*SyncScope{
"too many channels": {Channels: make([]string, MaxScopeChannels+1)},
"chanKey too long": {Channels: []string{strings.Repeat("a", MaxChanKeyLength+1)}},
"chanKey with slash": {Channels: []string{"a/b"}},
"chanKey with space": {Channels: []string{"a b"}},
"chanKey with unicode": {Channels: []string{"通道"}},
"chanKey with brace": {Channels: []string{"a{b}"}},
"empty chanKey": {Channels: []string{""}},
"blank chanKey": {Channels: []string{" "}},
"unknown triggerSource": {Channels: []string{"217"}, TriggerSource: "cron"},
}
for name, scope := range cases {
if _, err := NormalizeScope(scope); err == nil {
t.Fatalf("%s: expected an error", name)
}
}
}
func TestNormalizeScopeAcceptsBoundaries(t *testing.T) {
// Exactly the maximum count must pass.
channels := make([]string, MaxScopeChannels)
for i := range channels {
channels[i] = "chan-" + strings.Repeat("0", i%10) + string(rune('a'+i%26))
}
if _, err := NormalizeScope(&SyncScope{Channels: channels}); err != nil {
t.Fatalf("exactly %d channels must be accepted: %v", MaxScopeChannels, err)
}
// A chanKey of exactly the maximum length must pass.
if err := ValidateChanKey(strings.Repeat("a", MaxChanKeyLength)); err != nil {
t.Fatalf("chanKey of exactly %d characters must be accepted: %v", MaxChanKeyLength, err)
}
}
func TestNormalizeScopeDefaultsTriggerSource(t *testing.T) {
got, err := NormalizeScope(&SyncScope{Channels: []string{"217"}})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got.TriggerSource != TriggerSourceManual {
t.Fatalf("triggerSource = %q, want %q", got.TriggerSource, TriggerSourceManual)
}
}
func TestNormalizeScopeKeepsExplicitTriggerSource(t *testing.T) {
got, err := NormalizeScope(&SyncScope{Channels: []string{"217"}, TriggerSource: TriggerSourceAPI})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
if got.TriggerSource != TriggerSourceAPI {
t.Fatalf("triggerSource = %q, want %q", got.TriggerSource, TriggerSourceAPI)
}
}
func TestScopeChannelSetAndSample(t *testing.T) {
scope, err := NormalizeScope(&SyncScope{Channels: []string{"217", "173"}})
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
set := scope.ChannelSet()
if _, ok := set["173"]; !ok {
t.Fatalf("channel set missing 173: %v", set)
}
if len(set) != 2 {
t.Fatalf("channel set = %v, want 2 entries", set)
}
if sample := scope.ChannelSample(1); len(sample) != 1 || sample[0] != "173" {
t.Fatalf("sample = %v, want [173]", sample)
}
if sample := scope.ChannelSample(10); len(sample) != 2 {
t.Fatalf("sample = %v, want both channels", sample)
}
var nilScope *SyncScope
if nilScope.ChannelSet() != nil {
t.Fatalf("nil scope must return a nil channel set")
}
if nilScope.ChannelSample(10) != nil {
t.Fatalf("nil scope must return a nil sample")
}
}