package data import ( "testing" "time" "git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/consts" service "git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/service/data" ) func TestSubBatchFrozenOrderNewClaimFirstDoesNotCancelOlderCards(t *testing.T) { repo, ctx := testSubBatchDB(t) for _, q := range []string{ `UPDATE batch_detail SET item_id=CASE id WHEN 2 THEN 'B' ELSE 'A' END,snapshot='{"authorized":true}'`, `UPDATE batch_detail SET batch_type=109 WHERE batch_no='S-2'`, `INSERT INTO t_sim_card_info(id,tenant_id,iccid,data_service_status)VALUES(1,'tenant','A',1),(2,'tenant','B',1)`, } { if _, err := repo.database().Exec(ctx, q); err != nil { t.Fatal(err) } } older := testSub("S-1", "frozen-stop-AB", "a", []int64{1, 2}, []string{"A", "B"}) newer := testSub("S-2", "frozen-open-A", "a", []int64{3}, []string{"A"}) newer.BatchType = 109 newer.Request.ReqData.OperType = "0" for _, sub := range []service.SubBatch{older, newer} { if err := repo.PrepareSubBatch(ctx, sub); err != nil { t.Fatal(err) } } if got, err := repo.ClaimSubBatch(ctx, newer.OperationID, "new-first", time.Now()); err != nil || got != nil { t.Fatalf("newer overtook frozen predecessor: %+v %v", got, err) } row, err := repo.database().GetOne(ctx, "SELECT status FROM data_operation_sub_batch WHERE operation_id=?", older.OperationID) if err != nil || row["status"].String() != "prepared" { t.Fatalf("older work cancelled: %v %v", row, err) } first, err := repo.ClaimSubBatch(ctx, older.OperationID, "old-first", time.Now()) if err != nil || first == nil { t.Fatalf("older not claimable: %+v %v", first, err) } if err := repo.CompleteSubBatch(ctx, *first, []service.Outcome{{Success: true}, {Success: true}}); err != nil { t.Fatal(err) } rows, err := repo.database().GetAll(ctx, "SELECT iccid,data_service_status FROM t_sim_card_info ORDER BY iccid") if err != nil || len(rows) != 2 || rows[0]["data_service_status"].Int() != 0 || rows[1]["data_service_status"].Int() != 0 { t.Fatalf("stop A/B not persisted: %v %v", rows, err) } if _, err := repo.database().Exec(ctx, "UPDATE data_operation_sub_batch SET next_check_at=NULL WHERE operation_id=?", newer.OperationID); err != nil { t.Fatal(err) } second, err := repo.ClaimSubBatch(ctx, newer.OperationID, "new-after-old", time.Now()) if err != nil || second == nil { t.Fatalf("newer not released after old terminal: %+v %v", second, err) } if err := repo.CompleteSubBatch(ctx, *second, []service.Outcome{{Success: true}}); err != nil { t.Fatal(err) } rows, err = repo.database().GetAll(ctx, "SELECT iccid,data_service_status FROM t_sim_card_info ORDER BY iccid") if err != nil || rows[0]["data_service_status"].Int() != 1 || rows[1]["data_service_status"].Int() != 0 { t.Fatalf("reverse should reopen only A, keep B stopped: %v %v", rows, err) } details, err := repo.database().GetAll(ctx, "SELECT detail_status FROM batch_detail ORDER BY id") if err != nil || len(details) != 3 { t.Fatalf("details: %v %v", details, err) } for _, d := range details { if d["detail_status"].Int() != consts.BatchDetailStatusSuccess { t.Fatalf("card was omitted or cancelled: %v", details) } } } func TestSubBatchParentOrderSurvivesReverseFreezeOrder(t *testing.T) { repo, ctx := testSubBatchDB(t) older := testSub("S-1", "parent-old-freeze-second", "a", []int64{1, 2}, []string{"A", "B"}) newer := testSub("S-2", "parent-new-freeze-first", "a", []int64{3}, []string{"A"}) for _, b := range []service.SubBatch{newer, older} { if err := repo.PrepareSubBatch(ctx, b); err != nil { t.Fatal(err) } } oldStored, _ := repo.GetSubBatch(ctx, older.OperationID) newStored, _ := repo.GetSubBatch(ctx, newer.OperationID) if oldStored.ID <= newStored.ID { t.Fatal("未实际复现父批与冻结ID相反顺序") } if got, err := repo.ClaimSubBatch(ctx, newer.OperationID, "new-first", time.Now()); err != nil || got != nil { t.Fatalf("新批越过已冻结的旧父批: %+v %v", got, err) } if got, err := repo.ClaimSubBatch(ctx, older.OperationID, "old-first", time.Now()); err != nil || got == nil { t.Fatalf("旧停AB应完整可领取: %+v %v", got, err) } n, err := repo.database().Model(claimTable).Ctx(ctx).Where("active_operation_id", older.OperationID).Count() if err != nil || n != 2 { t.Fatalf("旧批不能遗漏B: %d %v", n, err) } } func TestSubBatchPreparedPredecessorWaitHasFiniteUnsubmittedOutcome(t *testing.T) { repo, ctx := testSubBatchDB(t) older := testSub("S-1", "prepared-old", "a", []int64{1}, []string{"A"}) newer := testSub("S-2", "prepared-new", "a", []int64{3}, []string{"A"}) for _, sub := range []service.SubBatch{older, newer} { if err := repo.PrepareSubBatch(ctx, sub); err != nil { t.Fatal(err) } } if _, err := repo.database().Exec(ctx, "UPDATE data_operation_sub_batch SET created_at=?,next_check_at=NULL WHERE operation_id=?", time.Now().Add(-16*time.Minute), newer.OperationID); err != nil { t.Fatal(err) } if got, err := repo.ClaimSubBatch(ctx, newer.OperationID, "expired", time.Now()); err != nil || got != nil { t.Fatalf("expired later intent was submitted: %+v %v", got, err) } row, err := repo.database().GetOne(ctx, "SELECT status,submitted_at,poll_count FROM data_operation_sub_batch WHERE operation_id=?", newer.OperationID) if err != nil || row["status"].String() != "review" || !row["submitted_at"].IsNil() || row["poll_count"].Int() != 0 { t.Fatalf("later intent not finitely closed unsent: %v %v", row, err) } row, err = repo.database().GetOne(ctx, "SELECT status FROM data_operation_sub_batch WHERE operation_id=?", older.OperationID) if err != nil || row["status"].String() != "prepared" { t.Fatalf("older intent cancelled: %v %v", row, err) } }