Die Verbindungszeichenkette wurde mit fmt.Sprintf zusammengeklebt. Ein Passwort mit % und @ ergab eine kaputte URL: parse "postgres://aether:8UoNj%CoWJ7ze@qy@localhost:5432/aether": invalid URL escape "%Co" Der Server kam nicht über die Migrationen hinaus. Die key=value-Form hätte dasselbe Problem mit einem Leerzeichen gehabt, nur stiller. config.DSN() liefert jetzt eine postgres://-URL, gebaut mit net/url und net.JoinHostPort (klammert IPv6). pgxpool und golang-migrate nehmen sie beide unverändert – dsnToURL, splitDSN, indexByte und orDefault in pkg/db sind ersatzlos entfallen. Test deckt %, @, :, /, Leerzeichen und IPv6 ab. Dazu: DB_HOST und DB_PORT fehlten in der schlanken .env.example, weshalb localhost statt postgres gegriffen hat. APP_ENV ergänzt. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
65 lines
1.8 KiB
Go
65 lines
1.8 KiB
Go
package config
|
||
|
||
import (
|
||
"net/url"
|
||
"testing"
|
||
)
|
||
|
||
func parseDSN(s string) (*url.URL, error) { return url.Parse(s) }
|
||
|
||
// Ein Passwort darf jedes Zeichen enthalten. Die Fälle hier sind nicht
|
||
// ausgedacht: `8UoNj%CoWJ7ze@qy` stammt aus einer echten Einrichtung und
|
||
// legte den Server lahm – „invalid URL escape %Co", noch vor den Migrationen.
|
||
func TestDSNEscapesPassword(t *testing.T) {
|
||
cases := []struct {
|
||
name string
|
||
pw string
|
||
}{
|
||
{"prozent und at", "8UoNj%CoWJ7ze@qy"},
|
||
{"doppelpunkt", "gehei:mnis"},
|
||
{"schraegstrich", "a/b?c#d"},
|
||
{"leerzeichen", "mit leer zeichen"},
|
||
{"nur sonderzeichen", "%%@@::////"},
|
||
}
|
||
for _, tc := range cases {
|
||
t.Run(tc.name, func(t *testing.T) {
|
||
c := &Config{
|
||
DBHost: "postgres", DBPort: "5432", DBName: "aether",
|
||
DBUser: "aether", DBPassword: tc.pw,
|
||
}
|
||
got := c.DSN()
|
||
// Die Probe ist nicht der erzeugte Text, sondern ob er sich
|
||
// wieder auseinandernehmen lässt – genau das tun pgx und
|
||
// golang-migrate mit ihm.
|
||
u, err := parseDSN(got)
|
||
if err != nil {
|
||
t.Fatalf("DSN nicht parsebar: %v (%s)", err, got)
|
||
}
|
||
if pw, _ := u.User.Password(); pw != tc.pw {
|
||
t.Errorf("Passwort kam als %q zurück, erwartet %q", pw, tc.pw)
|
||
}
|
||
if u.User.Username() != "aether" {
|
||
t.Errorf("Nutzer %q", u.User.Username())
|
||
}
|
||
if u.Host != "postgres:5432" {
|
||
t.Errorf("Host %q", u.Host)
|
||
}
|
||
if u.Path != "/aether" {
|
||
t.Errorf("Datenbank %q", u.Path)
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
// IPv6 muss geklammert werden, sonst frisst der Doppelpunkt den Port.
|
||
func TestDSNIPv6(t *testing.T) {
|
||
c := &Config{DBHost: "::1", DBPort: "5432", DBName: "aether", DBUser: "u"}
|
||
u, err := parseDSN(c.DSN())
|
||
if err != nil {
|
||
t.Fatalf("nicht parsebar: %v", err)
|
||
}
|
||
if u.Hostname() != "::1" || u.Port() != "5432" {
|
||
t.Errorf("Host %q Port %q", u.Hostname(), u.Port())
|
||
}
|
||
}
|