diff options
Diffstat (limited to 'src/lib/pgsql/tests/pgsql_connection_unittest.cc')
-rw-r--r-- | src/lib/pgsql/tests/pgsql_connection_unittest.cc | 651 |
1 files changed, 651 insertions, 0 deletions
diff --git a/src/lib/pgsql/tests/pgsql_connection_unittest.cc b/src/lib/pgsql/tests/pgsql_connection_unittest.cc new file mode 100644 index 0000000..ab09009 --- /dev/null +++ b/src/lib/pgsql/tests/pgsql_connection_unittest.cc @@ -0,0 +1,651 @@ +// Copyright (C) 2021-2023 Internet Systems Consortium, Inc. ("ISC") +// +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +#include <config.h> + +#include <database/db_exceptions.h> +#include <pgsql/pgsql_connection.h> +#include <pgsql/pgsql_exchange.h> +#include <pgsql/testutils/pgsql_schema.h> +#include <pgsql/tests/pgsql_basics.h> +#include <testutils/gtest_utils.h> + +#include <boost/lexical_cast.hpp> + +#include <gtest/gtest.h> + +#include <sstream> +#include <vector> + +using namespace isc; +using namespace isc::db; +using namespace isc::db::test; +using namespace isc::util; + +namespace { + +// A small extension of PgSqlBasicsTest that instantiates the actual Kea schema. +// Those tests tend to be a bit heavy (especially with the CB and its tables), +// so please consider adding your tests in PgSqlBasicsTest, unless you really need +// the full schema. +class PgSqlSchemaTest: public PgSqlBasicsTest { +public: + PgSqlSchemaTest() : PgSqlBasicsTest() { + destroySchema(); // We don't need this fake schema with just "basics" table. + + // Create the actual full Kea schema. + isc::db::test::createPgSQLSchema(true, true); + } + + virtual ~PgSqlSchemaTest() { + // Clean up after ourselves. + isc::db::test::destroyPgSQLSchema(true, true); + } +}; + +/// @brief Checks if the schema version is really as expected. +TEST_F(PgSqlSchemaTest, schemaVersion) { + + PgSqlResultPtr r; + std::string sql = "SELECT version, minor FROM schema_version"; + RUN_SQL(r, sql, PGRES_TUPLES_OK); + // There should be one row with 7,0 returned or whatever the latest schema is. + ASSERT_EQ(r->getRows(), 1) << "failed to check schema version, expected 1 row, have: " + << r->getRows(); + + int value = 0; + // Get row 0, column 0 (i.e. version field) + ASSERT_FALSE(PgSqlExchange::isColumnNull(*r, 0, 0)); + ASSERT_NO_THROW(PgSqlExchange::getColumnValue(*r, 0, 0, value)); + EXPECT_EQ(value, PGSQL_SCHEMA_VERSION_MAJOR) + << "invalid schema version reported, major expected " << PGSQL_SCHEMA_VERSION_MAJOR + << ", actual:" << value; + + // Get row 0, column 1 (i.e. minor field) + ASSERT_FALSE(PgSqlExchange::isColumnNull(*r, 0, 1)); + ASSERT_NO_THROW(PgSqlExchange::getColumnValue(*r, 0, 1, value)); + EXPECT_EQ(value, PGSQL_SCHEMA_VERSION_MINOR) + << "invalid schema version reported, minor expected " << PGSQL_SCHEMA_VERSION_MINOR + << ", actual:" << value; + +} + +/// @brief Test fixture for exercising higher order PgSqlConnection functions +/// selectQuery, insertQuery, updateDeleteQuery. These tests only use two of +/// the columns in the BASICS table: int_col and text_col. Inserting rows with +/// varying types and values are tested above. These tests focus on the higher +/// order function mechanics. +class PgSqlConnectionTest : public PgSqlBasicsTest { +public: + + /// @brief Indexes of prepared statements used within the tests. + enum StatementIndex { + GET_BY_INT_VALUE, + GET_BY_INT_RANGE, + DELETE_BY_INT_RANGE, + INSERT_VALUE, + UPDATE_BY_INT_VALUE, + GET_ALL_ROWS, + DELETE_ALL_ROWS, + NUM_STATEMENTS + }; + + /// @brief Array of tagged PgSql statements. + typedef std::array<PgSqlTaggedStatement, NUM_STATEMENTS> TaggedStatementArray; + + /// @brief Prepared PgSql statements used in the tests. + TaggedStatementArray tagged_statements = {{ + { 1, { OID_INT4 }, "GET_BY_INT_VALUE", + "SELECT int_col, text_col" + " FROM basics WHERE int_col = $1" }, + + { 2, { OID_INT4, OID_INT4 }, "GET_BY_INT_RANGE", + "SELECT int_col, text_col" + " FROM basics WHERE int_col >= $1 and int_col <= $2" }, + + { 2, { OID_INT4, OID_INT4 }, "DEL_BY_INT_RANGE", + "DELETE FROM basics WHERE int_col >= $1 and int_col <= $2" }, + + { 2, { OID_INT4, OID_TEXT }, "INSERT_INT_TEXT", + "INSERT INTO basics (int_col,text_col)" + " VALUES ($1, $2)" }, + + { 2, { OID_INT4, OID_TEXT }, "UPDATE_BY_INT_VALUE", + "UPDATE basics SET text_col = $2" + " WHERE int_col = $1" }, + + { 0, { OID_NONE }, "GET_ALL_ROWS", + "SELECT int_col, text_col FROM basics" }, + + { 0, { OID_NONE }, "DELETE_ALL_ROWS", + "DELETE FROM basics" } + }}; + + /// @brief Structure for holding data values describing a single + /// row. These tests only use two of the columns in the BASICS table: + /// int_col and text_col. Inserting rows with varying types and values + /// are tested above. These tests focus on the higher order mechanics. + struct TestRow { + int int_col; + std::string text_col; + + bool operator==(const TestRow& other) const { + return (int_col == other.int_col && + text_col == other.text_col); + } + }; + + /// @brief Defines a set of test rows. + typedef std::vector<TestRow> TestRowSet; + + /// @brief Constructor. + PgSqlConnectionTest() : PgSqlBasicsTest() {}; + + /// @brief Destructor. + virtual ~PgSqlConnectionTest() { + if (conn_->isTransactionStarted()) { + conn_->rollback(); + } + }; + + /// @brief SetUp function which prepares the tagged statements. + virtual void SetUp() { + ASSERT_NO_THROW_LOG(conn_->prepareStatements(tagged_statements.begin(), + tagged_statements.end())); + } + + /// @brief Tests inserting data into the database. + /// + /// @param insert_rows Collection of rows of data to insert. Note that + /// each row is inserted as a separate statement execution. + void testInsert(const TestRowSet& insert_rows) { + for (auto row : insert_rows ) { + // Set the insert parameters based on the current insert row. + PsqlBindArray in_bindings; + in_bindings.add(row.int_col); + in_bindings.add(row.text_col); + + // Insert the row into the database. + conn_->insertQuery(tagged_statements[INSERT_VALUE], in_bindings); + } + } + + /// @brief Tests fetching data using PgSqlConnection::selectQuery() + /// + /// Selects rows from the BASICS table whose int_col value falls within + /// an inclusive range. + /// + /// @param expected_rows Collection of rows of data that we expect to be + /// fetched. Note the rows should be in the order you expect them to be + /// returned from the database. + /// @param begin_int beginning of the range to include. + /// @param end_int end fo the range to include. + void testSelect(const TestRowSet& expected_rows, const int begin_int, const int end_int) { + // Set the where clause parameters to the desired range values. + PsqlBindArray in_bindings; + in_bindings.add(begin_int); + in_bindings.add(end_int); + + // Row set that will receive the fetched rows. + TestRowSet fetched_rows; + + // Run the select. The row consumption lambda should populate + // fetched_rows based on the the result set returned by the select. + conn_->selectQuery(tagged_statements[GET_BY_INT_RANGE], in_bindings, + [&](PgSqlResult& r, int row) { + TestRow fetched_row; + if (row >= expected_rows.size()) { + // We have too many rows. + isc_throw(Unexpected, "row index exceeded expected row count of " + << expected_rows.size()); + } + + // First column should be int_col and not NULL. + if (PgSqlExchange::isColumnNull(r, row, 0)) { + isc_throw(Unexpected, "first column is null!"); + } + + // Fetch the int_col value. + PgSqlExchange::getColumnValue(r, row, 0, fetched_row.int_col); + + // Second column should be text and not NULL. + if (PgSqlExchange::isColumnNull(r, row, 1)) { + isc_throw(Unexpected, "second column is null!"); + } + + // Fetch the text_col value. + PgSqlExchange::getColumnValue(r, row, 1, fetched_row.text_col); + + // Add the fetched row into set of fetched rows. + fetched_rows.push_back(fetched_row); + }); + + // Make sure fetched rows match the expected rows. + ASSERT_EQ(fetched_rows, expected_rows); + } + + /// @brief Tests updating data using PgSqlConnection::updateDeleteQuery() + /// + /// In this test, the input data is a set of rows that describe + /// which rows in the database to update and how. For each row + /// in the set we find the record in the database with matching + /// int_col value and replace its text_col value with the the + /// text value from the input the row. + /// + /// @param update_rows Collection of rows of data to update. + void testUpdate(const TestRowSet& update_rows) { + size_t update_count = 0; + for (auto row : update_rows ) { + // Set the text value and where clause parameters based on the + // this row's values. + PsqlBindArray in_bindings; + in_bindings.add(row.int_col); + in_bindings.add(row.text_col); + + // Update the database. + update_count += conn_->updateDeleteQuery(tagged_statements[UPDATE_BY_INT_VALUE], + in_bindings); + } + + // Number of rows updated should match rows we passed in. + ASSERT_EQ(update_count, update_rows.size()); + } + + /// @brief Tests deleting data using PgSqlConnection::updateDeleteQuery() + /// + /// Deletes rows from the BASICS table whose int_col value falls within + /// an inclusive range. + /// + /// @param begin_int beginning of the range to include. + /// @param end_int end of the range to include. + /// @param expected_delete_count number of rows of data we expect to be + /// deleted. + void testDelete(const int begin_int, const int end_int, size_t expected_delete_count) { + // Set the where clause parameters to the desired range values. + PsqlBindArray in_bindings; + in_bindings.add(begin_int); + in_bindings.add(end_int); + + // Execute the delete statement. + size_t delete_count = 0; + delete_count = conn_->updateDeleteQuery(tagged_statements[DELETE_BY_INT_RANGE], + in_bindings); + + // Verify the number of records deleted is as expected. + ASSERT_EQ(delete_count, expected_delete_count); + } +}; + +/// @brief Verifies basics of input parameter sanity checking and statement +/// execution enforced by executePreparedStatement. Higher order tests +/// verify actual data CRUD results. +TEST_F(PgSqlConnectionTest, executePreparedStatement) { + + // Executing with no parameters when they are required should throw. + // First we'll omit the bindings (defaults to empty). + PgSqlResultPtr r; + ASSERT_THROW_MSG(r = conn_->executePreparedStatement(tagged_statements[INSERT_VALUE]), + InvalidOperation, + "executePreparedStatement: expected: 2 parameters, given: 0," + " statement: INSERT_INT_TEXT, SQL: INSERT INTO basics " + "(int_col,text_col) VALUES ($1, $2)"); + + // Now we'll pass in an empty array. + PsqlBindArray in_bindings; + ASSERT_THROW_MSG(r = conn_->executePreparedStatement(tagged_statements[INSERT_VALUE], + in_bindings), + InvalidOperation, + "executePreparedStatement: expected: 2 parameters, given: 0," + " statement: INSERT_INT_TEXT, SQL: INSERT INTO basics " + "(int_col,text_col) VALUES ($1, $2)"); + + // Executing without parameters when none are expected should be fine. + // First we'll simply omit the array. + ASSERT_NO_THROW(r = conn_->executePreparedStatement(tagged_statements[GET_ALL_ROWS])); + + // Now with an empty array. + ASSERT_NO_THROW(r = conn_->executePreparedStatement(tagged_statements[GET_ALL_ROWS], in_bindings)); + + // Executing with parameters when none are required should throw. + in_bindings.add(1); + in_bindings.add(2); + ASSERT_THROW_MSG(r = conn_->executePreparedStatement(tagged_statements[GET_ALL_ROWS], + in_bindings), + InvalidOperation, + "executePreparedStatement: expected: 0 parameters, given: 2," + " statement: GET_ALL_ROWS, SQL: SELECT int_col, text_col FROM basics"); + + // Executing with the correct number of parameters should work. + ASSERT_NO_THROW(r = conn_->executePreparedStatement(tagged_statements[GET_BY_INT_RANGE], + in_bindings)); + + // Executing with too many parameters should fail. + in_bindings.add(3); + ASSERT_THROW_MSG(r = conn_->executePreparedStatement(tagged_statements[GET_BY_INT_RANGE], + in_bindings), + InvalidOperation, + "executePreparedStatement: expected: 2 parameters, given: 3," + " statement: GET_BY_INT_RANGE, SQL: SELECT int_col, text_col" + " FROM basics WHERE int_col >= $1 and int_col <= $2"); +} + +/// @brief Verify that we can insert rows with +/// PgSqlConnection::insertQuery() and fetch +/// them using PgSqlConnection::selectQuery(). +TEST_F(PgSqlConnectionTest, insertSelectTest) { + + // Define the list of rows we want to insert. + TestRowSet insert_rows = { + { 7, "seven" }, + { 8, "eight" }, + { 9, "nine" }, + }; + + // Insert the rows. + ASSERT_NO_THROW_LOG(testInsert(insert_rows)); + + // Make sure we can fetch a single row. + ASSERT_NO_THROW_LOG(testSelect(TestRowSet({{ 8, "eight" }}), 8, 8)); + + // Make sure we can fetch all the rows. + ASSERT_NO_THROW_LOG(testSelect(insert_rows, 7, 9)); +} + +/// @brief Verify that we can update rows with +/// PgSqlConnection::updateDeleteQuery() +TEST_F(PgSqlConnectionTest, updateTest) { + + // Define the list of rows we want to insert. + TestRowSet insert_rows = { + { 7, "seven" }, + { 8, "eight" }, + { 9, "nine" }, + }; + + // Insert the rows. + ASSERT_NO_THROW_LOG(testInsert(insert_rows)); + + // Define the list of updates. + TestRowSet update_rows = { + { 8, "ate" }, + { 9, "mine" } + }; + + // Update the rows. + ASSERT_NO_THROW_LOG(testUpdate(update_rows)); + + // Fetch the updated rows. + ASSERT_NO_THROW_LOG(testSelect(update_rows, 8, 9)); +} + +/// @brief Verify that we can delete rows with +/// PgSqlConnection::updateDeleteQuery() +TEST_F(PgSqlConnectionTest, deleteTest) { + + // Define the list of rows we want to insert. + TestRowSet insert_rows = { + { 6, "six" }, + { 7, "seven" }, + { 8, "eight" }, + { 9, "nine" }, + }; + + // Insert the rows. + ASSERT_NO_THROW_LOG(testInsert(insert_rows)); + + // Fetch the all rows. + ASSERT_NO_THROW_LOG(testSelect(insert_rows, 0, 10)); + + // Delete rows 7 and 8. + ASSERT_NO_THROW_LOG(testDelete(7, 8, 2)); + + // Fetch the all rows. + ASSERT_NO_THROW_LOG(testSelect(TestRowSet({{6, "six"}, {9, "nine"}}), 0, 10)); +} + +// Verifies that transaction nesting and operations: start, commit, +// and rollback work correctly. +TEST_F(PgSqlConnectionTest, transactions) { + ASSERT_FALSE(conn_->isTransactionStarted()); + + // Two inserts within a transaction and successful commit. + TestRowSet two_rows = {{1, "one"}, {2, "two"}}; + conn_->startTransaction(); + ASSERT_TRUE(conn_->isTransactionStarted()); + ASSERT_NO_THROW_LOG(testInsert(two_rows)); + conn_->commit(); + + // Should not be in a transaction and we should have both + // rows we inserted. + ASSERT_FALSE(conn_->isTransactionStarted()); + ASSERT_NO_THROW_LOG(testSelect(two_rows, 0, 10)); + + // Add third row but roll back the transaction. We should still have + // two rows in the table. + conn_->startTransaction(); + ASSERT_TRUE(conn_->isTransactionStarted()); + ASSERT_NO_THROW_LOG(testInsert({{ 3, "three"}})); + conn_->rollback(); + + // We should not be in a transaction and should still have + // only the first two rows. + ASSERT_FALSE(conn_->isTransactionStarted()); + ASSERT_NO_THROW_LOG(testSelect(two_rows, 0, 10)); + + // Nested transaction. The inner transaction should be ignored and the outer + // transaction rolled back. We should have only the original two rows in the + // database. + conn_->startTransaction(); + EXPECT_TRUE(conn_->isTransactionStarted()); + ASSERT_NO_THROW_LOG(testInsert({{ 3, "three"}})); + + conn_->startTransaction(); + EXPECT_TRUE(conn_->isTransactionStarted()); + ASSERT_NO_THROW_LOG(testInsert({{ 4, "four"}})); + + // First commit should do nothing other than decrement + // the transaction ref count. + conn_->commit(); + ASSERT_TRUE(conn_->isTransactionStarted()); + + // Rollback should end the transaction without committing changes. + conn_->rollback(); + ASSERT_FALSE(conn_->isTransactionStarted()); + + // We should still have only the first two rows. + ASSERT_NO_THROW_LOG(testSelect(two_rows, 0, 10)); + + // Nested transaction. The inner transaction is rolled back but this should + // be ignored because nested transactions are not supported. We should + // have two new rows. + + // Insert five row. + conn_->startTransaction(); + ASSERT_TRUE(conn_->isTransactionStarted()); + TestRow row_five({ 5, "five" }); + ASSERT_NO_THROW_LOG(testInsert(TestRowSet({ row_five }))); + two_rows.push_back(row_five); + + // Insert six row. + conn_->startTransaction(); + ASSERT_TRUE(conn_->isTransactionStarted()); + TestRow row_six({ 6, "six" }); + ASSERT_NO_THROW_LOG(testInsert(TestRowSet({ row_six }))); + two_rows.push_back(row_six); + + // Rollback should do nothing other than decrement the + // reference count. + conn_->rollback(); + EXPECT_TRUE(conn_->isTransactionStarted()); + + // Commit should complete the transaction and commit the inserts. + conn_->commit(); + EXPECT_FALSE(conn_->isTransactionStarted()); + ASSERT_NO_THROW_LOG(testSelect(two_rows, 0, 10)); + + // Committing or rolling back a not started transaction is a coding error. + EXPECT_THROW(conn_->commit(), isc::Unexpected); + EXPECT_THROW(conn_->rollback(), isc::Unexpected); +} + +// Verifies that savepoints operate correctly. +TEST_F(PgSqlConnectionTest, savepoints) { + // We want to trigger DuplicateEntry errors so let's + // add a unique constraint to the table. + ASSERT_NO_THROW(conn_->executeSQL("ALTER TABLE basics ADD CONSTRAINT" + " unique_int_col UNIQUE (int_col);")); + // Verify we are not in a transaction. + ASSERT_FALSE(conn_->isTransactionStarted()); + + // Creating or rollback to savepoints outside of transactions + // should throw. + ASSERT_THROW_MSG(conn_->createSavepoint("rubbish"), InvalidOperation, + "no transaction, cannot create savepoint: rubbish"); + ASSERT_THROW_MSG(conn_->rollbackToSavepoint("rubbish"), InvalidOperation, + "no transaction, cannot rollback to savepoint: rubbish"); + + // Test that we can create and rollback to a savepoint, then + // committing only the pre savepoint work. + TestRowSet first_row = {{1, "one"}}; + ASSERT_NO_THROW_LOG(conn_->startTransaction()); + ASSERT_TRUE(conn_->isTransactionStarted()); + ASSERT_NO_THROW_LOG(testInsert(first_row)); + + // Create a savepoint. + ASSERT_NO_THROW_LOG(conn_->createSavepoint("sp_one")); + + // Insert a second row, without committing it. + TestRowSet second_row = {{2, "two"}}; + ASSERT_NO_THROW_LOG(testInsert(second_row)); + + // Rollback to the savepoint. + ASSERT_NO_THROW_LOG(conn_->rollbackToSavepoint("sp_one")); + + // Commit the transaction. + conn_->commit(); + + // We should not be in a transaction but we should + // only have the first row. + ASSERT_FALSE(conn_->isTransactionStarted()); + ASSERT_NO_THROW_LOG(testSelect(first_row, 0, 10)); + + // Now we'll test that we can create and rollback to a + // savepoint after Postgresql aborts an insert due to + // duplicate key error. We should still be able to + // commit the pre-savepoint and post rollback work. + ASSERT_NO_THROW_LOG(conn_->startTransaction()); + ASSERT_TRUE(conn_->isTransactionStarted()); + ASSERT_NO_THROW_LOG(testInsert(second_row)); + + // Create a savepoint. + ASSERT_NO_THROW_LOG(conn_->createSavepoint("sp_two")); + + // Attempt to insert a duplicate first row. + ASSERT_THROW(testInsert(first_row), DuplicateEntry); + + // Rollback to the savepoint. + ASSERT_NO_THROW_LOG(conn_->rollbackToSavepoint("sp_two")); + + // Now insert a third row. + TestRowSet third_row = {{3, "three"}}; + ASSERT_NO_THROW_LOG(testInsert(third_row)); + + // Commit the transaction. + conn_->commit(); + + // We should not be in a transaction and we should + // two rows. + ASSERT_FALSE(conn_->isTransactionStarted()); + TestRowSet three_rows{{1, "one"}, {2, "two"}, {3, "three"}}; + ASSERT_NO_THROW_LOG(testSelect(three_rows, 0, 10)); +} + +// Tests that invalid port value causes an error. +TEST_F(PgSqlConnectionTest, portInvalid) { + std::string conn_str = connectionString(PGSQL_VALID_TYPE, VALID_NAME, + VALID_USER, VALID_PASSWORD, + INVALID_PORT_1); + PgSqlConnection conn(DatabaseConnection::parse(conn_str)); + EXPECT_THROW(conn.getConnParameters(), DbInvalidPort); +} + +// Tests that valid connection timeout is accepted. +TEST_F(PgSqlConnectionTest, connectionTimeout) { + std::string conn_str = connectionString(PGSQL_VALID_TYPE, VALID_NAME, + VALID_USER, VALID_PASSWORD, + VALID_TIMEOUT); + PgSqlConnection conn(DatabaseConnection::parse(conn_str)); + std::string parameters; + ASSERT_NO_THROW_LOG(parameters = conn.getConnParameters()); + EXPECT_TRUE(parameters.find("connect_timeout = 10") != std::string::npos) + << "parameter not found in " << parameters; +} + +// Tests that invalid timeout value type causes an error. +TEST_F(PgSqlConnectionTest, connectionTimeoutInvalid) { + std::string conn_str = connectionString(PGSQL_VALID_TYPE, VALID_NAME, + VALID_USER, VALID_PASSWORD, + INVALID_TIMEOUT_1); + PgSqlConnection conn(DatabaseConnection::parse(conn_str)); + EXPECT_THROW(conn.getConnParameters(), DbInvalidTimeout); +} + +// Tests that a negative connection timeout value causes an error. +TEST_F(PgSqlConnectionTest, connectionTimeoutInvalid2) { + std::string conn_str = connectionString(PGSQL_VALID_TYPE, VALID_NAME, + VALID_USER, VALID_PASSWORD, + INVALID_TIMEOUT_2); + PgSqlConnection conn(DatabaseConnection::parse(conn_str)); + EXPECT_THROW(conn.getConnParameters(), DbInvalidTimeout); +} + +// Tests that a zero connection timeout value causes an error. +TEST_F(PgSqlConnectionTest, connectionTimeoutInvalid3) { + std::string conn_str = connectionString(PGSQL_VALID_TYPE, VALID_NAME, + VALID_USER, VALID_PASSWORD, + INVALID_TIMEOUT_3); + PgSqlConnection conn(DatabaseConnection::parse(conn_str)); + EXPECT_THROW(conn.getConnParameters(), DbInvalidTimeout); +} + +// Tests that valid tcp user timeout is accepted. This parameter is +// supported by PostgreSQL 12 and later. +#ifdef HAVE_PGSQL_TCP_USER_TIMEOUT +TEST_F(PgSqlConnectionTest, tcpUserTimeout) { + std::string conn_str = connectionString(PGSQL_VALID_TYPE, VALID_NAME, + VALID_USER, VALID_PASSWORD, + VALID_TCP_USER_TIMEOUT); + PgSqlConnection conn(DatabaseConnection::parse(conn_str)); + std::string parameters; + ASSERT_NO_THROW_LOG(parameters = conn.getConnParameters()); + EXPECT_TRUE(parameters.find("tcp_user_timeout = 8000") != std::string::npos) + << "parameter not found in " << parameters; +} +#endif + +// Tests that a zero tcp user timeout is accepted. +TEST_F(PgSqlConnectionTest, tcpUserTimeoutZero) { + std::string conn_str = connectionString(PGSQL_VALID_TYPE, VALID_NAME, + VALID_USER, VALID_PASSWORD, + VALID_TCP_USER_TIMEOUT_ZERO); + PgSqlConnection conn(DatabaseConnection::parse(conn_str)); + std::string parameters; + ASSERT_NO_THROW_LOG(parameters = conn.getConnParameters()); + EXPECT_FALSE(parameters.find("tcp_user_timeout") != std::string::npos) + << "parameter found in " << parameters << " but expected to be gone"; +} + +// Tests that an invalid tcp user timeout causes an error. +TEST_F(PgSqlConnectionTest, tcpUserTimeoutInvalid) { + std::string conn_str = connectionString(PGSQL_VALID_TYPE, VALID_NAME, + VALID_USER, VALID_PASSWORD, + INVALID_TIMEOUT_1); + PgSqlConnection conn(DatabaseConnection::parse(conn_str)); + EXPECT_THROW(conn.getConnParameters(), DbInvalidTimeout); +} + + +}; // namespace |