254 lines
10 KiB
Go
254 lines
10 KiB
Go
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
|
|
}
|