mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-06-27 00:42:03 +08:00
- 为 MongoDB 结果展示、单元格编辑和行编辑接入类型感知格式化与解析 - 支持 ObjectId、日期、Int32、Int64、Double、Decimal128、UUID 等常见类型保真 - 统一 v1/v2 驱动查询结果的 Extended JSON 输出与 ApplyChanges BSON 恢复 - 补充前端提交链路与后端类型转换回归测试
266 lines
8.8 KiB
Go
266 lines
8.8 KiB
Go
//go:build gonavi_full_drivers || gonavi_mongodb_driver
|
|
|
|
package db
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
|
|
"GoNavi-Wails/internal/connection"
|
|
|
|
"go.mongodb.org/mongo-driver/v2/bson"
|
|
)
|
|
|
|
func TestApplyMongoURI_ExplicitHostDoesNotAdoptURIHosts(t *testing.T) {
|
|
config := connection.ConnectionConfig{
|
|
Host: "10.10.10.10",
|
|
Port: 27017,
|
|
URI: "mongodb://localhost:27017/admin",
|
|
}
|
|
|
|
got := applyMongoURI(config)
|
|
if got.Host != "10.10.10.10" {
|
|
t.Fatalf("expected host to remain explicit, got %q", got.Host)
|
|
}
|
|
if len(got.Hosts) != 0 {
|
|
t.Fatalf("expected hosts to remain empty when explicit host exists, got %v", got.Hosts)
|
|
}
|
|
}
|
|
|
|
func TestApplyMongoURI_ExplicitHostsDoesNotAdoptURIHosts(t *testing.T) {
|
|
config := connection.ConnectionConfig{
|
|
Host: "10.10.10.10",
|
|
Port: 27017,
|
|
Hosts: []string{"10.10.10.10:27017", "10.10.10.11:27017"},
|
|
URI: "mongodb://localhost:27017,localhost:27018/admin?replicaSet=rs0",
|
|
}
|
|
|
|
got := applyMongoURI(config)
|
|
if len(got.Hosts) != 2 || got.Hosts[0] != "10.10.10.10:27017" {
|
|
t.Fatalf("expected explicit hosts to stay untouched, got %v", got.Hosts)
|
|
}
|
|
}
|
|
|
|
func TestMongoURI_MergesConnectionParams(t *testing.T) {
|
|
uri := (&MongoDB{}).getURI(connection.ConnectionConfig{
|
|
Host: "mongo.local",
|
|
Port: 27017,
|
|
Database: "app",
|
|
ConnectionParams: "retryWrites=true&readPreference=secondaryPreferred",
|
|
})
|
|
|
|
if !strings.Contains(uri, "retryWrites=true") {
|
|
t.Fatalf("uri 缺少 retryWrites 参数:%s", uri)
|
|
}
|
|
if !strings.Contains(uri, "readPreference=secondaryPreferred") {
|
|
t.Fatalf("uri 缺少 readPreference 参数:%s", uri)
|
|
}
|
|
}
|
|
|
|
func TestMongoURI_MergesConnectionParamsIntoExistingURI(t *testing.T) {
|
|
uri := (&MongoDB{}).getURI(connection.ConnectionConfig{
|
|
URI: "mongodb://mongo.local:27017/app?authSource=admin",
|
|
ConnectionParams: "retryWrites=true",
|
|
})
|
|
|
|
if !strings.Contains(uri, "authSource=admin") || !strings.Contains(uri, "retryWrites=true") {
|
|
t.Fatalf("uri 未合并已有 URI query 与额外参数:%s", uri)
|
|
}
|
|
}
|
|
|
|
func TestBuildMongoChangeFilter_ConvertsExplicitOIDToObjectID(t *testing.T) {
|
|
const oidHex = "507f1f77bcf86cd799439011"
|
|
|
|
filter := buildMongoChangeFilter(map[string]interface{}{
|
|
"_id": map[string]interface{}{"$oid": oidHex},
|
|
"name": oidHex,
|
|
})
|
|
|
|
gotID, ok := filter["_id"].(bson.ObjectID)
|
|
if !ok {
|
|
t.Fatalf("expected _id to be bson.ObjectID, got %T", filter["_id"])
|
|
}
|
|
if gotID.Hex() != oidHex {
|
|
t.Fatalf("unexpected ObjectID: got=%s want=%s", gotID.Hex(), oidHex)
|
|
}
|
|
if filter["name"] != oidHex {
|
|
t.Fatalf("non-_id 24 hex string should stay string, got %T %v", filter["name"], filter["name"])
|
|
}
|
|
}
|
|
|
|
func TestBuildMongoChangeFilter_LeavesIDHexStringUntouched(t *testing.T) {
|
|
const oidHex = "507f1f77bcf86cd799439011"
|
|
|
|
filter := buildMongoChangeFilter(map[string]interface{}{
|
|
"_id": oidHex,
|
|
})
|
|
|
|
if filter["_id"] != oidHex {
|
|
t.Fatalf("plain _id string should stay string, got %T %v", filter["_id"], filter["_id"])
|
|
}
|
|
}
|
|
|
|
func TestBuildMongoObjectIDLocatorValue_EncodesObjectID(t *testing.T) {
|
|
const oidHex = "507f1f77bcf86cd799439011"
|
|
oid, err := bson.ObjectIDFromHex(oidHex)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
locator := buildMongoObjectIDLocatorValue(oid)
|
|
locatorMap, ok := locator.(bson.M)
|
|
if !ok {
|
|
t.Fatalf("expected locator bson.M, got %T", locator)
|
|
}
|
|
if locatorMap["$oid"] != oidHex {
|
|
t.Fatalf("unexpected locator value: %v", locatorMap["$oid"])
|
|
}
|
|
}
|
|
|
|
func TestCopyMongoChangeDocument_LeavesInsertIDStringUntouched(t *testing.T) {
|
|
const oidHex = "507f1f77bcf86cd799439011"
|
|
|
|
doc := copyMongoChangeDocument(map[string]interface{}{
|
|
"_id": oidHex,
|
|
})
|
|
|
|
if doc["_id"] != oidHex {
|
|
t.Fatalf("insert _id string should stay string, got %T %v", doc["_id"], doc["_id"])
|
|
}
|
|
}
|
|
|
|
func TestConvertBsonValue_EncodesMongoTypedValues(t *testing.T) {
|
|
const oidHex = "507f1f77bcf86cd799439011"
|
|
oid, err := bson.ObjectIDFromHex(oidHex)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
decimalValue, err := bson.ParseDecimal128("12.34")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
converted, ok := convertBsonValue(bson.M{
|
|
"_id": oid,
|
|
"createdAt": bson.DateTime(1719100800000),
|
|
"count32": int32(7),
|
|
"count64": int64(8),
|
|
"ratio": 1.5,
|
|
"price": decimalValue,
|
|
"uid": bson.Binary{
|
|
Subtype: 0x04,
|
|
Data: []byte{0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78, 0x12, 0x34, 0x56, 0x78},
|
|
},
|
|
"nested": bson.M{
|
|
"innerId": oid,
|
|
},
|
|
"items": bson.A{int32(1), int64(2)},
|
|
}).(map[string]interface{})
|
|
if !ok {
|
|
t.Fatalf("expected converted document map, got %T", converted)
|
|
}
|
|
|
|
if converted["_id"].(map[string]interface{})["$oid"] != oidHex {
|
|
t.Fatalf("unexpected ObjectID wrapper: %#v", converted["_id"])
|
|
}
|
|
if converted["createdAt"].(map[string]interface{})["$date"].(map[string]interface{})["$numberLong"] != "1719100800000" {
|
|
t.Fatalf("unexpected date wrapper: %#v", converted["createdAt"])
|
|
}
|
|
if converted["count32"].(map[string]interface{})["$numberInt"] != "7" {
|
|
t.Fatalf("unexpected int32 wrapper: %#v", converted["count32"])
|
|
}
|
|
if converted["count64"].(map[string]interface{})["$numberLong"] != "8" {
|
|
t.Fatalf("unexpected int64 wrapper: %#v", converted["count64"])
|
|
}
|
|
if converted["ratio"] != 1.5 {
|
|
t.Fatalf("plain double should stay float64, got %T %#v", converted["ratio"], converted["ratio"])
|
|
}
|
|
if converted["price"].(map[string]interface{})["$numberDecimal"] != "12.34" {
|
|
t.Fatalf("unexpected decimal wrapper: %#v", converted["price"])
|
|
}
|
|
if converted["uid"].(map[string]interface{})["$binary"].(map[string]interface{})["base64"] != "EjRWeBI0VngSNFZ4EjRWeA==" {
|
|
t.Fatalf("unexpected binary wrapper: %#v", converted["uid"])
|
|
}
|
|
|
|
nestedDoc, ok := converted["nested"].(map[string]interface{})
|
|
if !ok {
|
|
t.Fatalf("expected nested map, got %T", converted["nested"])
|
|
}
|
|
if nestedDoc["innerId"].(map[string]interface{})["$oid"] != oidHex {
|
|
t.Fatalf("unexpected nested ObjectID wrapper: %#v", nestedDoc["innerId"])
|
|
}
|
|
|
|
items, ok := converted["items"].([]interface{})
|
|
if !ok || len(items) != 2 {
|
|
t.Fatalf("unexpected items wrapper: %#v", converted["items"])
|
|
}
|
|
if items[0].(map[string]interface{})["$numberInt"] != "1" || items[1].(map[string]interface{})["$numberLong"] != "2" {
|
|
t.Fatalf("unexpected numeric array wrappers: %#v", items)
|
|
}
|
|
}
|
|
|
|
func TestCopyMongoChangeDocument_DecodesExtendedJSONWrappers(t *testing.T) {
|
|
doc := copyMongoChangeDocument(map[string]interface{}{
|
|
"_id": map[string]interface{}{"$oid": "507f1f77bcf86cd799439011"},
|
|
"createdAt": map[string]interface{}{"$date": map[string]interface{}{"$numberLong": "1719100800000"}},
|
|
"count32": map[string]interface{}{"$numberInt": "7"},
|
|
"count64": map[string]interface{}{"$numberLong": "8"},
|
|
"ratio": map[string]interface{}{"$numberDouble": "1.5"},
|
|
"price": map[string]interface{}{"$numberDecimal": "12.34"},
|
|
"uid": map[string]interface{}{
|
|
"$binary": map[string]interface{}{
|
|
"base64": "EjRWeBI0VngSNFZ4EjRWeA==",
|
|
"subType": "04",
|
|
},
|
|
},
|
|
"nested": map[string]interface{}{
|
|
"innerId": map[string]interface{}{"$oid": "507f1f77bcf86cd799439012"},
|
|
},
|
|
"items": []interface{}{
|
|
map[string]interface{}{"$numberInt": "1"},
|
|
map[string]interface{}{"$numberLong": "2"},
|
|
},
|
|
})
|
|
|
|
if _, ok := doc["_id"].(bson.ObjectID); !ok {
|
|
t.Fatalf("expected _id to decode to bson.ObjectID, got %T", doc["_id"])
|
|
}
|
|
if got, ok := doc["createdAt"].(bson.DateTime); !ok || got != bson.DateTime(1719100800000) {
|
|
t.Fatalf("expected createdAt bson.DateTime, got %T %#v", doc["createdAt"], doc["createdAt"])
|
|
}
|
|
if got, ok := doc["count32"].(int32); !ok || got != 7 {
|
|
t.Fatalf("expected count32 int32, got %T %#v", doc["count32"], doc["count32"])
|
|
}
|
|
if got, ok := doc["count64"].(int64); !ok || got != 8 {
|
|
t.Fatalf("expected count64 int64, got %T %#v", doc["count64"], doc["count64"])
|
|
}
|
|
if got, ok := doc["ratio"].(float64); !ok || got != 1.5 {
|
|
t.Fatalf("expected ratio float64, got %T %#v", doc["ratio"], doc["ratio"])
|
|
}
|
|
if _, ok := doc["price"].(bson.Decimal128); !ok {
|
|
t.Fatalf("expected price bson.Decimal128, got %T", doc["price"])
|
|
}
|
|
if binaryValue, ok := doc["uid"].(bson.Binary); !ok || binaryValue.Subtype != 0x04 || len(binaryValue.Data) != 16 {
|
|
t.Fatalf("expected uid bson.Binary UUID, got %T %#v", doc["uid"], doc["uid"])
|
|
}
|
|
|
|
nestedDoc, ok := doc["nested"].(bson.D)
|
|
if !ok || len(nestedDoc) != 1 || nestedDoc[0].Key != "innerId" {
|
|
t.Fatalf("expected nested bson.D, got %T %#v", doc["nested"], doc["nested"])
|
|
}
|
|
if _, ok := nestedDoc[0].Value.(bson.ObjectID); !ok {
|
|
t.Fatalf("expected nested innerId ObjectID, got %T", nestedDoc[0].Value)
|
|
}
|
|
|
|
items, ok := doc["items"].(bson.A)
|
|
if !ok || len(items) != 2 {
|
|
t.Fatalf("expected items bson.A, got %T %#v", doc["items"], doc["items"])
|
|
}
|
|
if got, ok := items[0].(int32); !ok || got != 1 {
|
|
t.Fatalf("expected items[0] int32, got %T %#v", items[0], items[0])
|
|
}
|
|
if got, ok := items[1].(int64); !ok || got != 2 {
|
|
t.Fatalf("expected items[1] int64, got %T %#v", items[1], items[1])
|
|
}
|
|
}
|