[INS-170] Unify JDBC URL Parsing Across Detector and Analyzer (Continued) (#4606)

* changes to unify with analyzer
This commit is contained in:
Mustansir
2026-01-09 12:40:34 +05:00
committed by GitHub
parent 964eab023f
commit 4e02afb015
11 changed files with 559 additions and 45 deletions
+8 -26
View File
@@ -83,7 +83,7 @@ matchLoop:
}
if verify {
j, err := newJDBC(logCtx, jdbcConn)
j, err := NewJDBC(logCtx, jdbcConn)
if err != nil {
continue
}
@@ -206,31 +206,13 @@ func tryRedactRegex(conn string) (string, bool) {
return newConn, true
}
var supportedSubprotocols = map[string]func(logContext.Context, string) (jdbc, error){
"mysql": ParseMySQL,
"postgresql": ParsePostgres,
"sqlserver": ParseSqlServer,
var supportedSubprotocols = map[string]func(logContext.Context, string) (JDBC, error){
"mysql": parseMySQL,
"postgresql": parsePostgres,
"sqlserver": parseSqlServer,
}
type pingResult struct {
err error
determinate bool
}
// ConnectionInfo holds parsed connection information
type ConnectionInfo struct {
Host string // includes port if specified, e.g., "host:port"
Database string
User string
Password string
Params map[string]string
}
type jdbc interface {
ping(context.Context) pingResult
}
func newJDBC(ctx logContext.Context, conn string) (jdbc, error) {
func NewJDBC(ctx logContext.Context, conn string) (JDBC, error) {
// expected format: "jdbc:{subprotocol}:{subname}"
if !strings.HasPrefix(strings.ToLower(conn), "jdbc:") {
return nil, errors.New("expected jdbc prefix")
@@ -242,11 +224,11 @@ func newJDBC(ctx logContext.Context, conn string) (jdbc, error) {
return nil, errors.New("expected a colon separated subprotocol and subname")
}
// get the subprotocol parser
parser, ok := supportedSubprotocols[strings.ToLower(subprotocol)]
if !ok {
return nil, errors.New("unsupported subprotocol")
return nil, fmt.Errorf("unsupported subprotocol: %s", subprotocol)
}
return parser(ctx, subname)
}
+46
View File
@@ -7,6 +7,9 @@ import (
"github.com/google/go-cmp/cmp"
"github.com/kylelemons/godebug/pretty"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
logContext "github.com/trufflesecurity/trufflehog/v3/pkg/context"
"github.com/trufflesecurity/trufflehog/v3/pkg/detectors"
"github.com/trufflesecurity/trufflehog/v3/pkg/engine/ahocorasick"
)
@@ -183,3 +186,46 @@ func TestJdbc_FromDataWithIgnorePattern(t *testing.T) {
})
}
}
func TestParseJDBCURL_EdgeCases(t *testing.T) {
t.Run("MySQL with special characters in password", func(t *testing.T) {
// Special chars: @ # $ % ^ & * ( )
jdbcURL := "jdbc:mysql://user:p@ss%23word@localhost:3306/testdb"
jdbc, err := NewJDBC(logContext.Background(), jdbcURL)
require.NoError(t, err)
info := jdbc.GetConnectionInfo()
assert.NoError(t, err)
assert.NotNil(t, info)
assert.Equal(t, "user", info.User)
// URL encoding should be handled by url.Parse
})
t.Run("PostgreSQL with empty database", func(t *testing.T) {
jdbcURL := "jdbc:postgresql://user:pass@localhost:5432"
jdbc, err := NewJDBC(logContext.Background(), jdbcURL)
require.NoError(t, err)
info := jdbc.GetConnectionInfo()
assert.Equal(t, "postgres", info.Database) // default
})
t.Run("SQL Server with multiple semicolon params", func(t *testing.T) {
jdbcURL := "jdbc:sqlserver://localhost:1433;database=testdb;user=sa;password=Pass123;encrypt=true;trustServerCertificate=false"
jdbc, err := NewJDBC(logContext.Background(), jdbcURL)
require.NoError(t, err)
info := jdbc.GetConnectionInfo()
assert.Equal(t, "testdb", info.Database)
assert.Equal(t, "sa", info.User)
assert.Equal(t, "Pass123", info.Password)
})
t.Run("MySQL missing host", func(t *testing.T) {
// Missing // after prefix - will trigger error
jdbcURL := "jdbc:mysql:/testdb"
_, err := NewJDBC(logContext.Background(), jdbcURL)
assert.Error(t, err)
assert.Contains(t, err.Error(), "expected host to start with //")
})
}
+56
View File
@@ -0,0 +1,56 @@
package jdbc
import (
"context"
)
type DatabaseType int
const (
Unknown DatabaseType = iota
MySQL
PostgreSQL
SQLServer
)
func (dt DatabaseType) String() string {
switch dt {
case MySQL:
return "mysql"
case PostgreSQL:
return "postgresql"
case SQLServer:
return "sqlserver"
default:
return "unknown"
}
}
type pingResult struct {
err error
determinate bool
}
// ConnectionInfo holds parsed connection information
type ConnectionInfo struct {
Host string // includes port if specified, e.g., "host:port"
Database string
User string
Password string
Params map[string]string
}
type jdbcPinger interface {
ping(context.Context) pingResult
}
// public interfaces for analyzer
type JDBCParser interface {
GetConnectionInfo() *ConnectionInfo
GetDBType() DatabaseType
BuildConnectionString() string
}
type JDBC interface {
jdbcPinger
JDBCParser
}
+18 -4
View File
@@ -16,12 +16,26 @@ type MysqlJDBC struct {
ConnectionInfo
}
var _ JDBC = (*MysqlJDBC)(nil)
func (s *MysqlJDBC) ping(ctx context.Context) pingResult {
return ping(ctx, "mysql", isMySQLErrorDeterminate,
BuildMySQLConnectionString(s.Host, "", s.User, s.Password, s.Params))
buildMySQLConnectionString(s.Host, "", s.User, s.Password, s.Params))
}
func BuildMySQLConnectionString(host, database, user, password string, params map[string]string) string {
func (s *MysqlJDBC) GetDBType() DatabaseType {
return MySQL
}
func (s *MysqlJDBC) GetConnectionInfo() *ConnectionInfo {
return &s.ConnectionInfo
}
func (s *MysqlJDBC) BuildConnectionString() string {
return buildMySQLConnectionString(s.Host, s.Database, s.User, s.Password, s.Params)
}
func buildMySQLConnectionString(host, database, user, password string, params map[string]string) string {
conn := host + "/" + database
userPass := user
if password != "" {
@@ -56,7 +70,7 @@ func isMySQLErrorDeterminate(err error) bool {
return false
}
func ParseMySQL(ctx logContext.Context, subname string) (jdbc, error) {
func parseMySQL(ctx logContext.Context, subname string) (JDBC, error) {
// expected form: [subprotocol:]//[user:password@]HOST[/DB][?key=val[&key=val]]
if !strings.HasPrefix(subname, "//") {
return nil, errors.New("expected host to start with //")
@@ -86,7 +100,7 @@ func ParseMySQL(ctx logContext.Context, subname string) (jdbc, error) {
}, nil
}
func parseMySQLURI(ctx logContext.Context, subname string) (jdbc, error) {
func parseMySQLURI(ctx logContext.Context, subname string) (JDBC, error) {
// for standard URI format, which is all i've seen for JDBC
u, err := url.Parse(subname)
+1 -1
View File
@@ -91,7 +91,7 @@ func TestMySQL(t *testing.T) {
}
for _, tt := range tests {
t.Run(tt.input, func(t *testing.T) {
j, err := ParseMySQL(logContext.Background(), tt.input)
j, err := parseMySQL(logContext.Background(), tt.input)
if err != nil {
got := result{ParseErr: true}
+145 -2
View File
@@ -4,6 +4,8 @@ import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
logContext "github.com/trufflesecurity/trufflehog/v3/pkg/context"
)
@@ -58,7 +60,7 @@ func TestParseMySQLMissingCredentials(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := logContext.AddLogger(context.Background())
j, err := ParseMySQL(ctx, tt.subname)
j, err := parseMySQL(ctx, tt.subname)
if tt.shouldBeNil {
if j != nil {
@@ -102,7 +104,7 @@ func TestParseMySQLUsernameRecognition(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := logContext.AddLogger(context.Background())
j, err := ParseMySQL(ctx, tt.subname)
j, err := parseMySQL(ctx, tt.subname)
if err != nil {
t.Fatalf("parseMySQL() error = %v", err)
}
@@ -115,3 +117,144 @@ func TestParseMySQLUsernameRecognition(t *testing.T) {
})
}
}
func TestMySQL_ParseJDBCURL(t *testing.T) {
tests := []struct {
name string
jdbcURL string
wantHost string
wantDB string
wantUser string
wantPass string
wantErr bool
}{
{
name: "basic URL with all parts",
jdbcURL: "jdbc:mysql://root:password@localhost:3306/testdb",
wantHost: "tcp(localhost:3306)",
wantDB: "testdb",
wantUser: "root",
wantPass: "password",
},
{
name: "URL with default port",
jdbcURL: "jdbc:mysql://user:pass@dbhost/mydb",
wantHost: "tcp(dbhost)",
wantDB: "mydb",
wantUser: "user",
wantPass: "pass",
},
{
name: "URL with query params for credentials",
jdbcURL: "jdbc:mysql://dbhost:3307/testdb?user=admin&password=secret",
wantHost: "tcp(dbhost:3307)",
wantDB: "testdb",
wantUser: "admin",
wantPass: "secret",
},
{
name: "invalid URL - missing jdbc:mysql prefix",
jdbcURL: "postgresql://user:pass@localhost/db",
wantErr: true,
},
{
name: "invalid URL - missing //",
jdbcURL: "jdbc:mysql:user:pass@localhost/db",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
jdbc, err := NewJDBC(logContext.Background(), tt.jdbcURL)
if tt.wantErr {
assert.Error(t, err)
return
}
require.NoError(t, err)
info := jdbc.GetConnectionInfo()
assert.Equal(t, tt.wantHost, info.Host)
assert.Equal(t, tt.wantDB, info.Database)
assert.Equal(t, tt.wantUser, info.User)
assert.Equal(t, tt.wantPass, info.Password)
})
}
}
func TestMySQL_ParseJDBCURL_DSNAddressParsing(t *testing.T) {
tests := []struct {
name string
jdbcURL string
wantHost string
}{
{
name: "DSN format with explicit port",
jdbcURL: "jdbc:mysql://myuser:mypass@tcp(localhost:3307)/mydb",
wantHost: "tcp(localhost:3307)",
},
{
name: "DSN format with default port",
jdbcURL: "jdbc:mysql://myuser:mypass@tcp(db.example.com:3306)/testdb",
wantHost: "tcp(db.example.com:3306)",
},
{
name: "DSN format without port",
jdbcURL: "jdbc:mysql://myuser:mypass@tcp(myhost)/mydb",
wantHost: "tcp(myhost:3306)",
},
{
name: "Simple host:port format",
jdbcURL: "jdbc:mysql://root:[email protected]:3308/database",
wantHost: "tcp(mysql.server.com:3308)",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
jdbc, err := NewJDBC(logContext.Background(), tt.jdbcURL)
require.NoError(t, err)
info := jdbc.GetConnectionInfo()
assert.Equal(t, tt.wantHost, info.Host)
})
}
}
func TestMySQL_BuildNativeConnectionString(t *testing.T) {
tests := []struct {
name string
info *ConnectionInfo
wantUser string
wantPass string
wantHost string
wantDB string
}{
{
name: "basic connection",
info: &ConnectionInfo{
Host: "localhost",
Database: "testdb",
User: "root",
Password: "secret",
},
wantUser: "root",
wantPass: "secret",
wantHost: "localhost",
wantDB: "testdb",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
mysqlJDBC := &MysqlJDBC{
ConnectionInfo: *tt.info,
}
connStr := mysqlJDBC.BuildConnectionString()
// MySQL format: [user[:password]@]tcp(host:port)/database?timeout=10s
assert.Contains(t, connStr, tt.wantUser)
assert.Contains(t, connStr, tt.wantPass)
assert.Contains(t, connStr, tt.wantHost)
assert.Contains(t, connStr, "/"+tt.wantDB)
})
}
}
+18 -4
View File
@@ -16,6 +16,8 @@ type PostgresJDBC struct {
ConnectionInfo
}
var _ JDBC = (*PostgresJDBC)(nil)
func (s *PostgresJDBC) ping(ctx context.Context) pingResult {
// It is crucial that we try to build a connection string ourselves before using the one we found. This is because
// if the found connection string doesn't include a username, the driver will attempt to connect using the current
@@ -23,11 +25,23 @@ func (s *PostgresJDBC) ping(ctx context.Context) pingResult {
// contrast, when we build a connection string ourselves, if there's no username, we try 'postgres' instead, which
// actually has a chance of working.
return ping(ctx, "postgres", isPostgresErrorDeterminate,
BuildPostgresConnectionString(s.Host, s.User, s.Password, "postgres", s.Params, true),
BuildPostgresConnectionString(s.Host, s.User, s.Password, "postgres", s.Params, false),
buildPostgresConnectionString(s.Host, s.User, s.Password, "postgres", s.Params, true),
buildPostgresConnectionString(s.Host, s.User, s.Password, "postgres", s.Params, false),
)
}
func (s *PostgresJDBC) GetDBType() DatabaseType {
return PostgreSQL
}
func (s *PostgresJDBC) GetConnectionInfo() *ConnectionInfo {
return &s.ConnectionInfo
}
func (s *PostgresJDBC) BuildConnectionString() string {
return buildPostgresConnectionString(s.Host, s.User, s.Password, s.Database, s.Params, true)
}
func isPostgresErrorDeterminate(err error) bool {
// Postgres codes from https://www.postgresql.org/docs/current/errcodes-appendix.html
if pqErr, isPostgresError := err.(*pq.Error); isPostgresError {
@@ -58,7 +72,7 @@ func joinKeyValues(m map[string]string, sep string) string {
return strings.Join(data, sep)
}
func ParsePostgres(ctx logContext.Context, subname string) (jdbc, error) {
func parsePostgres(ctx logContext.Context, subname string) (JDBC, error) {
// expected form: [subprotocol:]//[user:password@]HOST[/DB][?key=val[&key=val]]
if !strings.HasPrefix(subname, "//") {
@@ -122,7 +136,7 @@ func ParsePostgres(ctx logContext.Context, subname string) (jdbc, error) {
return postgresJDBC, nil
}
func BuildPostgresConnectionString(host string, user string, password string, dbName string, params map[string]string, includeDbName bool) string {
func buildPostgresConnectionString(host string, user string, password string, dbName string, params map[string]string, includeDbName bool) string {
data := map[string]string{
// default user
"user": "postgres",
@@ -121,7 +121,7 @@ func TestPostgres(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
j, err := ParsePostgres(logContext.Background(), tt.input)
j, err := parsePostgres(logContext.Background(), tt.input)
if err != nil {
got := result{ParseErr: true}
+142 -2
View File
@@ -4,6 +4,8 @@ import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
logContext "github.com/trufflesecurity/trufflehog/v3/pkg/context"
)
@@ -47,7 +49,7 @@ func TestParsePostgresMissingCredentials(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := logContext.AddLogger(context.Background())
j, err := ParsePostgres(ctx, tt.subname)
j, err := parsePostgres(ctx, tt.subname)
if tt.shouldBeNil {
if j != nil {
@@ -86,7 +88,7 @@ func TestParsePostgresUsernameRecognition(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ctx := logContext.AddLogger(context.Background())
j, err := ParsePostgres(ctx, tt.subname)
j, err := parsePostgres(ctx, tt.subname)
if err != nil {
t.Fatalf("ParsePostgres() error = %v", err)
}
@@ -98,3 +100,141 @@ func TestParsePostgresUsernameRecognition(t *testing.T) {
})
}
}
func TestPostgreSQLHandler_ParseJDBCURL(t *testing.T) {
tests := []struct {
name string
jdbcURL string
wantHost string
wantDB string
wantUser string
wantPass string
wantSSLMode string
wantErr bool
}{
{
name: "basic URL with all parts",
jdbcURL: "jdbc:postgresql://postgres:secret@localhost:5432/mydb",
wantHost: "localhost:5432",
wantDB: "mydb",
wantUser: "postgres",
wantPass: "secret",
},
{
name: "URL with default port",
jdbcURL: "jdbc:postgresql://user:pass@dbhost/testdb",
wantHost: "dbhost",
wantDB: "testdb",
wantUser: "user",
wantPass: "pass",
},
{
name: "URL with default database",
jdbcURL: "jdbc:postgresql://user:pass@dbhost:5433",
wantHost: "dbhost:5433",
wantDB: "postgres",
wantUser: "user",
wantPass: "pass",
},
{
name: "URL with SSL mode",
jdbcURL: "jdbc:postgresql://user:pass@dbhost:5432/mydb?sslmode=require",
wantHost: "dbhost:5432",
wantDB: "mydb",
wantUser: "user",
wantPass: "pass",
wantSSLMode: "require",
},
{
name: "invalid URL - missing jdbc:postgresql prefix",
jdbcURL: "mysql://user:pass@localhost/db",
wantErr: true,
},
{
name: "invalid URL - missing //",
jdbcURL: "jdbc:postgresql:user:pass@localhost/db",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
jdbc, err := NewJDBC(logContext.Background(), tt.jdbcURL)
if tt.wantErr {
assert.Error(t, err)
return
}
require.NoError(t, err)
info := jdbc.GetConnectionInfo()
assert.Equal(t, tt.wantHost, info.Host)
assert.Equal(t, tt.wantDB, info.Database)
assert.Equal(t, tt.wantUser, info.User)
assert.Equal(t, tt.wantPass, info.Password)
if tt.wantSSLMode != "" {
assert.Equal(t, tt.wantSSLMode, info.Params["sslmode"])
}
})
}
}
func TestPostgreSQLHandler_BuildNativeConnectionString(t *testing.T) {
tests := []struct {
name string
info *ConnectionInfo
want map[string]string // key-value pairs that should be in the connection string
}{
{
name: "basic connection",
info: &ConnectionInfo{
Host: "localhost",
Database: "testdb",
User: "postgres",
Password: "secret",
Params: map[string]string{
"connect_timeout": "10",
},
},
want: map[string]string{
"host": "localhost",
"dbname": "testdb",
"user": "postgres",
"password": "secret",
"connect_timeout": "10",
},
},
{
name: "with SSL mode",
info: &ConnectionInfo{
Host: "dbhost:5433",
Database: "mydb",
User: "user",
Password: "pass",
Params: map[string]string{"sslmode": "require"},
},
want: map[string]string{
"host": "dbhost",
"port": "5433",
"dbname": "mydb",
"sslmode": "require",
"user": "user",
"password": "pass",
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
jdbc := &PostgresJDBC{
ConnectionInfo: *tt.info,
}
connStr := jdbc.BuildConnectionString()
// Verify all expected key-value pairs are in the connection string
for key, expectedValue := range tt.want {
expectedPair := key + "=" + expectedValue
assert.Contains(t, connStr, expectedPair)
}
})
}
}
+17 -3
View File
@@ -15,9 +15,23 @@ type SqlServerJDBC struct {
ConnectionInfo
}
var _ JDBC = (*SqlServerJDBC)(nil)
func (s *SqlServerJDBC) ping(ctx context.Context) pingResult {
return ping(ctx, "mssql", isSqlServerErrorDeterminate,
BuildSQLServerConnectionString(s.Host, s.User, s.Password, "master", map[string]string{"connection+timeout": "5"}))
buildSQLServerConnectionString(s.Host, s.User, s.Password, "master", map[string]string{"connection+timeout": "5"}))
}
func (s *SqlServerJDBC) GetDBType() DatabaseType {
return SQLServer
}
func (s *SqlServerJDBC) GetConnectionInfo() *ConnectionInfo {
return &s.ConnectionInfo
}
func (s *SqlServerJDBC) BuildConnectionString() string {
return buildSQLServerConnectionString(s.Host, s.User, s.Password, s.Database, s.Params)
}
func isSqlServerErrorDeterminate(err error) bool {
@@ -34,7 +48,7 @@ func isSqlServerErrorDeterminate(err error) bool {
return false
}
func ParseSqlServer(ctx logContext.Context, subname string) (jdbc, error) {
func parseSqlServer(ctx logContext.Context, subname string) (JDBC, error) {
if !strings.HasPrefix(subname, "//") {
return nil, errors.New("expected connection to start with //")
}
@@ -93,7 +107,7 @@ func ParseSqlServer(ctx logContext.Context, subname string) (jdbc, error) {
}, nil
}
func BuildSQLServerConnectionString(host, user, password, database string, params map[string]string) string {
func buildSQLServerConnectionString(host, user, password, database string, params map[string]string) string {
conn := fmt.Sprintf("sqlserver://%s:%s@%s?database=%s", user, password, host, database)
if len(params) > 0 {
for k, v := range params {
+107 -2
View File
@@ -4,6 +4,8 @@ import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
logContext "github.com/trufflesecurity/trufflehog/v3/pkg/context"
)
@@ -54,7 +56,7 @@ func TestParseSqlServerMissingCredentials(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
ctx := logContext.AddLogger(context.Background())
j, err := ParseSqlServer(ctx, tt.subname)
j, err := parseSqlServer(ctx, tt.subname)
if tt.shouldBeNil {
if j != nil {
@@ -101,7 +103,7 @@ func TestParseSqlServerUserIgnoredBug2(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
ctx := logContext.AddLogger(context.Background())
j, err := ParseSqlServer(ctx, tt.subname)
j, err := parseSqlServer(ctx, tt.subname)
if err != nil {
t.Fatalf("parseSqlServer() error = %v", err)
}
@@ -115,3 +117,106 @@ func TestParseSqlServerUserIgnoredBug2(t *testing.T) {
})
}
}
func TestSQLServerHandler_ParseJDBCURL(t *testing.T) {
tests := []struct {
name string
jdbcURL string
wantHost string
wantDB string
wantUser string
wantPass string
wantErr bool
}{
{
name: "basic URL with semicolon params",
jdbcURL: "jdbc:sqlserver://localhost:1433;database=testdb;user=sa;password=Pass123",
wantHost: "localhost:1433",
wantDB: "testdb",
wantUser: "sa",
wantPass: "Pass123",
},
{
name: "URL with default port and database",
jdbcURL: "jdbc:sqlserver://dbhost;user=testuser;password=secret",
wantHost: "dbhost:1433",
wantDB: "master",
wantUser: "testuser",
wantPass: "secret",
},
{
name: "URL with port in host",
jdbcURL: "jdbc:sqlserver://server.example.com:1434;databaseName=mydb;userId=admin;pwd=admin123",
wantHost: "server.example.com:1434",
wantDB: "mydb",
wantUser: "admin",
wantPass: "admin123",
},
{
name: "invalid URL - missing jdbc:sqlserver prefix",
jdbcURL: "jdbc:mysql://localhost/db",
wantErr: true,
},
{
name: "invalid URL - missing //",
jdbcURL: "jdbc:sqlserver:localhost;database=test",
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
jdbc, err := NewJDBC(logContext.Background(), tt.jdbcURL)
if tt.wantErr {
assert.Error(t, err)
return
}
require.NoError(t, err)
info := jdbc.GetConnectionInfo()
assert.Equal(t, tt.wantHost, info.Host)
assert.Equal(t, tt.wantDB, info.Database)
assert.Equal(t, tt.wantUser, info.User)
assert.Equal(t, tt.wantPass, info.Password)
})
}
}
func TestSQLServerHandler_BuildNativeConnectionString(t *testing.T) {
tests := []struct {
name string
info *ConnectionInfo
wantUser string
wantPass string
wantHost string
wantDB string
}{
{
name: "basic connection",
info: &ConnectionInfo{
Host: "localhost",
Database: "testdb",
User: "sa",
Password: "Pass123",
},
wantUser: "sa",
wantPass: "Pass123",
wantHost: "localhost",
wantDB: "testdb",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
jdbc := &SqlServerJDBC{
ConnectionInfo: *tt.info,
}
connStr := jdbc.BuildConnectionString()
// SQL Server format: sqlserver://user:password@host:port?database=db&connection+timeout=10
assert.Contains(t, connStr, tt.wantUser)
assert.Contains(t, connStr, tt.wantPass)
assert.Contains(t, connStr, tt.wantHost)
assert.Contains(t, connStr, "database="+tt.wantDB)
})
}
}