//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]) } }