sync: 同步 v1.0.4 开源代码并脱敏
This commit is contained in:
@@ -3,15 +3,29 @@ package inbound
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gcfg"
|
||||
"github.com/xuri/excelize/v2"
|
||||
|
||||
"git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/batch"
|
||||
"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"
|
||||
)
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
adapter, err := gcfg.NewAdapterContent("batch:\n inbound:\n upstreamBatchSize: 1\n")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
g.Cfg().SetAdapter(adapter)
|
||||
os.Exit(m.Run())
|
||||
}
|
||||
|
||||
func TestAdminSourceTypeContract(t *testing.T) {
|
||||
if consts.BatchSourceTypeRange != 1 || consts.BatchSourceTypeBatchInput != 2 || consts.BatchSourceTypeFile != 3 {
|
||||
t.Fatalf("source_type contract mismatch: range=%d input=%d file=%d",
|
||||
@@ -19,6 +33,47 @@ func TestAdminSourceTypeContract(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestCascadeFailurePreservesPerCardValidation(t *testing.T) {
|
||||
task := &Task{Rows: []*Row{
|
||||
{ICCID: "invalid", Reason: "卡号不合法"},
|
||||
{ICCID: "valid"},
|
||||
}}
|
||||
applyCascadeFailure(task)
|
||||
if countFailedRows(task.Rows) != 1 || !IsCascadeFailure(task.Rows[1]) {
|
||||
t.Fatalf("unexpected validation outcomes: %+v", task.Rows)
|
||||
}
|
||||
if task.Rows[0].Reason != "卡号不合法" || inboundRowStatus(task.Rows[0]) != "失败" || inboundRowStatus(task.Rows[1]) != "未执行" {
|
||||
t.Fatalf("wrong per-card status: %+v", task.Rows)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateCollectsLaterInboundFailuresBeforeBlockingBatch(t *testing.T) {
|
||||
task := &Task{Rows: []*Row{
|
||||
{ICCID: "bad", ChanKey: "channel"},
|
||||
{ICCID: "8986000000000000001", MSISDN: "13800008000", IMSI: "460001234567890", ChanKey: "channel"},
|
||||
{ICCID: "8986000000000000002", MSISDN: "invalid", IMSI: "invalid", ChanKey: "channel"},
|
||||
}}
|
||||
executor := &executor{repository: &inboundRepositoryStub{}}
|
||||
if err := executor.validate(context.Background(), task); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
applyCascadeFailure(task)
|
||||
if !strings.Contains(task.Rows[0].Reason, "ICCID格式不正确") {
|
||||
t.Fatalf("first failure was lost: %q", task.Rows[0].Reason)
|
||||
}
|
||||
if !IsCascadeFailure(task.Rows[1]) {
|
||||
t.Fatalf("validated card was not blocked: %q", task.Rows[1].Reason)
|
||||
}
|
||||
for _, reason := range []string{"MSISDN格式不正确", "IMSI格式不正确"} {
|
||||
if !strings.Contains(task.Rows[2].Reason, reason) {
|
||||
t.Fatalf("later card was not fully validated: %q", task.Rows[2].Reason)
|
||||
}
|
||||
}
|
||||
if countFailedRows(task.Rows) != 2 || countValidRows(task.Rows) != 0 {
|
||||
t.Fatalf("batch must have two validation failures and no executable cards: %+v", task)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateRowRequiresAtLeastOneCardIdentifierAndChanKey(t *testing.T) {
|
||||
row := &Row{}
|
||||
validateRow(row, "")
|
||||
@@ -32,7 +87,7 @@ func TestValidateRowRequiresAtLeastOneCardIdentifierAndChanKey(t *testing.T) {
|
||||
|
||||
func TestParseInputTextAcceptsOneIdentifierPerLine(t *testing.T) {
|
||||
rows := parseInputText(strings.Join([]string{
|
||||
"89860401780000567308",
|
||||
"89860401786527567308",
|
||||
"14100007308",
|
||||
"460046527567308",
|
||||
}, "\n"))
|
||||
@@ -40,7 +95,7 @@ func TestParseInputTextAcceptsOneIdentifierPerLine(t *testing.T) {
|
||||
if len(rows) != 3 {
|
||||
t.Fatalf("row count = %d, want 3", len(rows))
|
||||
}
|
||||
if rows[0].ICCID != "89860401780000567308" || rows[1].MSISDN != "14100007308" || rows[2].IMSI != "460046527567308" {
|
||||
if rows[0].ICCID != "89860401786527567308" || rows[1].MSISDN != "14100007308" || rows[2].IMSI != "460046527567308" {
|
||||
t.Fatalf("unexpected parsed rows: %+v", rows)
|
||||
}
|
||||
for _, row := range rows {
|
||||
@@ -111,7 +166,7 @@ func TestValidateRowSelectedMSISDNDoesNotRequireOtherIdentifiers(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestValidateRowAllowsAlphaNumericICCID(t *testing.T) {
|
||||
row := &Row{ICCID: "898604F4152000015473", MSISDN: "13800008000", IMSI: "460001234567890", ChanKey: "chan-a"}
|
||||
row := &Row{ICCID: "898604F4152391115473", MSISDN: "13800008000", IMSI: "460001234567890", ChanKey: "chan-a"}
|
||||
validateRow(row, "")
|
||||
if row.Reason != "" {
|
||||
t.Fatalf("unexpected validation reason: %q", row.Reason)
|
||||
@@ -149,14 +204,14 @@ func TestMarkSameBatchDuplicatesChecksIMSI(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestValidateUpstreamBasicInfoAcceptsMatchingIdentifiers(t *testing.T) {
|
||||
rows := []*Row{{RowNo: 1, ICCID: "898604F4152000015473", MSISDN: "1440000615473", IMSI: "460087436015473", ChanKey: "chan-a"}}
|
||||
rows := []*Row{{RowNo: 1, ICCID: "898604F4152391115473", MSISDN: "1441543615473", IMSI: "460087436015473", ChanKey: "chan-a"}}
|
||||
upstream := &basicInfoUpstreamStub{response: []model.SimBaseRes[model.SimBasicInfoItem]{
|
||||
{
|
||||
Code: 0,
|
||||
SimKey: &model.SimKey{ChanKey: "chan-a", ICCID: "898604F4152000015473"},
|
||||
SimKey: &model.SimKey{ChanKey: "chan-a", ICCID: "898604F4152391115473"},
|
||||
ResData: model.SimBasicInfoItem{
|
||||
ICCID: "898604F4152000015473",
|
||||
MSISDN: "1440000615473",
|
||||
ICCID: "898604F4152391115473",
|
||||
MSISDN: "1441543615473",
|
||||
IMSI: "460087436015473",
|
||||
},
|
||||
},
|
||||
@@ -176,7 +231,7 @@ func TestValidateUpstreamBasicInfoAcceptsMatchingIdentifiers(t *testing.T) {
|
||||
t.Fatalf("unexpected upstream inputs: %+v", upstream.inputs)
|
||||
}
|
||||
requestKey := upstream.inputs[0].SimKeys[0]
|
||||
if requestKey.ChanKey != "chan-a" || requestKey.ICCID != "898604F4152000015473" {
|
||||
if requestKey.ChanKey != "chan-a" || requestKey.ICCID != "898604F4152391115473" {
|
||||
t.Fatalf("unexpected upstream request key: %+v", requestKey)
|
||||
}
|
||||
if requestKey.MSISDN != "" || requestKey.IMSI != "" {
|
||||
@@ -185,11 +240,11 @@ func TestValidateUpstreamBasicInfoAcceptsMatchingIdentifiers(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestValidateUpstreamBasicInfoRequiresCompleteResData(t *testing.T) {
|
||||
rows := []*Row{{RowNo: 1, ICCID: "898604F4152000015473", MSISDN: "1440000615473", IMSI: "460087436015473", ChanKey: "chan-a"}}
|
||||
rows := []*Row{{RowNo: 1, ICCID: "898604F4152391115473", MSISDN: "1441543615473", IMSI: "460087436015473", ChanKey: "chan-a"}}
|
||||
upstream := &basicInfoUpstreamStub{response: []model.SimBaseRes[model.SimBasicInfoItem]{
|
||||
{
|
||||
Code: 0,
|
||||
SimKey: &model.SimKey{ChanKey: "chan-a", ICCID: "898604f4152000015473", MSISDN: "1440000615473", IMSI: "460087436015473"},
|
||||
SimKey: &model.SimKey{ChanKey: "chan-a", ICCID: "898604f4152391115473", MSISDN: "1441543615473", IMSI: "460087436015473"},
|
||||
ResData: model.SimBasicInfoItem{
|
||||
ActiveDate: "2026-08-12 00:00:00",
|
||||
OpenDate: "2026-08-12 00:00:00",
|
||||
@@ -232,7 +287,7 @@ func TestValidateUpstreamBasicInfoMatchesByMSISDNAndBackfillsCodes(t *testing.T)
|
||||
Code: 0,
|
||||
SimKey: &model.SimKey{ChanKey: "chan-a", MSISDN: "14100007308"},
|
||||
ResData: model.SimBasicInfoItem{
|
||||
ICCID: "89860401780000567308",
|
||||
ICCID: "89860401786527567308",
|
||||
MSISDN: "14100007308",
|
||||
IMSI: "460046527567308",
|
||||
},
|
||||
@@ -246,7 +301,7 @@ func TestValidateUpstreamBasicInfoMatchesByMSISDNAndBackfillsCodes(t *testing.T)
|
||||
if rows[0].Reason != "" {
|
||||
t.Fatalf("unexpected reason: %q", rows[0].Reason)
|
||||
}
|
||||
if rows[0].ICCID != "89860401780000567308" || rows[0].IMSI != "460046527567308" {
|
||||
if rows[0].ICCID != "89860401786527567308" || rows[0].IMSI != "460046527567308" {
|
||||
t.Fatalf("expected upstream codes to be backfilled, got %+v", rows[0])
|
||||
}
|
||||
requestKey := upstream.inputs[0].SimKeys[0]
|
||||
@@ -263,7 +318,7 @@ func TestValidateBatchInputSingleMSISDNBackfillsBeforeCompletenessCheck(t *testi
|
||||
Code: 0,
|
||||
SimKey: &model.SimKey{ChanKey: "chan-a", MSISDN: "14100007308"},
|
||||
ResData: model.SimBasicInfoItem{
|
||||
ICCID: "89860401780000567308",
|
||||
ICCID: "89860401786527567308",
|
||||
MSISDN: "14100007308",
|
||||
IMSI: "460046527567308",
|
||||
},
|
||||
@@ -282,7 +337,7 @@ func TestValidateBatchInputSingleMSISDNBackfillsBeforeCompletenessCheck(t *testi
|
||||
if task.ValidCount != 1 {
|
||||
t.Fatalf("valid count = %d, want 1, reason=%q", task.ValidCount, rows[0].Reason)
|
||||
}
|
||||
if rows[0].ICCID != "89860401780000567308" || rows[0].IMSI != "460046527567308" {
|
||||
if rows[0].ICCID != "89860401786527567308" || rows[0].IMSI != "460046527567308" {
|
||||
t.Fatalf("expected upstream codes to be backfilled, got %+v", rows[0])
|
||||
}
|
||||
if upstream.calls != 1 {
|
||||
@@ -293,7 +348,7 @@ func TestValidateBatchInputSingleMSISDNBackfillsBeforeCompletenessCheck(t *testi
|
||||
func TestValidateSkipsUpstreamBasicInfoWhenDisabled(t *testing.T) {
|
||||
rows := []*Row{{
|
||||
RowNo: 1,
|
||||
ICCID: "89860401780000567308",
|
||||
ICCID: "89860401786527567308",
|
||||
MSISDN: "14100007308",
|
||||
IMSI: "460046527567308",
|
||||
ChanKey: "chan-a",
|
||||
@@ -323,7 +378,7 @@ func TestValidateSkipsUpstreamBasicInfoWhenDisabled(t *testing.T) {
|
||||
func TestValidateRunsUpstreamBasicInfoWhenEnabled(t *testing.T) {
|
||||
rows := []*Row{{
|
||||
RowNo: 1,
|
||||
ICCID: "89860401780000567308",
|
||||
ICCID: "89860401786527567308",
|
||||
MSISDN: "14100007308",
|
||||
IMSI: "460046527567308",
|
||||
ChanKey: "chan-a",
|
||||
@@ -363,7 +418,7 @@ func TestPopulateExcelRowsReturnsRetryableErrorWhenSourceIsUnreadable(t *testing
|
||||
|
||||
func TestValidateUpstreamBasicInfoWaitsForRateLimitBeforeQuery(t *testing.T) {
|
||||
var order []string
|
||||
rows := []*Row{{RowNo: 1, ICCID: "89860401780000567308", MSISDN: "14100007308", IMSI: "460046527567308", ChanKey: "chan-a"}}
|
||||
rows := []*Row{{RowNo: 1, ICCID: "89860401786527567308", MSISDN: "14100007308", IMSI: "460046527567308", ChanKey: "chan-a"}}
|
||||
limiter := &recordingRateLimiter{
|
||||
onWait: func(context.Context, string, int) error {
|
||||
order = append(order, "limit")
|
||||
@@ -377,9 +432,9 @@ func TestValidateUpstreamBasicInfoWaitsForRateLimitBeforeQuery(t *testing.T) {
|
||||
response: []model.SimBaseRes[model.SimBasicInfoItem]{
|
||||
{
|
||||
Code: 0,
|
||||
SimKey: &model.SimKey{ChanKey: "chan-a", ICCID: "89860401780000567308"},
|
||||
SimKey: &model.SimKey{ChanKey: "chan-a", ICCID: "89860401786527567308"},
|
||||
ResData: model.SimBasicInfoItem{
|
||||
ICCID: "89860401780000567308",
|
||||
ICCID: "89860401786527567308",
|
||||
MSISDN: "14100007308",
|
||||
IMSI: "460046527567308",
|
||||
},
|
||||
@@ -406,7 +461,7 @@ func TestValidateUpstreamBasicInfoWaitsForRateLimitBeforeQuery(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestValidateUpstreamBasicInfoStopsWhenRateLimitFails(t *testing.T) {
|
||||
rows := []*Row{{RowNo: 1, ICCID: "89860401780000567308", ChanKey: "chan-a"}}
|
||||
rows := []*Row{{RowNo: 1, ICCID: "89860401786527567308", ChanKey: "chan-a"}}
|
||||
upstream := &basicInfoUpstreamStub{}
|
||||
limiter := &recordingRateLimiter{err: errors.New("redis down")}
|
||||
|
||||
@@ -460,14 +515,14 @@ func TestValidateUpstreamBasicInfoRejectsUpstreamError(t *testing.T) {
|
||||
func TestParseTableRowsAcceptsChineseInboundHeaders(t *testing.T) {
|
||||
rows := parseTableRows([][]string{
|
||||
{"ICCID号", "手机号", "国际移动用户识别码", "国际移动设备识别码", "通道编码", "卡类型", "上游套餐编码", "供应商编码"},
|
||||
{"898604F4152000015473", "1440000615473", "460087436015473", "865111111111111", "channel_003", "通用插拔 SIM 卡", "UP001", "SUP001"},
|
||||
{"898604F4152391115473", "1441543615473", "460087436015473", "865111111111111", "channel_003", "通用插拔 SIM 卡", "UP001", "SUP001"},
|
||||
})
|
||||
|
||||
if len(rows) != 1 {
|
||||
t.Fatalf("row count = %d, want 1", len(rows))
|
||||
}
|
||||
row := rows[0]
|
||||
if row.ICCID != "898604F4152000015473" || row.MSISDN != "1440000615473" ||
|
||||
if row.ICCID != "898604F4152391115473" || row.MSISDN != "1441543615473" ||
|
||||
row.IMSI != "460087436015473" || row.IMEI != "865111111111111" || row.ChanKey != "channel_003" {
|
||||
t.Fatalf("unexpected parsed row: %+v", row)
|
||||
}
|
||||
@@ -479,19 +534,88 @@ func TestParseTableRowsAcceptsChineseInboundHeaders(t *testing.T) {
|
||||
func TestParseTableRowsAcceptsRequiredMarkerHeaders(t *testing.T) {
|
||||
rows := parseTableRows([][]string{
|
||||
{"ICCID*", "MSISDN*", "IMSI*", "通道编号*"},
|
||||
{"89860401780000567308", "14100007308", "460046527567308", "channel_003"},
|
||||
{"89860401786527567308", "14100007308", "460046527567308", "channel_003"},
|
||||
})
|
||||
|
||||
if len(rows) != 1 {
|
||||
t.Fatalf("row count = %d, want 1", len(rows))
|
||||
}
|
||||
row := rows[0]
|
||||
if row.ICCID != "89860401780000567308" || row.MSISDN != "14100007308" ||
|
||||
if row.ICCID != "89860401786527567308" || row.MSISDN != "14100007308" ||
|
||||
row.IMSI != "460046527567308" || row.ChanKey != "channel_003" {
|
||||
t.Fatalf("unexpected parsed row: %+v", row)
|
||||
}
|
||||
}
|
||||
|
||||
func TestInboundExcelHeaderRequiresRecognizedIdentifier(t *testing.T) {
|
||||
for _, fields := range [][]string{{"随机内容", "乱码"}, {"通道编号", "卡类型"}, {"ICCID", "通道编号"}, {"MSISDN", "通道编号"}, {"IMSI", "通道编号"}} {
|
||||
got := hasInboundIdentifierHeader(headerIndex(fields))
|
||||
want := fields[0] == "ICCID" || fields[0] == "MSISDN" || fields[0] == "IMSI"
|
||||
if got != want {
|
||||
t.Fatalf("header %v valid = %t, want %t", fields, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestPopulateExcelRowsRejectsInvalidHeaderAndAcceptsMSISDNHeader(t *testing.T) {
|
||||
for _, test := range []struct {
|
||||
name string
|
||||
header []string
|
||||
valid bool
|
||||
}{
|
||||
{name: "invalid", header: []string{"乱码", "通道编号"}},
|
||||
{name: "msisdn", header: []string{"MSISDN", "通道编号"}, valid: true},
|
||||
} {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
workbook := excelize.NewFile()
|
||||
defer func() { _ = workbook.Close() }()
|
||||
for index, value := range test.header {
|
||||
cell, err := excelize.CoordinatesToCellName(index+1, 1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := workbook.SetCellValue("Sheet1", cell, value); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if err := workbook.SetCellValue("Sheet1", "A2", "14100007308"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
content, err := workbook.WriteToBuffer()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
task := &Task{Source: Source{ImportFileOssID: 1}}
|
||||
repository := &inboundRepositoryStub{readOssContent: content.Bytes(), readOssName: "cards.xlsx"}
|
||||
err = (&executor{repository: repository}).populateExcelRows(context.Background(), task)
|
||||
if !test.valid {
|
||||
if err == nil || len(task.Rows) != 0 {
|
||||
t.Fatalf("invalid header: err=%v, rows=%v", err, task.Rows)
|
||||
}
|
||||
return
|
||||
}
|
||||
if err != nil || len(task.Rows) != 1 || task.Rows[0].MSISDN != "14100007308" {
|
||||
t.Fatalf("valid MSISDN file: err=%v, rows=%v", err, task.Rows)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateInvalidRowSkipsUpstreamForEntireBatch(t *testing.T) {
|
||||
rows := []*Row{
|
||||
{RowNo: 2, ICCID: "invalid", ChanKey: "channel_003"},
|
||||
{RowNo: 3, ICCID: "89860401786527567308", ChanKey: "channel_003"},
|
||||
}
|
||||
upstream := &basicInfoUpstreamStub{}
|
||||
task := &Task{Rows: rows, Options: Options{ChanKey: "channel_003", ValidateUpstreamBasicInfo: "1"}}
|
||||
if err := (&executor{repository: &inboundRepositoryStub{}, upstream: upstream}).validate(context.Background(), task); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if upstream.calls != 0 || task.ValidCount != 0 || !hasFailedRows(rows) {
|
||||
t.Fatalf("invalid batch should skip upstream: calls=%d valid=%d", upstream.calls, task.ValidCount)
|
||||
}
|
||||
}
|
||||
|
||||
func TestValidateSkipsUpstreamWhenLocalIdentifierFormatFails(t *testing.T) {
|
||||
rows := []*Row{{RowNo: 1, ICCID: "4", MSISDN: "4", IMSI: "4", ChanKey: "chan-a"}}
|
||||
upstream := &basicInfoUpstreamStub{}
|
||||
@@ -539,8 +663,8 @@ func TestValidateRequiresCompleteIdentifiersAfterUpstreamStep(t *testing.T) {
|
||||
func TestValidateNormalizesExcelInboundOptionsAndBackfillsBatchInfo(t *testing.T) {
|
||||
rows := parseTableRows([][]string{
|
||||
{"ICCID*", "MSISDN*", "IMSI*", "通道编号*", "卡类型", "上游套餐编码", "供应商编码", "是否贴片转插拔", "是否运营商达量断网"},
|
||||
{"89860401780000567308", "14100007308", "460046527567308", "channel_003", "MP1-消费级插拔物联卡", "UP001", "SUP001", "是", "否"},
|
||||
{"89860401780000567309", "14100007309", "460046527567309", "channel_003", "", "UP001", "SUP001", "", ""},
|
||||
{"89860401786527567308", "14100007308", "460046527567308", "channel_003", "MP1-消费级插拔物联卡", "UP001", "SUP001", "是", "否"},
|
||||
{"89860401786527567309", "14100007309", "460046527567309", "channel_003", "", "UP001", "SUP001", "", ""},
|
||||
})
|
||||
task := &Task{Rows: rows, Batch: Batch{SourceType: consts.BatchSourceTypeFile}}
|
||||
|
||||
@@ -563,8 +687,8 @@ func TestValidateNormalizesExcelInboundOptionsAndBackfillsBatchInfo(t *testing.T
|
||||
func TestValidateRejectsMixedInboundOptionsInOneExcelBatch(t *testing.T) {
|
||||
rows := parseTableRows([][]string{
|
||||
{"ICCID*", "MSISDN*", "IMSI*", "通道编号*", "卡类型"},
|
||||
{"89860401780000567308", "14100007308", "460046527567308", "channel_003", "1"},
|
||||
{"89860401780000567309", "14100007309", "460046527567309", "channel_003", "2"},
|
||||
{"89860401786527567308", "14100007308", "460046527567308", "channel_003", "1"},
|
||||
{"89860401786527567309", "14100007309", "460046527567309", "channel_003", "2"},
|
||||
})
|
||||
task := &Task{Rows: rows, Batch: Batch{SourceType: consts.BatchSourceTypeFile}}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user