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),属既有缺陷,本仓未引入亦未修复。
This commit is contained in:
@@ -0,0 +1,124 @@
|
||||
package zset
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/testutil"
|
||||
)
|
||||
|
||||
func newCoveredRun(t *testing.T, prefix, run string, count int) (*Store, context.Context) {
|
||||
t.Helper()
|
||||
store, err := New(testutil.Redis(t), prefix)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
ctx := context.Background()
|
||||
if err := store.CreateRun(ctx, RunMeta{RunID: run}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
tasks := make([]Task, count)
|
||||
for i := range tasks {
|
||||
tasks[i] = Task{RunID: run, TaskID: fmt.Sprintf("%s:%d", run, i), CardID: int64(i + 1), ChanKey: "channel", TempKey: "sim_yd_v5", ICCID: fmt.Sprintf("card-%d", i)}
|
||||
}
|
||||
if n, err := store.EnqueueRunBatch(ctx, run, "channel", tasks, time.Now()); err != nil || n != count {
|
||||
t.Fatalf("enqueue %d: %v", n, err)
|
||||
}
|
||||
if err := store.MarkProducerDone(ctx, run); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return store, ctx
|
||||
}
|
||||
|
||||
// TestCoveredOutcomeStaysOutOfFailurePool pins the queue-side contract of a
|
||||
// covered result: the task leaves the round like a success, the run counts it
|
||||
// as completed work, and nothing is written to the failure hash or failedCount.
|
||||
// Code 14000 ("carrier has no record of this card") reaches the queue through
|
||||
// this path, so a regression here restores the 4900-entry failure pool seen in
|
||||
// production on 2026-10-20.
|
||||
func TestCoveredOutcomeStaysOutOfFailurePool(t *testing.T) {
|
||||
store, ctx := newCoveredRun(t, "covered:", "run", 3)
|
||||
now := time.Now()
|
||||
claims, err := store.ClaimRun(ctx, "run", "channel", now, now.Add(time.Minute), 3)
|
||||
if err != nil || len(claims) != 3 {
|
||||
t.Fatalf("claim: %d %v", len(claims), err)
|
||||
}
|
||||
outcomes := []TaskOutcome{
|
||||
{Success: true},
|
||||
{Covered: true, ErrorClass: "no_record", Code: 14000, Message: "系统无记录,请检查输入参数是否正确"},
|
||||
{Covered: true, ErrorClass: "data_invalid", Message: "carrier returned no imei value"},
|
||||
}
|
||||
if n, err := store.FinalizeRunTasks(ctx, claims, outcomes); err != nil || n != 3 {
|
||||
t.Fatalf("finalize: %d %v", n, err)
|
||||
}
|
||||
|
||||
failureKey, _ := store.FailedKey("run")
|
||||
size, err := store.RedisClient().Do(ctx, "HLEN", failureKey)
|
||||
if err != nil || size.Int64() != 0 {
|
||||
t.Fatalf("failure pool entries = %d err=%v, want 0", size.Int64(), err)
|
||||
}
|
||||
meta, err := store.GetRunMeta(ctx, "run")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if meta.FailedCount != 0 || meta.SuccessCount != 3 {
|
||||
t.Fatalf("counters: success=%d failed=%d, want 3/0", meta.SuccessCount, meta.FailedCount)
|
||||
}
|
||||
// The round was still advanced: covered tasks must free the capacity they
|
||||
// claimed, otherwise the run can never complete.
|
||||
if meta.ActiveCount != 0 || meta.InflightCount != 0 || meta.NextRoundCount != 0 {
|
||||
t.Fatalf("queue capacity not released: %+v", meta)
|
||||
}
|
||||
|
||||
stateKey, _ := store.TaskStateKey("run")
|
||||
state, err := store.RedisClient().Do(ctx, "HGET", stateKey, "run:1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
values := state.MapStrAny()
|
||||
if values["status"] != taskStatusCovered || fmt.Sprint(values["lastCode"]) != "14000" {
|
||||
t.Fatalf("covered task state = %v", values)
|
||||
}
|
||||
|
||||
completed, err := store.CompleteRunIfIdle(ctx, "run")
|
||||
if err != nil || !completed {
|
||||
t.Fatalf("complete: %t %v", completed, err)
|
||||
}
|
||||
if meta, err = store.GetRunMeta(ctx, "run"); err != nil || meta.Status != "completed" {
|
||||
t.Fatalf("status = %q err=%v, want completed (no failures)", meta.Status, err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestFailureOutcomeStillCountsAsFailure is the guard rail for the test above:
|
||||
// only Covered is exempt. An ordinary terminal failure keeps the historical
|
||||
// failure-pool behaviour.
|
||||
func TestFailureOutcomeStillCountsAsFailure(t *testing.T) {
|
||||
store, ctx := newCoveredRun(t, "covered-fail:", "run", 2)
|
||||
now := time.Now()
|
||||
claims, err := store.ClaimRun(ctx, "run", "channel", now, now.Add(time.Minute), 2)
|
||||
if err != nil || len(claims) != 2 {
|
||||
t.Fatalf("claim: %d %v", len(claims), err)
|
||||
}
|
||||
outcomes := []TaskOutcome{
|
||||
{Covered: true, ErrorClass: "no_record", Code: 14000},
|
||||
{ErrorClass: "business", FailureType: "business", Code: 11011, Message: "invalid card"},
|
||||
}
|
||||
if n, err := store.FinalizeRunTasks(ctx, claims, outcomes); err != nil || n != 2 {
|
||||
t.Fatalf("finalize: %d %v", n, err)
|
||||
}
|
||||
failureKey, _ := store.FailedKey("run")
|
||||
size, err := store.RedisClient().Do(ctx, "HLEN", failureKey)
|
||||
if err != nil || size.Int64() != 1 {
|
||||
t.Fatalf("failure pool entries = %d err=%v, want 1", size.Int64(), err)
|
||||
}
|
||||
exists, err := store.RedisClient().Do(ctx, "HEXISTS", failureKey, "run:1")
|
||||
if err != nil || exists.Int() != 1 {
|
||||
t.Fatalf("the business failure is missing from the pool: %v %v", exists, err)
|
||||
}
|
||||
meta, err := store.GetRunMeta(ctx, "run")
|
||||
if err != nil || meta.FailedCount != 1 || meta.SuccessCount != 1 {
|
||||
t.Fatalf("counters: %+v err=%v", meta, err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user