Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import (
"github.com/ProxySQL/dbdeployer/globals"
"github.com/ProxySQL/dbdeployer/providers"
mysqlprovider "github.com/ProxySQL/dbdeployer/providers/mysql"
proxysqlprovider "github.com/ProxySQL/dbdeployer/providers/proxysql"
"github.com/ProxySQL/dbdeployer/sandbox"
)

Expand Down Expand Up @@ -148,6 +149,7 @@ func init() {
if err := mysqlprovider.Register(providers.DefaultRegistry); err != nil {
panic(fmt.Sprintf("failed to register MySQL provider: %v", err))
}
_ = proxysqlprovider.Register(providers.DefaultRegistry)
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This ignores any error from ProxySQL provider registration. Registry.Register only errors on programming/configuration issues (e.g., duplicate provider name), not on missing binaries, so swallowing the error could hide a real startup bug. Consider handling the error explicitly (e.g., print a warning to stderr) while still keeping it non-fatal.

Suggested change
_ = proxysqlprovider.Register(providers.DefaultRegistry)
if err := proxysqlprovider.Register(providers.DefaultRegistry); err != nil {
fmt.Fprintf(os.Stderr, "warning: failed to register ProxySQL provider: %v\n", err)
}

Copilot uses AI. Check for mistakes.
cobra.OnInitialize(checkDefaultsFile)
rootCmd.CompletionOptions.DisableDefaultCmd = true
rootCmd.PersistentFlags().StringVar(&defaults.CustomConfigurationFile, globals.ConfigLabel, defaults.ConfigurationFile, "configuration file")
Expand Down
75 changes: 75 additions & 0 deletions providers/proxysql/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package proxysql

import (
"fmt"
"strings"
)

type BackendServer struct {
Host string
Port int
Hostgroup int
MaxConns int
}

type ProxySQLConfig struct {
AdminHost string
AdminPort int
AdminUser string
AdminPassword string
MySQLPort int
DataDir string
Backends []BackendServer
MonitorUser string
MonitorPass string
}

func GenerateConfig(cfg ProxySQLConfig) string {
var b strings.Builder

b.WriteString(fmt.Sprintf("datadir=\"%s\"\n\n", cfg.DataDir))

b.WriteString("admin_variables=\n{\n")
b.WriteString(fmt.Sprintf(" admin_credentials=\"%s:%s\"\n", cfg.AdminUser, cfg.AdminPassword))
b.WriteString(fmt.Sprintf(" mysql_ifaces=\"%s:%d\"\n", cfg.AdminHost, cfg.AdminPort))
b.WriteString("}\n\n")

b.WriteString("mysql_variables=\n{\n")
b.WriteString(fmt.Sprintf(" interfaces=\"%s:%d\"\n", cfg.AdminHost, cfg.MySQLPort))
b.WriteString(fmt.Sprintf(" monitor_username=\"%s\"\n", cfg.MonitorUser))
b.WriteString(fmt.Sprintf(" monitor_password=\"%s\"\n", cfg.MonitorPass))
b.WriteString(" monitor_connect_interval=2000\n")
b.WriteString(" monitor_ping_interval=2000\n")
b.WriteString("}\n\n")

if len(cfg.Backends) > 0 {
b.WriteString("mysql_servers=\n(\n")
for i, srv := range cfg.Backends {
b.WriteString(" {\n")
b.WriteString(fmt.Sprintf(" address=\"%s\"\n", srv.Host))
b.WriteString(fmt.Sprintf(" port=%d\n", srv.Port))
b.WriteString(fmt.Sprintf(" hostgroup=%d\n", srv.Hostgroup))
maxConns := srv.MaxConns
if maxConns == 0 {
maxConns = 200
}
b.WriteString(fmt.Sprintf(" max_connections=%d\n", maxConns))
b.WriteString(" }")
if i < len(cfg.Backends)-1 {
b.WriteString(",")
}
b.WriteString("\n")
}
b.WriteString(")\n\n")
}

b.WriteString("mysql_users=\n(\n")
b.WriteString(" {\n")
b.WriteString(fmt.Sprintf(" username=\"%s\"\n", cfg.MonitorUser))
b.WriteString(fmt.Sprintf(" password=\"%s\"\n", cfg.MonitorPass))
b.WriteString(" default_hostgroup=0\n")
b.WriteString(" }\n")
b.WriteString(")\n")

return b.String()
}
50 changes: 50 additions & 0 deletions providers/proxysql/config_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package proxysql

import (
"strings"
"testing"
)

func TestGenerateConfigBasic(t *testing.T) {
cfg := ProxySQLConfig{
AdminHost: "127.0.0.1", AdminPort: 6032,
AdminUser: "admin", AdminPassword: "admin",
MySQLPort: 6033, DataDir: "/tmp/test",
MonitorUser: "msandbox", MonitorPass: "msandbox",
}
result := GenerateConfig(cfg)
checks := []string{
`admin_credentials="admin:admin"`,
`interfaces="127.0.0.1:6033"`,
`monitor_username="msandbox"`,
`mysql_ifaces="127.0.0.1:6032"`,
}
for _, check := range checks {
if !strings.Contains(result, check) {
t.Errorf("missing %q in config output", check)
}
}
}

func TestGenerateConfigWithBackends(t *testing.T) {
cfg := ProxySQLConfig{
AdminHost: "127.0.0.1", AdminPort: 6032,
AdminUser: "admin", AdminPassword: "admin",
MySQLPort: 6033, DataDir: "/tmp/test",
MonitorUser: "msandbox", MonitorPass: "msandbox",
Backends: []BackendServer{
{Host: "127.0.0.1", Port: 3306, Hostgroup: 0, MaxConns: 100},
{Host: "127.0.0.1", Port: 3307, Hostgroup: 1, MaxConns: 100},
},
}
result := GenerateConfig(cfg)
if !strings.Contains(result, "mysql_servers=") {
t.Error("missing mysql_servers section")
}
if !strings.Contains(result, "port=3306") {
t.Error("missing first backend")
}
if !strings.Contains(result, "hostgroup=1") {
t.Error("missing reader hostgroup")
}
}
167 changes: 167 additions & 0 deletions providers/proxysql/proxysql.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
package proxysql

import (
"fmt"
"os"
"os/exec"
"path/filepath"
"strconv"
"strings"

"github.com/ProxySQL/dbdeployer/providers"
)

const ProviderName = "proxysql"

type ProxySQLProvider struct{}

func NewProxySQLProvider() *ProxySQLProvider { return &ProxySQLProvider{} }

func (p *ProxySQLProvider) Name() string { return ProviderName }

func (p *ProxySQLProvider) ValidateVersion(version string) error {
parts := strings.Split(version, ".")
if len(parts) < 2 {
return fmt.Errorf("invalid ProxySQL version format: %q (expected X.Y or X.Y.Z)", version)
}
return nil
}

func (p *ProxySQLProvider) DefaultPorts() providers.PortRange {
return providers.PortRange{BasePort: 6032, PortsPerInstance: 2}
}

func (p *ProxySQLProvider) FindBinary(version string) (string, error) {
path, err := exec.LookPath("proxysql")
if err != nil {
return "", fmt.Errorf("proxysql binary not found in PATH: %w", err)
}
return path, nil
}

func (p *ProxySQLProvider) CreateSandbox(config providers.SandboxConfig) (*providers.SandboxInfo, error) {
binaryPath, err := p.FindBinary(config.Version)
if err != nil {
return nil, err
}

dataDir := filepath.Join(config.Dir, "data")
if err := os.MkdirAll(dataDir, 0755); err != nil {
return nil, fmt.Errorf("creating data directory: %w", err)
}

adminPort := config.AdminPort
if adminPort == 0 {
adminPort = config.Port
}
mysqlPort := adminPort + 1
Comment on lines +54 to +57
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Port assignment looks inverted vs SandboxConfig semantics: SandboxConfig.Port is documented as the primary port and AdminPort as the management port, but when AdminPort==0 this code uses config.Port as the admin port and derives mysqlPort=adminPort+1. That makes it hard for callers to set/understand ports, and SandboxInfo.Port later returns the admin port. Consider treating config.Port as the MySQL listener (6033) and config.AdminPort as the admin interface (6032), or at least returning mysqlPort as SandboxInfo.Port (and exposing admin port via another field/option).

Suggested change
if adminPort == 0 {
adminPort = config.Port
}
mysqlPort := adminPort + 1
mysqlPort := config.Port
// Ensure ports follow documented semantics:
// - config.Port is the primary/MySQL listener (default 6033)
// - config.AdminPort is the management/admin interface (default 6032)
// Derive the missing one if only one is provided, or use defaults if both are zero.
if adminPort == 0 && mysqlPort == 0 {
// Neither port provided: fall back to provider defaults (6032 admin, 6033 MySQL).
defaultPorts := p.DefaultPorts()
adminPort = defaultPorts.BasePort
mysqlPort = adminPort + 1
} else {
if adminPort == 0 {
// Only MySQL port provided: derive admin port just below it.
adminPort = mysqlPort - 1
} else if mysqlPort == 0 {
// Only admin port provided: derive MySQL port just above it.
mysqlPort = adminPort + 1
}
}

Copilot uses AI. Check for mistakes.

adminUser := config.DbUser
if adminUser == "" {
adminUser = "admin"
}
adminPassword := config.DbPassword
if adminPassword == "" {
adminPassword = "admin"
}

monitorUser := config.Options["monitor_user"]
if monitorUser == "" {
monitorUser = "msandbox"
}
monitorPass := config.Options["monitor_password"]
if monitorPass == "" {
monitorPass = "msandbox"
}

host := config.Host
if host == "" {
host = "127.0.0.1"
}

proxyCfg := ProxySQLConfig{
AdminHost: host,
AdminPort: adminPort,
AdminUser: adminUser,
AdminPassword: adminPassword,
MySQLPort: mysqlPort,
DataDir: dataDir,
MonitorUser: monitorUser,
MonitorPass: monitorPass,
Backends: parseBackends(config.Options),
}

cfgContent := GenerateConfig(proxyCfg)
cfgPath := filepath.Join(config.Dir, "proxysql.cnf")
if err := os.WriteFile(cfgPath, []byte(cfgContent), 0644); err != nil {

Check failure on line 96 in providers/proxysql/proxysql.go

View workflow job for this annotation

GitHub Actions / Lint

G306: Expect WriteFile permissions to be 0600 or less (gosec)

Check failure on line 96 in providers/proxysql/proxysql.go

View workflow job for this annotation

GitHub Actions / Lint

G306: Expect WriteFile permissions to be 0600 or less (gosec)
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The generated config contains admin/monitor credentials but is written with mode 0644, making it world-readable on multi-user systems. Use a more restrictive permission (e.g., 0600) or avoid writing secrets into the config when possible.

Suggested change
if err := os.WriteFile(cfgPath, []byte(cfgContent), 0644); err != nil {
if err := os.WriteFile(cfgPath, []byte(cfgContent), 0600); err != nil {

Copilot uses AI. Check for mistakes.
return nil, fmt.Errorf("writing config: %w", err)
}

// Write lifecycle scripts
scripts := map[string]string{
"start": fmt.Sprintf("#!/bin/bash\ncd %s\n%s --initial -c %s -D %s &\nSBPID=$!\necho $SBPID > %s/proxysql.pid\nsleep 2\nif kill -0 $SBPID 2>/dev/null; then\n echo 'ProxySQL started (pid '$SBPID')'\nelse\n echo 'ProxySQL failed to start'\n exit 1\nfi\n",
config.Dir, binaryPath, cfgPath, dataDir, config.Dir),
Comment on lines +102 to +103
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The start script always runs ProxySQL with --initial. ProxySQL uses --initial to reset/empty the on-disk SQLite DB in the datadir, so using it on every start will discard persisted config on restarts. The start script should only use --initial for first bootstrap (e.g., when the DB file doesn't exist), and do normal starts without it thereafter.

Suggested change
"start": fmt.Sprintf("#!/bin/bash\ncd %s\n%s --initial -c %s -D %s &\nSBPID=$!\necho $SBPID > %s/proxysql.pid\nsleep 2\nif kill -0 $SBPID 2>/dev/null; then\n echo 'ProxySQL started (pid '$SBPID')'\nelse\n echo 'ProxySQL failed to start'\n exit 1\nfi\n",
config.Dir, binaryPath, cfgPath, dataDir, config.Dir),
"start": fmt.Sprintf("#!/bin/bash\ncd %s\nDBFILE=%s/proxysql.db\nINITIAL=\"\"\nif [ ! -f \"$DBFILE\" ]; then\n INITIAL=\"--initial\"\nfi\n%s $INITIAL -c %s -D %s &\nSBPID=$!\necho $SBPID > %s/proxysql.pid\nsleep 2\nif kill -0 $SBPID 2>/dev/null; then\n echo 'ProxySQL started (pid '$SBPID')'\nelse\n echo 'ProxySQL failed to start'\n exit 1\nfi\n",
config.Dir, dataDir, binaryPath, cfgPath, dataDir, config.Dir),

Copilot uses AI. Check for mistakes.
"stop": fmt.Sprintf("#!/bin/bash\nPIDFILE=%s/proxysql.pid\nif [ -f $PIDFILE ]; then\n PID=$(cat $PIDFILE)\n kill $PID 2>/dev/null\n sleep 1\n kill -0 $PID 2>/dev/null && kill -9 $PID 2>/dev/null\n rm -f $PIDFILE\n echo 'ProxySQL stopped'\nelse\n echo 'ProxySQL not running (no pid file)'\nfi\n",
config.Dir),
"status": fmt.Sprintf("#!/bin/bash\nPIDFILE=%s/proxysql.pid\nif [ -f $PIDFILE ] && kill -0 $(cat $PIDFILE) 2>/dev/null; then\n echo 'ProxySQL running (pid '$(cat $PIDFILE)')'\nelse\n echo 'ProxySQL not running'\n exit 1\nfi\n",
Comment on lines +102 to +106
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The generated shell scripts interpolate paths (cd <dir>, binaryPath, cfgPath, dataDir, PIDFILE) without any quoting/escaping. If the sandbox dir or binary path contains spaces or shell metacharacters, the scripts can break or behave unexpectedly. Consider shell-escaping/quoting these interpolated values consistently.

Suggested change
"start": fmt.Sprintf("#!/bin/bash\ncd %s\n%s --initial -c %s -D %s &\nSBPID=$!\necho $SBPID > %s/proxysql.pid\nsleep 2\nif kill -0 $SBPID 2>/dev/null; then\n echo 'ProxySQL started (pid '$SBPID')'\nelse\n echo 'ProxySQL failed to start'\n exit 1\nfi\n",
config.Dir, binaryPath, cfgPath, dataDir, config.Dir),
"stop": fmt.Sprintf("#!/bin/bash\nPIDFILE=%s/proxysql.pid\nif [ -f $PIDFILE ]; then\n PID=$(cat $PIDFILE)\n kill $PID 2>/dev/null\n sleep 1\n kill -0 $PID 2>/dev/null && kill -9 $PID 2>/dev/null\n rm -f $PIDFILE\n echo 'ProxySQL stopped'\nelse\n echo 'ProxySQL not running (no pid file)'\nfi\n",
config.Dir),
"status": fmt.Sprintf("#!/bin/bash\nPIDFILE=%s/proxysql.pid\nif [ -f $PIDFILE ] && kill -0 $(cat $PIDFILE) 2>/dev/null; then\n echo 'ProxySQL running (pid '$(cat $PIDFILE)')'\nelse\n echo 'ProxySQL not running'\n exit 1\nfi\n",
"start": fmt.Sprintf("#!/bin/bash\ncd \"%s\"\n\"%s\" --initial -c \"%s\" -D \"%s\" &\nSBPID=$!\necho \"$SBPID\" > \"%s/proxysql.pid\"\nsleep 2\nif kill -0 \"$SBPID\" 2>/dev/null; then\n echo 'ProxySQL started (pid '$SBPID')'\nelse\n echo 'ProxySQL failed to start'\n exit 1\nfi\n",
config.Dir, binaryPath, cfgPath, dataDir, config.Dir),
"stop": fmt.Sprintf("#!/bin/bash\nPIDFILE=\"%s/proxysql.pid\"\nif [ -f \"$PIDFILE\" ]; then\n PID=$(cat \"$PIDFILE\")\n kill \"$PID\" 2>/dev/null\n sleep 1\n kill -0 \"$PID\" 2>/dev/null && kill -9 \"$PID\" 2>/dev/null\n rm -f \"$PIDFILE\"\n echo 'ProxySQL stopped'\nelse\n echo 'ProxySQL not running (no pid file)'\nfi\n",
config.Dir),
"status": fmt.Sprintf("#!/bin/bash\nPIDFILE=\"%s/proxysql.pid\"\nif [ -f \"$PIDFILE\" ] && kill -0 \"$(cat \"$PIDFILE\")\" 2>/dev/null; then\n echo 'ProxySQL running (pid '$(cat \"$PIDFILE\")')'\nelse\n echo 'ProxySQL not running'\n exit 1\nfi\n",

Copilot uses AI. Check for mistakes.
config.Dir),
"use": fmt.Sprintf("#!/bin/bash\nmysql -h %s -P %d -u %s -p%s --prompt 'ProxySQL Admin> ' \"$@\"\n",
host, adminPort, adminUser, adminPassword),
"use_proxy": fmt.Sprintf("#!/bin/bash\nmysql -h %s -P %d -u %s -p%s --prompt 'ProxySQL> ' \"$@\"\n",
host, mysqlPort, monitorUser, monitorPass),
}

for name, content := range scripts {
scriptPath := filepath.Join(config.Dir, name)
if err := os.WriteFile(scriptPath, []byte(content), 0755); err != nil {

Check failure on line 116 in providers/proxysql/proxysql.go

View workflow job for this annotation

GitHub Actions / Lint

G306: Expect WriteFile permissions to be 0600 or less (gosec)

Check failure on line 116 in providers/proxysql/proxysql.go

View workflow job for this annotation

GitHub Actions / Lint

G306: Expect WriteFile permissions to be 0600 or less (gosec)
Comment on lines +110 to +116
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The generated use/use_proxy scripts embed passwords directly on the mysql client command line (-p<pass>), which can leak via process listings and are also stored in world-readable executable files (0755). Prefer prompting for the password, using a restricted-permission defaults file (e.g., my.proxy.cnf mode 0600), or passing creds via environment with tight file perms (and consider 0700 for the scripts directory/files).

Suggested change
"use_proxy": fmt.Sprintf("#!/bin/bash\nmysql -h %s -P %d -u %s -p%s --prompt 'ProxySQL> ' \"$@\"\n",
host, mysqlPort, monitorUser, monitorPass),
}
for name, content := range scripts {
scriptPath := filepath.Join(config.Dir, name)
if err := os.WriteFile(scriptPath, []byte(content), 0755); err != nil {
"use_proxy": fmt.Sprintf("#!/bin/bash\nmysql -h %s -P %d -u %s --prompt 'ProxySQL> ' \"$@\"\n",
host, mysqlPort, monitorUser),
}
for name, content := range scripts {
scriptPath := filepath.Join(config.Dir, name)
if err := os.WriteFile(scriptPath, []byte(content), 0700); err != nil {

Copilot uses AI. Check for mistakes.
return nil, fmt.Errorf("writing script %s: %w", name, err)
}
}

return &providers.SandboxInfo{
Dir: config.Dir,
Port: adminPort,
Status: "stopped",
}, nil
}

func (p *ProxySQLProvider) StartSandbox(dir string) error {
cmd := exec.Command("bash", filepath.Join(dir, "start"))

Check failure on line 129 in providers/proxysql/proxysql.go

View workflow job for this annotation

GitHub Actions / Lint

G204: Subprocess launched with a potential tainted input or cmd arguments (gosec)

Check failure on line 129 in providers/proxysql/proxysql.go

View workflow job for this annotation

GitHub Actions / Lint

G204: Subprocess launched with a potential tainted input or cmd arguments (gosec)
output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("start failed: %s: %w", string(output), err)
}
return nil
}

func (p *ProxySQLProvider) StopSandbox(dir string) error {
cmd := exec.Command("bash", filepath.Join(dir, "stop"))

Check failure on line 138 in providers/proxysql/proxysql.go

View workflow job for this annotation

GitHub Actions / Lint

G204: Subprocess launched with a potential tainted input or cmd arguments (gosec)

Check failure on line 138 in providers/proxysql/proxysql.go

View workflow job for this annotation

GitHub Actions / Lint

G204: Subprocess launched with a potential tainted input or cmd arguments (gosec)
output, err := cmd.CombinedOutput()
if err != nil {
return fmt.Errorf("stop failed: %s: %w", string(output), err)
}
return nil
}

func Register(reg *providers.Registry) error {
return reg.Register(NewProxySQLProvider())
}

func parseBackends(options map[string]string) []BackendServer {
raw, ok := options["backends"]
if !ok || raw == "" {
return nil
}
var backends []BackendServer
for _, entry := range strings.Split(raw, ",") {
parts := strings.Split(entry, ":")
if len(parts) >= 3 {
port, _ := strconv.Atoi(parts[1])
hg, _ := strconv.Atoi(parts[2])
backends = append(backends, BackendServer{
Host: parts[0], Port: port, Hostgroup: hg, MaxConns: 200,
})
}
Comment on lines +157 to +164
Copy link

Copilot AI Mar 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

parseBackends silently ignores malformed backend entries and ignores Atoi errors (defaulting port/hostgroup to 0). This can generate an invalid config without surfacing an error to the user. Consider validating each entry (exactly 3 fields, successful int parses, port>0, hostgroup>=0) and either returning an error from CreateSandbox or skipping invalid entries with a clear warning/error message.

Suggested change
parts := strings.Split(entry, ":")
if len(parts) >= 3 {
port, _ := strconv.Atoi(parts[1])
hg, _ := strconv.Atoi(parts[2])
backends = append(backends, BackendServer{
Host: parts[0], Port: port, Hostgroup: hg, MaxConns: 200,
})
}
entry = strings.TrimSpace(entry)
if entry == "" {
continue
}
parts := strings.Split(entry, ":")
if len(parts) != 3 {
fmt.Fprintf(os.Stderr, "proxysql: ignoring invalid backend %q (expected host:port:hostgroup)\n", entry)
continue
}
host := strings.TrimSpace(parts[0])
if host == "" {
fmt.Fprintf(os.Stderr, "proxysql: ignoring backend %q with empty host\n", entry)
continue
}
port, err := strconv.Atoi(strings.TrimSpace(parts[1]))
if err != nil || port <= 0 {
fmt.Fprintf(os.Stderr, "proxysql: ignoring backend %q with invalid port %q\n", entry, parts[1])
continue
}
hg, err := strconv.Atoi(strings.TrimSpace(parts[2]))
if err != nil || hg < 0 {
fmt.Fprintf(os.Stderr, "proxysql: ignoring backend %q with invalid hostgroup %q\n", entry, parts[2])
continue
}
backends = append(backends, BackendServer{
Host: host, Port: port, Hostgroup: hg, MaxConns: 200,
})

Copilot uses AI. Check for mistakes.
}
return backends
}
57 changes: 57 additions & 0 deletions providers/proxysql/proxysql_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package proxysql

import (
"testing"

"github.com/ProxySQL/dbdeployer/providers"
)

func TestProxySQLProviderName(t *testing.T) {
p := NewProxySQLProvider()
if p.Name() != "proxysql" {
t.Errorf("expected 'proxysql', got %q", p.Name())
}
}

func TestProxySQLProviderValidateVersion(t *testing.T) {
p := NewProxySQLProvider()
tests := []struct {
version string
wantErr bool
}{
{"2.7.0", false},
{"3.0.0", false},
{"invalid", true},
}
for _, tt := range tests {
err := p.ValidateVersion(tt.version)
if (err != nil) != tt.wantErr {
t.Errorf("ValidateVersion(%q) error = %v, wantErr %v", tt.version, err, tt.wantErr)
}
}
}

func TestProxySQLProviderRegister(t *testing.T) {
reg := providers.NewRegistry()
if err := Register(reg); err != nil {
t.Fatalf("Register failed: %v", err)
}
p, err := reg.Get("proxysql")
if err != nil {
t.Fatalf("Get failed: %v", err)
}
if p.Name() != "proxysql" {
t.Errorf("expected 'proxysql', got %q", p.Name())
}
}

func TestProxySQLFindBinary(t *testing.T) {
p := NewProxySQLProvider()
path, err := p.FindBinary("2.7.0")
if err != nil {
t.Skipf("proxysql not installed, skipping: %v", err)
}
if path == "" {
t.Error("expected non-empty path")
}
}
Loading