sync: 同步 v1.0.4 开源代码并脱敏
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user