package app import ( "context" "reflect" "testing" "git.5iot.com/iotlcm-public/iotlcm-app/internal/consts" "git.5iot.com/iotlcm-public/iotlcm-app/internal/library/contexts" "git.5iot.com/iotlcm-public/iotlcm-app/internal/model" "git.5iot.com/iotlcm-public/iotlcm-app/internal/model/entity" _ "github.com/gogf/gf/contrib/drivers/sqlite/v2" "github.com/gogf/gf/v2/database/gdb" "github.com/gogf/gf/v2/frame/g" ) func TestAppCustomerScopeSQL(t *testing.T) { ctx := context.Background() previous := gdb.GetConfig("default") if err := gdb.SetConfigGroup("default", gdb.ConfigGroup{{Type: "sqlite", Link: "sqlite::@file(:memory:)", MaxOpenConnCount: 1}}); err != nil { t.Fatal(err) } database := g.DB() t.Cleanup(func() { if err := database.GetCore().ClearCacheAll(ctx); err != nil { t.Error(err) } if err := database.Close(ctx); err != nil { t.Error(err) } if err := gdb.SetConfigGroup("default", previous); err != nil { t.Error(err) } }) if err := database.GetCore().ClearCacheAll(ctx); err != nil { t.Fatal(err) } statements := []string{ "CREATE TABLE sys_menu (menu_id INTEGER PRIMARY KEY, perms TEXT, status TEXT, deleted_at DATETIME)", "INSERT INTO sys_menu VALUES (1,'agent:dept:list','0',NULL)", "CREATE TABLE sys_config (config_id INTEGER PRIMARY KEY, tenant_id TEXT, config_key TEXT, config_value TEXT, deleted_at DATETIME)", "INSERT INTO sys_config VALUES (1,'A','iot.register.app.dept','900',NULL)", "CREATE TABLE sys_dept (dept_id INTEGER PRIMARY KEY, tenant_id TEXT, parent_id INTEGER, dept_type INTEGER, order_num INTEGER, dept_name TEXT, dept_key TEXT, status TEXT, deleted_at DATETIME)", "INSERT INTO sys_dept VALUES (100,'A',0,1,0,'顶级','TOP','0',NULL),(101,'A',100,1,1,'客户甲','A101','0',NULL),(102,'A',101,1,2,'客户乙','A102','1',NULL),(103,'A',100,0,3,'普通部门','D103','0',NULL),(104,'A',100,1,4,'已删除','A104','0','2026-01-01'),(200,'B',0,1,5,'其他租户','B200','0',NULL),(900,'A',100,1,6,'注册组织','R900','0',NULL),(901,'A',900,1,7,'注册下级','R901','0',NULL)", "CREATE TABLE sys_role_dept (role_id INTEGER, dept_id INTEGER, tenant_id TEXT)", "INSERT INTO sys_role_dept VALUES (9,102,'A'),(9,200,'B')", } for _, statement := range statements { if _, err := database.Exec(ctx, statement); err != nil { t.Fatal(err) } } t.Run("卡统计包含全部状态并隔离租户与删除记录", func(t *testing.T) { statements := []string{ "CREATE TABLE t_sim_card_info (id INTEGER PRIMARY KEY, tenant_id TEXT, dept_id INTEGER, boss_card_status INTEGER, package_status INTEGER, deleted_at DATETIME)", "INSERT INTO t_sim_card_info VALUES (1,'A',101,2,2,NULL),(2,'A',101,2,3,NULL),(3,'A',101,4,2,NULL),(4,'A',101,1,0,NULL),(5,'A',101,6,0,NULL),(6,'A',101,7,0,NULL),(7,'A',101,8,0,NULL),(8,'A',101,9,0,NULL),(9,'A',101,NULL,NULL,NULL),(10,'A',101,88,0,NULL),(11,'A',101,2,2,'2026-01-01'),(12,'B',101,2,2,NULL),(13,'A',102,4,2,NULL)", } for _, statement := range statements { if _, err := database.Exec(ctx, statement); err != nil { t.Fatal(err) } } stats, err := appCardStatsByDept(ctx, "A", []int64{101, 999}) if err != nil { t.Fatal(err) } stat := stats[101] if stat == nil || stat.CardCount != 10 || stat.NormalCardCount != 1 || len(stats) != 1 { t.Fatalf("unexpected stats: stat=%+v, departments=%d", stat, len(stats)) } expected := []model.AppCustomerStatusCount{{Status: 0, Count: 1}, {Status: 1, Count: 1}, {Status: 2, Count: 2}, {Status: 4, Count: 1}, {Status: 6, Count: 1}, {Status: 7, Count: 1}, {Status: 8, Count: 1}, {Status: 9, Count: 1}, {Status: 88, Count: 1}} if !reflect.DeepEqual(stat.CardStatusCounts, expected) { t.Fatalf("actual=%+v expected=%+v", stat.CardStatusCounts, expected) } item := appEnterpriseCustomerModel(&entity.SysDept{DeptId: 101}, nil, 0, stat) if !reflect.DeepEqual(item.CardStatusCounts, expected) || item.CardCount != 10 { t.Fatalf("model lost card status counts: %+v", item) } packageCounts := make(map[int]int) for _, count := range stat.PackageStatusCounts { packageCounts[count.Status] = count.Count } if !reflect.DeepEqual(packageCounts, map[int]int{-1: 1, 0: 6, 2: 2, 3: 1}) { t.Fatalf("unexpected package counts: %+v", packageCounts) } if !reflect.DeepEqual(item.PackageStatusCounts, stat.PackageStatusCounts) { t.Fatalf("model lost package counts: %+v", item) } if _, err := database.Exec(ctx, "INSERT INTO t_sim_card_info VALUES (14,'A',105,2,2,NULL),(15,'A',105,2,2,NULL),(16,'A',105,2,2,NULL),(17,'A',105,2,2,NULL),(18,'A',105,2,2,NULL),(19,'A',105,2,2,NULL),(20,'A',105,2,3,NULL),(21,'A',105,2,3,NULL)"); err != nil { t.Fatal(err) } activatedStats, err := appCardStatsByDept(ctx, "A", []int64{105}) if err != nil { t.Fatal(err) } activated := activatedStats[105] if activated == nil || activated.CardCount != 8 || activated.NormalCardCount != 6 || !reflect.DeepEqual(activated.CardStatusCounts, []model.AppCustomerStatusCount{{Status: 2, Count: 8}}) { t.Fatalf("unexpected activated stats: %+v", activated) } if !reflect.DeepEqual(activated.PackageStatusCounts, []model.AppCustomerStatusCount{{Status: 2, Count: 6}, {Status: 3, Count: 2}}) { t.Fatalf("expired packages must not be counted as stopped cards: %+v", activated) } }) user := &entity.SysUser{UserId: 7, TenantId: "A", DeptId: 100} cases := []struct { name, scope string expected []int64 }{ {"全部仍限制租户和企业类型", consts.SysRoleDataScopeAll, []int64{101, 102}}, {"直属下级不含孙级", consts.SysRoleDataScopeOrgNextLevel, []int64{101}}, {"本级及下级包含孙级", consts.SysRoleDataScopeDeptAndBelow, []int64{101, 102}}, {"自定义指定孙级", consts.SysRoleDataScopeCustom, []int64{102}}, {"仅本级没有客户", consts.SysRoleDataScopeDept, []int64{}}, {"仅本人没有组织权限", consts.SysRoleDataScopePersonal, []int64{}}, {"未知范围拒绝", "invalid", []int64{}}, } for _, item := range cases { t.Run(item.name, func(t *testing.T) { scopeCtx := contexts.Set(ctx, &model.Context{User: &model.Identity{BaseClaims: model.BaseClaims{ID: user.UserId, TenantId: user.TenantId, DeptId: user.DeptId}}}) contexts.SetDataValue(scopeCtx, "currentRoleId", "9") contexts.SetDataValue(scopeCtx, "currentRoleDataScope", item.scope) rows, err := appEnterpriseAuthorizedCustomerDepts(scopeCtx, user) if err != nil { t.Fatal(err) } actual := appDeptIds(rows) if !reflect.DeepEqual(actual, item.expected) { t.Fatalf("actual=%v expected=%v", actual, item.expected) } for _, candidate := range []int64{100, 101, 102, 103, 104, 200, 900, 901, 999} { visible, err := appEnterpriseCustomerDeptVisible(scopeCtx, user, candidate) if err != nil { t.Fatal(err) } expected := false for _, id := range item.expected { if candidate == id { expected = true } } if visible != expected { t.Fatalf("detail visibility for %d: actual=%v expected=%v", candidate, visible, expected) } } }) } }