package app import ( "testing" "git.5iot.com/iotlcm-public/iotlcm-app/internal/consts" "git.5iot.com/iotlcm-public/iotlcm-app/internal/model/entity" "github.com/gogf/gf/v2/os/gtime" ) func TestAppLifecycleShouldPublishWorkerIncludesRestart(t *testing.T) { for _, code := range []string{"stop", "start", "restart"} { operation, err := appCardLifecycleOperationByCode(code) if err != nil { t.Fatalf("operation %s: %v", code, err) } if !appLifecycleShouldPublishWorker(operation) { t.Fatalf("%s should publish worker batch", code) } } if appLifecycleShouldPublishWorker(appCardLifecycleOperation{BatchType: -1}) { t.Fatal("unsupported batch type should not publish worker batch") } } func TestAppLifecycleFailReasonForResponseHidesInternalSQL(t *testing.T) { reason := "save data service detail 238726 result: UPDATE `t_sim_card_info` SET `traffic_cutoff_status`=null: Error 1048 (23000)" got := appLifecycleFailReasonForResponse("failed", reason) want := "后台执行失败,请稍后重试;如持续失败,请联系管理员并提供批次号" if got != want { t.Fatalf("appLifecycleFailReasonForResponse() = %q, want %q", got, want) } } func TestAppLifecycleFailReasonForResponseKeepsBusinessReason(t *testing.T) { const reason = "当前卡片状态不允许停用数据服务" if got := appLifecycleFailReasonForResponse("failed", reason); got != reason { t.Fatalf("appLifecycleFailReasonForResponse() = %q, want %q", got, reason) } } func TestAppLifecycleBatchInfoMatchesWorkerRestartProtocol(t *testing.T) { operation, err := appCardLifecycleOperationByCode("restart") if err != nil { t.Fatal(err) } info := appLifecycleBatchInfo(operation, []*appCardLifecycleItem{testLifecycleItem()}, "RST20260901094936", "manual", gtime.NewFromStr("2026-09-01 09:49:36")) data := info.Map() if info.Get("operation").String() != "restart" { t.Fatalf("operation = %q", info.Get("operation").String()) } if info.Get("stopOperType").String() != consts.SimCardStatusOperTypeStop { t.Fatalf("stopOperType = %q", info.Get("stopOperType").String()) } if info.Get("resumeOperType").String() != consts.SimCardStatusOperTypeStart { t.Fatalf("resumeOperType = %q", info.Get("resumeOperType").String()) } if info.Get("resumeDelaySeconds").Int() != appLifecycleRestartDelaySeconds { t.Fatalf("resumeDelaySeconds = %d", info.Get("resumeDelaySeconds").Int()) } if info.Get("restart.phase").String() != appLifecycleRestartPhaseCreated { t.Fatalf("restart.phase = %q", info.Get("restart.phase").String()) } if _, ok := data["operType"]; ok { t.Fatal("restart batch should not contain single-stage operType") } if _, ok := data["onelinkMock"]; ok { t.Fatal("worker batch info should not contain onelinkMock") } if _, ok := data["localMock"]; ok { t.Fatal("worker batch info should not contain localMock") } } func TestAppLifecycleBatchInfoUsesOperTypeForStopAndStart(t *testing.T) { tests := []struct { code string want string }{ {code: "stop", want: consts.SimCardStatusOperTypeStop}, {code: "start", want: consts.SimCardStatusOperTypeStart}, } for _, tt := range tests { operation, err := appCardLifecycleOperationByCode(tt.code) if err != nil { t.Fatalf("operation %s: %v", tt.code, err) } info := appLifecycleBatchInfo(operation, []*appCardLifecycleItem{testLifecycleItem()}, "BATCH", "", gtime.Now()) if got := info.Get("operType").String(); got != tt.want { t.Fatalf("%s operType = %q, want %q", tt.code, got, tt.want) } } } func TestAppLifecycleBatchInfoMatchesWorkerDataServiceProtocol(t *testing.T) { tests := []struct { code string batchType int operType int }{ {code: "dataStart", batchType: consts.BatchTypeDataStart, operType: consts.SimCardDataOperTypeStart}, {code: "dataStop", batchType: consts.BatchTypeDataStop, operType: consts.SimCardDataOperTypeStop}, } for _, tt := range tests { operation, err := appCardLifecycleOperationByCode(tt.code) if err != nil { t.Fatalf("operation %s: %v", tt.code, err) } if operation.BatchType != tt.batchType { t.Fatalf("%s batch type = %d, want %d", tt.code, operation.BatchType, tt.batchType) } if !appLifecycleShouldPublishWorker(operation) { t.Fatalf("%s should publish worker batch", tt.code) } info := appLifecycleBatchInfo(operation, []*appCardLifecycleItem{testLifecycleItem()}, "BATCH", "", gtime.Now()) if got := info.Get("operType").Int(); got != tt.operType { t.Fatalf("%s operType = %d, want %d", tt.code, got, tt.operType) } if got := info.Get("apnName").String(); got != consts.SimCardDataApnName { t.Fatalf("%s apnName = %q", tt.code, got) } } } func TestAppLifecyclePendingDetailSnapshotDoesNotMarkWorkerBatchAsMock(t *testing.T) { operation, err := appCardLifecycleOperationByCode("restart") if err != nil { t.Fatal(err) } snapshot := appLifecyclePendingDetailSnapshot(operation, testLifecycleItem(), "RST20260901094936") data := snapshot.Map() if _, ok := data["onelinkMock"]; ok { t.Fatal("worker detail snapshot should not contain onelinkMock") } if _, ok := data["localMock"]; ok { t.Fatal("worker detail snapshot should not contain localMock") } } func testLifecycleItem() *appCardLifecycleItem { card := &appCardQueryRow{ TSimCardInfo: entity.TSimCardInfo{ Id: 1, Iccid: "89860400000000000001", Msisdn: "1064900000001", Imsi: "460040000000001", ChanKey: "onelink", BossCardStatus: consts.V3ApiSimBossCardStatusActivated, }, } return &appCardLifecycleItem{ Card: card, RowNo: 2, StatusBefore: card.BossCardStatus, } }