feat(ui): file browser and connection string builder dialogs (#44)
Enter on File Path inputs opens a file browser (load/save, extension filter, hidden toggle, overwrite confirm). Enter on Connection String inputs opens a builder for PostgreSQL, MSSQL and SQLite with masked password/preview, parsing and optional connection test. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 5.5
parent
b38f53c603
commit
0235a28add
@@ -0,0 +1,143 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestBuildConnString(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
fields ConnFields
|
||||
mask bool
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "postgres defaults with db",
|
||||
fields: func() ConnFields { f := DefaultConnFields(ConnPostgres); f.Database = "app"; return f }(),
|
||||
want: "postgres://postgres@localhost:5432/app?sslmode=disable",
|
||||
},
|
||||
{
|
||||
name: "postgres password unmasked",
|
||||
fields: ConnFields{Kind: ConnPostgres, Host: "db", Port: "5433", Database: "x", User: "u", Password: "p@ss/w", SSLMode: "require"},
|
||||
want: "postgres://u:p%40ss%2Fw@db:5433/x?sslmode=require",
|
||||
},
|
||||
{
|
||||
name: "postgres password masked",
|
||||
fields: ConnFields{Kind: ConnPostgres, Host: "db", Port: "5432", Database: "x", User: "u", Password: "secret"},
|
||||
mask: true,
|
||||
want: "postgres://u:****@db:5432/x",
|
||||
},
|
||||
{
|
||||
name: "mssql",
|
||||
fields: ConnFields{Kind: ConnMSSQL, Host: "sql", Port: "1433", Database: "shop", User: "sa", Password: "pw", SSLMode: "disable"},
|
||||
want: "sqlserver://sa:pw@sql:1433?database=shop&encrypt=disable",
|
||||
},
|
||||
{
|
||||
name: "sqlite is the plain path",
|
||||
fields: ConnFields{Kind: ConnSQLite, FilePath: "/tmp/a b.db"},
|
||||
want: "/tmp/a b.db",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := BuildConnString(tt.fields, tt.mask); got != tt.want {
|
||||
t.Errorf("got %q, want %q", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMaskedBuildHidesPassword(t *testing.T) {
|
||||
f := ConnFields{Kind: ConnMSSQL, Host: "h", User: "u", Password: "hunter2"}
|
||||
if got := BuildConnString(f, true); strings.Contains(got, "hunter2") {
|
||||
t.Errorf("masked string leaks password: %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseConnString(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
in string
|
||||
want ConnFields
|
||||
}{
|
||||
{
|
||||
name: "postgres full",
|
||||
in: "postgres://u:p%40ss@db:5433/app?sslmode=require&application_name=x",
|
||||
want: ConnFields{Kind: ConnPostgres, Host: "db", Port: "5433", Database: "app", User: "u", Password: "p@ss", SSLMode: "require"},
|
||||
},
|
||||
{
|
||||
name: "postgresql scheme, default port",
|
||||
in: "postgresql://u@db/app",
|
||||
want: ConnFields{Kind: ConnPostgres, Host: "db", Port: "5432", Database: "app", User: "u"},
|
||||
},
|
||||
{
|
||||
name: "mssql",
|
||||
in: "sqlserver://sa:pw@sql:1444?database=shop&encrypt=true",
|
||||
want: ConnFields{Kind: ConnMSSQL, Host: "sql", Port: "1444", Database: "shop", User: "sa", Password: "pw", SSLMode: "true"},
|
||||
},
|
||||
{
|
||||
name: "sqlite path",
|
||||
in: "/data/app.db",
|
||||
want: ConnFields{Kind: ConnSQLite, FilePath: "/data/app.db"},
|
||||
},
|
||||
{
|
||||
name: "sqlite scheme",
|
||||
in: "sqlite:///data/app.db",
|
||||
want: ConnFields{Kind: ConnSQLite, FilePath: "/data/app.db"},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := ParseConnString(tt.in, ConnPostgres)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got.Extra = nil
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("got %+v, want %+v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseConnStringEmptyUsesHintDefaults(t *testing.T) {
|
||||
got, err := ParseConnString(" ", ConnMSSQL)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got.Kind != ConnMSSQL || got.Port != "1433" || got.Host != "localhost" {
|
||||
t.Errorf("unexpected defaults: %+v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseConnStringInvalid(t *testing.T) {
|
||||
if _, err := ParseConnString("postgres://u:p@host:badport/db", ConnPostgres); err == nil {
|
||||
t.Error("expected error for invalid port")
|
||||
}
|
||||
}
|
||||
|
||||
func TestConnStringRoundTrip(t *testing.T) {
|
||||
for _, in := range []string{
|
||||
"postgres://u:pw@db:5433/app?application_name=x&sslmode=require",
|
||||
"sqlserver://sa:pw@sql:1433?application+name=x&database=shop&encrypt=false",
|
||||
} {
|
||||
f, err := ParseConnString(in, ConnPostgres)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := BuildConnString(f, false); got != in {
|
||||
t.Errorf("round trip: got %q, want %q", got, in)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTestConnectionSQLite(t *testing.T) {
|
||||
if err := TestConnection(ConnFields{Kind: ConnSQLite}); err == nil {
|
||||
t.Error("expected error for empty path")
|
||||
}
|
||||
if err := TestConnection(ConnFields{Kind: ConnSQLite, FilePath: t.TempDir() + "/missing.db"}); err == nil {
|
||||
t.Error("expected error for missing file")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user