sync: 同步 v1.0.4 开源代码并脱敏
This commit is contained in:
@@ -0,0 +1,163 @@
|
||||
package upstreamapi
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/go-sql-driver/mysql"
|
||||
_ "github.com/gogf/gf/contrib/drivers/mysql/v2"
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"gopkg.in/yaml.v3"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-upstream-chan/internal/model"
|
||||
)
|
||||
|
||||
// 仅显式授权的loopback专用实例可写;身份核验始终先于DDL和DML。
|
||||
func TestApnOperationMySQLIntegration(t *testing.T) {
|
||||
if os.Getenv("APN_MYSQL_INTEGRATION") != "1" {
|
||||
t.Skip("未启用本机专用MySQL集成;不会连接默认业务数据库")
|
||||
}
|
||||
const port = "3306"
|
||||
const name = "iotdb_apn_chan_test_20261015"
|
||||
const expectedUUID = "4fa545af-ab36-11f1-be02-040e3c9de361"
|
||||
const expectedDir = "D:/MySQL/MySQL Server 8.0/Data/"
|
||||
if value := os.Getenv("APN_MYSQL_PORT"); value != "" && value != port {
|
||||
t.Fatal("只允许已核实的127.0.0.1:3306")
|
||||
}
|
||||
if value := os.Getenv("APN_MYSQL_DATABASE"); value != "" && value != name {
|
||||
t.Fatal("只允许指定chan测试库")
|
||||
}
|
||||
configPath := filepath.Join("..", "..", "..", "..", "iotlcm-sms", "deploy", "config.yaml")
|
||||
configBody, err := os.ReadFile(configPath)
|
||||
if err != nil {
|
||||
t.Fatal("无法读取本机已有配置")
|
||||
}
|
||||
var config struct {
|
||||
SceneDbDsn string `yaml:"sceneDbDsn"`
|
||||
}
|
||||
if err := yaml.Unmarshal(configBody, &config); err != nil {
|
||||
t.Fatal("本机已有配置解析失败")
|
||||
}
|
||||
dsn, err := mysql.ParseDSN(config.SceneDbDsn)
|
||||
if err != nil || dsn.Net != "tcp" || (dsn.Addr != "127.0.0.1:3306" && dsn.Addr != "localhost:3306") {
|
||||
t.Fatal("已有配置不是已授权本机连接,禁止连接")
|
||||
}
|
||||
db, err := gdb.New(gdb.ConfigNode{Type: "mysql", Host: "127.0.0.1", Port: port, User: dsn.User, Pass: dsn.Passwd, Name: name})
|
||||
if err != nil {
|
||||
t.Fatal("建立本机测试连接失败")
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
identity, err := db.GetOne(ctx, "SELECT @@hostname AS hostname,@@port AS port,@@datadir AS datadir,@@server_uuid AS server_uuid,DATABASE() AS dbname")
|
||||
if err != nil {
|
||||
t.Fatal("读取本机实例身份失败")
|
||||
}
|
||||
normalize := func(s string) string {
|
||||
return strings.ToLower(strings.TrimRight(filepath.ToSlash(strings.ReplaceAll(s, "\\", "/")), "/"))
|
||||
}
|
||||
if identity["port"].String() != port || identity["server_uuid"].String() != expectedUUID || identity["dbname"].String() != name || normalize(identity["datadir"].String()) != normalize(expectedDir) {
|
||||
t.Fatalf("隔离实例身份不符,禁止写入:%v", identity)
|
||||
}
|
||||
t.Logf("本机专用库核验通过 hostname=%s port=%s uuid=%s datadir=%s database=%s", identity["hostname"], identity["port"], identity["server_uuid"], identity["datadir"], identity["dbname"])
|
||||
exists, err := db.GetValue(ctx, "SELECT COUNT(*) FROM information_schema.tables WHERE table_schema=DATABASE() AND table_name='t_apn_operation'")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if exists.Int() == 0 {
|
||||
_, err = db.Exec(ctx, "CREATE TABLE t_apn_operation (operation_id varchar(128) NOT NULL,chan_key varchar(128) NOT NULL,request_hash char(64) NOT NULL,response_json longtext NOT NULL,created_at datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),updated_at datetime(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),PRIMARY KEY(operation_id,chan_key)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
migration, err := os.ReadFile(filepath.Join("..", "..", "..", "..", "iotlcm-database", "SQL", "20261015_apn_operation_chan.sql"))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
master, err := db.Master()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
connection, err := master.Conn(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer connection.Close()
|
||||
for repeat := 0; repeat < 2; repeat++ {
|
||||
for _, statement := range strings.Split(string(migration), ";") {
|
||||
if strings.TrimSpace(statement) == "" {
|
||||
continue
|
||||
}
|
||||
if _, err := connection.ExecContext(ctx, statement); err != nil {
|
||||
t.Fatalf("迁移执行失败:%v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
store := sqlApnOperationStore{db: db}
|
||||
id := fmt.Sprintf("integration-%d", time.Now().UnixNano())
|
||||
channel := "isolated-test-channel"
|
||||
hash := strings.Repeat("a", 64)
|
||||
claimed, err := store.Claim(ctx, id, channel, hash)
|
||||
if err != nil || !claimed {
|
||||
t.Fatalf("Claim失败:%v %v", claimed, err)
|
||||
}
|
||||
t.Cleanup(func() {
|
||||
_, _ = db.Exec(context.Background(), "DELETE FROM t_apn_operation WHERE operation_id=? AND chan_key=?", id, channel)
|
||||
})
|
||||
if claimed, err = store.Claim(ctx, id, channel, hash); err != nil || claimed {
|
||||
t.Fatalf("重复Claim未幂等:%v %v", claimed, err)
|
||||
}
|
||||
if err := store.Save(ctx, id+"-missing", channel, hash, "[]"); err == nil {
|
||||
t.Fatal("不存在的映射保存必须失败")
|
||||
}
|
||||
if err := store.Save(ctx, id, channel, strings.Repeat("b", 64), "[]"); err == nil {
|
||||
t.Fatal("不同摘要不得覆盖")
|
||||
}
|
||||
if claimed, err = store.Claim(ctx, strings.Repeat("x", 129), channel, hash); err == nil || claimed {
|
||||
t.Fatal("非法ID不得被INSERT忽略为成功")
|
||||
}
|
||||
var wg sync.WaitGroup
|
||||
errors := make(chan error, 10)
|
||||
for i := 0; i < 10; i++ {
|
||||
wg.Add(1)
|
||||
go func(i int) {
|
||||
defer wg.Done()
|
||||
key := &model.SimKey{ChanKey: channel, ICCID: fmt.Sprintf("fake-%d", i)}
|
||||
body, _ := json.Marshal(model.SimCardOperateChangeApnOutput{&model.SimBaseRes[model.SimCardOperateChangeApnItem]{SimKey: key, ResData: model.SimCardOperateChangeApnItem{Pending: true, OrderNum: fmt.Sprintf("fake-job-%d", i)}}})
|
||||
errors <- store.Save(ctx, id, channel, hash, string(body))
|
||||
}(i)
|
||||
}
|
||||
wg.Wait()
|
||||
close(errors)
|
||||
for err := range errors {
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
_, saved, err := store.Load(ctx, id, channel)
|
||||
var output model.SimCardOperateChangeApnOutput
|
||||
if err != nil || json.Unmarshal([]byte(saved), &output) != nil || len(output) != 10 {
|
||||
t.Fatalf("并发保存丢失卡映射:%s %v", saved, err)
|
||||
}
|
||||
unknown := &model.SimCardOperateChangeApnInput{SimKeys: []*model.SimKey{output[0].SimKey}}
|
||||
body, _ := json.Marshal(unknownApnOperation(unknown, "模拟checkpoint后错误"))
|
||||
if err := store.Save(ctx, id, channel, hash, string(body)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, merged, err := store.Load(ctx, id, channel)
|
||||
if err != nil || merged != saved {
|
||||
t.Fatalf("已存jobId被unknown覆盖:%s %v", merged, err)
|
||||
}
|
||||
if err := store.Save(ctx, id, channel, hash, "[]"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
row, err := db.GetOne(ctx, "SELECT revision FROM t_apn_operation WHERE operation_id=? AND chan_key=?", id, channel)
|
||||
if err != nil || row["revision"].Int() != 12 {
|
||||
t.Fatalf("每次保存必须影响一行并递增revision:%v %v", row, err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user