Skip to content

Commit 3f822ce

Browse files
committed
Add SQLDriverConnect diagnostic-record format guards
Two regression tests verify that when SQLDriverConnect fails the returned diagnostic record is well-formed: a 5-character SQLSTATE, a message length that matches strlen of the message, and a message body that is more than a single byte. The wrong-password test skips itself gracefully if the underlying Firebird server uses embedded or trusted authentication (the configuration used by CI), because in that mode the bad password is not rejected and the failure path can't be exercised.
1 parent ca97f46 commit 3f822ce

1 file changed

Lines changed: 127 additions & 0 deletions

File tree

tests/test_connect_options.cpp

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// Tests SQLConnect, SQLDriverConnect, attribute persistence, and transaction behavior.
44

55
#include "test_helpers.h"
6+
#include <cctype>
67
#include <thread>
78
#include <chrono>
89

@@ -709,3 +710,129 @@ TEST_F(ConnectionResetTest, ResetResetsQueryTimeout) {
709710
EXPECT_EQ(timeout, 0u)
710711
<< "Query timeout should be 0 after connection reset";
711712
}
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

Comments
 (0)