Files
MyGoNavi/internal/sync/migration_capability_test.go
Syngnat c91a1555a4 feat(sync): 完善跨数据源迁移与增量同步内核
- 增加迁移能力矩阵、对象字段映射与安全预检守卫
- 支持批大小、行错误隔离、水位增量和变更事件重放
- 复用 CDC 目标会话并补齐分页、投影与增量同步测试
2026-08-08 19:06:12 +08:00

332 lines
11 KiB
Go

package sync
import (
"GoNavi-Wails/internal/connection"
"testing"
)
func TestResolveMigrationCapability_MySQLToPostgresUsesFullPlanner(t *testing.T) {
got := ResolveMigrationCapability(
connection.ConnectionConfig{Type: "mysql"},
connection.ConnectionConfig{Type: "postgres"},
)
want := MigrationCapability{
SourceType: "mysql",
TargetType: "postgres",
SourceModel: MigrationDataModelRelational,
TargetModel: MigrationDataModelRelational,
Planner: "mysql-pglike-planner",
SupportLevel: MigrationSupportLevelFull,
CanExecute: true,
SupportsAutoCreate: true,
SupportsAutoAddColumns: true,
RequiresExistingTarget: false,
}
if got != want {
t.Fatalf("unexpected capability: got %+v want %+v", got, want)
}
}
func TestResolveMigrationCapability_PostgresToKingbaseUsesSameFamilyPlanner(t *testing.T) {
got := ResolveMigrationCapability(
connection.ConnectionConfig{Type: "postgres"},
connection.ConnectionConfig{Type: "kingbase"},
)
want := MigrationCapability{
SourceType: "postgres",
TargetType: "kingbase",
SourceModel: MigrationDataModelRelational,
TargetModel: MigrationDataModelRelational,
Planner: "pglike-pglike-planner",
SupportLevel: MigrationSupportLevelFull,
CanExecute: true,
SupportsAutoCreate: true,
SupportsAutoAddColumns: true,
RequiresExistingTarget: false,
}
if got != want {
t.Fatalf("unexpected capability: got %+v want %+v", got, want)
}
}
func TestResolveMigrationCapability_OracleToSQLServerUsesExistingTargetCompatibilityMode(t *testing.T) {
got := ResolveMigrationCapability(
connection.ConnectionConfig{Type: "oracle"},
connection.ConnectionConfig{Type: "sqlserver"},
)
want := MigrationCapability{
SourceType: "oracle",
TargetType: "sqlserver",
SourceModel: MigrationDataModelRelational,
TargetModel: MigrationDataModelRelational,
Planner: "generic-legacy-planner",
SupportLevel: MigrationSupportLevelPartial,
CanExecute: true,
SupportsAutoCreate: false,
SupportsAutoAddColumns: false,
RequiresExistingTarget: true,
}
if got != want {
t.Fatalf("unexpected capability: got %+v want %+v", got, want)
}
}
func TestResolveMigrationCapability_MongoToOracleReportsPlannedNonExecutablePath(t *testing.T) {
got := ResolveMigrationCapability(
connection.ConnectionConfig{Type: "mongodb"},
connection.ConnectionConfig{Type: "oracle"},
)
want := MigrationCapability{
SourceType: "mongodb",
TargetType: "oracle",
SourceModel: MigrationDataModelDocument,
TargetModel: MigrationDataModelRelational,
Planner: "mongo-relational-inference-planner",
SupportLevel: MigrationSupportLevelPlanned,
CanExecute: false,
SupportsAutoCreate: false,
SupportsAutoAddColumns: false,
RequiresExistingTarget: true,
}
if got != want {
t.Fatalf("unexpected capability: got %+v want %+v", got, want)
}
}
func TestResolveMigrationCapability_RedisToMongoReportsFullKeyspaceBridge(t *testing.T) {
got := ResolveMigrationCapability(
connection.ConnectionConfig{Type: "redis"},
connection.ConnectionConfig{Type: "mongodb"},
)
want := MigrationCapability{
SourceType: "redis",
TargetType: "mongodb",
SourceModel: MigrationDataModelKeyValue,
TargetModel: MigrationDataModelDocument,
Planner: "redis-mongo-keyspace-planner",
SupportLevel: MigrationSupportLevelFull,
CanExecute: true,
SupportsAutoCreate: true,
SupportsAutoAddColumns: false,
RequiresExistingTarget: false,
}
if got != want {
t.Fatalf("unexpected capability: got %+v want %+v", got, want)
}
}
func TestResolveMigrationCapability_MongoToRedisReportsFullKeyspaceBridge(t *testing.T) {
got := ResolveMigrationCapability(
connection.ConnectionConfig{Type: "mongodb"},
connection.ConnectionConfig{Type: "redis"},
)
want := MigrationCapability{
SourceType: "mongodb",
TargetType: "redis",
SourceModel: MigrationDataModelDocument,
TargetModel: MigrationDataModelKeyValue,
Planner: "mongo-redis-keyspace-planner",
SupportLevel: MigrationSupportLevelFull,
CanExecute: true,
SupportsAutoCreate: true,
SupportsAutoAddColumns: false,
RequiresExistingTarget: false,
}
if got != want {
t.Fatalf("unexpected capability: got %+v want %+v", got, want)
}
}
func TestResolveMigrationCapability_CustomPostgresUsesResolvedDriverFamily(t *testing.T) {
got := ResolveMigrationCapability(
connection.ConnectionConfig{Type: "custom", Driver: "pgx"},
connection.ConnectionConfig{Type: "kingbase"},
)
want := MigrationCapability{
SourceType: "postgres",
TargetType: "kingbase",
SourceModel: MigrationDataModelRelational,
TargetModel: MigrationDataModelRelational,
Planner: "pglike-pglike-planner",
SupportLevel: MigrationSupportLevelFull,
CanExecute: true,
SupportsAutoCreate: true,
SupportsAutoAddColumns: true,
RequiresExistingTarget: false,
}
if got != want {
t.Fatalf("unexpected capability: got %+v want %+v", got, want)
}
}
func TestResolveMigrationCapability_KafkaToQdrantIsUnsupported(t *testing.T) {
got := ResolveMigrationCapability(
connection.ConnectionConfig{Type: "kafka"},
connection.ConnectionConfig{Type: "qdrant"},
)
want := MigrationCapability{
SourceType: "kafka",
TargetType: "qdrant",
SourceModel: MigrationDataModelCustom,
TargetModel: MigrationDataModelCustom,
Planner: "",
SupportLevel: MigrationSupportLevelUnsupported,
CanExecute: false,
SupportsAutoCreate: false,
SupportsAutoAddColumns: false,
RequiresExistingTarget: true,
}
if got != want {
t.Fatalf("unexpected capability: got %+v want %+v", got, want)
}
}
func TestResolveMigrationCapability_RedisToNonMongoTargetIsUnsupported(t *testing.T) {
got := ResolveMigrationCapability(
connection.ConnectionConfig{Type: "redis"},
connection.ConnectionConfig{Type: "postgres"},
)
if got.SupportLevel != MigrationSupportLevelUnsupported || got.CanExecute {
t.Fatalf("expected Redis to non-Mongo target to be blocked, got %+v", got)
}
}
func TestResolveMigrationCapability_GoldenDBUsesMySQLPlannerFamily(t *testing.T) {
got := ResolveMigrationCapability(
connection.ConnectionConfig{Type: "goldendb"},
connection.ConnectionConfig{Type: "postgres"},
)
if got.SourceType != "mysql" || got.Planner != "mysql-pglike-planner" || !got.CanExecute {
t.Fatalf("expected GoldenDB to use the MySQL migration family, got %+v", got)
}
}
func TestResolveMigrationCapability_UsesDirectionalEndpointContract(t *testing.T) {
tests := []struct {
name string
source connection.ConnectionConfig
target connection.ConnectionConfig
level MigrationSupportLevel
canRun bool
}{
{
name: "Trino can be a compatibility source",
source: connection.ConnectionConfig{Type: "trino"},
target: connection.ConnectionConfig{Type: "mysql"},
level: MigrationSupportLevelPartial,
canRun: true,
},
{
name: "Trino is not a writable target",
source: connection.ConnectionConfig{Type: "mysql"},
target: connection.ConnectionConfig{Type: "trino"},
level: MigrationSupportLevelUnsupported,
},
{
name: "Sphinx is not a writable target",
source: connection.ConnectionConfig{Type: "mysql"},
target: connection.ConnectionConfig{Type: "sphinx"},
level: MigrationSupportLevelUnsupported,
},
{
name: "Elasticsearch is not a table migration source",
source: connection.ConnectionConfig{Type: "elasticsearch"},
target: connection.ConnectionConfig{Type: "mysql"},
level: MigrationSupportLevelUnsupported,
},
{
name: "Nacos is not a database target",
source: connection.ConnectionConfig{Type: "mysql"},
target: connection.ConnectionConfig{Type: "nacos"},
level: MigrationSupportLevelUnsupported,
},
{
name: "unknown custom SQL driver stays in compatibility mode",
source: connection.ConnectionConfig{Type: "custom", Driver: "acme-sql"},
target: connection.ConnectionConfig{Type: "mysql"},
level: MigrationSupportLevelPartial,
canRun: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := ResolveMigrationCapability(tt.source, tt.target)
if got.SupportLevel != tt.level || got.CanExecute != tt.canRun {
t.Fatalf("unexpected capability: got %+v, want level=%s canRun=%v", got, tt.level, tt.canRun)
}
})
}
}
func TestResolveMigrationCapability_OceanBaseOracleUsesExistingTargetCompatibility(t *testing.T) {
tests := []struct {
name string
source connection.ConnectionConfig
target connection.ConnectionConfig
}{
{
name: "Oracle tenant as source",
source: connection.ConnectionConfig{
Type: "oceanbase",
OceanBaseProtocol: "oracle",
},
target: connection.ConnectionConfig{Type: "postgres"},
},
{
name: "Oracle tenant as target",
source: connection.ConnectionConfig{Type: "mysql"},
target: connection.ConnectionConfig{
Type: "oceanbase",
ConnectionParams: "protocol=oracle",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := ResolveMigrationCapability(tt.source, tt.target)
if got.Planner != "oceanbase-oracle-existing-target" ||
got.SupportLevel != MigrationSupportLevelPartial ||
!got.CanExecute || got.SupportsAutoCreate || !got.RequiresExistingTarget {
t.Fatalf("unexpected OceanBase Oracle capability: %+v", got)
}
})
}
}
func TestResolveMigrationCapability_OceanBaseOracleDoesNotBypassEndpointContract(t *testing.T) {
got := ResolveMigrationCapability(
connection.ConnectionConfig{
Type: "oceanbase",
OceanBaseProtocol: "oracle",
},
connection.ConnectionConfig{Type: "qdrant"},
)
if got.SupportLevel != MigrationSupportLevelUnsupported || got.CanExecute {
t.Fatalf("expected unsupported target to win over OceanBase compatibility mode, got %+v", got)
}
}
func TestResolveMigrationCapability_PostgresToMySQLReportsPlannerAutoAddColumns(t *testing.T) {
got := ResolveMigrationCapability(
connection.ConnectionConfig{Type: "postgres"},
connection.ConnectionConfig{Type: "mysql"},
)
if got.Planner != "pglike-mysql-planner" || !got.SupportsAutoAddColumns {
t.Fatalf("expected the PostgreSQL to MySQL planner's auto-add support, got %+v", got)
}
}