2026-07-31 10:30:03 +08:00
|
|
|
package service
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
2026-07-31 11:16:19 +08:00
|
|
|
"encoding/json"
|
2026-07-31 10:30:03 +08:00
|
|
|
"testing"
|
2026-07-31 11:16:19 +08:00
|
|
|
|
|
|
|
|
"github.com/1024XEngineer/xinfra/server/internal/model"
|
2026-07-31 10:30:03 +08:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func TestValidatePostgreSQLDeliveryInput(t *testing.T) {
|
|
|
|
|
valid := PostgreSQLDeliveryInput{BusinessLineID: 1, TargetID: 2, Namespace: "team-a", ClusterName: "orders-pg", VersionMajor: "16", Topology: "standalone", CPUMilli: 500, MemoryMi: 2048, StorageGi: 20}
|
|
|
|
|
if err := validatePostgreSQLDeliveryInput(valid, []string{"/data"}); err != nil {
|
|
|
|
|
t.Fatalf("valid standalone input rejected: %v", err)
|
|
|
|
|
}
|
|
|
|
|
aliasVersion := valid
|
|
|
|
|
aliasVersion.VersionMajor = ""
|
|
|
|
|
aliasVersion.PostgreSQLVersion = "15"
|
|
|
|
|
if err := validatePostgreSQLDeliveryInput(aliasVersion, []string{"/data"}); err != nil {
|
|
|
|
|
t.Fatalf("postgresql_version alias rejected: %v", err)
|
|
|
|
|
}
|
|
|
|
|
replicated := valid
|
|
|
|
|
replicated.VersionMajor = "15"
|
|
|
|
|
replicated.Topology = "primary_replica"
|
|
|
|
|
replicated.ReplicaCount = 2
|
|
|
|
|
replicated.TargetHosts = []string{"pg-a", "pg-b", "pg-c"}
|
|
|
|
|
replicated.DataRoot = "/data/postgresql"
|
|
|
|
|
replicated.MaxConnections = 500
|
|
|
|
|
if err := validatePostgreSQLDeliveryInput(replicated, []string{"/data"}); err != nil {
|
|
|
|
|
t.Fatalf("valid primary_replica input rejected: %v", err)
|
|
|
|
|
}
|
|
|
|
|
for name, mutate := range map[string]func(*PostgreSQLDeliveryInput){
|
|
|
|
|
"unsupported version": func(in *PostgreSQLDeliveryInput) { in.VersionMajor = "14" },
|
|
|
|
|
"bad topology": func(in *PostgreSQLDeliveryInput) { in.Topology = "patroni" },
|
|
|
|
|
"standalone replicas": func(in *PostgreSQLDeliveryInput) { in.ReplicaCount = 1 },
|
|
|
|
|
"missing replicas": func(in *PostgreSQLDeliveryInput) { in.Topology = "primary_replica" },
|
|
|
|
|
"too many replicas": func(in *PostgreSQLDeliveryInput) { in.Topology = "primary_replica"; in.ReplicaCount = 8 },
|
|
|
|
|
"duplicate hosts": func(in *PostgreSQLDeliveryInput) {
|
|
|
|
|
in.Topology = "primary_replica"
|
|
|
|
|
in.ReplicaCount = 1
|
|
|
|
|
in.TargetHosts = []string{"pg-a", "pg-a"}
|
|
|
|
|
},
|
|
|
|
|
"wrong host count": func(in *PostgreSQLDeliveryInput) {
|
|
|
|
|
in.Topology = "primary_replica"
|
|
|
|
|
in.ReplicaCount = 2
|
|
|
|
|
in.TargetHosts = []string{"pg-a", "pg-b"}
|
|
|
|
|
},
|
|
|
|
|
"bad root": func(in *PostgreSQLDeliveryInput) { in.DataRoot = "/tmp/postgresql" },
|
|
|
|
|
"unsupported root disk": func(in *PostgreSQLDeliveryInput) { in.DataRoot = "/disk1/postgresql" },
|
|
|
|
|
} {
|
|
|
|
|
input := valid
|
|
|
|
|
mutate(&input)
|
|
|
|
|
if err := validatePostgreSQLDeliveryInput(input, []string{"/data"}); err == nil {
|
|
|
|
|
t.Errorf("%s was accepted", name)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestAllocatePostgreSQLPort(t *testing.T) {
|
|
|
|
|
if port, err := allocatePostgreSQLPort(0, nil); err != nil || port != postgresqlPortPoolStart {
|
|
|
|
|
t.Fatalf("expected pool start %d, got %d err=%v", postgresqlPortPoolStart, port, err)
|
|
|
|
|
}
|
|
|
|
|
if port, err := allocatePostgreSQLPort(0, []int{15432, 15433}); err != nil || port != 15434 {
|
|
|
|
|
t.Fatalf("expected 15434, got %d err=%v", port, err)
|
|
|
|
|
}
|
|
|
|
|
if _, err := allocatePostgreSQLPort(15432, []int{15432}); err == nil {
|
|
|
|
|
t.Fatal("occupied requested port was accepted")
|
|
|
|
|
}
|
|
|
|
|
if _, err := allocatePostgreSQLPort(5432, nil); err == nil {
|
|
|
|
|
t.Fatal("port outside the PostgreSQL pool was accepted")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestAllocateReachablePostgreSQLPortSkipsListeningPorts(t *testing.T) {
|
|
|
|
|
probed := []int{}
|
|
|
|
|
port, err := allocateReachablePostgreSQLPort(context.Background(), "pg.example", nil, func(_ context.Context, host string, port int) bool {
|
|
|
|
|
if host != "pg.example" {
|
|
|
|
|
t.Fatalf("unexpected probe host %q", host)
|
|
|
|
|
}
|
|
|
|
|
probed = append(probed, port)
|
|
|
|
|
return port == postgresqlPortPoolStart
|
|
|
|
|
})
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("allocate reachable port: %v", err)
|
|
|
|
|
}
|
|
|
|
|
if port != postgresqlPortPoolStart+1 {
|
|
|
|
|
t.Fatalf("port = %d, want %d", port, postgresqlPortPoolStart+1)
|
|
|
|
|
}
|
|
|
|
|
if len(probed) != 2 || probed[0] != postgresqlPortPoolStart || probed[1] != postgresqlPortPoolStart+1 {
|
|
|
|
|
t.Fatalf("unexpected probes: %v", probed)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestSelectPostgreSQLHosts(t *testing.T) {
|
|
|
|
|
hosts := []targetHost{{Name: "pg-a"}, {Name: "pg-b"}, {Name: "pg-c"}}
|
|
|
|
|
selected, err := selectPostgreSQLHosts(hosts, nil, 2)
|
|
|
|
|
if err != nil || len(selected) != 2 || selected[0].Name != "pg-a" || selected[1].Name != "pg-b" {
|
|
|
|
|
t.Fatalf("unexpected automatic selection: %+v err=%v", selected, err)
|
|
|
|
|
}
|
|
|
|
|
selected, err = selectPostgreSQLHosts(hosts, []string{"pg-c", "pg-a"}, 2)
|
|
|
|
|
if err != nil || selected[0].Name != "pg-c" || selected[1].Name != "pg-a" {
|
|
|
|
|
t.Fatalf("unexpected pinned selection: %+v err=%v", selected, err)
|
|
|
|
|
}
|
|
|
|
|
if _, err := selectPostgreSQLHosts(hosts, []string{"missing"}, 1); err == nil {
|
|
|
|
|
t.Fatal("host outside the pool was accepted")
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-07-31 11:16:19 +08:00
|
|
|
|
|
|
|
|
func TestBuildPostgreSQLCloudDMRegisterRequest(t *testing.T) {
|
|
|
|
|
req := buildPostgreSQLCloudDMRegisterRequest(
|
|
|
|
|
model.PostgreSQLInstance{ID: 42, InstanceID: "orders-pg-primary", HostIP: "10.0.0.10", Port: 15432, VersionMajor: "16", Role: "primary"},
|
|
|
|
|
model.PostgreSQLCluster{ID: 7, Name: "orders-pg"},
|
|
|
|
|
"postgres",
|
|
|
|
|
"secret",
|
|
|
|
|
)
|
|
|
|
|
if req.SourceSystem != "xinfra" || req.ResourceType != "POSTGRESQL_INSTANCE" {
|
|
|
|
|
t.Fatalf("unexpected request metadata: %#v", req)
|
|
|
|
|
}
|
|
|
|
|
if req.ExternalResourceID != "postgresql-instance:42" {
|
|
|
|
|
t.Fatalf("unexpected externalResourceId: %q", req.ExternalResourceID)
|
|
|
|
|
}
|
|
|
|
|
if req.DataSource.DSType != "PostgreSQL" || req.DataSource.Host != "10.0.0.10:15432" || req.DataSource.UserName != "postgres" || req.DataSource.Password != "secret" {
|
|
|
|
|
t.Fatalf("unexpected PostgreSQL data source: %#v", req.DataSource)
|
|
|
|
|
}
|
|
|
|
|
if req.DataSource.PostgreSQLVersion != "16" || req.DataSource.DefaultSchema != "postgres" {
|
|
|
|
|
t.Fatalf("unexpected PostgreSQL version/schema: %#v", req.DataSource)
|
|
|
|
|
}
|
|
|
|
|
raw, err := json.Marshal(req)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("marshal PostgreSQL CloudDM request: %v", err)
|
|
|
|
|
}
|
|
|
|
|
var decoded map[string]any
|
|
|
|
|
if err := json.Unmarshal(raw, &decoded); err != nil {
|
|
|
|
|
t.Fatalf("decode PostgreSQL CloudDM request: %v", err)
|
|
|
|
|
}
|
|
|
|
|
dataSource := decoded["dataSource"].(map[string]any)
|
|
|
|
|
if dataSource["postgresql_version"] != "16" {
|
|
|
|
|
t.Fatalf("postgresql_version missing from contract: %#v", dataSource)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestPostgreSQLCloudDMExternalResourceID(t *testing.T) {
|
|
|
|
|
if got := postgresqlCloudDMExternalResourceID(99); got != "postgresql-instance:99" {
|
|
|
|
|
t.Fatalf("external resource id = %q", got)
|
|
|
|
|
}
|
|
|
|
|
}
|