139 lines
4.7 KiB
Go
139 lines
4.7 KiB
Go
package upstream
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/gogf/gf/v2/frame/g"
|
|
"github.com/gogf/gf/v2/os/gcfg"
|
|
"git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/model"
|
|
)
|
|
|
|
func fullAPNSchema() map[string]map[string]bool {
|
|
schema := make(map[string]map[string]bool, len(apnRequiredSchema))
|
|
for table, columns := range apnRequiredSchema {
|
|
schema[table] = make(map[string]bool, len(columns))
|
|
for _, column := range columns {
|
|
schema[table][column] = true
|
|
}
|
|
}
|
|
return schema
|
|
}
|
|
|
|
// TestValidateAPNSchemaRequiresAllTablesAndColumns 固化依赖判据:
|
|
// 缺表、缺列都必须给出可据此执行迁移的明确原因,而不是笼统的「未就绪」。
|
|
func TestValidateAPNSchemaRequiresAllTablesAndColumns(t *testing.T) {
|
|
if err := validateAPNSchema(fullAPNSchema()); err != nil {
|
|
t.Fatalf("完整结构不应报错:%v", err)
|
|
}
|
|
|
|
missingTable := fullAPNSchema()
|
|
delete(missingTable, "data_operation_card_claim")
|
|
err := validateAPNSchema(missingTable)
|
|
if err == nil || !strings.Contains(err.Error(), "data_operation_card_claim") || !strings.Contains(err.Error(), "缺少数据表") {
|
|
t.Fatalf("缺表未被识别:%v", err)
|
|
}
|
|
|
|
missingColumn := fullAPNSchema()
|
|
delete(missingColumn["t_apn_operation"], "revision")
|
|
err = validateAPNSchema(missingColumn)
|
|
if err == nil || !strings.Contains(err.Error(), "t_apn_operation.revision") || !strings.Contains(err.Error(), "缺少字段") {
|
|
t.Fatalf("缺列未被识别:%v", err)
|
|
}
|
|
|
|
// 表全部缺失时优先报缺表,避免一次报几十个字段干扰排查。
|
|
err = validateAPNSchema(map[string]map[string]bool{})
|
|
if err == nil || !strings.Contains(err.Error(), "缺少数据表") {
|
|
t.Fatalf("空库应优先报缺表:%v", err)
|
|
}
|
|
}
|
|
|
|
// TestAsyncApnSubmitBlockedWhenDependencyNotReady 验证依赖未就绪时在发请求前就拒绝,
|
|
// 并把错误标记为「未发送」,避免上游误判为已办理。
|
|
func TestAsyncApnSubmitBlockedWhenDependencyNotReady(t *testing.T) {
|
|
oldAdapter := g.Cfg().GetAdapter()
|
|
defer g.Cfg().SetAdapter(oldAdapter)
|
|
oldReady := apnDependencyReady
|
|
defer func() { apnDependencyReady = oldReady }()
|
|
apnDependencyReady = func(ctx context.Context) error {
|
|
return errors.New("缺少数据表 data_operation_card_claim")
|
|
}
|
|
|
|
probes, submits := 0, 0
|
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
if r.URL.Path == "/sim/query/sim-apn-capability" {
|
|
probes++
|
|
return
|
|
}
|
|
submits++
|
|
}))
|
|
defer server.Close()
|
|
adapter, err := gcfg.NewAdapterContent(fmt.Sprintf(`{"upstream":{"proxy":{"address":%q,"timeout":"1s"}}}`, strings.TrimPrefix(server.URL, "http://")))
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
g.Cfg().SetAdapter(adapter)
|
|
|
|
_, err = New().SubmitSimApnFunction(context.Background(), &model.SimCardApnChangeInput{
|
|
SimKeys: []*model.SimKey{{ChanKey: "test", ICCID: "fake-card"}},
|
|
ReqData: model.SimCardApnChangeInputItem{OperationID: "fake-operation"},
|
|
})
|
|
var notSent interface{ APNRequestNotSent() bool }
|
|
if err == nil || !strings.Contains(err.Error(), "依赖未就绪") {
|
|
t.Fatalf("依赖未就绪应拒绝提交:%v", err)
|
|
}
|
|
if !errors.As(err, ¬Sent) || !notSent.APNRequestNotSent() {
|
|
t.Fatalf("依赖拒绝必须标记为未发送:%v", err)
|
|
}
|
|
if probes != 0 || submits != 0 {
|
|
t.Fatalf("依赖未就绪时不应发出任何请求:probes=%d submits=%d", probes, submits)
|
|
}
|
|
}
|
|
|
|
// TestReadinessCacheDoesNotCacheCanceledVerification 覆盖审查发现的场景:
|
|
// 调用方取消导致的校验失败不能进进程级缓存,否则会误挡后续所有提交。
|
|
func TestReadinessCacheDoesNotCacheCanceledVerification(t *testing.T) {
|
|
cache := &apnReadinessCache{ttl: time.Minute}
|
|
calls := 0
|
|
cache.verify = func(ctx context.Context) error {
|
|
calls++
|
|
if calls == 1 {
|
|
return context.Canceled
|
|
}
|
|
return nil
|
|
}
|
|
if err := cache.check(context.Background()); !errors.Is(err, context.Canceled) {
|
|
t.Fatalf("首次应返回取消错误:%v", err)
|
|
}
|
|
if err := cache.check(context.Background()); err != nil {
|
|
t.Fatalf("取消不应被缓存,第二次应重新校验并放行:%v", err)
|
|
}
|
|
if calls != 2 {
|
|
t.Fatalf("取消后应重新校验,实际调用%d次", calls)
|
|
}
|
|
}
|
|
|
|
// TestReadinessCacheReusesResult 保证依赖检查不会每次提交都查库,同时迁移完成后能自动恢复。
|
|
func TestReadinessCacheReusesResult(t *testing.T) {
|
|
cache := &apnReadinessCache{ttl: time.Minute}
|
|
calls := 0
|
|
cache.verify = func(ctx context.Context) error {
|
|
calls++
|
|
return nil
|
|
}
|
|
for i := 0; i < 3; i++ {
|
|
if err := cache.check(context.Background()); err != nil {
|
|
t.Fatalf("第%d次检查报错:%v", i+1, err)
|
|
}
|
|
}
|
|
if calls != 1 {
|
|
t.Fatalf("缓存期内应只查一次库,实际%d次", calls)
|
|
}
|
|
}
|