github.com/jackc/pgx/v5@v5.5.5/pgconn/config_test.go (about) 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 // Windows gives us the username here as `DOMAIN\user` or `LOCALPCNAME\user`, 44 // but the libpq default is just the `user` portion, so we strip off the first part. 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 // Test all sslmodes 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 // https://github.com/jackc/pgconn/issues/72 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 // https://github.com/jackc/pgconn/issues/47 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 // https://github.com/jackc/pgconn/issues/49 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 // Can't test function equality, so just test that they are set or not. 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 // Windows gives us the username here as `DOMAIN\user` or `LOCALPCNAME\user`, 926 // but the libpq default is just the `user` portion, so we strip off the first part. 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 // not testing no environment at all as that would use default host and that can vary. 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 }