chore: 并入 iotlcm-admin-svr 历史到 admin-svr/(原提交 49818c6)

This commit is contained in:
MiFaZhan
2026-11-02 13:27:59 +08:00
1250 changed files with 154111 additions and 0 deletions
@@ -0,0 +1,268 @@
package exportcenter
import (
"context"
"errors"
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/xuri/excelize/v2"
"reflect"
"strings"
"testing"
"time"
"github.com/gogf/gf/v2/os/gtime"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
)
func TestSubmitQueueBoundedBeforePersistence(t *testing.T) {
for len(slots) < cap(slots) {
slots <- struct{}{}
}
defer func() {
for len(slots) > 0 {
<-slots
}
}()
_, err := Submit(context.Background(), &Request{
BizKey: "test", BizName: "test", Tags: func() ([]string, error) { return nil, nil },
Fetch: func(context.Context, int, int) ([]interface{}, int, error) {
return []interface{}{1}, 1, nil
},
})
if err == nil || !strings.Contains(err.Error(), "排队已满") {
t.Fatalf("队列满时应拒绝提交: %v", err)
}
}
func TestColumnDictBatchAndExcel(t *testing.T) {
columns := []model.ExportColumn{{Field: "status", DictType: "enabled"}, {Field: "", DictType: "ignored"}, {Field: "other", DictType: "enabled"}, {Field: "inverse", DictType: "yes", DictValueMap: map[string]string{"0": "1", "1": "0"}}}
calls := 0
dicts, err := loadColumnDicts(context.Background(), columns, func(_ context.Context, types []string) ([]model.SysDictDataListModel, error) {
calls++
if !reflect.DeepEqual(types, []string{"enabled", "yes"}) {
t.Fatalf("字典未去重: %v", types)
}
return []model.SysDictDataListModel{{DictType: "enabled", DictValue: "0", DictLabel: "启用"}, {DictType: "enabled", DictValue: "1", DictLabel: "停用"}, {DictType: "yes", DictValue: "1", DictLabel: "否"}}, nil
})
if err != nil || calls != 1 {
t.Fatalf("批量加载失败: %v %d", err, calls)
}
for _, tc := range []struct{ input, want interface{} }{{0, "启用"}, {"0", "启用"}, {1, "停用"}, {"", ""}, {nil, nil}, {"未知", "未知"}, {"启用", "启用"}} {
values := []interface{}{tc.input, "保留", 0}
translateRow(values, columns, dicts)
if !reflect.DeepEqual(values, []interface{}{tc.want, "保留", "否"}) {
t.Fatalf("映射 %v 得到 %v", tc.input, values)
}
}
f := excelize.NewFile()
defer f.Close()
stream, err := f.NewStreamWriter("Sheet1")
if err != nil {
t.Fatal(err)
}
values := []interface{}{0, "未知", 0}
translateRow(values, columns, dicts)
if err = stream.SetRow("A1", values); err != nil {
t.Fatal(err)
}
if err = stream.Flush(); err != nil {
t.Fatal(err)
}
buffer, err := f.WriteToBuffer()
if err != nil {
t.Fatal(err)
}
read, err := excelize.OpenReader(buffer)
if err != nil {
t.Fatal(err)
}
defer read.Close()
rows, err := read.GetRows("Sheet1")
if err != nil || !reflect.DeepEqual(rows, [][]string{{"启用", "未知", "否"}}) {
t.Fatalf("xlsx 内容: %v %v", rows, err)
}
}
func TestColumnDictNoMetadataAndLoadFailure(t *testing.T) {
called := false
_, err := loadColumnDicts(context.Background(), nil, func(context.Context, []string) ([]model.SysDictDataListModel, error) { called = true; return nil, nil })
if called || err != nil {
t.Fatal("旧页面不应加载字典")
}
failure := errors.New("字典加载失败")
_, err = loadColumnDicts(context.Background(), []model.ExportColumn{{Field: "status", DictType: "x"}}, func(context.Context, []string) ([]model.SysDictDataListModel, error) { return nil, failure })
if !errors.Is(err, failure) {
t.Fatal("应传播字典加载错误")
}
}
func TestNestedDictFieldsAndFormattedRow(t *testing.T) {
row := struct {
BatchInfo *gjson.Json `json:"batchInfo"`
}{gjson.New(map[string]interface{}{"inboundOptions": map[string]interface{}{"status": 0}, "legacy": 1})}
columns := []model.ExportColumn{{Field: "batchInfo.inboundOptions.status"}, {Field: "batchInfo.inboundOptions.missing", FallbackField: "batchInfo.legacy"}}
req := &Request{Columns: columns}
_, fields, _ := resolveColumns(req)
values := rowValues(row, fields, req)
if !reflect.DeepEqual(values, []interface{}{0, 1}) {
t.Fatalf("JSON 嵌套与备用字段: %v", values)
}
req = &Request{Columns: []model.ExportColumn{{Field: "status", DictType: "x"}}, RowFields: []string{"status"}, ToRow: func(interface{}) []interface{} { return []interface{}{"已销户"} }}
values = rowValues(struct {
Status int `json:"status"`
}{9}, fieldList{"status"}, req)
translateRow(values, req.Columns, map[string]map[string]string{"x": {"9": "已销户"}})
if values[0] != "已销户" {
t.Fatalf("已中文行被改变: %v", values)
}
}
type columnSampleRow struct {
Iccid string `json:"iccid"`
DeptName string `json:"deptName"`
UsedFlowMb float64 `json:"usedFlowMb"`
ActiveAt *gtime.Time `json:"activeAt"`
Extra *string `json:"extra"`
}
func TestResolveColumnsUsesRequestedOrder(t *testing.T) {
req := &Request{
Columns: []model.ExportColumn{
{Field: "deptName", Label: "所属企业"},
{Field: "iccid"},
{Field: " ", Label: "空字段忽略"},
{Field: "usedFlowMb", Label: "已用流量(MB)"},
},
Tags: func() ([]string, error) {
t.Fatal("有列清单时不应回退到 Tags")
return nil, nil
},
}
header, fields, err := resolveColumns(req)
if err != nil {
t.Fatalf("resolveColumns error: %v", err)
}
wantHeader := []interface{}{"所属企业", "iccid", "已用流量(MB)"}
if len(header) != len(wantHeader) {
t.Fatalf("header len = %d, want %d", len(header), len(wantHeader))
}
for i := range wantHeader {
if header[i] != wantHeader[i] {
t.Fatalf("header[%d] = %v, want %v", i, header[i], wantHeader[i])
}
}
wantFields := fieldList{"deptName", "iccid", "usedFlowMb"}
if len(fields) != len(wantFields) {
t.Fatalf("fields len = %d, want %d", len(fields), len(wantFields))
}
for i := range wantFields {
if fields[i] != wantFields[i] {
t.Fatalf("fields[%d] = %q, want %q", i, fields[i], wantFields[i])
}
}
}
func TestResolveColumnsFallsBackToTags(t *testing.T) {
req := &Request{
Tags: func() ([]string, error) { return []string{"ICCID", "所属企业"}, nil },
}
header, fields, err := resolveColumns(req)
if err != nil {
t.Fatalf("resolveColumns error: %v", err)
}
if fields != nil {
t.Fatalf("fields should be nil when no columns, got %v", fields)
}
if len(header) != 2 || header[0] != "ICCID" || header[1] != "所属企业" {
t.Fatalf("unexpected header: %v", header)
}
}
func TestValuesByFields(t *testing.T) {
activeAt := gtime.NewFromTime(time.Date(2026, 10, 19, 15, 4, 5, 0, time.Local))
row := &columnSampleRow{
Iccid: "8986001",
DeptName: "测试企业",
UsedFlowMb: 12.5,
ActiveAt: activeAt,
}
values := valuesByFields(row, fieldList{"deptName", "missingField", "usedFlowMb", "activeAt", "extra"})
if len(values) != 5 {
t.Fatalf("values len = %d, want 5", len(values))
}
if values[0] != "测试企业" {
t.Fatalf("deptName = %v", values[0])
}
if values[1] != "" {
t.Fatalf("missing field should be empty, got %v", values[1])
}
if values[2] != 12.5 {
t.Fatalf("usedFlowMb = %v", values[2])
}
if values[3] != "2026-10-19 15:04:05" {
t.Fatalf("activeAt = %v", values[3])
}
if values[4] != "" {
t.Fatalf("nil pointer should be empty, got %v", values[4])
}
}
func TestValuesByFieldsKeepsRowOrder(t *testing.T) {
row := &columnSampleRow{Iccid: "A", DeptName: "B"}
values := valuesByFields(row, fieldList{"iccid", "deptName"})
if values[0] != "A" || values[1] != "B" {
t.Fatalf("unexpected values: %v", values)
}
}
// TestValueMapColumn 覆盖非字典的固定值映射(页面用三元表达式渲染的列)。
func TestValueMapColumn(t *testing.T) {
columns := []model.ExportColumn{
{
Field: "isFlowPool",
ValueMap: map[string]string{"1": "池卡", "0": "单卡"},
ValueDefault: "单卡",
},
{Field: "status", DictType: "common_status"},
{Field: "keep", ValueMap: map[string]string{"1": "是"}},
}
dicts := map[string]map[string]string{"common_status": {"1": "正常", "0": "停用"}}
// 1/0 命中映射;9 未命中走兜底;状态列仍走字典
values := []interface{}{1, 1, 1}
translateRow(values, columns, dicts)
if !reflect.DeepEqual(values, []interface{}{"池卡", "正常", "是"}) {
t.Fatalf("命中映射失败: %v", values)
}
// 0 必须映射,不能被当成空值跳过
values = []interface{}{0, "0", 1}
translateRow(values, columns, dicts)
if values[0] != "单卡" || values[1] != "停用" {
t.Fatalf("0 值映射失败: %v", values)
}
// 未命中且无兜底时保留原值
values = []interface{}{9, "", 7}
translateRow(values, columns, dicts)
if values[0] != "单卡" {
t.Fatalf("兜底失败: %v", values)
}
if values[2] != 7 {
t.Fatalf("无兜底应保留原值: %v", values)
}
}
// TestMissingRowFields 自定义 ToRow 导出:列 field 必须能在 RowFields 里找到。
func TestMissingRowFields(t *testing.T) {
rowFields := []string{"iccid", "createdBy"}
if missing := missingRowFields(fieldList{"iccid"}, rowFields); len(missing) != 0 {
t.Fatalf("不应有缺失: %v", missing)
}
if missing := missingRowFields(fieldList{"iccid", "createdBySumma"}, rowFields); !reflect.DeepEqual(missing, []string{"createdBySumma"}) {
t.Fatalf("缺失识别错误: %v", missing)
}
}