sync: 同步 v1.0.4 开源代码并脱敏

This commit is contained in:
MiFaZhan
2026-10-29 14:52:43 +08:00
parent 32e4316ee4
commit 6893e21f07
136 changed files with 9851 additions and 436 deletions
@@ -0,0 +1,237 @@
package data
import (
"context"
"fmt"
"path/filepath"
"testing"
_ "github.com/gogf/gf/contrib/drivers/sqlite/v2"
"github.com/gogf/gf/v2/database/gdb"
"git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/consts"
"git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/model"
"git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/service/batchresult"
service "git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/service/data"
)
func TestAccuracyEmptyValidationBatchFinalizesFailed(t *testing.T) {
for _, batchType := range []int{107, 109, 110} {
t.Run(fmt.Sprint(batchType), func(t *testing.T) {
repo, generator, ctx := accuracyTestRepository(t, batchType)
if err := service.NewExecutor(repo, &accuracyNoUpstream{t: t}).Execute(ctx, "ACCURACY"); err != nil {
t.Fatal(err)
}
row, err := repo.database().GetOne(ctx, "SELECT batch_async_status,run_result,valid_count,success_count,fail_count,fail_reason FROM batch_main WHERE batch_no='ACCURACY'")
if err != nil {
t.Fatal(err)
}
if row["batch_async_status"].Int() != consts.BatchAsyncStatusFailed || row["run_result"].Int() != consts.BatchRunResultFailed {
t.Fatalf("零明细校验失败被误报成功: %+v", row)
}
if row["valid_count"].Int() != 0 || row["success_count"].Int() != 0 || row["fail_count"].Int() != 0 || row["fail_reason"].String() == "" {
t.Fatalf("空批次不得虚构行数或丢失失败原因: %+v", row)
}
if len(generator.requests) != 1 || len(generator.requests[0].Rows) != 0 {
t.Fatalf("空批次结果文件行数错误: %+v", generator.requests)
}
download, err := repo.database().GetOne(ctx, "SELECT total_count,success_count,fail_count FROM t_resource_batch_download WHERE batch_no='ACCURACY'")
if err != nil || download["total_count"].Int() != 0 || download["success_count"].Int() != 0 || download["fail_count"].Int() != 0 {
t.Fatalf("空批次下载统计错误: %+v err=%v", download, err)
}
})
}
}
func TestAccuracyAuthorizedSnapshotDoesNotPassValidation(t *testing.T) {
repo, _, ctx := accuracyTestRepository(t, 110)
if _, err := repo.database().Exec(ctx, `INSERT INTO batch_detail (id,batch_no,batch_type,row_no,item_id,detail_status,snapshot) VALUES (1,'ACCURACY',110,1,'',1,'{"authorized":true}')`); err != nil {
t.Fatal(err)
}
task, err := repo.Load(ctx, "ACCURACY")
if err != nil {
t.Fatal(err)
}
if len(task.Details) != 1 || !task.Details[0].Authorized || task.Details[0].ValidationPassed {
t.Fatalf("授权标志不能被当成校验通过: %+v", task.Details)
}
}
func TestAccuracyFrozenOwnershipDrivesValidCount(t *testing.T) {
repo, _, ctx := accuracyTestRepository(t, 110)
// 冻结归属才代表已通过提交前校验:授权标记不等于校验通过,
// 未被冻结进子批的明细(校验被拒)也不得计入有效数。
if _, err := repo.database().Exec(ctx, "INSERT INTO data_operation_sub_detail (detail_id,operation_id,batch_no,iccid) VALUES (1,'op-1','ACCURACY','frozen-card')"); err != nil {
t.Fatal(err)
}
task := &service.Task{Batch: service.Batch{BatchNo: "ACCURACY", BatchType: 110}, Details: []*service.Detail{
{ID: 1, Authorized: true, DetailStatus: consts.BatchDetailStatusUnknown, Reason: "补查窗口耗尽,结果未知,禁止重发"},
{ID: 2, Authorized: true, DetailStatus: consts.BatchDetailStatusFailed, Reason: "ICCID does not exist in SIM card records"},
}}
if err := repo.markFrozenValidation(ctx, task); err != nil {
t.Fatal(err)
}
if !task.Details[0].ValidationPassed || task.Details[1].ValidationPassed {
t.Fatalf("冻结归属判定错误: %+v", task.Details)
}
task.ValidationOK = true
if count := validDetailCount(task); count != 1 {
t.Fatalf("恢复时有效数错误: got=%d want=1", count)
}
}
func TestAccuracyFinalizeEmptyBatchPreservesValidationReason(t *testing.T) {
repo, _, ctx := accuracyTestRepository(t, 109)
operation, err := service.OperationByBatchType(109)
if err != nil {
t.Fatal(err)
}
task := &service.Task{Batch: service.Batch{BatchNo: "ACCURACY", BatchType: 109}, Operation: operation, FailureReason: "导入文件没有可执行行"}
if err := repo.Finalize(ctx, task); err != nil {
t.Fatal(err)
}
row, err := repo.database().GetOne(ctx, "SELECT batch_async_status,run_result,fail_reason FROM batch_main WHERE batch_no='ACCURACY'")
if err != nil {
t.Fatal(err)
}
if row["batch_async_status"].Int() != consts.BatchAsyncStatusFailed || row["run_result"].Int() != consts.BatchRunResultFailed || row["fail_reason"].String() != "导入文件没有可执行行" || task.FailureReason != "导入文件没有可执行行" {
t.Fatalf("空批次原始校验原因被覆盖: %+v task=%+v", row, task)
}
}
func TestAccuracyValidationFailureKeepsPerRowReasons(t *testing.T) {
for _, batchType := range []int{107, 109, 110} {
t.Run(fmt.Sprint(batchType), func(t *testing.T) {
repo, generator, ctx := accuracyTestRepository(t, batchType)
operation, err := service.OperationByBatchType(batchType)
if err != nil {
t.Fatal(err)
}
task := &service.Task{Batch: service.Batch{BatchNo: "ACCURACY", BatchType: batchType}, Operation: operation, Details: []*service.Detail{
{ID: 1, RowNo: 1, ICCID: "same-card", Reason: "MSISDN does not match the SIM card record"},
{ID: 2, RowNo: 2, ICCID: "same-card", Reason: "IMSI does not match the SIM card record"},
}}
for _, detail := range task.Details {
if _, err := repo.database().Exec(ctx, "INSERT INTO batch_detail (id,batch_no,batch_type,row_no,item_id,detail_status) VALUES (?,'ACCURACY',?,?,?,1)", detail.ID, batchType, detail.RowNo, detail.ICCID); err != nil {
t.Fatal(err)
}
}
if err := repo.SaveValidationFailure(ctx, task); err != nil {
t.Fatal(err)
}
if err := repo.Finalize(ctx, task); err != nil {
t.Fatal(err)
}
if len(generator.requests) != 1 || len(generator.requests[0].Rows) != len(task.Details) {
t.Fatalf("逐行导出缺失: %+v", generator.requests)
}
for i, detail := range task.Details {
row, err := repo.database().GetOne(ctx, "SELECT detail_status,error_reason FROM batch_detail WHERE id=?", detail.ID)
if err != nil || row["detail_status"].Int() != consts.BatchDetailStatusFailed || row["error_reason"].String() != detail.Reason {
t.Fatalf("逐行原因落库错误: row=%+v detail=%+v err=%v", row, detail, err)
}
exported := generator.requests[0].Rows[i]
if exported.Success || exported.DetailStatus != consts.BatchDetailStatusFailed || exported.Reason != detail.Reason {
t.Fatalf("逐行原因导出被改写: %+v", exported)
}
}
})
}
}
func TestAccuracyFinalizeUnknownStillRequiresReview(t *testing.T) {
for _, statuses := range [][]int{{consts.BatchDetailStatusUnknown}, {consts.BatchDetailStatusSuccess, consts.BatchDetailStatusUnknown}, {consts.BatchDetailStatusFailed, consts.BatchDetailStatusUnknown}} {
t.Run(fmt.Sprint(statuses), func(t *testing.T) {
repo, _, ctx := accuracyTestRepository(t, 110)
task := &service.Task{Batch: service.Batch{BatchNo: "ACCURACY", BatchType: 110}}
for i, status := range statuses {
task.Details = append(task.Details, &service.Detail{ID: int64(i + 1), DetailStatus: status, Reason: "该卡结果待核查"})
}
if err := repo.Finalize(ctx, task); err != nil {
t.Fatal(err)
}
row, err := repo.database().GetOne(ctx, "SELECT batch_async_status,run_result,fail_count,batch_info FROM batch_main WHERE batch_no='ACCURACY'")
if err != nil {
t.Fatal(err)
}
wantFailed := 0
if statuses[0] == consts.BatchDetailStatusFailed {
wantFailed = 1
}
if row["batch_async_status"].Int() != consts.BatchAsyncStatusReview || row["run_result"].Int() != consts.BatchRunResultReview || row["fail_count"].Int() != wantFailed {
t.Fatalf("未知被误计成功或明确失败: %+v", row)
}
})
}
}
func TestAccuracyBatchResultCounts(t *testing.T) {
for _, tc := range []struct {
success, failed int
status, result int
}{{0, 0, consts.BatchAsyncStatusFailed, consts.BatchRunResultFailed}, {0, 2, consts.BatchAsyncStatusFailed, consts.BatchRunResultFailed}, {2, 0, consts.BatchAsyncStatusSuccess, consts.BatchRunResultSuccess}, {1, 1, consts.BatchAsyncStatusPartialSuccess, consts.BatchRunResultPartialSuccess}} {
t.Run(fmt.Sprintf("success=%d/failed=%d", tc.success, tc.failed), func(t *testing.T) {
status, result := batchResult(tc.success, tc.failed)
if status != tc.status || result != tc.result {
t.Fatalf("汇总结果=(%d,%d),期望=(%d,%d)", status, result, tc.status, tc.result)
}
})
}
}
func accuracyTestRepository(t *testing.T, batchType int) (*Repository, *accuracyArtifactGenerator, context.Context) {
t.Helper()
ctx := context.Background()
db, err := gdb.New(gdb.ConfigNode{Type: "sqlite", Link: "sqlite::@file(" + filepath.ToSlash(filepath.Join(t.TempDir(), "accuracy.db")) + ")"})
if err != nil {
t.Fatal(err)
}
t.Cleanup(func() {
if err := db.Close(ctx); err != nil {
t.Error(err)
}
})
for _, statement := range []string{
"CREATE TABLE batch_main (id INTEGER PRIMARY KEY,batch_no TEXT,batch_type INTEGER,batch_async_status INTEGER,tenant_id TEXT,start_time DATETIME,created_by INTEGER,created_dept INTEGER,valid_count INTEGER,success_count INTEGER,fail_count INTEGER,run_result INTEGER,fail_reason TEXT,batch_info TEXT,end_time DATETIME,updated_by INTEGER)",
"CREATE TABLE batch_detail (id INTEGER PRIMARY KEY,batch_no TEXT,batch_type INTEGER,row_no INTEGER,item_id TEXT,detail_status INTEGER,error_code TEXT,error_reason TEXT,snapshot TEXT,updated_by INTEGER)",
"CREATE TABLE data_operation_parent (batch_no TEXT PRIMARY KEY,ready INTEGER DEFAULT 0,finalize_token TEXT,finalize_until DATETIME,last_reason TEXT)",
"CREATE TABLE data_operation_sub_batch (id INTEGER PRIMARY KEY,batch_no TEXT,status TEXT)",
"CREATE TABLE data_operation_sub_detail (detail_id INTEGER PRIMARY KEY, operation_id TEXT, batch_no TEXT, iccid TEXT, UNIQUE(batch_no,iccid))",
"CREATE TABLE sys_oss (id INTEGER PRIMARY KEY AUTOINCREMENT,tenant_id TEXT,file_name TEXT,original_name TEXT,file_suffix TEXT,path TEXT,url TEXT,created_dept INTEGER,created_by INTEGER,updated_by INTEGER,service TEXT,md5 TEXT,file_size INTEGER)",
"CREATE TABLE t_resource_batch_download (id INTEGER PRIMARY KEY AUTOINCREMENT,batch_no TEXT,task_name TEXT,resource_type INTEGER,export_scene INTEGER,total_count INTEGER,success_count INTEGER,fail_count INTEGER,download_status INTEGER,oss_id INTEGER,file_name TEXT,file_ext TEXT,file_url TEXT,file_path TEXT,file_size INTEGER,file_md5 TEXT,download_count INTEGER,max_download_count INTEGER,dept_id INTEGER,start_time DATETIME,finish_time DATETIME,tenant_id TEXT,created_dept INTEGER,created_by INTEGER,updated_by INTEGER)",
} {
if _, err := db.Exec(ctx, statement); err != nil {
t.Fatal(err)
}
}
if _, err := db.Exec(ctx, "INSERT INTO batch_main (id,batch_no,batch_type,batch_async_status,tenant_id) VALUES (1,'ACCURACY',?,2,'isolated')", batchType); err != nil {
t.Fatal(err)
}
if _, err := db.Exec(ctx, "INSERT INTO data_operation_parent (batch_no,ready) VALUES ('ACCURACY',0)"); err != nil {
t.Fatal(err)
}
generator := &accuracyArtifactGenerator{}
return &Repository{db: db, resultFiles: generator}, generator, ctx
}
type accuracyArtifactGenerator struct{ requests []batchresult.Request }
func (g *accuracyArtifactGenerator) Create(_ context.Context, request batchresult.Request) (batchresult.Artifact, error) {
g.requests = append(g.requests, request)
return batchresult.Artifact{FileName: "accuracy.xlsx", OriginalName: request.BatchNo + "-result.xlsx", FileSuffix: ".xlsx", Path: "accuracy.xlsx", URL: "/accuracy.xlsx"}, nil
}
type accuracyNoUpstream struct{ t *testing.T }
func (u *accuracyNoUpstream) ChangeSimApnFunction(context.Context, *model.SimCardApnChangeInput) ([]model.SimBaseRes[model.SimCardApnChangeOutputItem], error) {
u.t.Fatal("隔离回归不得调用同步上游")
return nil, nil
}
func (u *accuracyNoUpstream) SubmitSimApnFunction(context.Context, *model.SimCardApnChangeInput) ([]model.SimBaseRes[model.SimCardApnChangeOutputItem], error) {
u.t.Fatal("隔离回归不得提交上游")
return nil, nil
}
func (u *accuracyNoUpstream) QuerySimApnFunctionResult(context.Context, *model.SimCardApnChangeInput) ([]model.SimBaseRes[model.SimCardApnChangeOutputItem], error) {
u.t.Fatal("隔离回归不得查询上游")
return nil, nil
}