sync: 同步 v1.0.4 并脱敏配置与镜像

This commit is contained in:
MiFaZhan
2026-10-29 14:36:49 +08:00
parent 743e8e6e6e
commit 2206a393f7
516 changed files with 18119 additions and 2253 deletions
@@ -0,0 +1,384 @@
package renew
import (
"context"
"path/filepath"
"reflect"
"testing"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/consts"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/batchinput/text"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/entity"
"github.com/xuri/excelize/v2"
)
func TestExcelTemplateHeaders(t *testing.T) {
t.Parallel()
expected := []string{
"ICCID",
"MSISDN",
"IMSI",
"续费年限",
"是否走线下",
}
if !reflect.DeepEqual(ExcelTemplateHeaders(), expected) {
t.Fatalf("unexpected headers: %#v", ExcelTemplateHeaders())
}
}
func TestRenewDurationValueToMonths(t *testing.T) {
t.Parallel()
tests := []struct {
name string
unit string
value int
want int
}{
{name: "one month", unit: consts.RenewDurationUnitMonth, value: 1, want: 1},
{name: "one year", unit: consts.RenewDurationUnitYear, value: 1, want: 12},
{name: "unknown unit defaults to year", unit: "unknown", value: 2, want: 24},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
t.Parallel()
if got := consts.DurationValueToMonths(test.unit, test.value); got != test.want {
t.Fatalf("DurationValueToMonths(%q, %d) = %d, want %d", test.unit, test.value, got, test.want)
}
})
}
}
func TestRenewDisplayDurationAndAmount(t *testing.T) {
t.Parallel()
yearSnapshot := &model.TSimCardRenewPriceSnapshot{
RenewDurationUnit: consts.RenewDurationUnitYear,
RenewUnitPrice: 10,
}
yearParam := &model.TSimCardRenewBatchSubmitParam{DurationYears: 1}
if got := renewDisplayDurationValue(yearParam); got != 1 {
t.Fatalf("renewDisplayDurationValue(year) = %d, want 1", got)
}
if got := renewActualDurationMonths(yearSnapshot, renewDisplayDurationValue(yearParam)); got != 12 {
t.Fatalf("renewActualDurationMonths(year) = %d, want 12", got)
}
yearAmount, err := resolveRenewAmount(yearSnapshot, yearParam, 2)
if err != nil {
t.Fatalf("resolveRenewAmount(year) failed: %v", err)
}
if yearAmount != 20 {
t.Fatalf("resolveRenewAmount(year) = %.2f, want 20.00", yearAmount)
}
monthSnapshot := &model.TSimCardRenewPriceSnapshot{
RenewDurationUnit: consts.RenewDurationUnitMonth,
RenewUnitPrice: 10,
}
monthParam := &model.TSimCardRenewBatchSubmitParam{DurationMonths: 1}
if got := renewActualDurationMonths(monthSnapshot, renewDisplayDurationValue(monthParam)); got != 12 {
t.Fatalf("renewActualDurationMonths(month snapshot) = %d, want 12", got)
}
actualMonthParam := &model.TSimCardRenewBatchSubmitParam{DurationMonths: 12}
if got := renewDisplayDurationValue(actualMonthParam); got != 1 {
t.Fatalf("renewDisplayDurationValue(actual months legacy) = %d, want 1", got)
}
monthAmount, err := resolveRenewAmount(monthSnapshot, monthParam, 2)
if err != nil {
t.Fatalf("resolveRenewAmount(month) failed: %v", err)
}
if monthAmount != 20 {
t.Fatalf("resolveRenewAmount(month) = %.2f, want 20.00", monthAmount)
}
}
func TestApplyExecutionOptionsKeepsDisplayDurationValue(t *testing.T) {
t.Parallel()
in := &model.TSimCardRenewBatchSubmitParam{}
applyExecutionOptions(in, &executionOptions{
DurationYears: 2,
DurationMonths: 2,
PaymentMode: renewPaymentModeBalance,
})
if in.DurationYears != 2 || in.DurationMonths != 2 {
t.Fatalf("duration should keep display value: %#v", in)
}
if got := renewDisplayDurationValue(in); got != 2 {
t.Fatalf("renewDisplayDurationValue = %d, want 2", got)
}
}
func TestRenewDeptAncestorsContain(t *testing.T) {
t.Parallel()
tests := []struct {
name string
ancestors string
deptID int64
want bool
}{
{name: "contains direct ancestor", ancestors: "0,100,200", deptID: 100, want: true},
{name: "contains with spaces", ancestors: "0, 100 ,200", deptID: 100, want: true},
{name: "does not match partial id", ancestors: "0,1001,200", deptID: 100, want: false},
{name: "empty ancestors", ancestors: "", deptID: 100, want: false},
{name: "invalid dept", ancestors: "0,100,200", deptID: 0, want: false},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
t.Parallel()
if got := renewDeptAncestorsContain(test.ancestors, test.deptID); got != test.want {
t.Fatalf("renewDeptAncestorsContain(%q, %d) = %v, want %v", test.ancestors, test.deptID, got, test.want)
}
})
}
}
func TestParseExcelFileRowsAndOptions(t *testing.T) {
t.Parallel()
filePath := filepath.Join(t.TempDir(), "renew.xlsx")
f := excelize.NewFile()
headers := ExcelTemplateHeaders()
if err := f.SetSheetRow("Sheet1", "A1", &headers); err != nil {
t.Fatalf("set headers failed: %v", err)
}
row := []any{
"8986000000000000001",
"13800008000",
"460001234567890",
"1",
"是",
}
if err := f.SetSheetRow("Sheet1", "A2", &row); err != nil {
t.Fatalf("set row failed: %v", err)
}
if err := f.SaveAs(filePath); err != nil {
t.Fatalf("save excel failed: %v", err)
}
_ = f.Close()
rows, options, err := ParseExcelFileRows(context.Background(), filePath)
if err != nil {
t.Fatalf("parse excel failed: %v", err)
}
if len(rows) != 1 {
t.Fatalf("unexpected row count: %d", len(rows))
}
if rows[0].Reason != "" {
t.Fatalf("unexpected row reason: %s", rows[0].Reason)
}
if rows[0].Msisdn != "13800008000" {
t.Fatalf("unexpected msisdn: %s", rows[0].Msisdn)
}
if rows[0].Imsi != "460001234567890" {
t.Fatalf("unexpected imsi: %s", rows[0].Imsi)
}
if options == nil {
t.Fatal("options should not be nil")
}
if options.DurationYears != 1 {
t.Fatalf("unexpected durationYears: %d", options.DurationYears)
}
if options.PaymentMode != "offline" {
t.Fatalf("unexpected paymentMode: %s", options.PaymentMode)
}
submitParam := options.ToBatchSubmitParam()
if submitParam.DurationYears != 1 || submitParam.DurationMonths != 12 {
t.Fatalf("unexpected submit duration fields: %#v", submitParam)
}
if submitParam.PaymentMode != "offline" {
t.Fatalf("unexpected submit paymentMode: %#v", submitParam)
}
if !submitParam.OfflineContractCompleted {
t.Fatalf("excel submit should mark offline contract as confirmed: %#v", submitParam)
}
if submitParam.SourceBatchNo != "" || submitParam.BuyerNote != "" || submitParam.AdminNote != "" || submitParam.Remark != "" {
t.Fatalf("excel submit param should not carry removed fields: %#v", submitParam)
}
confirmParam := options.ToBatchConfirmParam("RB20260706001")
if confirmParam.BatchNo != "RB20260706001" {
t.Fatalf("unexpected batchNo: %s", confirmParam.BatchNo)
}
if confirmParam.DurationYears != 1 || confirmParam.DurationMonths != 12 {
t.Fatalf("unexpected confirm duration fields: %#v", confirmParam)
}
if confirmParam.PaymentMode != "offline" {
t.Fatalf("unexpected confirm paymentMode: %#v", confirmParam)
}
}
func TestParseExcelFileRowsRejectsMultipleSheetsWithData(t *testing.T) {
t.Parallel()
filePath := filepath.Join(t.TempDir(), "renew-multi.xlsx")
f := excelize.NewFile()
headers := ExcelTemplateHeaders()
if err := f.SetSheetRow("Sheet1", "A1", &headers); err != nil {
t.Fatalf("set headers failed: %v", err)
}
row := []any{
"8986000000000000001",
"13800008000",
"460001234567890",
"1",
"是",
}
if err := f.SetSheetRow("Sheet1", "A2", &row); err != nil {
t.Fatalf("set row failed: %v", err)
}
_, err := f.NewSheet("Sheet2")
if err != nil {
t.Fatalf("new sheet failed: %v", err)
}
if err := f.SetSheetRow("Sheet2", "A1", &headers); err != nil {
t.Fatalf("set sheet2 headers failed: %v", err)
}
if err := f.SetSheetRow("Sheet2", "A2", &row); err != nil {
t.Fatalf("set sheet2 row failed: %v", err)
}
if err := f.SaveAs(filePath); err != nil {
t.Fatalf("save excel failed: %v", err)
}
_ = f.Close()
if _, _, err := ParseExcelFileRows(context.Background(), filePath); err == nil {
t.Fatal("expected multiple-sheet excel to be rejected")
}
}
func TestPrepareExcelProcessorUsesPrepareBatchType(t *testing.T) {
t.Parallel()
processor := NewPrepareExcelProcessor()
cfg := processor.Config()
if cfg.BatchType != consts.BatchTypePrepare {
t.Fatalf("prepare excel batch type = %d, want %d", cfg.BatchType, consts.BatchTypePrepare)
}
if cfg.BatchNoPrefix != consts.BatchNoPrefixRenew {
t.Fatalf("prepare excel batch prefix = %q, want %q", cfg.BatchNoPrefix, consts.BatchNoPrefixRenew)
}
}
func TestParseRenewInputLineSupportsSingleAndTripleFields(t *testing.T) {
t.Parallel()
tests := []struct {
name string
line text.FieldLine
wantCode string
wantIccid string
wantMsisdn string
wantImsi string
wantReason string
}{
{
name: "single value",
line: text.FieldLine{RowNo: 2, Fields: []string{"89860401786597927028"}},
wantCode: "89860401786597927028",
wantIccid: "89860401786597927028",
},
{
name: "triple values",
line: text.FieldLine{RowNo: 3, Fields: []string{"89860401786597927028", "14100007028", "460046597927028"}},
wantCode: "89860401786597927028",
wantIccid: "89860401786597927028",
wantMsisdn: "14100007028",
wantImsi: "460046597927028",
},
{
name: "invalid format",
line: text.FieldLine{RowNo: 4, Fields: []string{"a", "b"}},
wantReason: "格式错误",
},
}
for _, test := range tests {
test := test
t.Run(test.name, func(t *testing.T) {
t.Parallel()
row := parseRenewInputLine(test.line)
if row.InputCode != test.wantCode {
t.Fatalf("input code = %q, want %q", row.InputCode, test.wantCode)
}
if row.Iccid != test.wantIccid {
t.Fatalf("iccid = %q, want %q", row.Iccid, test.wantIccid)
}
if row.Msisdn != test.wantMsisdn {
t.Fatalf("msisdn = %q, want %q", row.Msisdn, test.wantMsisdn)
}
if row.Imsi != test.wantImsi {
t.Fatalf("imsi = %q, want %q", row.Imsi, test.wantImsi)
}
if test.wantReason != "" && row.Reason == "" {
t.Fatal("expected row reason")
}
})
}
}
func TestRenewIdentityResolverResolvesAnyCardIdentity(t *testing.T) {
t.Parallel()
resolver := newRenewIdentityResolverFromCards([]*entity.TSimCardInfo{
{
Id: 1,
Iccid: "89860401786597927028",
Msisdn: "14100007028",
Imsi: "460046597927028",
},
{
Id: 2,
Iccid: "89860401786597927029",
Msisdn: "14100007029",
Imsi: "460046597927029",
},
})
row := &Row{RowNo: 2, InputCode: "14100007028", Msisdn: "14100007028"}
resolver.resolveRow(row)
if row.Reason != "" {
t.Fatalf("unexpected reason: %s", row.Reason)
}
if row.Iccid != "89860401786597927028" || row.Msisdn != "14100007028" || row.Imsi != "460046597927028" {
t.Fatalf("unexpected resolved row: %#v", row)
}
row = &Row{RowNo: 3, InputCode: "460046597927029", Imsi: "460046597927029"}
resolver.resolveRow(row)
if row.Reason != "" {
t.Fatalf("unexpected reason for imsi row: %s", row.Reason)
}
if row.Iccid != "89860401786597927029" || row.Msisdn != "14100007029" || row.Imsi != "460046597927029" {
t.Fatalf("unexpected imsi resolved row: %#v", row)
}
}
func TestRenewIdentityResolverRejectsAmbiguousOrMismatchedIdentity(t *testing.T) {
t.Parallel()
resolver := newRenewIdentityResolverFromCards([]*entity.TSimCardInfo{
{Id: 1, Iccid: "89860401786597927028", Msisdn: "14100007028", Imsi: "460046597927028"},
{Id: 2, Iccid: "89860401786597927029", Msisdn: "89860401786597927028", Imsi: "460046597927029"},
})
ambiguous := &Row{RowNo: 2, InputCode: "89860401786597927028", Iccid: "89860401786597927028"}
resolver.resolveRow(ambiguous)
if ambiguous.Reason == "" {
t.Fatal("expected ambiguous reason")
}
mismatched := &Row{RowNo: 3, InputCode: "89860401786597927028", Iccid: "89860401786597927028", Msisdn: "00000000000"}
resolver.resolveRow(mismatched)
if mismatched.Reason == "" {
t.Fatal("expected mismatch reason")
}
}
@@ -0,0 +1,187 @@
package renew
import (
"context"
"testing"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/consts"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/batchimport"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/entity"
"github.com/gogf/gf/v2/os/gtime"
)
func TestBuildBatchInfoOmitsRemovedEmptyFields(t *testing.T) {
t.Parallel()
info := NewProcessor().BuildBatchInfo(context.Background(), batchimport.SubmitRequest{
BizParams: &model.TSimCardRenewBatchSubmitParam{
DurationMonths: 12,
GiftDurationMonths: 2,
Amount: "100.00",
OfflineContractCompleted: true,
},
ExecutionResult: &CompleteOutput{
Quantity: 1,
PackageCode: "PKG001",
OrderSn: "ORDER001",
},
}, &batchimport.Result{}, nil, nil, 0)
data := info.Map()
for _, key := range []string{"sourceBatchNo", "buyerNote", "adminNote", "remark"} {
if _, ok := data[key]; ok {
t.Fatalf("batch info should omit %s when empty: %#v", key, data)
}
}
if data["durationMonths"] != 12 {
t.Fatalf("unexpected durationMonths: %#v", data["durationMonths"])
}
if data["giftDurationMonths"] != 2 {
t.Fatalf("unexpected giftDurationMonths: %#v", data["giftDurationMonths"])
}
if data["offlineContractCompleted"] != true {
t.Fatalf("unexpected offlineContractCompleted: %#v", data["offlineContractCompleted"])
}
}
func TestCurrentPackagePeriodKey(t *testing.T) {
t.Parallel()
bind := &entity.TSimCardBindPkg{
EffectiveTime: gtime.NewFromStr("2026-01-01"),
ExpectedExpireTime: gtime.NewFromStr("2026-12-31"),
}
if got, want := currentPackagePeriodKey(bind), "2026-01-01|2026-12-31"; got != want {
t.Fatalf("currentPackagePeriodKey() = %q, want %q", got, want)
}
if got := currentPackagePeriodKey(&entity.TSimCardBindPkg{}); got != "" {
t.Fatalf("currentPackagePeriodKey() for empty bind = %q, want empty", got)
}
}
func TestRenewBaseExpireTimePrefersLaterCardExpire(t *testing.T) {
t.Parallel()
card := &entity.TSimCardInfo{
FlowPkgEndAt: gtime.NewFromStr("2026-10-31 00:00:00"),
}
bind := &entity.TSimCardBindPkg{
ExpectedExpireTime: gtime.NewFromStr("2026-08-31 00:00:00"),
}
if got, want := renewBaseExpireTime(card, bind).Format("Y-m-d"), "2026-10-31"; got != want {
t.Fatalf("renewBaseExpireTime() = %q, want %q", got, want)
}
}
func TestRenewBeginDateFromExpireUsesExpireDate(t *testing.T) {
t.Parallel()
beginAt := renewBeginDateFromExpire(gtime.NewFromStr("2026-08-31 23:59:59"))
if beginAt == nil {
t.Fatal("renew begin date is nil")
}
if got, want := beginAt.Format("Y-m-d H:i:s"), "2026-08-31 23:59:59"; got != want {
t.Fatalf("renewBeginDateFromExpire() = %q, want %q", got, want)
}
}
func TestValidateRenewRowRejectsExpireAfterSixMonths(t *testing.T) {
t.Parallel()
card := &entity.TSimCardInfo{
CurrentPackageCode: "PKG001",
PackageStatus: consts.PackageStatusEffective,
}
bind := &entity.TSimCardBindPkg{
PackageCode: "PKG001",
ExpectedExpireTime: gtime.Now().AddDate(0, 6, 1),
EffectiveTime: gtime.Now().AddDate(-1, 0, 0),
EffectiveStatus: consts.EffectiveStatusEffective,
}
if got, want := validateRenewRow("", batchimport.OperatorContext{}, card, bind), "距离到期超过6个月,不能续费"; got != want {
t.Fatalf("validateRenewRow() = %q, want %q", got, want)
}
}
func TestValidateRenewRowAllowsExpireWithinSixMonths(t *testing.T) {
t.Parallel()
card := &entity.TSimCardInfo{
CurrentPackageCode: "PKG001",
PackageStatus: consts.PackageStatusEffective,
}
bind := &entity.TSimCardBindPkg{
PackageCode: "PKG001",
ExpectedExpireTime: gtime.Now().AddDate(0, 6, 0),
EffectiveTime: gtime.Now().AddDate(-1, 0, 0),
EffectiveStatus: consts.EffectiveStatusEffective,
}
if got := validateRenewRow("", batchimport.OperatorContext{}, card, bind); got != "" {
t.Fatalf("validateRenewRow() = %q, want empty", got)
}
}
func TestValidateRenewRowRejectsStoppedCard(t *testing.T) {
t.Parallel()
card := &entity.TSimCardInfo{
BossCardStatus: consts.V3ApiSimBossCardStatusStopped,
CurrentPackageCode: "PKG001",
PackageStatus: consts.PackageStatusEffective,
}
bind := &entity.TSimCardBindPkg{
PackageCode: "PKG001",
}
if got, want := validateRenewRow("", batchimport.OperatorContext{}, card, bind), "ICCID当前为停机状态,不能续费"; got != want {
t.Fatalf("validateRenewRow() = %q, want %q", got, want)
}
}
func TestRenewPrepareSubmitParamUsesPrepareSourceBatchNo(t *testing.T) {
t.Parallel()
req := &batchimport.SubmitRequest{
BizParams: &model.TSimCardRenewBatchPrepareParam{SourceBatchNo: " OUT20260807001 "},
}
got := renewPrepareSubmitParam(req)
if got.SourceBatchNo != "OUT20260807001" {
t.Fatalf("SourceBatchNo = %q, want OUT20260807001", got.SourceBatchNo)
}
}
func TestRenewPrepareSubmitParamFallsBackToRequestSourceBatchNo(t *testing.T) {
t.Parallel()
req := &batchimport.SubmitRequest{
BizParams: &model.TSimCardRenewBatchSubmitParam{},
SourceBatchNo: " OUT20260807002 ",
}
got := renewPrepareSubmitParam(req)
if got.SourceBatchNo != "OUT20260807002" {
t.Fatalf("SourceBatchNo = %q, want OUT20260807002", got.SourceBatchNo)
}
}
func TestPrepareProcessorValidateRowsDoesNotBlockSubmit(t *testing.T) {
t.Parallel()
rows := []batchimport.Row{
&Row{RowNo: 1, Iccid: "8986001"},
&Row{RowNo: 2, Iccid: "8986001"},
}
if err := NewPrepareProcessor().ValidateRows(context.Background(), rows); err != nil {
t.Fatalf("ValidateRows() error = %v", err)
}
for _, item := range rows {
row := item.(*Row)
if row.Reason != "" {
t.Fatalf("row %d reason = %q, want empty before worker prepare", row.RowNo, row.Reason)
}
}
}