96b1edff45
iotlcm-upstream-chan:运营商上游通道适配服务(统一 /sim/* 协议)。 - 内联原私有依赖:yunze-public → internal/pub/(10 包 18 文件), yunze-pb/pb → internal/pb/(9 文件);Consul 注册改用官方 gogf/gf/contrib/registry/consul/v2(API 兼容) - Go module:yz-svr → git.5iot.com/iotlcm-public/iotlcm-upstream-chan - 修复 mingqian 包测试:实现重构后 `adapter.operations` 字段已移除, 测试断言按当前实现语义重写(不改产品行为) - 凭据与内部标识脱敏:MySQL/Redis/Consul 凭据、私网与生产 IP、 内网域名、真实手机号、注释里的真实 token/transid - 移除内部资产:CI 触发器、生产通道种子 SQL、真实凭据测试资源文档 - Dockerfile 改用公共基础镜像,移除写死的 registry 凭据 - 补 Apache-2.0 LICENSE,重写 README,LF 行尾 + .gitattributes
206 lines
7.6 KiB
Go
206 lines
7.6 KiB
Go
package upstreamimpl
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"sync/atomic"
|
|
"testing"
|
|
"time"
|
|
|
|
"git.5iot.com/iotlcm-public/iotlcm-upstream-chan/internal/logic/upstreamimpl/dx"
|
|
"git.5iot.com/iotlcm-public/iotlcm-upstream-chan/internal/model"
|
|
)
|
|
|
|
func TestDXBatchDayAvailable(t *testing.T) {
|
|
now := time.Date(2026, 9, 20, 12, 0, 0, 0, dxBatchLocation)
|
|
tests := map[string]bool{
|
|
"20260913": true,
|
|
"20260919": true,
|
|
"20260912": false,
|
|
"20260920": false,
|
|
"20260921": false,
|
|
}
|
|
for queryDate, want := range tests {
|
|
if got := dxBatchDayAvailable(queryDate, now); got != want {
|
|
t.Fatalf("dxBatchDayAvailable(%q) = %v, want %v", queryDate, got, want)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestDXBatchMonthAvailable(t *testing.T) {
|
|
now := time.Date(2026, 9, 20, 12, 0, 0, 0, dxBatchLocation)
|
|
if !dxBatchMonthAvailable("202609", now) {
|
|
t.Fatal("current month should use telecom batch API")
|
|
}
|
|
if dxBatchMonthAvailable("202608", now) || dxBatchMonthAvailable("invalid", now) {
|
|
t.Fatal("historical or invalid month must not use telecom current-month batch API")
|
|
}
|
|
}
|
|
|
|
func TestQuerySimDataUsageMonthlyUsesBatchForCurrentMonth(t *testing.T) {
|
|
var calls atomic.Int32
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
calls.Add(1)
|
|
if r.URL.Path != "/api/v1/batchQry/batchQryFlowByMonth" {
|
|
t.Fatalf("path = %q", r.URL.Path)
|
|
}
|
|
var request dxBatchRequest
|
|
if err := json.NewDecoder(r.Body).Decode(&request); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(request.AccessNumbers) != 2 || request.CustNumber != "cust-1" {
|
|
t.Fatalf("request = %+v", request)
|
|
}
|
|
_, _ = w.Write([]byte(`{"code":0,"msg":"查询成功","data":[{"accessNumber":"138****8001","result":"1","unit":"MB","flowAmount":"12.5"},{"accessNumber":"138****8002","result":"1","unit":"MB","flowAmount":"7.5"}]}`))
|
|
}))
|
|
defer server.Close()
|
|
|
|
sim := &sSimDX{client: dx.NewClient(dx.Config{BaseURL: server.URL, AppKey: "test", AppSecret: "secret"}), custNumber: "cust-1"}
|
|
queryMonth := time.Now().In(dxBatchLocation).Format("200601")
|
|
output, err := sim.QuerySimDataUsageMonthly(context.Background(), &model.SimDataUsageMonthlyInput{
|
|
SimKeys: []*model.SimKey{{MSISDN: "138****8001"}, {MSISDN: "138****8002"}},
|
|
ReqData: model.SimDataUsageMonthlyInputItem{QueryDate: queryMonth},
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if calls.Load() != 1 || len(output) != 2 || output[0].ResData.DataAmount != 12.5 || output[1].ResData.DataAmount != 7.5 {
|
|
t.Fatalf("calls=%d output=%+v", calls.Load(), output)
|
|
}
|
|
}
|
|
|
|
func TestQuerySimDataUsageDailyLegacyFallbackIsBounded(t *testing.T) {
|
|
var active atomic.Int32
|
|
var maximum atomic.Int32
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
current := active.Add(1)
|
|
defer active.Add(-1)
|
|
for {
|
|
previous := maximum.Load()
|
|
if current <= previous || maximum.CompareAndSwap(previous, current) {
|
|
break
|
|
}
|
|
}
|
|
time.Sleep(30 * time.Millisecond)
|
|
w.Header().Set("Content-Type", "application/xml")
|
|
_, _ = w.Write([]byte(`<root><NEW_DATA_TICKET_QRsp><IRESULT>0</IRESULT><SMSG>OK</SMSG><TOTAL_BYTES_CNT>1MB</TOTAL_BYTES_CNT></NEW_DATA_TICKET_QRsp></root>`))
|
|
}))
|
|
defer server.Close()
|
|
|
|
sim := &sSimDX{client: dx.NewClient(dx.Config{BaseURL: server.URL, AppKey: "test", AppSecret: "secret", Timeout: time.Second})}
|
|
keys := make([]*model.SimKey, 12)
|
|
for index := range keys {
|
|
keys[index] = &model.SimKey{MSISDN: "138****8003"}
|
|
}
|
|
output, err := sim.QuerySimDataUsageDaily(context.Background(), &model.SimDataUsageDailyInput{
|
|
SimKeys: keys,
|
|
ReqData: model.SimDataUsageDailyInputItem{QueryDate: "20260101"},
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(output) != len(keys) {
|
|
t.Fatalf("results = %d, want %d", len(output), len(keys))
|
|
}
|
|
if maximum.Load() > dxLegacyUsageConcurrency || maximum.Load() < 2 {
|
|
t.Fatalf("maximum concurrency = %d, want 2..%d", maximum.Load(), dxLegacyUsageConcurrency)
|
|
}
|
|
}
|
|
|
|
func TestQuerySimDataUsageDailyRejectsMalformedXML(t *testing.T) {
|
|
cases := []struct {
|
|
name string
|
|
body string
|
|
}{
|
|
{name: "missing business node", body: `<root><ERROR>denied</ERROR></root>`},
|
|
{name: "missing result", body: `<root><NEW_DATA_TICKET_QRsp><TOTAL_BYTES_CNT>12MB</TOTAL_BYTES_CNT></NEW_DATA_TICKET_QRsp></root>`},
|
|
{name: "missing amount", body: `<root><NEW_DATA_TICKET_QRsp><IRESULT>0</IRESULT></NEW_DATA_TICKET_QRsp></root>`},
|
|
{name: "invalid amount", body: `<root><NEW_DATA_TICKET_QRsp><IRESULT>0</IRESULT><TOTAL_BYTES_CNT>invalidMB</TOTAL_BYTES_CNT></NEW_DATA_TICKET_QRsp></root>`},
|
|
{name: "non numeric result", body: `<root><NEW_DATA_TICKET_QRsp><IRESULT>ERROR</IRESULT></NEW_DATA_TICKET_QRsp></root>`},
|
|
}
|
|
for _, item := range cases {
|
|
t.Run(item.name, func(t *testing.T) {
|
|
server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
|
writer.Header().Set("Content-Type", "application/xml")
|
|
_, _ = writer.Write([]byte(item.body))
|
|
}))
|
|
defer server.Close()
|
|
|
|
sim := &sSimDX{client: dx.NewClient(dx.Config{BaseURL: server.URL, AppKey: "test", AppSecret: "test"})}
|
|
output, err := sim.QuerySimDataUsageDaily(context.Background(), &model.SimDataUsageDailyInput{
|
|
SimKeys: []*model.SimKey{{ICCID: "audit-card", MSISDN: "138****8001"}},
|
|
ReqData: model.SimDataUsageDailyInputItem{QueryDate: "20260101"},
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(output) != 1 || output[0].Code == 0 {
|
|
t.Fatalf("malformed response accepted: %+v", output)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestQuerySimDataUsageDailyBatchFailureFallsBackToLegacy(t *testing.T) {
|
|
var batchCalls atomic.Int32
|
|
var singleCalls atomic.Int32
|
|
server := httptest.NewServer(http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
|
|
if request.URL.Path == "/api/v1/batchQry/batchQryFlowByDate" {
|
|
batchCalls.Add(1)
|
|
writer.Header().Set("Content-Type", "application/json")
|
|
_, _ = writer.Write([]byte(`{"code":403,"msg":"denied"}`))
|
|
return
|
|
}
|
|
singleCalls.Add(1)
|
|
writer.Header().Set("Content-Type", "application/xml")
|
|
_, _ = writer.Write([]byte(`<root><NEW_DATA_TICKET_QRsp><IRESULT>0</IRESULT><TOTAL_BYTES_CNT>12MB</TOTAL_BYTES_CNT></NEW_DATA_TICKET_QRsp></root>`))
|
|
}))
|
|
defer server.Close()
|
|
|
|
sim := &sSimDX{client: dx.NewClient(dx.Config{BaseURL: server.URL, AppKey: "test", AppSecret: "test"}), custNumber: "audit-customer"}
|
|
queryDate := time.Now().In(dxBatchLocation).AddDate(0, 0, -1).Format("20060102")
|
|
output, err := sim.QuerySimDataUsageDaily(context.Background(), &model.SimDataUsageDailyInput{
|
|
SimKeys: []*model.SimKey{{MSISDN: "138****8001"}},
|
|
ReqData: model.SimDataUsageDailyInputItem{QueryDate: queryDate},
|
|
})
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if batchCalls.Load() != 1 || singleCalls.Load() != 1 {
|
|
t.Fatalf("batch calls=%d, single calls=%d, want 1 each", batchCalls.Load(), singleCalls.Load())
|
|
}
|
|
if len(output) != 1 || output[0].Code != 0 || output[0].ResData.DataAmount != 12 {
|
|
t.Fatalf("unexpected fallback output: %+v", output)
|
|
}
|
|
}
|
|
|
|
func TestParseDXDataUsage(t *testing.T) {
|
|
cases := []struct {
|
|
value string
|
|
want float64
|
|
invalid bool
|
|
}{
|
|
{value: "0MB", want: 0},
|
|
{value: "12.5MB", want: 12.5},
|
|
{value: " 1 gb ", want: 1024},
|
|
{value: "1024KB", want: 1},
|
|
{value: "12", want: 12},
|
|
{value: "", invalid: true},
|
|
{value: "invalidMB", invalid: true},
|
|
{value: "-1MB", invalid: true},
|
|
{value: "NaNMB", invalid: true},
|
|
{value: "InfMB", invalid: true},
|
|
{value: "1e308GB", invalid: true},
|
|
}
|
|
for _, item := range cases {
|
|
t.Run(item.value, func(t *testing.T) {
|
|
amount, err := parseDXDataUsage(item.value)
|
|
if (err != nil) != item.invalid || (!item.invalid && amount != item.want) {
|
|
t.Fatalf("parseDXDataUsage(%q) = %v, %v, want %v, invalid=%v", item.value, amount, err, item.want, item.invalid)
|
|
}
|
|
})
|
|
}
|
|
}
|