Files
iotlcm/worker-svr/internal/queue/zset/run_covered_test.go
T

125 lines
4.6 KiB
Go

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)
}
}