sync: 同步 v1.0.4 开源代码并脱敏
This commit is contained in:
@@ -0,0 +1,253 @@
|
||||
package data
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"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/model/entity"
|
||||
)
|
||||
|
||||
func TestAccuracyInvalidDuplicateKeepsValidTrafficCutoffRow(t *testing.T) {
|
||||
for _, invalidFirst := range []bool{true, false} {
|
||||
for _, invalidInput := range []string{"MSISDN", "IMSI", "authorization", "existing"} {
|
||||
t.Run(fmt.Sprintf("%s/invalidFirst=%v", invalidInput, invalidFirst), func(t *testing.T) {
|
||||
operation, err := OperationByBatchType(107)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
card := &Card{ID: 10, ICCID: "same-card", MSISDN: "1001", IMSI: "2001", ChanKey: "channel", DataServiceStatus: 1}
|
||||
invalid := &Detail{ID: 1, ICCID: card.ICCID, Authorized: true, Card: card}
|
||||
var wantReason string
|
||||
switch invalidInput {
|
||||
case "MSISDN":
|
||||
invalid.InputMSISDN = "wrong"
|
||||
wantReason = "MSISDN与卡档案不一致"
|
||||
case "IMSI":
|
||||
invalid.InputIMSI = "wrong"
|
||||
wantReason = "IMSI与卡档案不一致"
|
||||
case "authorization":
|
||||
invalid.Authorized = false
|
||||
wantReason = "ICCID不在当前操作员授权范围内"
|
||||
case "existing":
|
||||
wantReason = "导入行格式校验失败"
|
||||
invalid.Reason = wantReason
|
||||
}
|
||||
valid := &Detail{ID: 2, ICCID: card.ICCID, Authorized: true, Card: card}
|
||||
otherInvalid := &Detail{ID: 3, ICCID: card.ICCID, Reason: "该行独立校验失败"}
|
||||
duplicate := &Detail{ID: 4, ICCID: card.ICCID, Authorized: true, Card: card}
|
||||
details := []*Detail{invalid, valid, otherInvalid, duplicate}
|
||||
if !invalidFirst {
|
||||
details[0], details[1] = details[1], details[0]
|
||||
}
|
||||
task := &Task{Batch: Batch{BatchNo: "ACCURACY-CUT", BatchType: 107}, Operation: operation, ApnName: "CMIOT", Details: details}
|
||||
base := &accuracyRepository{task: task, saved: map[int64]Outcome{}}
|
||||
repo := &accuracyDurableRepository{accuracyRepository: base}
|
||||
if err := NewExecutor(repo, &accuracyNoUpstream{t: t}).Execute(context.Background(), task.Batch.BatchNo); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(repo.prepared) != 1 || len(repo.prepared[0].DetailIDs) != 1 || repo.prepared[0].DetailIDs[0] != valid.ID {
|
||||
t.Fatalf("有效行未独立冻结: %+v", repo.prepared)
|
||||
}
|
||||
if valid.Reason != "" || valid.DetailStatus == consts.BatchDetailStatusFailed {
|
||||
t.Fatalf("有效行被无效重复行阻断: %+v", valid)
|
||||
}
|
||||
for _, check := range []struct {
|
||||
detail *Detail
|
||||
reason string
|
||||
}{{invalid, wantReason}, {otherInvalid, "该行独立校验失败"}, {duplicate, "重复ICCID,未提交上游"}} {
|
||||
outcome, ok := base.saved[check.detail.ID]
|
||||
if !ok || outcome.Success || outcome.Message != check.reason || check.detail.Reason != check.reason {
|
||||
t.Fatalf("逐行原因被改写: id=%d detail=%+v outcome=%+v", check.detail.ID, check.detail, outcome)
|
||||
}
|
||||
}
|
||||
if repo.prepared[0].Request.ReqData.OperType != "1" || operation.TargetStatus != 0 {
|
||||
t.Fatalf("107开停方向倒置: %+v", repo.prepared[0])
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccuracyFrozenRequestsKeepAPNDirection(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
batchType int
|
||||
operType string
|
||||
target int
|
||||
}{{107, "1", 0}, {109, "0", 1}, {110, "1", 0}} {
|
||||
t.Run(fmt.Sprint(tc.batchType), func(t *testing.T) {
|
||||
operation, err := OperationByBatchType(tc.batchType)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
card := &Card{ID: 10, ICCID: "card", MSISDN: "1001", ChanKey: "channel", DataServiceStatus: 1 - tc.target}
|
||||
task := &Task{
|
||||
Batch: Batch{BatchNo: "ACCURACY-DIRECTION", BatchType: tc.batchType}, Operation: operation, ApnName: "CMIOT",
|
||||
Details: []*Detail{{ID: 1, ICCID: card.ICCID, Authorized: true, Card: card}, {ID: 2, ICCID: card.ICCID, Authorized: true, Card: card}},
|
||||
}
|
||||
repo := &accuracyDurableRepository{accuracyRepository: &accuracyRepository{task: task, saved: map[int64]Outcome{}}}
|
||||
if err := NewExecutor(repo, &accuracyNoUpstream{t: t}).Execute(context.Background(), task.Batch.BatchNo); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(repo.prepared) != 1 || len(repo.prepared[0].DetailIDs) != 1 || repo.prepared[0].DetailIDs[0] != 1 {
|
||||
t.Fatalf("同ICCID未只保留首个有效行: %+v", repo.prepared)
|
||||
}
|
||||
if operation.TargetStatus != tc.target || repo.prepared[0].Request.ReqData.OperType != tc.operType {
|
||||
t.Fatalf("批次%d开停方向错误: operation=%+v request=%+v", tc.batchType, operation, repo.prepared[0].Request)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccuracyEmptyBatchKeepsValidationReason(t *testing.T) {
|
||||
for _, batchType := range []int{107, 109, 110} {
|
||||
t.Run(fmt.Sprint(batchType), func(t *testing.T) {
|
||||
operation, err := OperationByBatchType(batchType)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
task := &Task{Batch: Batch{BatchNo: "ACCURACY-EMPTY", BatchType: batchType}, Operation: operation}
|
||||
repo := &accuracyRepository{task: task}
|
||||
if err := NewExecutor(repo, &accuracyNoUpstream{t: t}).Execute(context.Background(), task.Batch.BatchNo); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !repo.validationSaved || !repo.finalized || task.ValidationOK || task.FailureReason == "" {
|
||||
t.Fatalf("空明细校验失败原因缺失: task=%+v saved=%v finalized=%v", task, repo.validationSaved, repo.finalized)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type accuracyRepository struct {
|
||||
task *Task
|
||||
saved map[int64]Outcome
|
||||
validationSaved bool
|
||||
finalized bool
|
||||
}
|
||||
|
||||
func (r *accuracyRepository) Load(context.Context, string) (*Task, error) { return r.task, nil }
|
||||
func (r *accuracyRepository) SaveValidationFailure(context.Context, *Task) error {
|
||||
r.validationSaved = true
|
||||
return nil
|
||||
}
|
||||
func (r *accuracyRepository) SaveResult(_ context.Context, _ *Task, detail *Detail, outcome Outcome) (Outcome, error) {
|
||||
r.saved[detail.ID] = outcome
|
||||
return outcome, nil
|
||||
}
|
||||
func (r *accuracyRepository) Finalize(context.Context, *Task) error {
|
||||
r.finalized = true
|
||||
return nil
|
||||
}
|
||||
|
||||
type accuracyDurableRepository struct {
|
||||
*accuracyRepository
|
||||
SubBatchRepository
|
||||
prepared []SubBatch
|
||||
}
|
||||
|
||||
func (r *accuracyDurableRepository) ResumeOrPrepare(ctx context.Context, _ string, prepare func(context.Context) error) error {
|
||||
return prepare(ctx)
|
||||
}
|
||||
func (r *accuracyDurableRepository) PrepareSubBatch(_ context.Context, sub SubBatch) error {
|
||||
r.prepared = append(r.prepared, sub)
|
||||
return nil
|
||||
}
|
||||
|
||||
func TestAccuracyCanExecuteProcessingRequiresExistingParent(t *testing.T) {
|
||||
ctx := context.Background()
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
parent bool
|
||||
readErr error
|
||||
wantOK bool
|
||||
wantErr bool
|
||||
}{
|
||||
{name: "旧Processing无凭据不可接管"},
|
||||
{name: "新批次ready0已持久化凭据", parent: true, wantOK: true},
|
||||
{name: "缺少迁移查询失败安全拒绝", readErr: errors.New("no such table: data_operation_parent"), wantErr: true},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
repo := &accuracyParentRepository{accuracyDurableRepository: &accuracyDurableRepository{accuracyRepository: &accuracyRepository{}}, present: tc.parent, readErr: tc.readErr}
|
||||
e := NewExecutor(repo, &accuracyNoUpstream{t: t}).(*executor)
|
||||
ok, err := e.CanExecuteProcessing(ctx, &entity.BatchMain{BatchNo: " APN-EXISTING "})
|
||||
if ok != tc.wantOK || (err != nil) != tc.wantErr || repo.lookups != 1 || repo.batchNo != "APN-EXISTING" {
|
||||
t.Fatalf("parent gate ok=%v err=%v lookups=%d batchNo=%q", ok, err, repo.lookups, repo.batchNo)
|
||||
}
|
||||
})
|
||||
}
|
||||
base := &accuracyRepository{}
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
repo Repository
|
||||
upstream Upstream
|
||||
}{
|
||||
{name: "缺少持久仓储", repo: base, upstream: &accuracyNoUpstream{t: t}},
|
||||
{name: "仅同步上游", repo: &accuracyParentRepository{accuracyDurableRepository: &accuracyDurableRepository{accuracyRepository: base}}, upstream: &accuracySyncOnlyUpstream{}},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
e := NewExecutor(tc.repo, tc.upstream).(*executor)
|
||||
ok, err := e.CanExecuteProcessing(ctx, &entity.BatchMain{BatchNo: "APN-EXISTING"})
|
||||
if ok || err != nil {
|
||||
t.Fatalf("不支持持久异步不得接管Processing: ok=%v err=%v", ok, err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestAccuracyDurableRepositoryWithSyncUpstreamFinalizesValidationFailure(t *testing.T) {
|
||||
for _, batchType := range []int{107, 109, 110} {
|
||||
t.Run(fmt.Sprint(batchType), func(t *testing.T) {
|
||||
operation, err := OperationByBatchType(batchType)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
task := &Task{Batch: Batch{BatchNo: "SYNC-FAILED", BatchType: batchType}, Operation: operation}
|
||||
base := &accuracyRepository{task: task}
|
||||
repo := &accuracyDurableRepository{accuracyRepository: base}
|
||||
if err := NewExecutor(repo, &accuracySyncOnlyUpstream{}).Execute(context.Background(), task.Batch.BatchNo); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !base.validationSaved || !base.finalized || task.FailureReason == "" {
|
||||
t.Fatalf("同步兜底遗漏校验失败父批: saved=%v finalized=%v task=%+v", base.validationSaved, base.finalized, task)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type accuracyParentRepository struct {
|
||||
*accuracyDurableRepository
|
||||
present bool
|
||||
readErr error
|
||||
lookups int
|
||||
batchNo string
|
||||
}
|
||||
|
||||
func (r *accuracyParentRepository) HasDurableParent(_ context.Context, batchNo string) (bool, error) {
|
||||
r.lookups++
|
||||
r.batchNo = batchNo
|
||||
return r.present, r.readErr
|
||||
}
|
||||
|
||||
type accuracySyncOnlyUpstream struct{}
|
||||
|
||||
func (*accuracySyncOnlyUpstream) ChangeSimApnFunction(context.Context, *model.SimCardApnChangeInput) ([]model.SimBaseRes[model.SimCardApnChangeOutputItem], error) {
|
||||
panic("校验失败不得发运营商请求")
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user