chore: 并入 iotlcm-worker-svr 历史到 worker-svr/(原提交 96f0f4e)

This commit is contained in:
MiFaZhan
2026-11-02 13:28:03 +08:00
422 changed files with 80993 additions and 0 deletions
@@ -0,0 +1,252 @@
package monthlyusage
import (
"context"
"errors"
"fmt"
"strings"
"testing"
zsetconsumer "git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/consumer/zset"
"git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/model"
queue "git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/queue/zset"
monthlyusagerepository "git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/repository/monthlyusage"
)
type usageClientStub struct {
output []model.SimBaseRes[model.SimDataUsageItem]
err error
}
func (s *usageClientStub) QuerySimDataUsage(context.Context, *model.SimDataUsageInput) ([]model.SimBaseRes[model.SimDataUsageItem], error) {
return s.output, s.err
}
type usageRepositoryStub struct {
calls int
updates []monthlyusagerepository.UsageResult
err error
}
func (s *usageRepositoryStub) ApplyBatch(_ context.Context, updates []monthlyusagerepository.UsageResult) error {
s.calls++
s.updates = append(s.updates, updates...)
return s.err
}
func TestQueryClassifiesCurrentUsageResults(t *testing.T) {
task := queue.Task{TaskID: "1", ChanKey: "telecom-a", TempKey: telecomUsageTemplate, CardID: 1, ICCID: "89860001"}
tests := []struct {
name string
client *usageClientStub
success bool
errorClass string
retryable bool
blockChannel bool
}{
{
name: "success",
client: &usageClientStub{output: []model.SimBaseRes[model.SimDataUsageItem]{{
SimKey: &model.SimKey{ICCID: task.ICCID},
ResData: model.SimDataUsageItem{DataUsage: 12.5},
}}},
success: true,
},
{
name: "mobile busy is terminal business failure",
client: &usageClientStub{output: []model.SimBaseRes[model.SimDataUsageItem]{{SimKey: &model.SimKey{ICCID: task.ICCID}, Code: 13000, Message: "busy"}}},
errorClass: "business",
},
{
name: "gateway internal failure is retryable",
client: &usageClientStub{output: []model.SimBaseRes[model.SimDataUsageItem]{{SimKey: &model.SimKey{ICCID: task.ICCID}, Code: 9999, Message: "upstream timeout"}}},
errorClass: "network",
retryable: true,
},
{
name: "gateway business error code remains terminal",
client: &usageClientStub{output: []model.SimBaseRes[model.SimDataUsageItem]{{SimKey: &model.SimKey{ICCID: task.ICCID}, Code: 9999, ErrCode: "9999", Message: "invalid request"}}},
errorClass: "business",
},
{
name: "telecom threshold blocks channel via errCode",
client: &usageClientStub{output: []model.SimBaseRes[model.SimDataUsageItem]{{SimKey: &model.SimKey{ICCID: task.ICCID}, Code: 9999, Message: "流量查询失败", ErrCode: "S00004"}}},
errorClass: "channel_limited",
blockChannel: true,
},
{
name: "message text alone does not block channel",
client: &usageClientStub{output: []model.SimBaseRes[model.SimDataUsageItem]{{SimKey: &model.SimKey{ICCID: task.ICCID}, Code: 13000, Message: "carrier S00004 mentioned in text"}}},
errorClass: "business",
},
{
name: "network failure is terminal for run",
client: &usageClientStub{err: errors.New("connection reset")},
errorClass: "network",
retryable: true,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
handler, err := NewHandler(test.client, &usageRepositoryStub{})
if err != nil {
t.Fatal(err)
}
result := handler.Query(context.Background(), task)
if result.Success != test.success || result.ErrorClass != test.errorClass || result.Retryable != test.retryable || result.BlockChannel != test.blockChannel {
t.Fatalf("result = %+v", result)
}
})
}
}
func TestMobileTemplatesClassifyBusyForRoundRetry(t *testing.T) {
for template := range mobileUsageTemplates {
for code := mobileBusyFirstCode; code <= mobileBusyLastCode; code++ {
t.Run(fmt.Sprintf("%s/code-%d", template, code), func(t *testing.T) {
handler, err := NewHandler(&usageClientStub{output: []model.SimBaseRes[model.SimDataUsageItem]{{
SimKey: &model.SimKey{ICCID: "89860001"}, Code: code, Message: "system busy",
}}}, &usageRepositoryStub{})
if err != nil {
t.Fatal(err)
}
result := handler.Query(context.Background(), queue.Task{TaskID: "busy", TempKey: template, ICCID: "89860001"})
if result.ErrorClass != mobileBusyClass || !result.Retryable || result.BlockChannel {
t.Fatalf("template %q code %d result = %+v", template, code, result)
}
})
}
}
}
func TestLanshangSharedTokenTemplatesRetryAcrossRounds(t *testing.T) {
for _, template := range []string{"lanshang_8000", "lanshang_8003"} {
for _, code := range []int{13000, 13001} {
t.Run(fmt.Sprintf("%s/code-%d", template, code), func(t *testing.T) {
errorClass, retryable, blockChannel := classifyUsageFailure(template, code, "")
if errorClass != mobileBusyClass || !retryable || blockChannel {
t.Fatalf("template %q code %d = (%q, %t, %t)", template, code, errorClass, retryable, blockChannel)
}
})
}
}
}
func TestMobileBusyRangeBoundariesAndPermanentCodes(t *testing.T) {
template := "sim_onelink_kedang_v1"
tests := []struct {
name string
code int
errCode string
mobileBusy bool
retryable bool
}{
{name: "13007 is last busy code", code: 13007, mobileBusy: true, retryable: true},
{name: "13008 is outside busy range", code: 13008},
{name: "12021 token expiry is retryable but not busy", code: 12021, retryable: true},
{name: "11011 permanent failure is not busy", code: 11011},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
errorClass, retryable, _ := classifyUsageFailure(template, test.code, test.errCode)
if test.mobileBusy {
if errorClass != mobileBusyClass || !retryable {
t.Fatalf("code %d = (%q, %t)", test.code, errorClass, retryable)
}
return
}
if errorClass == mobileBusyClass {
t.Fatalf("code %d = (%q, %t)", test.code, errorClass, retryable)
}
if retryable != test.retryable {
t.Fatalf("code %d retryable = %t, want %t (errorClass=%q)", test.code, retryable, test.retryable, errorClass)
}
})
}
}
func TestTelecomTemplateIsNeverMobileBusy(t *testing.T) {
for code := mobileBusyFirstCode; code <= mobileBusyLastCode; code++ {
errorClass, retryable, blockChannel := classifyUsageFailure(telecomUsageTemplate, code, "")
if errorClass == mobileBusyClass || retryable || blockChannel {
t.Fatalf("telecom code %d = (%q, %t, %t)", code, errorClass, retryable, blockChannel)
}
}
}
func TestUnknownTemplateDefaultsToGenericNotMobile(t *testing.T) {
if carrier := usageCarrierForTemplate("sim_dx_v2_future"); carrier != "generic" {
t.Fatalf("unknown template carrier = %q", carrier)
}
errorClass, retryable, _ := classifyUsageFailure("sim_dx_v2_future", 13000, "")
if errorClass == mobileBusyClass || retryable {
t.Fatalf("unknown template 13000 = (%q, %t)", errorClass, retryable)
}
}
func TestTelecomV3ChannelLimitClassification(t *testing.T) {
errorClass, retryable, blockChannel := classifyUsageFailure(" SIM_DX_V3 ", 9999, "S00004")
if errorClass != "channel_limited" || retryable || !blockChannel {
t.Fatalf("v3 channel limit = (%q, %t, %t)", errorClass, retryable, blockChannel)
}
}
func TestNonTelecomS00004DoesNotBlockChannel(t *testing.T) {
handler, err := NewHandler(&usageClientStub{output: []model.SimBaseRes[model.SimDataUsageItem]{{
SimKey: &model.SimKey{ICCID: "89860001"}, Code: 9999, ErrCode: errCodeTelecomChannelLimited,
}}}, &usageRepositoryStub{})
if err != nil {
t.Fatal(err)
}
result := handler.Query(context.Background(), queue.Task{TaskID: "mobile", TempKey: "sim_yd_v5", ICCID: "89860001"})
if result.ErrorClass != "business" || result.BlockChannel {
t.Fatalf("result = %+v", result)
}
}
func TestApplyWritesSuccessfulResultsInOneBatch(t *testing.T) {
repository := &usageRepositoryStub{}
handler, err := NewHandler(&usageClientStub{}, repository)
if err != nil {
t.Fatal(err)
}
results := []zsetconsumer.Result{
{Task: queue.Task{CardID: 1}, Success: true, UsageMB: 1.5},
{Task: queue.Task{CardID: 2}, ErrorClass: "business", Code: 13000},
{Task: queue.Task{CardID: 3}, Success: true, UsageMB: 3.5},
}
if err = handler.Apply(context.Background(), results); err != nil {
t.Fatal(err)
}
if repository.calls != 1 || len(repository.updates) != 2 {
t.Fatalf("repository calls = %d, updates = %+v", repository.calls, repository.updates)
}
if repository.updates[0].CardID != 1 || repository.updates[1].CardID != 3 {
t.Fatalf("updates = %+v", repository.updates)
}
}
func TestQueryPreservesNetworkFailureMessage(t *testing.T) {
handler, err := NewHandler(&usageClientStub{err: errors.New("connection refused")}, &usageRepositoryStub{})
if err != nil {
t.Fatal(err)
}
result := handler.Query(context.Background(), queue.Task{ICCID: "89860001"})
if result.ErrorClass != "network" || !strings.Contains(result.Message, "connection refused") {
t.Fatalf("result = %+v", result)
}
}
func TestQuerySkipsIccidCheckWhenTaskHasNoIccid(t *testing.T) {
handler, err := NewHandler(&usageClientStub{output: []model.SimBaseRes[model.SimDataUsageItem]{{
SimKey: &model.SimKey{ICCID: ""},
ResData: model.SimDataUsageItem{DataUsage: 7.5},
}}}, &usageRepositoryStub{})
if err != nil {
t.Fatal(err)
}
result := handler.Query(context.Background(), queue.Task{TaskID: "1", ChanKey: "telecom-a", CardID: 1, MSISDN: "14900000001"})
if !result.Success || result.UsageMB != 7.5 {
t.Fatalf("result = %+v", result)
}
}