Files
iotlcm/internal/service/allocate/executor_test.go
T
MiFaZhan 2bec257289 chore: 初始开源发布
iotlcm-worker-svr:异步执行中枢(RabbitMQ 消费者 + 全量同步编排)。

- Go module:iotlcm-worker-svr → git.5iot.com/iotlcm-public/iotlcm-worker-svr(214 文件)
- 凭据与内部标识脱敏:MySQL/Redis 凭据、私网与生产 IP、内网域名、
  库名 xiujie_admin → iotlcm、哨兵名占位
- 159 处真实手机号 → 合法 11 位编造号(保留号段与后 4 位,维持互异,
  不破坏「校验 MSISDN 格式」等测试断言)
- 移除内部资产:CI 触发器、Jenkins 触发测试文档、预演门禁与镜像
  digest 记录
- Dockerfile 改用公共基础镜像(golang:1.25-alpine → alpine:3.23),
  移除写死的 git 凭据
- README 重写(架构/全量同步模型/配置/已知失败),补 Apache-2.0
  LICENSE,LF 行尾 + .gitattributes

已知:3 个测试包在源仓同一提交上同样失败(client/upstream、
service/allocate、service/inbound),属既有缺陷,本仓未引入亦未修复。
2026-10-22 16:37:56 +08:00

198 lines
6.4 KiB
Go

package allocate
import (
"context"
"errors"
"strings"
"testing"
"time"
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/os/gtime"
"git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/consts"
"git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/model/entity"
)
func TestAllocateProcessingRetryAfterHasLargeBatchDefault(t *testing.T) {
if got := allocateProcessingRetryAfter(context.Background()); got < 30*time.Minute {
t.Fatalf("allocateProcessingRetryAfter() = %s, want at least 30m", got)
}
}
func TestIsAllocateParentRequiresTypeAndRole(t *testing.T) {
parent := &entity.BatchMain{
BatchType: consts.BatchTypeAllocate,
BatchInfo: gjson.New(`{"jobRole":"allocate_parent"}`),
}
if !isAllocateParent(parent) {
t.Fatal("expected allocate parent metadata to be recognized")
}
parent.SourceBatchNo = "ALC-CHILD"
if isAllocateParent(parent) {
t.Fatal("child batch must not be recognized as allocate parent")
}
}
func TestIsAllocateDeadlock(t *testing.T) {
if !isAllocateDeadlock(errors.New("Error 1213 (40001): Deadlock found when trying to get lock")) {
t.Fatal("expected MySQL deadlock to be retryable")
}
if isAllocateDeadlock(errors.New("validation failed")) {
t.Fatal("validation errors must not be treated as deadlocks")
}
}
func TestResolveAllocateParentStartTime(t *testing.T) {
parentStart := gtime.New("2026-09-29 12:59:00")
childStart := gtime.New("2026-09-29 12:59:32")
fallback := gtime.New("2026-09-29 13:00:21")
if got := resolveAllocateParentStartTime(parentStart, childStart, fallback); got != parentStart {
t.Fatalf("parent start time = %v, want existing parent time %v", got, parentStart)
}
if got := resolveAllocateParentStartTime(nil, childStart, fallback); got != childStart {
t.Fatalf("parent start time = %v, want earliest child time %v", got, childStart)
}
if got := resolveAllocateParentStartTime(nil, nil, fallback); got != fallback {
t.Fatalf("parent start time = %v, want fallback time %v", got, fallback)
}
}
func TestAllocateBatchSizesRemainBounded(t *testing.T) {
if allocateCardQueryBatchSize <= 0 || allocateCardQueryBatchSize > 1000 {
t.Fatalf("allocateCardQueryBatchSize = %d", allocateCardQueryBatchSize)
}
if allocateCardUpdateBatchSize <= 0 || allocateCardUpdateBatchSize > 1000 {
t.Fatalf("allocateCardUpdateBatchSize = %d", allocateCardUpdateBatchSize)
}
if allocateDetailUpdateBatchSize <= 0 || allocateDetailUpdateBatchSize > 1000 {
t.Fatalf("allocateDetailUpdateBatchSize = %d", allocateDetailUpdateBatchSize)
}
}
func TestBuildDetailUpdateStatementUsesBoundParameters(t *testing.T) {
task := &Task{Batch: Batch{BatchNo: "ALC-1", CreatedBy: 7}}
details := []*Detail{
{ID: 11, DetailStatus: consts.BatchDetailStatusPending, Reason: "unsafe ' reason"},
{ID: 12, DetailStatus: consts.BatchDetailStatusRunning, Reason: "second"},
}
statement, args := buildDetailUpdateStatement(task, details, gtime.Now())
if strings.Contains(statement, details[0].Reason) {
t.Fatal("detail reason was interpolated into SQL")
}
if !strings.Contains(statement, "CASE") || !strings.Contains(statement, "IN (?,?)") {
t.Fatalf("unexpected statement: %s", statement)
}
if len(args) != len(details)*7+9 {
t.Fatalf("argument count = %d, want %d", len(args), len(details)*7+9)
}
}
func TestNormalizeAtomicParentResultRejectsPartialSuccess(t *testing.T) {
task := &Task{Details: []*Detail{
{ICCID: "8986000000000000001"},
{ICCID: "8986000000000000002", Reason: "目标客户不存在"},
}}
successCount, failCount := normalizeAtomicParentResult(task)
if successCount != 0 || failCount != len(task.Details) {
t.Fatalf("normalizeAtomicParentResult() = %d, %d; want 0, %d", successCount, failCount, len(task.Details))
}
for _, detail := range task.Details {
if detail.Success() {
t.Fatalf("detail %s remained successful", detail.ICCID)
}
}
status, result, _ := parentBatchResult(successCount, failCount)
if status != consts.BatchAsyncStatusFailed || result != consts.BatchRunResultFailed {
t.Fatalf("parentBatchResult() = %d, %d; want failed, failed", status, result)
}
}
func TestValidateDetailAllowsCurrentHolderReallocation(t *testing.T) {
task := &Task{
Batch: Batch{
CreatedDept: 100,
},
TargetsByID: map[int64]*Target{
200: {DeptID: 200, ParentID: 100},
},
}
detail := &Detail{
ICCID: "8986000000000000000",
TargetDeptID: 200,
Card: &Card{
ICCID: "8986000000000000000",
DeptID: 100,
AllocationBatchNo: "ALC202608050001",
PackageStatus: consts.PackageStatusEffective,
},
}
validateDetail(task, detail)
if detail.Reason != "" {
t.Fatalf("expected current holder reallocation to pass, got %q", detail.Reason)
}
}
func TestValidateDetailRejectsAncestorReallocationAfterCardMoved(t *testing.T) {
task := &Task{
Batch: Batch{
CreatedDept: 100,
},
TargetsByID: map[int64]*Target{
300: {DeptID: 300, ParentID: 100},
},
}
detail := &Detail{
ICCID: "8986000000000000000",
TargetDeptID: 300,
Card: &Card{
ICCID: "8986000000000000000",
DeptID: 200,
AllocationBatchNo: "ALC202608050001",
PackageStatus: consts.PackageStatusEffective,
},
}
validateDetail(task, detail)
if detail.Reason == "" {
t.Fatal("expected ancestor reallocation to be rejected after card moved")
}
}
func TestValidateTaskMarksWholeBatchFailed(t *testing.T) {
task := &Task{
Details: []*Detail{
{ICCID: "8986000000000000001"},
{ICCID: "8986000000000000002", Reason: "目标客户不存在"},
},
}
validateTask(task)
for _, detail := range task.Details {
if detail.Success() {
t.Fatalf("detail %s remained successful after atomic failure", detail.ICCID)
}
}
if task.FailureReason == "" {
t.Fatal("expected batch failure reason")
}
status, result := batchResult(countResult(task))
if status != consts.BatchAsyncStatusFailed || result != consts.BatchRunResultFailed {
t.Fatalf("batchResult() = %d, %d; want failed, failed", status, result)
}
}
func TestValidateDetailRejectsChangedCardICCID(t *testing.T) {
task := &Task{Batch: Batch{CreatedDept: 100}, TargetsByID: map[int64]*Target{200: {DeptID: 200, ParentID: 100}}}
detail := &Detail{ICCID: "8986000000000000001", TargetDeptID: 200, Card: &Card{ICCID: "8986000000000000002", DeptID: 100, PackageStatus: consts.PackageStatusEffective}}
validateDetail(task, detail)
if detail.Reason == "" {
t.Fatal("expected changed card ICCID to be rejected")
}
}