1 package pgconn_test
2
3 import (
4 "context"
5 "crypto/tls"
6 "fmt"
7 "os"
8 "os/user"
9 "runtime"
10 "strconv"
11 "strings"
12 "testing"
13 "time"
14
15 "github.com/jackc/pgx/v5/pgconn"
16 "github.com/stretchr/testify/assert"
17 "github.com/stretchr/testify/require"
18 )
19
20 func skipOnWindows(t *testing.T) {
21 if runtime.GOOS == "windows" {
22 t.Skip("FIXME: skipping on Windows, investigate why this test fails in CI environment")
23 }
24 }
25
26 func getDefaultPort(t *testing.T) uint16 {
27 if envPGPORT := os.Getenv("PGPORT"); envPGPORT != "" {
28 p, err := strconv.ParseUint(envPGPORT, 10, 16)
29 require.NoError(t, err)
30 return uint16(p)
31 }
32 return 5432
33 }
34
35 func getDefaultUser(t *testing.T) string {
36 if pguser := os.Getenv("PGUSER"); pguser != "" {
37 return pguser
38 }
39
40 var osUserName string
41 osUser, err := user.Current()
42 if err == nil {
43
44
45 if runtime.GOOS == "windows" && strings.Contains(osUser.Username, "\\") {
46 osUserName = osUser.Username[strings.LastIndex(osUser.Username, "\\")+1:]
47 } else {
48 osUserName = osUser.Username
49 }
50 }
51
52 return osUserName
53 }
54
55 func TestParseConfig(t *testing.T) {
56 skipOnWindows(t)
57 t.Parallel()
58
59 config, err := pgconn.ParseConfig("")
60 require.NoError(t, err)
61 defaultHost := config.Host
62
63 defaultUser := getDefaultUser(t)
64 defaultPort := getDefaultPort(t)
65
66 tests := []struct {
67 name string
68 connString string
69 config *pgconn.Config
70 }{
71
72 {
73 name: "sslmode not set (prefer)",
74 connString: "postgres://jack:secret@localhost:5432/mydb",
75 config: &pgconn.Config{
76 User: "jack",
77 Password: "secret",
78 Host: "localhost",
79 Port: 5432,
80 Database: "mydb",
81 TLSConfig: &tls.Config{
82 InsecureSkipVerify: true,
83 ServerName: "localhost",
84 },
85 RuntimeParams: map[string]string{},
86 Fallbacks: []*pgconn.FallbackConfig{
87 {
88 Host: "localhost",
89 Port: 5432,
90 TLSConfig: nil,
91 },
92 },
93 },
94 },
95 {
96 name: "sslmode disable",
97 connString: "postgres://jack:secret@localhost:5432/mydb?sslmode=disable",
98 config: &pgconn.Config{
99 User: "jack",
100 Password: "secret",
101 Host: "localhost",
102 Port: 5432,
103 Database: "mydb",
104 TLSConfig: nil,
105 RuntimeParams: map[string]string{},
106 },
107 },
108 {
109 name: "sslmode allow",
110 connString: "postgres://jack:secret@localhost:5432/mydb?sslmode=allow",
111 config: &pgconn.Config{
112 User: "jack",
113 Password: "secret",
114 Host: "localhost",
115 Port: 5432,
116 Database: "mydb",
117 TLSConfig: nil,
118 RuntimeParams: map[string]string{},
119 Fallbacks: []*pgconn.FallbackConfig{
120 {
121 Host: "localhost",
122 Port: 5432,
123 TLSConfig: &tls.Config{
124 InsecureSkipVerify: true,
125 ServerName: "localhost",
126 },
127 },
128 },
129 },
130 },
131 {
132 name: "sslmode prefer",
133 connString: "postgres://jack:secret@localhost:5432/mydb?sslmode=prefer",
134 config: &pgconn.Config{
135
136 User: "jack",
137 Password: "secret",
138 Host: "localhost",
139 Port: 5432,
140 Database: "mydb",
141 TLSConfig: &tls.Config{
142 InsecureSkipVerify: true,
143 ServerName: "localhost",
144 },
145 RuntimeParams: map[string]string{},
146 Fallbacks: []*pgconn.FallbackConfig{
147 {
148 Host: "localhost",
149 Port: 5432,
150 TLSConfig: nil,
151 },
152 },
153 },
154 },
155 {
156 name: "sslmode require",
157 connString: "postgres://jack:secret@localhost:5432/mydb?sslmode=require",
158 config: &pgconn.Config{
159 User: "jack",
160 Password: "secret",
161 Host: "localhost",
162 Port: 5432,
163 Database: "mydb",
164 TLSConfig: &tls.Config{
165 InsecureSkipVerify: true,
166 ServerName: "localhost",
167 },
168 RuntimeParams: map[string]string{},
169 },
170 },
171 {
172 name: "sslmode verify-ca",
173 connString: "postgres://jack:secret@localhost:5432/mydb?sslmode=verify-ca",
174 config: &pgconn.Config{
175 User: "jack",
176 Password: "secret",
177 Host: "localhost",
178 Port: 5432,
179 Database: "mydb",
180 TLSConfig: &tls.Config{
181 InsecureSkipVerify: true,
182 ServerName: "localhost",
183 },
184 RuntimeParams: map[string]string{},
185 },
186 },
187 {
188 name: "sslmode verify-full",
189 connString: "postgres://jack:secret@localhost:5432/mydb?sslmode=verify-full",
190 config: &pgconn.Config{
191 User: "jack",
192 Password: "secret",
193 Host: "localhost",
194 Port: 5432,
195 Database: "mydb",
196 TLSConfig: &tls.Config{ServerName: "localhost"},
197 RuntimeParams: map[string]string{},
198 },
199 },
200 {
201 name: "database url everything",
202 connString: "postgres://jack:secret@localhost:5432/mydb?sslmode=disable&application_name=pgxtest&search_path=myschema&connect_timeout=5",
203 config: &pgconn.Config{
204 User: "jack",
205 Password: "secret",
206 Host: "localhost",
207 Port: 5432,
208 Database: "mydb",
209 TLSConfig: nil,
210 ConnectTimeout: 5 * time.Second,
211 RuntimeParams: map[string]string{
212 "application_name": "pgxtest",
213 "search_path": "myschema",
214 },
215 },
216 },
217 {
218 name: "database url missing password",
219 connString: "postgres://jack@localhost:5432/mydb?sslmode=disable",
220 config: &pgconn.Config{
221 User: "jack",
222 Host: "localhost",
223 Port: 5432,
224 Database: "mydb",
225 TLSConfig: nil,
226 RuntimeParams: map[string]string{},
227 },
228 },
229 {
230 name: "database url missing user and password",
231 connString: "postgres://localhost:5432/mydb?sslmode=disable",
232 config: &pgconn.Config{
233 User: defaultUser,
234 Host: "localhost",
235 Port: 5432,
236 Database: "mydb",
237 TLSConfig: nil,
238 RuntimeParams: map[string]string{},
239 },
240 },
241 {
242 name: "database url missing port",
243 connString: "postgres://jack:secret@localhost:5432/mydb?sslmode=disable",
244 config: &pgconn.Config{
245 User: "jack",
246 Password: "secret",
247 Host: "localhost",
248 Port: 5432,
249 Database: "mydb",
250 TLSConfig: nil,
251 RuntimeParams: map[string]string{},
252 },
253 },
254 {
255 name: "database url unix domain socket host",
256 connString: "postgres:///foo?host=/tmp",
257 config: &pgconn.Config{
258 User: defaultUser,
259 Host: "/tmp",
260 Port: defaultPort,
261 Database: "foo",
262 TLSConfig: nil,
263 RuntimeParams: map[string]string{},
264 },
265 },
266 {
267 name: "database url unix domain socket host on windows",
268 connString: "postgres:///foo?host=C:\\tmp",
269 config: &pgconn.Config{
270 User: defaultUser,
271 Host: "C:\\tmp",
272 Port: defaultPort,
273 Database: "foo",
274 TLSConfig: nil,
275 RuntimeParams: map[string]string{},
276 },
277 },
278 {
279 name: "database url dbname",
280 connString: "postgres://localhost/?dbname=foo&sslmode=disable",
281 config: &pgconn.Config{
282 User: defaultUser,
283 Host: "localhost",
284 Port: defaultPort,
285 Database: "foo",
286 TLSConfig: nil,
287 RuntimeParams: map[string]string{},
288 },
289 },
290 {
291 name: "database url postgresql protocol",
292 connString: "postgresql://jack@localhost:5432/mydb?sslmode=disable",
293 config: &pgconn.Config{
294 User: "jack",
295 Host: "localhost",
296 Port: 5432,
297 Database: "mydb",
298 TLSConfig: nil,
299 RuntimeParams: map[string]string{},
300 },
301 },
302 {
303 name: "database url IPv4 with port",
304 connString: "postgresql://jack@127.0.0.1:5433/mydb?sslmode=disable",
305 config: &pgconn.Config{
306 User: "jack",
307 Host: "127.0.0.1",
308 Port: 5433,
309 Database: "mydb",
310 TLSConfig: nil,
311 RuntimeParams: map[string]string{},
312 },
313 },
314 {
315 name: "database url IPv6 with port",
316 connString: "postgresql://jack@[2001:db8::1]:5433/mydb?sslmode=disable",
317 config: &pgconn.Config{
318 User: "jack",
319 Host: "2001:db8::1",
320 Port: 5433,
321 Database: "mydb",
322 TLSConfig: nil,
323 RuntimeParams: map[string]string{},
324 },
325 },
326 {
327 name: "database url IPv6 no port",
328 connString: "postgresql://jack@[2001:db8::1]/mydb?sslmode=disable",
329 config: &pgconn.Config{
330 User: "jack",
331 Host: "2001:db8::1",
332 Port: defaultPort,
333 Database: "mydb",
334 TLSConfig: nil,
335 RuntimeParams: map[string]string{},
336 },
337 },
338 {
339 name: "DSN everything",
340 connString: "user=jack password=secret host=localhost port=5432 dbname=mydb sslmode=disable application_name=pgxtest search_path=myschema connect_timeout=5",
341 config: &pgconn.Config{
342 User: "jack",
343 Password: "secret",
344 Host: "localhost",
345 Port: 5432,
346 Database: "mydb",
347 TLSConfig: nil,
348 ConnectTimeout: 5 * time.Second,
349 RuntimeParams: map[string]string{
350 "application_name": "pgxtest",
351 "search_path": "myschema",
352 },
353 },
354 },
355 {
356 name: "DSN with escaped single quote",
357 connString: "user=jack\\'s password=secret host=localhost port=5432 dbname=mydb sslmode=disable",
358 config: &pgconn.Config{
359 User: "jack's",
360 Password: "secret",
361 Host: "localhost",
362 Port: 5432,
363 Database: "mydb",
364 TLSConfig: nil,
365 RuntimeParams: map[string]string{},
366 },
367 },
368 {
369 name: "DSN with escaped backslash",
370 connString: "user=jack password=sooper\\\\secret host=localhost port=5432 dbname=mydb sslmode=disable",
371 config: &pgconn.Config{
372 User: "jack",
373 Password: "sooper\\secret",
374 Host: "localhost",
375 Port: 5432,
376 Database: "mydb",
377 TLSConfig: nil,
378 RuntimeParams: map[string]string{},
379 },
380 },
381 {
382 name: "DSN with single quoted values",
383 connString: "user='jack' host='localhost' dbname='mydb' sslmode='disable'",
384 config: &pgconn.Config{
385 User: "jack",
386 Host: "localhost",
387 Port: defaultPort,
388 Database: "mydb",
389 TLSConfig: nil,
390 RuntimeParams: map[string]string{},
391 },
392 },
393 {
394 name: "DSN with single quoted value with escaped single quote",
395 connString: "user='jack\\'s' host='localhost' dbname='mydb' sslmode='disable'",
396 config: &pgconn.Config{
397 User: "jack's",
398 Host: "localhost",
399 Port: defaultPort,
400 Database: "mydb",
401 TLSConfig: nil,
402 RuntimeParams: map[string]string{},
403 },
404 },
405 {
406 name: "DSN with empty single quoted value",
407 connString: "user='jack' password='' host='localhost' dbname='mydb' sslmode='disable'",
408 config: &pgconn.Config{
409 User: "jack",
410 Host: "localhost",
411 Port: defaultPort,
412 Database: "mydb",
413 TLSConfig: nil,
414 RuntimeParams: map[string]string{},
415 },
416 },
417 {
418 name: "DSN with space between key and value",
419 connString: "user = 'jack' password = '' host = 'localhost' dbname = 'mydb' sslmode='disable'",
420 config: &pgconn.Config{
421 User: "jack",
422 Host: "localhost",
423 Port: defaultPort,
424 Database: "mydb",
425 TLSConfig: nil,
426 RuntimeParams: map[string]string{},
427 },
428 },
429 {
430 name: "URL multiple hosts",
431 connString: "postgres://jack:secret@foo,bar,baz/mydb?sslmode=disable",
432 config: &pgconn.Config{
433 User: "jack",
434 Password: "secret",
435 Host: "foo",
436 Port: defaultPort,
437 Database: "mydb",
438 TLSConfig: nil,
439 RuntimeParams: map[string]string{},
440 Fallbacks: []*pgconn.FallbackConfig{
441 {
442 Host: "bar",
443 Port: defaultPort,
444 TLSConfig: nil,
445 },
446 {
447 Host: "baz",
448 Port: defaultPort,
449 TLSConfig: nil,
450 },
451 },
452 },
453 },
454 {
455 name: "URL multiple hosts and ports",
456 connString: "postgres://jack:secret@foo:1,bar:2,baz:3/mydb?sslmode=disable",
457 config: &pgconn.Config{
458 User: "jack",
459 Password: "secret",
460 Host: "foo",
461 Port: 1,
462 Database: "mydb",
463 TLSConfig: nil,
464 RuntimeParams: map[string]string{},
465 Fallbacks: []*pgconn.FallbackConfig{
466 {
467 Host: "bar",
468 Port: 2,
469 TLSConfig: nil,
470 },
471 {
472 Host: "baz",
473 Port: 3,
474 TLSConfig: nil,
475 },
476 },
477 },
478 },
479
480 {
481 name: "URL without host but with port still uses default host",
482 connString: "postgres://jack:secret@:1/mydb?sslmode=disable",
483 config: &pgconn.Config{
484 User: "jack",
485 Password: "secret",
486 Host: defaultHost,
487 Port: 1,
488 Database: "mydb",
489 TLSConfig: nil,
490 RuntimeParams: map[string]string{},
491 },
492 },
493 {
494 name: "DSN multiple hosts one port",
495 connString: "user=jack password=secret host=foo,bar,baz port=5432 dbname=mydb sslmode=disable",
496 config: &pgconn.Config{
497 User: "jack",
498 Password: "secret",
499 Host: "foo",
500 Port: 5432,
501 Database: "mydb",
502 TLSConfig: nil,
503 RuntimeParams: map[string]string{},
504 Fallbacks: []*pgconn.FallbackConfig{
505 {
506 Host: "bar",
507 Port: 5432,
508 TLSConfig: nil,
509 },
510 {
511 Host: "baz",
512 Port: 5432,
513 TLSConfig: nil,
514 },
515 },
516 },
517 },
518 {
519 name: "DSN multiple hosts multiple ports",
520 connString: "user=jack password=secret host=foo,bar,baz port=1,2,3 dbname=mydb sslmode=disable",
521 config: &pgconn.Config{
522 User: "jack",
523 Password: "secret",
524 Host: "foo",
525 Port: 1,
526 Database: "mydb",
527 TLSConfig: nil,
528 RuntimeParams: map[string]string{},
529 Fallbacks: []*pgconn.FallbackConfig{
530 {
531 Host: "bar",
532 Port: 2,
533 TLSConfig: nil,
534 },
535 {
536 Host: "baz",
537 Port: 3,
538 TLSConfig: nil,
539 },
540 },
541 },
542 },
543 {
544 name: "multiple hosts and fallback tls",
545 connString: "user=jack password=secret host=foo,bar,baz dbname=mydb sslmode=prefer",
546 config: &pgconn.Config{
547 User: "jack",
548 Password: "secret",
549 Host: "foo",
550 Port: defaultPort,
551 Database: "mydb",
552 TLSConfig: &tls.Config{
553 InsecureSkipVerify: true,
554 ServerName: "foo",
555 },
556 RuntimeParams: map[string]string{},
557 Fallbacks: []*pgconn.FallbackConfig{
558 {
559 Host: "foo",
560 Port: defaultPort,
561 TLSConfig: nil,
562 },
563 {
564 Host: "bar",
565 Port: defaultPort,
566 TLSConfig: &tls.Config{
567 InsecureSkipVerify: true,
568 ServerName: "bar",
569 }},
570 {
571 Host: "bar",
572 Port: defaultPort,
573 TLSConfig: nil,
574 },
575 {
576 Host: "baz",
577 Port: defaultPort,
578 TLSConfig: &tls.Config{
579 InsecureSkipVerify: true,
580 ServerName: "baz",
581 }},
582 {
583 Host: "baz",
584 Port: defaultPort,
585 TLSConfig: nil,
586 },
587 },
588 },
589 },
590 {
591 name: "target_session_attrs read-write",
592 connString: "postgres://jack:secret@localhost:5432/mydb?sslmode=disable&target_session_attrs=read-write",
593 config: &pgconn.Config{
594 User: "jack",
595 Password: "secret",
596 Host: "localhost",
597 Port: 5432,
598 Database: "mydb",
599 TLSConfig: nil,
600 RuntimeParams: map[string]string{},
601 ValidateConnect: pgconn.ValidateConnectTargetSessionAttrsReadWrite,
602 },
603 },
604 {
605 name: "target_session_attrs read-only",
606 connString: "postgres://jack:secret@localhost:5432/mydb?sslmode=disable&target_session_attrs=read-only",
607 config: &pgconn.Config{
608 User: "jack",
609 Password: "secret",
610 Host: "localhost",
611 Port: 5432,
612 Database: "mydb",
613 TLSConfig: nil,
614 RuntimeParams: map[string]string{},
615 ValidateConnect: pgconn.ValidateConnectTargetSessionAttrsReadOnly,
616 },
617 },
618 {
619 name: "target_session_attrs primary",
620 connString: "postgres://jack:secret@localhost:5432/mydb?sslmode=disable&target_session_attrs=primary",
621 config: &pgconn.Config{
622 User: "jack",
623 Password: "secret",
624 Host: "localhost",
625 Port: 5432,
626 Database: "mydb",
627 TLSConfig: nil,
628 RuntimeParams: map[string]string{},
629 ValidateConnect: pgconn.ValidateConnectTargetSessionAttrsPrimary,
630 },
631 },
632 {
633 name: "target_session_attrs standby",
634 connString: "postgres://jack:secret@localhost:5432/mydb?sslmode=disable&target_session_attrs=standby",
635 config: &pgconn.Config{
636 User: "jack",
637 Password: "secret",
638 Host: "localhost",
639 Port: 5432,
640 Database: "mydb",
641 TLSConfig: nil,
642 RuntimeParams: map[string]string{},
643 ValidateConnect: pgconn.ValidateConnectTargetSessionAttrsStandby,
644 },
645 },
646 {
647 name: "target_session_attrs prefer-standby",
648 connString: "postgres://jack:secret@localhost:5432/mydb?sslmode=disable&target_session_attrs=prefer-standby",
649 config: &pgconn.Config{
650 User: "jack",
651 Password: "secret",
652 Host: "localhost",
653 Port: 5432,
654 Database: "mydb",
655 TLSConfig: nil,
656 RuntimeParams: map[string]string{},
657 ValidateConnect: pgconn.ValidateConnectTargetSessionAttrsPreferStandby,
658 },
659 },
660 {
661 name: "target_session_attrs any",
662 connString: "postgres://jack:secret@localhost:5432/mydb?sslmode=disable&target_session_attrs=any",
663 config: &pgconn.Config{
664 User: "jack",
665 Password: "secret",
666 Host: "localhost",
667 Port: 5432,
668 Database: "mydb",
669 TLSConfig: nil,
670 RuntimeParams: map[string]string{},
671 },
672 },
673 {
674 name: "target_session_attrs not set (any)",
675 connString: "postgres://jack:secret@localhost:5432/mydb?sslmode=disable",
676 config: &pgconn.Config{
677 User: "jack",
678 Password: "secret",
679 Host: "localhost",
680 Port: 5432,
681 Database: "mydb",
682 TLSConfig: nil,
683 RuntimeParams: map[string]string{},
684 },
685 },
686 {
687 name: "SNI is set by default",
688 connString: "postgres://jack:secret@sni.test:5432/mydb?sslmode=require",
689 config: &pgconn.Config{
690 User: "jack",
691 Password: "secret",
692 Host: "sni.test",
693 Port: 5432,
694 Database: "mydb",
695 TLSConfig: &tls.Config{
696 InsecureSkipVerify: true,
697 ServerName: "sni.test",
698 },
699 RuntimeParams: map[string]string{},
700 },
701 },
702 {
703 name: "SNI is not set for IPv4",
704 connString: "postgres://jack:secret@1.1.1.1:5432/mydb?sslmode=require",
705 config: &pgconn.Config{
706 User: "jack",
707 Password: "secret",
708 Host: "1.1.1.1",
709 Port: 5432,
710 Database: "mydb",
711 TLSConfig: &tls.Config{
712 InsecureSkipVerify: true,
713 },
714 RuntimeParams: map[string]string{},
715 },
716 },
717 {
718 name: "SNI is not set for IPv6",
719 connString: "postgres://jack:secret@[::1]:5432/mydb?sslmode=require",
720 config: &pgconn.Config{
721 User: "jack",
722 Password: "secret",
723 Host: "::1",
724 Port: 5432,
725 Database: "mydb",
726 TLSConfig: &tls.Config{
727 InsecureSkipVerify: true,
728 },
729 RuntimeParams: map[string]string{},
730 },
731 },
732 {
733 name: "SNI is not set when disabled (URL-style)",
734 connString: "postgres://jack:secret@sni.test:5432/mydb?sslmode=require&sslsni=0",
735 config: &pgconn.Config{
736 User: "jack",
737 Password: "secret",
738 Host: "sni.test",
739 Port: 5432,
740 Database: "mydb",
741 TLSConfig: &tls.Config{
742 InsecureSkipVerify: true,
743 },
744 RuntimeParams: map[string]string{},
745 },
746 },
747 {
748 name: "SNI is not set when disabled (key/value style)",
749 connString: "user=jack password=secret host=sni.test dbname=mydb sslmode=require sslsni=0",
750 config: &pgconn.Config{
751 User: "jack",
752 Password: "secret",
753 Host: "sni.test",
754 Port: defaultPort,
755 Database: "mydb",
756 TLSConfig: &tls.Config{
757 InsecureSkipVerify: true,
758 },
759 RuntimeParams: map[string]string{},
760 },
761 },
762 }
763
764 for i, tt := range tests {
765 config, err := pgconn.ParseConfig(tt.connString)
766 if !assert.Nilf(t, err, "Test %d (%s)", i, tt.name) {
767 continue
768 }
769
770 assertConfigsEqual(t, tt.config, config, fmt.Sprintf("Test %d (%s)", i, tt.name))
771 }
772 }
773
774
775 func TestParseConfigDSNWithTrailingEmptyEqualDoesNotPanic(t *testing.T) {
776 _, err := pgconn.ParseConfig("host= user= password= port= database=")
777 require.NoError(t, err)
778 }
779
780 func TestParseConfigDSNLeadingEqual(t *testing.T) {
781 _, err := pgconn.ParseConfig("= user=jack")
782 require.Error(t, err)
783 }
784
785
786 func TestParseConfigDSNTrailingBackslash(t *testing.T) {
787 _, err := pgconn.ParseConfig(`x=x\`)
788 require.Error(t, err)
789 assert.Contains(t, err.Error(), "invalid backslash")
790 }
791
792 func TestConfigCopyReturnsEqualConfig(t *testing.T) {
793 connString := "postgres://jack:secret@localhost:5432/mydb?application_name=pgxtest&search_path=myschema&connect_timeout=5"
794 original, err := pgconn.ParseConfig(connString)
795 require.NoError(t, err)
796
797 copied := original.Copy()
798 assertConfigsEqual(t, original, copied, "Test Config.Copy() returns equal config")
799 }
800
801 func TestConfigCopyOriginalConfigDidNotChange(t *testing.T) {
802 connString := "postgres://jack:secret@localhost:5432/mydb?application_name=pgxtest&search_path=myschema&connect_timeout=5&sslmode=prefer"
803 original, err := pgconn.ParseConfig(connString)
804 require.NoError(t, err)
805
806 copied := original.Copy()
807 assertConfigsEqual(t, original, copied, "Test Config.Copy() returns equal config")
808
809 copied.Port = uint16(5433)
810 copied.RuntimeParams["foo"] = "bar"
811 copied.Fallbacks[0].Port = uint16(5433)
812
813 assert.Equal(t, uint16(5432), original.Port)
814 assert.Equal(t, "", original.RuntimeParams["foo"])
815 assert.Equal(t, uint16(5432), original.Fallbacks[0].Port)
816 }
817
818 func TestConfigCopyCanBeUsedToConnect(t *testing.T) {
819 connString := os.Getenv("PGX_TEST_DATABASE")
820 original, err := pgconn.ParseConfig(connString)
821 require.NoError(t, err)
822
823 copied := original.Copy()
824 assert.NotPanics(t, func() {
825 _, err = pgconn.ConnectConfig(context.Background(), copied)
826 })
827 assert.NoError(t, err)
828 }
829
830 func TestNetworkAddress(t *testing.T) {
831 tests := []struct {
832 name string
833 host string
834 wantNet string
835 }{
836 {
837 name: "Default Unix socket address",
838 host: "/var/run/postgresql",
839 wantNet: "unix",
840 },
841 {
842 name: "Windows Unix socket address (standard drive name)",
843 host: "C:\\tmp",
844 wantNet: "unix",
845 },
846 {
847 name: "Windows Unix socket address (first drive name)",
848 host: "A:\\tmp",
849 wantNet: "unix",
850 },
851 {
852 name: "Windows Unix socket address (last drive name)",
853 host: "Z:\\tmp",
854 wantNet: "unix",
855 },
856 {
857 name: "Assume TCP for unknown formats",
858 host: "a/tmp",
859 wantNet: "tcp",
860 },
861 {
862 name: "loopback interface",
863 host: "localhost",
864 wantNet: "tcp",
865 },
866 {
867 name: "IP address",
868 host: "127.0.0.1",
869 wantNet: "tcp",
870 },
871 }
872 for i, tt := range tests {
873 gotNet, _ := pgconn.NetworkAddress(tt.host, 5432)
874
875 assert.Equalf(t, tt.wantNet, gotNet, "Test %d (%s)", i, tt.name)
876 }
877 }
878
879 func assertConfigsEqual(t *testing.T, expected, actual *pgconn.Config, testName string) {
880 if !assert.NotNil(t, expected) {
881 return
882 }
883 if !assert.NotNil(t, actual) {
884 return
885 }
886
887 assert.Equalf(t, expected.Host, actual.Host, "%s - Host", testName)
888 assert.Equalf(t, expected.Database, actual.Database, "%s - Database", testName)
889 assert.Equalf(t, expected.Port, actual.Port, "%s - Port", testName)
890 assert.Equalf(t, expected.User, actual.User, "%s - User", testName)
891 assert.Equalf(t, expected.Password, actual.Password, "%s - Password", testName)
892 assert.Equalf(t, expected.ConnectTimeout, actual.ConnectTimeout, "%s - ConnectTimeout", testName)
893 assert.Equalf(t, expected.RuntimeParams, actual.RuntimeParams, "%s - RuntimeParams", testName)
894
895
896 assert.Equalf(t, expected.ValidateConnect == nil, actual.ValidateConnect == nil, "%s - ValidateConnect", testName)
897 assert.Equalf(t, expected.AfterConnect == nil, actual.AfterConnect == nil, "%s - AfterConnect", testName)
898
899 if assert.Equalf(t, expected.TLSConfig == nil, actual.TLSConfig == nil, "%s - TLSConfig", testName) {
900 if expected.TLSConfig != nil {
901 assert.Equalf(t, expected.TLSConfig.InsecureSkipVerify, actual.TLSConfig.InsecureSkipVerify, "%s - TLSConfig InsecureSkipVerify", testName)
902 assert.Equalf(t, expected.TLSConfig.ServerName, actual.TLSConfig.ServerName, "%s - TLSConfig ServerName", testName)
903 }
904 }
905
906 if assert.Equalf(t, len(expected.Fallbacks), len(actual.Fallbacks), "%s - Fallbacks", testName) {
907 for i := range expected.Fallbacks {
908 assert.Equalf(t, expected.Fallbacks[i].Host, actual.Fallbacks[i].Host, "%s - Fallback %d - Host", testName, i)
909 assert.Equalf(t, expected.Fallbacks[i].Port, actual.Fallbacks[i].Port, "%s - Fallback %d - Port", testName, i)
910
911 if assert.Equalf(t, expected.Fallbacks[i].TLSConfig == nil, actual.Fallbacks[i].TLSConfig == nil, "%s - Fallback %d - TLSConfig", testName, i) {
912 if expected.Fallbacks[i].TLSConfig != nil {
913 assert.Equalf(t, expected.Fallbacks[i].TLSConfig.InsecureSkipVerify, actual.Fallbacks[i].TLSConfig.InsecureSkipVerify, "%s - Fallback %d - TLSConfig InsecureSkipVerify", testName)
914 assert.Equalf(t, expected.Fallbacks[i].TLSConfig.ServerName, actual.Fallbacks[i].TLSConfig.ServerName, "%s - Fallback %d - TLSConfig ServerName", testName)
915 }
916 }
917 }
918 }
919 }
920
921 func TestParseConfigEnvLibpq(t *testing.T) {
922 var osUserName string
923 osUser, err := user.Current()
924 if err == nil {
925
926
927 if runtime.GOOS == "windows" && strings.Contains(osUser.Username, "\\") {
928 osUserName = osUser.Username[strings.LastIndex(osUser.Username, "\\")+1:]
929 } else {
930 osUserName = osUser.Username
931 }
932 }
933
934 pgEnvvars := []string{"PGHOST", "PGPORT", "PGDATABASE", "PGUSER", "PGPASSWORD", "PGAPPNAME", "PGSSLMODE", "PGCONNECT_TIMEOUT", "PGSSLSNI"}
935
936 savedEnv := make(map[string]string)
937 for _, n := range pgEnvvars {
938 savedEnv[n] = os.Getenv(n)
939 }
940 defer func() {
941 for k, v := range savedEnv {
942 err := os.Setenv(k, v)
943 if err != nil {
944 t.Fatalf("Unable to restore environment: %v", err)
945 }
946 }
947 }()
948
949 tests := []struct {
950 name string
951 envvars map[string]string
952 config *pgconn.Config
953 }{
954 {
955
956 name: "PGHOST only",
957 envvars: map[string]string{"PGHOST": "123.123.123.123"},
958 config: &pgconn.Config{
959 User: osUserName,
960 Host: "123.123.123.123",
961 Port: 5432,
962 TLSConfig: &tls.Config{
963 InsecureSkipVerify: true,
964 },
965 RuntimeParams: map[string]string{},
966 Fallbacks: []*pgconn.FallbackConfig{
967 {
968 Host: "123.123.123.123",
969 Port: 5432,
970 TLSConfig: nil,
971 },
972 },
973 },
974 },
975 {
976 name: "All non-TLS environment",
977 envvars: map[string]string{
978 "PGHOST": "123.123.123.123",
979 "PGPORT": "7777",
980 "PGDATABASE": "foo",
981 "PGUSER": "bar",
982 "PGPASSWORD": "baz",
983 "PGCONNECT_TIMEOUT": "10",
984 "PGSSLMODE": "disable",
985 "PGAPPNAME": "pgxtest",
986 },
987 config: &pgconn.Config{
988 Host: "123.123.123.123",
989 Port: 7777,
990 Database: "foo",
991 User: "bar",
992 Password: "baz",
993 ConnectTimeout: 10 * time.Second,
994 TLSConfig: nil,
995 RuntimeParams: map[string]string{"application_name": "pgxtest"},
996 },
997 },
998 {
999 name: "SNI can be disabled via environment variable",
1000 envvars: map[string]string{
1001 "PGHOST": "test.foo",
1002 "PGSSLMODE": "require",
1003 "PGSSLSNI": "0",
1004 },
1005 config: &pgconn.Config{
1006 User: osUserName,
1007 Host: "test.foo",
1008 Port: 5432,
1009 TLSConfig: &tls.Config{
1010 InsecureSkipVerify: true,
1011 },
1012 RuntimeParams: map[string]string{},
1013 },
1014 },
1015 }
1016
1017 for i, tt := range tests {
1018 for _, n := range pgEnvvars {
1019 err := os.Unsetenv(n)
1020 require.NoError(t, err)
1021 }
1022
1023 for k, v := range tt.envvars {
1024 err := os.Setenv(k, v)
1025 require.NoError(t, err)
1026 }
1027
1028 config, err := pgconn.ParseConfig("")
1029 if !assert.Nilf(t, err, "Test %d (%s)", i, tt.name) {
1030 continue
1031 }
1032
1033 assertConfigsEqual(t, tt.config, config, fmt.Sprintf("Test %d (%s)", i, tt.name))
1034 }
1035 }
1036
1037 func TestParseConfigReadsPgPassfile(t *testing.T) {
1038 skipOnWindows(t)
1039 t.Parallel()
1040
1041 tf, err := os.CreateTemp("", "")
1042 require.NoError(t, err)
1043
1044 defer tf.Close()
1045 defer os.Remove(tf.Name())
1046
1047 _, err = tf.Write([]byte("test1:5432:curlydb:curly:nyuknyuknyuk"))
1048 require.NoError(t, err)
1049
1050 connString := fmt.Sprintf("postgres://curly@test1:5432/curlydb?sslmode=disable&passfile=%s", tf.Name())
1051 expected := &pgconn.Config{
1052 User: "curly",
1053 Password: "nyuknyuknyuk",
1054 Host: "test1",
1055 Port: 5432,
1056 Database: "curlydb",
1057 TLSConfig: nil,
1058 RuntimeParams: map[string]string{},
1059 }
1060
1061 actual, err := pgconn.ParseConfig(connString)
1062 assert.NoError(t, err)
1063
1064 assertConfigsEqual(t, expected, actual, "passfile")
1065 }
1066
1067 func TestParseConfigReadsPgServiceFile(t *testing.T) {
1068 skipOnWindows(t)
1069 t.Parallel()
1070
1071 tf, err := os.CreateTemp("", "")
1072 require.NoError(t, err)
1073
1074 defer tf.Close()
1075 defer os.Remove(tf.Name())
1076
1077 _, err = tf.Write([]byte(`
1078 [abc]
1079 host=abc.example.com
1080 port=9999
1081 dbname=abcdb
1082 user=abcuser
1083
1084 [def]
1085 host = def.example.com
1086 dbname = defdb
1087 user = defuser
1088 application_name = spaced string
1089 `))
1090 require.NoError(t, err)
1091
1092 defaultPort := getDefaultPort(t)
1093
1094 tests := []struct {
1095 name string
1096 connString string
1097 config *pgconn.Config
1098 }{
1099 {
1100 name: "abc",
1101 connString: fmt.Sprintf("postgres:///?servicefile=%s&service=%s", tf.Name(), "abc"),
1102 config: &pgconn.Config{
1103 Host: "abc.example.com",
1104 Database: "abcdb",
1105 User: "abcuser",
1106 Port: 9999,
1107 TLSConfig: &tls.Config{
1108 InsecureSkipVerify: true,
1109 ServerName: "abc.example.com",
1110 },
1111 RuntimeParams: map[string]string{},
1112 Fallbacks: []*pgconn.FallbackConfig{
1113 {
1114 Host: "abc.example.com",
1115 Port: 9999,
1116 TLSConfig: nil,
1117 },
1118 },
1119 },
1120 },
1121 {
1122 name: "def",
1123 connString: fmt.Sprintf("postgres:///?servicefile=%s&service=%s", tf.Name(), "def"),
1124 config: &pgconn.Config{
1125 Host: "def.example.com",
1126 Port: defaultPort,
1127 Database: "defdb",
1128 User: "defuser",
1129 TLSConfig: &tls.Config{
1130 InsecureSkipVerify: true,
1131 ServerName: "def.example.com",
1132 },
1133 RuntimeParams: map[string]string{"application_name": "spaced string"},
1134 Fallbacks: []*pgconn.FallbackConfig{
1135 {
1136 Host: "def.example.com",
1137 Port: defaultPort,
1138 TLSConfig: nil,
1139 },
1140 },
1141 },
1142 },
1143 {
1144 name: "conn string has precedence",
1145 connString: fmt.Sprintf("postgres://other.example.com:7777/?servicefile=%s&service=%s&sslmode=disable", tf.Name(), "abc"),
1146 config: &pgconn.Config{
1147 Host: "other.example.com",
1148 Database: "abcdb",
1149 User: "abcuser",
1150 Port: 7777,
1151 TLSConfig: nil,
1152 RuntimeParams: map[string]string{},
1153 },
1154 },
1155 }
1156
1157 for i, tt := range tests {
1158 config, err := pgconn.ParseConfig(tt.connString)
1159 if !assert.NoErrorf(t, err, "Test %d (%s)", i, tt.name) {
1160 continue
1161 }
1162
1163 assertConfigsEqual(t, tt.config, config, fmt.Sprintf("Test %d (%s)", i, tt.name))
1164 }
1165 }
1166
View as plain text