59eb34976a
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 已说明。
316 lines
11 KiB
Go
316 lines
11 KiB
Go
package iot
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
|
|
)
|
|
|
|
func TestCardChannelAssocExistsSQLUsesChannelKeyOnly(t *testing.T) {
|
|
query := cardChannelAssocExistsSQL("")
|
|
|
|
if !strings.Contains(query, "c.`chan_key` = a.`channel_id`") {
|
|
t.Fatalf("channel association must join by chan_key: %s", query)
|
|
}
|
|
if strings.Contains(query, "CAST(a.`channel_id`") {
|
|
t.Fatalf("channel association must not cast channel keys: %s", query)
|
|
}
|
|
if strings.Contains(query, "c.`id`") {
|
|
t.Fatalf("channel association must not join by channel primary key: %s", query)
|
|
}
|
|
}
|
|
|
|
func TestShouldCacheDiagnoseRegionLimit(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
tempKey string
|
|
chanKey string
|
|
want bool
|
|
}{
|
|
{name: "mobile direct", tempKey: "sim_yd_v5", want: true},
|
|
{name: "mobile shared token", tempKey: "sim_onelink_daihe_v1", want: true},
|
|
{name: "shared token model", tempKey: "shared_token", want: true},
|
|
{name: "telecom", tempKey: "sim_dx_v1", want: false},
|
|
{name: "third party", tempKey: "sim_sdtywl_v1", want: false},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
if got := shouldCacheDiagnoseRegionLimit(test.tempKey, test.chanKey); got != test.want {
|
|
t.Fatalf("shouldCacheDiagnoseRegionLimit(%q, %q) = %v, want %v", test.tempKey, test.chanKey, got, test.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIsDiagnoseRegionLimitCacheable(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
item *model.SimBaseRes[model.SimRegionLimitStatusItem]
|
|
want bool
|
|
}{
|
|
{name: "nil response"},
|
|
{
|
|
name: "successful region status",
|
|
item: &model.SimBaseRes[model.SimRegionLimitStatusItem]{Code: 0, ResData: model.SimRegionLimitStatusItem{RegionLimitStatus: "1"}},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "successful order status",
|
|
item: &model.SimBaseRes[model.SimRegionLimitStatusItem]{Code: 0, ResData: model.SimRegionLimitStatusItem{OrderStatus: "1"}},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "successful trigger status",
|
|
item: &model.SimBaseRes[model.SimRegionLimitStatusItem]{Code: 0, ResData: model.SimRegionLimitStatusItem{OrderStatus: "0", LimitStatus: "0"}},
|
|
want: true,
|
|
},
|
|
{
|
|
name: "upstream failure with populated status",
|
|
item: &model.SimBaseRes[model.SimRegionLimitStatusItem]{Code: 9999, ResData: model.SimRegionLimitStatusItem{RegionLimitStatus: "1"}},
|
|
},
|
|
{
|
|
name: "unknown status placeholders",
|
|
item: &model.SimBaseRes[model.SimRegionLimitStatusItem]{Code: 0, ResData: model.SimRegionLimitStatusItem{RegionLimitStatus: "2", OrderStatus: "-", LimitStatus: "-"}},
|
|
},
|
|
{
|
|
name: "details without status",
|
|
item: &model.SimBaseRes[model.SimRegionLimitStatusItem]{Code: 0, ResData: model.SimRegionLimitStatusItem{LimitType: "1", LimitArea: "area"}},
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
if got := isDiagnoseRegionLimitCacheable(test.item); got != test.want {
|
|
t.Fatalf("isDiagnoseRegionLimitCacheable() = %v, want %v", got, test.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestDiagnoseRegionModuleDoesNotCooldownForInvalidCachedData(t *testing.T) {
|
|
result := &simDiagnoseRegionQueryResult{
|
|
Cached: true,
|
|
CachedAt: "2026-09-24 12:00:00",
|
|
Item: &model.SimBaseRes[model.SimRegionLimitStatusItem]{
|
|
Code: 0,
|
|
ResData: model.SimRegionLimitStatusItem{
|
|
RegionLimitStatus: "2",
|
|
LimitStatus: "-",
|
|
},
|
|
},
|
|
}
|
|
|
|
status, message, updatedAt := diagnoseRegionModule(result)
|
|
if status != "error" {
|
|
t.Fatalf("status = %q, want error", status)
|
|
}
|
|
if strings.Contains(message, "一小时内只能查询一次") || strings.Contains(message, "后可再次查看") {
|
|
t.Fatalf("invalid cached result must not show cooldown hint: %q", message)
|
|
}
|
|
if updatedAt != "" {
|
|
t.Fatalf("updatedAt = %q, want empty for invalid cached data", updatedAt)
|
|
}
|
|
}
|
|
|
|
func TestDiagnoseCachedModule(t *testing.T) {
|
|
status, message := diagnoseCachedModule(true)
|
|
if status != "cached" || !strings.Contains(message, "缓存") {
|
|
t.Fatalf("cached module = (%q, %q)", status, message)
|
|
}
|
|
status, message = diagnoseCachedModule(false)
|
|
if status != "success" || message != "" {
|
|
t.Fatalf("fresh module = (%q, %q)", status, message)
|
|
}
|
|
}
|
|
|
|
func TestApplyDiagnoseRegionLimit(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
data model.SimRegionLimitStatusItem
|
|
want model.TSimDiagnoseInfoItem
|
|
}{
|
|
{
|
|
name: "not ordered is not applicable",
|
|
data: model.SimRegionLimitStatusItem{RegionLimitStatus: "1", OrderStatus: "1", LimitStatus: "0", LimitType: "0", LimitArea: "park"},
|
|
want: model.TSimDiagnoseInfoItem{RegionLimitStatus: emptyStringPtr("2"), RegionLimitOrderStatus: emptyStringPtr("1")},
|
|
},
|
|
{
|
|
name: "ordered and disconnected is triggered",
|
|
data: model.SimRegionLimitStatusItem{RegionLimitStatus: "0", OrderStatus: "0", LimitStatus: "0", LimitType: "4", LimitArea: "park"},
|
|
want: model.TSimDiagnoseInfoItem{RegionLimitStatus: emptyStringPtr("1"), RegionLimitOrderStatus: emptyStringPtr("0"), RegionLimitTriggerStatus: emptyStringPtr("0"), RegionLimitType: emptyStringPtr("4"), RegionLimitArea: emptyStringPtr("park")},
|
|
},
|
|
{
|
|
name: "ordered and connected is not triggered",
|
|
data: model.SimRegionLimitStatusItem{RegionLimitStatus: "1", OrderStatus: "0", LimitStatus: "1", LimitType: "1", LimitArea: "province"},
|
|
want: model.TSimDiagnoseInfoItem{RegionLimitStatus: emptyStringPtr("0"), RegionLimitOrderStatus: emptyStringPtr("0"), RegionLimitTriggerStatus: emptyStringPtr("1"), RegionLimitType: emptyStringPtr("1"), RegionLimitArea: emptyStringPtr("province")},
|
|
},
|
|
{
|
|
name: "ordered network unknown",
|
|
data: model.SimRegionLimitStatusItem{OrderStatus: "0", LimitStatus: "-", LimitType: "0"},
|
|
want: model.TSimDiagnoseInfoItem{RegionLimitStatus: emptyStringPtr("2"), RegionLimitOrderStatus: emptyStringPtr("0"), RegionLimitTriggerStatus: emptyStringPtr("-")},
|
|
},
|
|
{
|
|
name: "channel without order status keeps normalized status",
|
|
data: model.SimRegionLimitStatusItem{RegionLimitStatus: "2"},
|
|
want: model.TSimDiagnoseInfoItem{RegionLimitStatus: emptyStringPtr("2")},
|
|
},
|
|
}
|
|
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
result := &model.TSimDiagnoseInfoOutput{Info: &model.TSimDiagnoseInfoItem{}}
|
|
applyDiagnoseRegionLimit(result, &model.SimBaseRes[model.SimRegionLimitStatusItem]{ResData: test.data})
|
|
assertStringPtrValue(t, "region status", result.Info.RegionLimitStatus, test.want.RegionLimitStatus)
|
|
assertStringPtrValue(t, "order status", result.Info.RegionLimitOrderStatus, test.want.RegionLimitOrderStatus)
|
|
assertStringPtrValue(t, "trigger status", result.Info.RegionLimitTriggerStatus, test.want.RegionLimitTriggerStatus)
|
|
assertStringPtrValue(t, "limit type", result.Info.RegionLimitType, test.want.RegionLimitType)
|
|
assertStringPtrValue(t, "limit area", result.Info.RegionLimitArea, test.want.RegionLimitArea)
|
|
})
|
|
}
|
|
}
|
|
|
|
func assertStringPtrValue(t *testing.T, name string, got *string, want *string) {
|
|
t.Helper()
|
|
if stringPtrValue(got) != stringPtrValue(want) {
|
|
t.Fatalf("%s = %q, want %q", name, stringPtrValue(got), stringPtrValue(want))
|
|
}
|
|
}
|
|
|
|
func stringPtrValue(value *string) string {
|
|
if value == nil {
|
|
return ""
|
|
}
|
|
return *value
|
|
}
|
|
|
|
func TestValidateSimSessionResult(t *testing.T) {
|
|
t.Run("rejects empty upstream output", func(t *testing.T) {
|
|
if _, err := validateSimSessionResult(nil); err == nil {
|
|
t.Fatal("expected an error for empty output")
|
|
}
|
|
})
|
|
|
|
t.Run("rejects upstream business failure", func(t *testing.T) {
|
|
if _, err := validateSimSessionResult([]model.SimBaseRes[model.SimSessionItem]{{
|
|
Code: 1001,
|
|
Message: "upstream unavailable",
|
|
}}); err == nil {
|
|
t.Fatal("expected an error for failed output")
|
|
}
|
|
})
|
|
|
|
t.Run("returns successful session output", func(t *testing.T) {
|
|
want := model.SimSessionItem{Status: "0"}
|
|
got, err := validateSimSessionResult([]model.SimBaseRes[model.SimSessionItem]{{
|
|
Code: 0,
|
|
ResData: want,
|
|
}})
|
|
if err != nil {
|
|
t.Fatalf("validate session output: %v", err)
|
|
}
|
|
if got.ResData != want {
|
|
t.Fatalf("got %+v, want %+v", got.ResData, want)
|
|
}
|
|
})
|
|
}
|
|
|
|
// 13000 是移动 OneLink 在自身繁忙时随机返回的业务状态,属上游侧问题。
|
|
// 对用户展示时必须归属于上游,否则用户会误认为本平台故障。
|
|
func TestUpstreamBusyStatusIsAttributedToUpstream(t *testing.T) {
|
|
t.Run("busy code is rewritten to upstream message", func(t *testing.T) {
|
|
got := upstreamStatusDisplayMessage(simUpstreamBusyCode, "系统忙,请稍后再试")
|
|
if got != simUpstreamBusyMessage {
|
|
t.Fatalf("got %q, want %q", got, simUpstreamBusyMessage)
|
|
}
|
|
})
|
|
|
|
t.Run("busy message is recognized without the known code", func(t *testing.T) {
|
|
got := upstreamStatusDisplayMessage(8888, "系统忙,请稍后再试")
|
|
if got != simUpstreamBusyMessage {
|
|
t.Fatalf("got %q, want %q", got, simUpstreamBusyMessage)
|
|
}
|
|
})
|
|
|
|
t.Run("forbidden message is rewritten separately", func(t *testing.T) {
|
|
got := upstreamStatusDisplayMessage(0, "IP禁止访问")
|
|
if got != simUpstreamBusyForbiddenMessage {
|
|
t.Fatalf("got %q, want %q", got, simUpstreamBusyForbiddenMessage)
|
|
}
|
|
})
|
|
|
|
t.Run("unrelated business status keeps the original error", func(t *testing.T) {
|
|
got := upstreamStatusDisplayMessage(11011, "卡号不存在")
|
|
if got != "" {
|
|
t.Fatalf("got %q, want empty so the caller keeps the original message", got)
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestNewSimDataUsageErrorHidesUpstreamBusy(t *testing.T) {
|
|
err := newSimDataUsageError(simUpstreamBusyCode, "系统忙,请稍后再试")
|
|
if err == nil {
|
|
t.Fatal("expected an error")
|
|
}
|
|
if err.Error() != simUpstreamBusyMessage {
|
|
t.Fatalf("got %q, want %q", err.Error(), simUpstreamBusyMessage)
|
|
}
|
|
if strings.Contains(err.Error(), "13000") {
|
|
t.Fatalf("upstream status code must not leak to users: %q", err.Error())
|
|
}
|
|
}
|
|
|
|
func TestValidateSimCardStatusResultHidesUpstreamBusy(t *testing.T) {
|
|
_, err := validateSimCardStatusResult([]model.SimBaseRes[model.SimCardStatusItem]{{
|
|
Code: simUpstreamBusyCode,
|
|
Message: "系统忙,请稍后再试",
|
|
}}, "同步卡状态")
|
|
if err == nil {
|
|
t.Fatal("expected an error")
|
|
}
|
|
if err.Error() != simUpstreamBusyMessage {
|
|
t.Fatalf("got %q, want %q", err.Error(), simUpstreamBusyMessage)
|
|
}
|
|
}
|
|
|
|
func TestValidateSimCardStatusResultRejectsUnsupportedValue(t *testing.T) {
|
|
for _, status := range []int{-2, 3, 5, 10} {
|
|
_, err := validateSimCardStatusResult([]model.SimBaseRes[model.SimCardStatusItem]{{
|
|
Code: 0,
|
|
ResData: model.SimCardStatusItem{CardStatus: status},
|
|
}}, "sync status")
|
|
if err == nil {
|
|
t.Fatalf("unsupported status %d must not be written to card info", status)
|
|
}
|
|
}
|
|
for _, status := range []int{0, 1, 2, 4, 6, 7, 8, 9} {
|
|
_, err := validateSimCardStatusResult([]model.SimBaseRes[model.SimCardStatusItem]{{
|
|
Code: 0,
|
|
ResData: model.SimCardStatusItem{CardStatus: status},
|
|
}}, "sync status")
|
|
if err != nil {
|
|
t.Fatalf("platform status %d was rejected: %v", status, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestFormatUpstreamTestMessage(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
code int
|
|
message string
|
|
want string
|
|
}{
|
|
{name: "busy code", code: simUpstreamBusyCode, message: "系统忙,请稍后再试", want: simUpstreamBusyMessage},
|
|
{name: "empty message", code: 0, message: " ", want: "上游未返回结果"},
|
|
{name: "other business message", code: 11011, message: "卡号不存在", want: "卡号不存在"},
|
|
}
|
|
for _, test := range tests {
|
|
t.Run(test.name, func(t *testing.T) {
|
|
if got := formatUpstreamTestMessage(test.code, test.message); got != test.want {
|
|
t.Fatalf("got %q, want %q", got, test.want)
|
|
}
|
|
})
|
|
}
|
|
}
|