chore: 并入 iotlcm-worker-svr 历史到 worker-svr/(原提交 96f0f4e)
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