248 lines
8.4 KiB
Go
248 lines
8.4 KiB
Go
package batchresult
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"path/filepath"
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
|
|
"git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/consts"
|
|
|
|
"github.com/gogf/gf/v2/frame/g"
|
|
"github.com/gogf/gf/v2/os/gcfg"
|
|
"github.com/xuri/excelize/v2"
|
|
)
|
|
|
|
func TestWriteDataServiceWorkbookPreservesReviewStatusAndReason(t *testing.T) {
|
|
for _, batchType := range []int{consts.BatchTypeDataCut, consts.BatchTypeDataStart, consts.BatchTypeDataStop} {
|
|
t.Run(TaskName(batchType), func(t *testing.T) {
|
|
path := filepath.Join(t.TempDir(), "result.xlsx")
|
|
rows := []Row{
|
|
{RowNo: 1, ICCID: "test-success", DetailStatus: consts.BatchDetailStatusSuccess, Success: true},
|
|
{RowNo: 2, ICCID: "test-failed", DetailStatus: consts.BatchDetailStatusFailed, Reason: "上游明确拒绝"},
|
|
{RowNo: 3, ICCID: "test-unknown", DetailStatus: consts.BatchDetailStatusUnknown, Reason: "查询超时,尚未确认上游执行结果", UpstreamCode: 202, OrderNum: "test-job-id"},
|
|
{RowNo: 4, ICCID: "test-review-priority", DetailStatus: consts.BatchDetailStatusUnknown, Success: true, Reason: "明细仍待核查"},
|
|
{RowNo: 5, ICCID: "test-legacy-success", Success: true},
|
|
{RowNo: 6, ICCID: "test-legacy-failed", Reason: "未受理"},
|
|
}
|
|
if err := writeWorkbook(path, Request{BatchType: batchType, Rows: rows}); err != nil {
|
|
t.Fatalf("写入数据服务结果失败:%v", err)
|
|
}
|
|
file, err := excelize.OpenFile(path)
|
|
if err != nil {
|
|
t.Fatalf("打开数据服务结果失败:%v", err)
|
|
}
|
|
defer func() { _ = file.Close() }()
|
|
for cell, want := range map[string]string{
|
|
"G1": "执行结果", "H1": "失败/待核查原因",
|
|
"G2": "成功", "G3": "失败", "H3": "上游明确拒绝",
|
|
"G4": "待核查", "H4": rows[2].Reason, "I4": "202", "J4": "test-job-id",
|
|
"G5": "待核查", "H5": rows[3].Reason,
|
|
"G6": "成功", "G7": "失败", "H7": "未受理",
|
|
} {
|
|
got, err := file.GetCellValue("处理结果", cell)
|
|
if err != nil || got != want {
|
|
t.Fatalf("单元格 %s = %q,预期 %q,错误:%v", cell, got, want, err)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCreateSameBatchUsesIndependentResultFiles(t *testing.T) {
|
|
// Keep the generated workbook path below Excel's 207-character limit on Windows.
|
|
root, rootErr := os.MkdirTemp("", "batch-result-")
|
|
if rootErr != nil {
|
|
t.Fatal(rootErr)
|
|
}
|
|
t.Cleanup(func() {
|
|
if err := os.RemoveAll(root); err != nil {
|
|
t.Errorf("remove test result directory: %v", err)
|
|
}
|
|
})
|
|
t.Setenv("IOTLCM_WORKER_UPLOAD_ROOT", root)
|
|
oldAdapter := g.Cfg().GetAdapter()
|
|
adapter, err := gcfg.NewAdapterContent(`
|
|
system:
|
|
upload:
|
|
local:
|
|
path: "resource/upload"
|
|
`)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
g.Cfg().SetAdapter(adapter)
|
|
t.Cleanup(func() { g.Cfg().SetAdapter(oldAdapter) })
|
|
request := Request{BatchNo: "DATA-REVIEW", BatchType: consts.BatchTypeDataStart, TenantID: "tenant", Rows: []Row{{RowNo: 1, ICCID: "first-card", DetailStatus: consts.BatchDetailStatusUnknown, Reason: "待核查"}}}
|
|
requests := []Request{request, request}
|
|
requests[1].Rows = []Row{{RowNo: 1, ICCID: "second-card", DetailStatus: consts.BatchDetailStatusUnknown, Reason: "待核查"}}
|
|
artifacts := make([]Artifact, 2)
|
|
errors := make([]error, 2)
|
|
var workers sync.WaitGroup
|
|
for index := range requests {
|
|
workers.Add(1)
|
|
go func(index int) {
|
|
defer workers.Done()
|
|
artifacts[index], errors[index] = New().Create(context.Background(), requests[index])
|
|
}(index)
|
|
}
|
|
workers.Wait()
|
|
for _, err := range errors {
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
first, second := artifacts[0], artifacts[1]
|
|
defer RemoveLocalFile(first)
|
|
defer RemoveLocalFile(second)
|
|
if first.Path == second.Path || first.LocalPath == second.LocalPath || first.FileName == second.FileName {
|
|
t.Fatalf("同批结果物理文件冲突: first=%+v second=%+v", first, second)
|
|
}
|
|
if first.OriginalName != "DATA-REVIEW-result.xlsx" || second.OriginalName != first.OriginalName {
|
|
t.Fatalf("用户下载原始文件名被更改: %q / %q", first.OriginalName, second.OriginalName)
|
|
}
|
|
RemoveLocalFile(first)
|
|
if _, err := os.Stat(first.LocalPath); !os.IsNotExist(err) {
|
|
t.Fatalf("首次生成文件未删除: %v", err)
|
|
}
|
|
file, err := excelize.OpenFile(second.LocalPath)
|
|
if err != nil {
|
|
t.Fatalf("删除第一份后第二份不可读: %v", err)
|
|
}
|
|
defer file.Close()
|
|
got, err := file.GetCellValue("处理结果", "B2")
|
|
if err != nil || got != "second-card" {
|
|
t.Fatalf("第二份结果内容损坏: B2=%q err=%v", got, err)
|
|
}
|
|
}
|
|
|
|
func TestCreatePreservesWorkbookAndPermissionsForAllBatchTypes(t *testing.T) {
|
|
root, err := os.MkdirTemp("", "batchresult-")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
t.Cleanup(func() { _ = os.RemoveAll(root) })
|
|
t.Setenv("IOTLCM_WORKER_UPLOAD_ROOT", root)
|
|
oldAdapter := g.Cfg().GetAdapter()
|
|
adapter, err := gcfg.NewAdapterContent(`
|
|
system:
|
|
upload:
|
|
local:
|
|
path: "resource/upload"
|
|
`)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
g.Cfg().SetAdapter(adapter)
|
|
t.Cleanup(func() { g.Cfg().SetAdapter(oldAdapter) })
|
|
for _, batchType := range []int{1, 3, 4, 11, 12, 40, 41, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110} {
|
|
t.Run(strconv.Itoa(batchType), func(t *testing.T) {
|
|
request := Request{BatchNo: "COMPAT-" + strconv.Itoa(batchType), BatchType: batchType, TenantID: "tenant", Rows: []Row{
|
|
{RowNo: 1, ICCID: "test-success", ObjectCode: "test-success", Success: true},
|
|
{RowNo: 2, ICCID: "test-failed", ObjectCode: "test-failed", Reason: "原有失败原因"},
|
|
}}
|
|
baseline := filepath.Join(t.TempDir(), "baseline.xlsx")
|
|
if err := writeWorkbook(baseline, request); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
artifact, err := New().Create(context.Background(), request)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer RemoveLocalFile(artifact)
|
|
if artifact.OriginalName != request.BatchNo+"-result.xlsx" || filepath.Ext(artifact.LocalPath) != ".xlsx" {
|
|
t.Fatalf("结果文件名或后缀发生变化: %+v", artifact)
|
|
}
|
|
baselineInfo, err := os.Stat(baseline)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
actualInfo, err := os.Stat(artifact.LocalPath)
|
|
if err != nil || actualInfo.Mode().Perm() != baselineInfo.Mode().Perm() {
|
|
t.Fatalf("结果文件权限未保留原SaveAs行为: %v", err)
|
|
}
|
|
before, err := excelize.OpenFile(baseline)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer before.Close()
|
|
after, err := excelize.OpenFile(artifact.LocalPath)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
defer after.Close()
|
|
want, err := before.GetRows("处理结果")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
got, err := after.GetRows("处理结果")
|
|
if err != nil || !reflect.DeepEqual(got, want) {
|
|
t.Fatalf("随机物理名改变了批次%d工作簿内容: err=%v", batchType, err)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCreateWritesToMountedUploadAndKeepsLogicalPath(t *testing.T) {
|
|
wd := t.TempDir()
|
|
oldWd, err := os.Getwd()
|
|
if err != nil {
|
|
t.Fatalf("get working directory: %v", err)
|
|
}
|
|
if err = os.Chdir(wd); err != nil {
|
|
t.Fatalf("change working directory: %v", err)
|
|
}
|
|
defer func() {
|
|
_ = os.Chdir(oldWd)
|
|
}()
|
|
|
|
if err = os.MkdirAll(filepath.Join(wd, "upload"), 0o755); err != nil {
|
|
t.Fatalf("create mounted upload root: %v", err)
|
|
}
|
|
oldAdapter := g.Cfg().GetAdapter()
|
|
adapter, err := gcfg.NewAdapterContent(`
|
|
system:
|
|
upload:
|
|
local:
|
|
path: "resource/upload"
|
|
`)
|
|
if err != nil {
|
|
t.Fatalf("create config adapter: %v", err)
|
|
}
|
|
g.Cfg().SetAdapter(adapter)
|
|
defer g.Cfg().SetAdapter(oldAdapter)
|
|
|
|
artifact, err := New().Create(context.Background(), Request{
|
|
BatchNo: "INB202608140001001",
|
|
BatchType: 1,
|
|
TenantID: "000000",
|
|
Rows: []Row{{RowNo: 1, ICCID: "89860401786527567308", Success: true}},
|
|
})
|
|
if err != nil {
|
|
t.Fatalf("Create() error = %v", err)
|
|
}
|
|
defer RemoveLocalFile(artifact)
|
|
|
|
logicalPath := filepath.ToSlash(artifact.Path)
|
|
if !strings.HasPrefix(logicalPath, "resource/upload/000000/user/file/") {
|
|
t.Fatalf("logical path = %q, want resource/upload path", artifact.Path)
|
|
}
|
|
localPath := filepath.ToSlash(artifact.LocalPath)
|
|
if !strings.HasPrefix(localPath, "upload/000000/user/file/") {
|
|
t.Fatalf("local path = %q, want mounted upload path", artifact.LocalPath)
|
|
}
|
|
if _, err = os.Stat(artifact.LocalPath); err != nil {
|
|
t.Fatalf("result file was not written at local path %q: %v", artifact.LocalPath, err)
|
|
}
|
|
if _, err = os.Stat(artifact.Path); !os.IsNotExist(err) {
|
|
t.Fatalf("result file should not be written to logical-only path %q, stat error=%v", artifact.Path, err)
|
|
}
|
|
if !strings.HasPrefix(artifact.URL, "/upload/000000/user/file/") {
|
|
t.Fatalf("url = %q, want /upload URL", artifact.URL)
|
|
}
|
|
}
|