|
3 | 3 | // Tests SQLConnect, SQLDriverConnect, attribute persistence, and transaction behavior. |
4 | 4 |
|
5 | 5 | #include "test_helpers.h" |
| 6 | +#include <cctype> |
6 | 7 | #include <thread> |
7 | 8 | #include <chrono> |
8 | 9 |
|
@@ -709,3 +710,129 @@ TEST_F(ConnectionResetTest, ResetResetsQueryTimeout) { |
709 | 710 | EXPECT_EQ(timeout, 0u) |
710 | 711 | << "Query timeout should be 0 after connection reset"; |
711 | 712 | } |
| 713 | + |
| 714 | +// ===== Negative SQLDriverConnect tests ===== |
| 715 | +// |
| 716 | +// When SQLDriverConnect fails the driver must populate a well-formed diagnostic |
| 717 | +// record. A failed call that returns an empty SQLSTATE, a wild native code or |
| 718 | +// a message whose strlen does not match the reported length leaves callers |
| 719 | +// with no way to recover or branch on the error. |
| 720 | + |
| 721 | +namespace { |
| 722 | + |
| 723 | +// Replace the value of a single connection-string key (case-insensitive). If |
| 724 | +// the key isn't present the original string is returned unchanged. |
| 725 | +std::string ReplaceConnKey(const std::string &conn, |
| 726 | + const std::string &key, |
| 727 | + const std::string &newValue) { |
| 728 | + std::string out; |
| 729 | + size_t pos = 0; |
| 730 | + while (pos < conn.size()) { |
| 731 | + size_t end = conn.find(';', pos); |
| 732 | + if (end == std::string::npos) end = conn.size(); |
| 733 | + std::string token = conn.substr(pos, end - pos); |
| 734 | + size_t eq = token.find('='); |
| 735 | + std::string tokKey = (eq != std::string::npos) ? token.substr(0, eq) : token; |
| 736 | + std::string tokKeyLower = tokKey; |
| 737 | + std::string keyLower = key; |
| 738 | + for (auto &c : tokKeyLower) c = (char)tolower((unsigned char)c); |
| 739 | + for (auto &c : keyLower) c = (char)tolower((unsigned char)c); |
| 740 | + if (tokKeyLower == keyLower) |
| 741 | + out += tokKey + "=" + newValue; |
| 742 | + else |
| 743 | + out += token; |
| 744 | + if (end < conn.size()) out += ';'; |
| 745 | + pos = end + 1; |
| 746 | + } |
| 747 | + return out; |
| 748 | +} |
| 749 | + |
| 750 | +} // namespace |
| 751 | + |
| 752 | +// Failed login (wrong password) must produce a well-formed diagnostic record. |
| 753 | +// Note: Firebird in embedded/local-trusted-auth mode (which CI uses) does not |
| 754 | +// check the password, so this test skips itself when the connect unexpectedly |
| 755 | +// succeeds. |
| 756 | +TEST_F(ConnectOptionsTest, BadPasswordReturnsValidDiagRec) { |
| 757 | + AllocEnvAndDbc(); |
| 758 | + |
| 759 | + std::string badConn = ReplaceConnKey(GetConnectionString(), |
| 760 | + "PWD", "invalid-password-xyz"); |
| 761 | + |
| 762 | + SQLCHAR outStr[1024] = {}; |
| 763 | + SQLSMALLINT outLen = 0; |
| 764 | + SQLRETURN rc = SQLDriverConnect(hDbc, NULL, |
| 765 | + (SQLCHAR*)badConn.c_str(), SQL_NTS, |
| 766 | + outStr, sizeof(outStr), &outLen, |
| 767 | + SQL_DRIVER_NOPROMPT); |
| 768 | + if (SQL_SUCCEEDED(rc)) { |
| 769 | + GTEST_SKIP() << "Connect succeeded despite invalid password; the " |
| 770 | + "Firebird server is using embedded or trusted " |
| 771 | + "authentication and won't exercise the failure path."; |
| 772 | + } |
| 773 | + |
| 774 | + SQLCHAR sqlState[6] = {}; |
| 775 | + SQLINTEGER native = 0; |
| 776 | + SQLCHAR msg[1024] = {}; |
| 777 | + SQLSMALLINT msgLen = 0; |
| 778 | + SQLRETURN drc = SQLGetDiagRec(SQL_HANDLE_DBC, hDbc, 1, sqlState, &native, |
| 779 | + msg, sizeof(msg), &msgLen); |
| 780 | + ASSERT_EQ(drc, SQL_SUCCESS) |
| 781 | + << "SQLGetDiagRec must return a record after SQLDriverConnect failure"; |
| 782 | + |
| 783 | + EXPECT_EQ(strlen((char*)sqlState), 5u) |
| 784 | + << "SQLSTATE must be exactly 5 chars, got '" |
| 785 | + << (char*)sqlState << "' (len=" << strlen((char*)sqlState) << ")"; |
| 786 | + |
| 787 | + EXPECT_EQ((size_t)msgLen, strlen((char*)msg)) |
| 788 | + << "Reported message length (" << msgLen |
| 789 | + << ") must match strlen of returned message (" |
| 790 | + << strlen((char*)msg) << "); message='" << (char*)msg << "'"; |
| 791 | + |
| 792 | + EXPECT_GT(strlen((char*)msg), 1u) |
| 793 | + << "Message must contain more than a single byte; got '" |
| 794 | + << (char*)msg << "'"; |
| 795 | +} |
| 796 | + |
| 797 | +// Failed connect to a nonexistent database must also produce a well-formed |
| 798 | +// diagnostic record. |
| 799 | +TEST_F(ConnectOptionsTest, NonexistentDatabaseReturnsValidDiagRec) { |
| 800 | + AllocEnvAndDbc(); |
| 801 | + |
| 802 | + // Build a connection string with the same Driver but pointing at a path |
| 803 | + // that cannot exist. Try both spellings: Database= and Dbname=. |
| 804 | + std::string bogus = "/nonexistent/path/no-such-database.fdb"; |
| 805 | + std::string badConn = ReplaceConnKey(GetConnectionString(), "Database", bogus); |
| 806 | + badConn = ReplaceConnKey(badConn, "Dbname", bogus); |
| 807 | + |
| 808 | + SQLCHAR outStr[1024] = {}; |
| 809 | + SQLSMALLINT outLen = 0; |
| 810 | + SQLRETURN rc = SQLDriverConnect(hDbc, NULL, |
| 811 | + (SQLCHAR*)badConn.c_str(), SQL_NTS, |
| 812 | + outStr, sizeof(outStr), &outLen, |
| 813 | + SQL_DRIVER_NOPROMPT); |
| 814 | + ASSERT_FALSE(SQL_SUCCEEDED(rc)) |
| 815 | + << "Connect to nonexistent database was expected to fail"; |
| 816 | + |
| 817 | + SQLCHAR sqlState[6] = {}; |
| 818 | + SQLINTEGER native = 0; |
| 819 | + SQLCHAR msg[1024] = {}; |
| 820 | + SQLSMALLINT msgLen = 0; |
| 821 | + SQLRETURN drc = SQLGetDiagRec(SQL_HANDLE_DBC, hDbc, 1, sqlState, &native, |
| 822 | + msg, sizeof(msg), &msgLen); |
| 823 | + ASSERT_EQ(drc, SQL_SUCCESS) |
| 824 | + << "SQLGetDiagRec must return a record after SQLDriverConnect failure"; |
| 825 | + |
| 826 | + EXPECT_EQ(strlen((char*)sqlState), 5u) |
| 827 | + << "SQLSTATE must be exactly 5 chars, got '" |
| 828 | + << (char*)sqlState << "' (len=" << strlen((char*)sqlState) << ")"; |
| 829 | + |
| 830 | + EXPECT_EQ((size_t)msgLen, strlen((char*)msg)) |
| 831 | + << "Reported message length (" << msgLen |
| 832 | + << ") must match strlen of returned message (" |
| 833 | + << strlen((char*)msg) << "); message='" << (char*)msg << "'"; |
| 834 | + |
| 835 | + EXPECT_GT(strlen((char*)msg), 1u) |
| 836 | + << "Message must contain more than a single byte; got '" |
| 837 | + << (char*)msg << "'"; |
| 838 | +} |
0 commit comments