Files
iotlcm/admin-svr/server/internal/logic/iot/channel_display_test.go
T

233 lines
9.3 KiB
Go

package iot
import (
"context"
"encoding/json"
"errors"
"strings"
"testing"
_ "github.com/gogf/gf/contrib/drivers/sqlite/v2"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/frame/g"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/consts"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/contexts"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/entity"
)
func channelDisplayTestContext(tenant string, dept int64) context.Context {
return contexts.Set(context.Background(), &model.Context{User: &model.Identity{BaseClaims: model.BaseClaims{
ID: 1, TenantId: tenant, DeptId: dept,
Roles: []model.Role{{RoleId: consts.SuperAdminRoleId, DataScope: consts.SysRoleDataScopeAll}},
}}})
}
func TestChannelDisplayScope(t *testing.T) {
for _, tc := range []struct {
name string
dept *entity.SysDept
err error
want bool
}{
{name: "top organization", dept: &entity.SysDept{DeptId: 100, ParentId: 0}, want: true},
{name: "top organization legacy ancestors", dept: &entity.SysDept{DeptId: 100, ParentId: 1, Ancestors: "0"}, want: true},
{name: "downstream super admin still uses alias", dept: &entity.SysDept{DeptId: 100, ParentId: 50, Ancestors: "0,50"}},
{name: "missing organization"},
{name: "different organization", dept: &entity.SysDept{DeptId: 999, ParentId: 0}},
{name: "lookup failure", err: errors.New("lookup failed")},
} {
t.Run(tc.name, func(t *testing.T) {
ctx := channelDisplayTestContext("tenant-A", 100)
got := resolveChannelDisplayScope(ctx, func() (*entity.SysDept, error) { return tc.dept, tc.err })
if got != tc.want {
t.Fatalf("full name = %v, want %v", got, tc.want)
}
if got := canViewChannelFullName(ctx); got != tc.want {
t.Fatalf("cached scope = %v", got)
}
})
}
for _, ctx := range []context.Context{context.Background(), channelDisplayTestContext("", 100), channelDisplayTestContext("tenant-A", 0)} {
if resolveChannelDisplayScope(ctx, func() (*entity.SysDept, error) {
t.Fatal("invalid identity should not query organizations")
return nil, nil
}) {
t.Fatal("invalid identity got full names")
}
}
}
func TestChannelDisplayScopeCacheIdentity(t *testing.T) {
ctx := channelDisplayTestContext("tenant-A", 100)
calls := 0
load := func() (*entity.SysDept, error) {
calls++
return &entity.SysDept{DeptId: contexts.GetDeptId(ctx), ParentId: 0}, nil
}
if !resolveChannelDisplayScope(ctx, load) || !resolveChannelDisplayScope(ctx, load) || calls != 1 {
t.Fatal("scope not cached for same identity")
}
contexts.GetUser(ctx).DeptId = 200
resolveChannelDisplayScope(ctx, load)
contexts.GetUser(ctx).TenantId = "tenant-B"
resolveChannelDisplayScope(ctx, load)
if calls != 3 {
t.Fatalf("scope reused across organization or tenant: %d calls", calls)
}
}
func TestChannelDisplayAliasAndExport(t *testing.T) {
ctx := channelDisplayTestContext("tenant-A", 200)
resolveChannelDisplayScope(ctx, func() (*entity.SysDept, error) {
return &entity.SysDept{DeptId: 200, ParentId: 100, Ancestors: "0,100"}, nil
})
list := []*model.TSimCardInfoListModel{
{ChanName: "PRIVATE UPSTREAM NAME", ChanAlias: " 中国移动223 "},
{ChanName: "PRIVATE UPSTREAM NAME", ChanAlias: " "},
nil,
}
applyCardChannelDisplayNames(ctx, list)
if list[0].ChanName != "中国移动223" || list[1].ChanName != "--" {
t.Fatalf("name leak: %+v %+v", list[0], list[1])
}
for _, card := range list[:2] {
row := cancellationExportRow(&model.TSimCardCancellationListModel{ChanName: card.ChanName})
if row[7] != card.ChanName {
t.Fatalf("export name differs: %v", row[7])
}
}
if channelDisplayName(true, "full name", "alias") != "full name" {
t.Fatal("top organization name changed")
}
}
func TestChannelDisplaySQL(t *testing.T) {
db, err := gdb.New(gdb.ConfigNode{Type: "sqlite", Link: "sqlite::@file(:memory:)", MaxOpenConnCount: 1})
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() { _ = db.Close(context.Background()) })
if _, err = db.Exec(context.Background(), "CREATE TABLE channels(chan_name TEXT,chan_alias TEXT)"); err != nil {
t.Fatal(err)
}
for _, alias := range []interface{}{"中国移动223", "", " ", nil} {
if _, err = db.Exec(context.Background(), "INSERT INTO channels VALUES(?,?)", "PRIVATE UPSTREAM NAME", alias); err != nil {
t.Fatal(err)
}
}
for _, top := range []bool{false, true} {
ctx := channelDisplayTestContext("tenant-A", 100)
parent := int64(50)
if top {
parent = 0
}
resolveChannelDisplayScope(ctx, func() (*entity.SysDept, error) {
return &entity.SysDept{DeptId: 100, ParentId: parent, Ancestors: "0,50"}, nil
})
values, err := db.GetArray(ctx, "SELECT "+channelDisplayNameSQL(ctx, "channels")+" FROM channels ORDER BY rowid")
if err != nil {
t.Fatal(err)
}
for i, v := range values {
want := "--"
if i == 0 {
want = "中国移动223"
}
if top {
want = "PRIVATE UPSTREAM NAME"
}
if v.String() != want {
t.Fatalf("top=%v row=%d got=%q want=%q", top, i, v.String(), want)
}
}
}
}
// Exercise the real list query and export formatter against an isolated database.
func TestChannelDisplayCancellationList(t *testing.T) {
previous := gdb.GetConfig("default")
if err := gdb.SetConfig(gdb.Config{"default": gdb.ConfigGroup{{Type: "sqlite", Link: "sqlite::@file(:memory:)", MaxOpenConnCount: 1}}}); err != nil {
t.Fatal(err)
}
db := g.DB()
t.Cleanup(func() { _ = db.Close(context.Background()); _ = gdb.SetConfig(gdb.Config{"default": previous}) })
statements := []string{
"CREATE TABLE sys_dept(dept_id INTEGER,tenant_id TEXT,parent_id INTEGER,ancestors TEXT,dept_name TEXT,deleted_at TEXT)",
"INSERT INTO sys_dept VALUES(100,'tenant-A',0,'0','Top',NULL),(200,'tenant-A',100,'0,100','Customer',NULL)",
"CREATE TABLE t_sim_channels(id INTEGER,temp_key TEXT,chan_key TEXT,tenant_id TEXT,chan_name TEXT,chan_alias TEXT)",
"CREATE TABLE t_sim_channel_template(temp_key TEXT,temp_name TEXT,temp_model TEXT)",
"INSERT INTO t_sim_channels(id,chan_key,tenant_id,chan_name,chan_alias) VALUES(1,'SHARED','tenant-A','PRIVATE NAME A','中国移动223'),(2,'SHARED','tenant-B','PRIVATE NAME B','Other tenant alias')",
"CREATE TABLE t_sim_flow_package_info(package_code TEXT,package_name TEXT,tenant_id TEXT)",
"INSERT INTO t_sim_flow_package_info VALUES('PKG','套餐A','tenant-A'),('PKG','套餐B','tenant-B')",
"CREATE TABLE t_sim_card_cancellation(id INTEGER,iccid TEXT,msisdn TEXT,imsi TEXT,imei TEXT,dept_id INTEGER,chan_key TEXT,cancel_batch_no TEXT,cancelled_at TEXT,boss_card_status INTEGER,current_package_code TEXT,inbound_batch_no TEXT,outbound_batch_no TEXT,allocation_batch_no TEXT,card_type INTEGER,net_model INTEGER,remark TEXT,created_by INTEGER,tenant_id TEXT)",
"INSERT INTO t_sim_card_cancellation(id,iccid,dept_id,chan_key,created_by,tenant_id,current_package_code) VALUES(1,'CARD-A',200,'SHARED',0,'tenant-A','PKG'),(2,'CARD-B',200,'SHARED',0,'tenant-B','PKG')",
}
for _, sql := range statements {
if _, err := db.Exec(context.Background(), sql); err != nil {
t.Fatal(err)
}
}
for _, dept := range []int64{200, 100} {
ctx := channelDisplayTestContext("tenant-A", dept)
contexts.GetUser(ctx).Roles = []model.Role{{RoleId: 99, DataScope: consts.SysRoleDataScopeAll}}
in := &model.TSimCardCancellationListParam{}
in.Page = 1
in.PageSize = 20
list, total, err := NewIotTSimCardCancellation().List(ctx, in)
if err != nil {
t.Fatal(err)
}
if total != 1 || len(list) != 1 || list[0].Iccid != "CARD-A" {
t.Fatalf("cross tenant rows: total=%d list=%+v", total, list)
}
want := "中国移动223"
if dept == 100 {
want = "PRIVATE NAME A"
}
if list[0].PackageName != "套餐A" {
t.Fatalf("cross tenant package: %q", list[0].PackageName)
}
if list[0].ChanName != want {
t.Fatalf("dept=%d name=%q want=%q", dept, list[0].ChanName, want)
}
if row := cancellationExportRow(list[0]); row[7] != want {
t.Fatalf("export name=%v", row[7])
}
channelQuery := &model.TSimChannelsListParam{}
channelQuery.Page = 1
channelQuery.PageSize = 20
channelQuery.ChanName = want
channels, count, err := NewIotTSimChannels().List(ctx, channelQuery)
if err != nil {
t.Fatal(err)
}
if count != 1 || len(channels) != 1 || channels[0].ChanName != want {
t.Fatalf("channel options: count=%d rows=%+v", count, channels)
}
stockNames, err := loadStockChannelNames(ctx, []string{"SHARED"})
if err != nil || stockNames["SHARED"] != want {
t.Fatalf("stock channel name=%v err=%v", stockNames, err)
}
channelView, err := NewIotTSimChannels().View(ctx, &model.TSimChannelsViewParam{Id: 1})
if err != nil || channelView == nil || channelView.ChanName != want {
t.Fatalf("channel view=%+v err=%v", channelView, err)
}
}
}
func TestChannelDisplayCancellationMissingAlias(t *testing.T) {
ctx := channelDisplayTestContext("tenant-A", 200)
resolveChannelDisplayScope(ctx, func() (*entity.SysDept, error) { return &entity.SysDept{DeptId: 200, ParentId: 100}, nil })
for _, alias := range []string{"", " "} {
name := channelDisplayName(canViewChannelFullName(ctx), "PRIVATE NAME", alias)
encoded, err := json.Marshal(&model.TSimCardCancellationListModel{ChanName: name})
if err != nil {
t.Fatal(err)
}
if strings.Contains(string(encoded), "PRIVATE NAME") || name != "--" {
t.Fatalf("response exposes full name: %s", encoded)
}
}
}