avs-device-sdk/CapabilityAgents/Bluetooth/test/SQLiteBluetoothStorageTest.cpp

416 lines
14 KiB
C++

/*
* Copyright 2018 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0/
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
#include <fstream>
#include <list>
#include <memory>
#include <sstream>
#include <unordered_map>
#include <vector>
#include <gmock/gmock.h>
#include <gtest/gtest.h>
#include <AVSCommon/Utils/Configuration/ConfigurationNode.h>
#include <Bluetooth/SQLiteBluetoothStorage.h>
namespace alexaClientSDK {
namespace capabilityAgents {
namespace bluetooth {
namespace test {
using namespace ::testing;
using namespace avsCommon::utils;
using namespace avsCommon::utils::configuration;
/// Test Database file name. Can be changed if there are conflicts.
static const std::string TEST_DATABASE = "SQLiteBluetoothStorageTestDatabase.db";
// clang-format off
static const std::string BLUETOOTH_JSON = R"(
{
"bluetooth" : {
"databaseFilePath":")" + TEST_DATABASE + R"("
}
}
)";
// clang-format on
/// Error message for when the file already exists.
static const std::string FILE_EXISTS_ERROR = "Database File " + TEST_DATABASE + " already exists.";
/// Test MAC Address.
static const std::string TEST_MAC = "01:23:45:67:89:ab";
/// Second Test MAC Address.
static const std::string TEST_MAC_2 = "11:23:45:67:89:ab";
/// Test UUID.
static const std::string TEST_UUID = "650f973b-c2ab-4c6e-bff4-3788cd521340";
/// Second Test UUID.
static const std::string TEST_UUID_2 = "750f973b-c2ab-4c6e-bff4-3788cd521340";
/**
* Checks whether a file exists in the file system.
*
* @param file The file name.
* @return Whether the file exists.
*/
static bool fileExists(const std::string& file) {
std::ifstream dbFile(file);
return dbFile.good();
}
class SQLiteBluetoothStorageTest : public ::testing::Test {
public:
/// SetUp before each test case.
void SetUp();
/// TearDown after each test case.
void TearDown();
protected:
/// Cleanup function to close and delete the database.
void closeAndDeleteDB();
/**
* Helper function that abstracts the test logic for getOrderedMac teste cases.
*
* @param ascending Whether we're testing the ascending or descending case.
*/
void getOrderedMacHelper(bool ascending);
/**
* Helper function that abstracts the logic for getMacToUuid and getUuidToMac test cases.
*
* @param retrieveRows Either the getMacToUuid or getUuidToMac function.
* @param macToUuids A map of macToUuids to initialize the database with.
* @param expected The expected results.
*/
void getRowsHelper(
std::function<bool(SQLiteBluetoothStorage&, std::unordered_map<std::string, std::string>*)> retrieveRows,
const std::unordered_map<std::string, std::string>& macToUuids,
const std::unordered_map<std::string, std::string>& expected);
/**
* Helper function that abstracts the logic for getMac and getUuid test cases.
*
* @param retrieveValue Either the getMac or getUuid function.
* @param key The key (either the mac or uuid).
* @param expectedValue The expected value (either the mac or uuid).
* @param macToUuids A map of macToUuids to initialize the database with.
*/
void getMacOrUuidHelper(
std::function<bool(SQLiteBluetoothStorage&, const std::string&, std::string*)> retrieveValue,
const std::string& key,
const std::string& expectedValue,
const std::unordered_map<std::string, std::string>& macToUuids);
/// The database instance. Protected because it needs to be accessed in test cases.
std::unique_ptr<SQLiteBluetoothStorage> m_db;
};
void SQLiteBluetoothStorageTest::closeAndDeleteDB() {
if (m_db) {
m_db->close();
}
m_db.reset();
if (fileExists(TEST_DATABASE)) {
remove(TEST_DATABASE.c_str());
}
}
void SQLiteBluetoothStorageTest::SetUp() {
// Ensure the db file does not exist already. We don't want to overwrite anything.
if (fileExists(TEST_DATABASE)) {
ADD_FAILURE() << FILE_EXISTS_ERROR;
exit(1);
}
// Initialize Global ConfigurationNode with valid value.
auto json = std::shared_ptr<std::stringstream>(new std::stringstream());
*json << BLUETOOTH_JSON;
std::vector<std::shared_ptr<std::istream>> jsonStream;
jsonStream.push_back(json);
ASSERT_TRUE(ConfigurationNode::initialize(jsonStream));
m_db = SQLiteBluetoothStorage::create(ConfigurationNode::getRoot());
ASSERT_THAT(m_db, NotNull());
ASSERT_TRUE(m_db->createDatabase());
}
void SQLiteBluetoothStorageTest::TearDown() {
configuration::ConfigurationNode::uninitialize();
closeAndDeleteDB();
}
void SQLiteBluetoothStorageTest::getOrderedMacHelper(bool ascending) {
ASSERT_TRUE(m_db->insertByMac(TEST_MAC, TEST_UUID));
ASSERT_TRUE(m_db->insertByMac(TEST_MAC_2, TEST_UUID_2));
std::list<std::string> expected;
if (ascending) {
expected.push_back(TEST_MAC);
expected.push_back(TEST_MAC_2);
} else {
expected.push_back(TEST_MAC_2);
expected.push_back(TEST_MAC);
}
std::list<std::string> rows;
ASSERT_TRUE(m_db->getOrderedMac(ascending, &rows));
ASSERT_THAT(rows, Eq(expected));
}
void SQLiteBluetoothStorageTest::getRowsHelper(
std::function<bool(SQLiteBluetoothStorage&, std::unordered_map<std::string, std::string>*)> retrieveRows,
const std::unordered_map<std::string, std::string>& macToUuids,
const std::unordered_map<std::string, std::string>& expected) {
for (const auto& macAndUuid : macToUuids) {
ASSERT_TRUE(m_db->insertByMac(macAndUuid.first, macAndUuid.second));
}
std::unordered_map<std::string, std::string> rows;
ASSERT_TRUE(retrieveRows(*m_db, &rows));
ASSERT_THAT(rows, Eq(expected));
}
void SQLiteBluetoothStorageTest::getMacOrUuidHelper(
std::function<bool(SQLiteBluetoothStorage&, const std::string&, std::string*)> retrieveValue,
const std::string& key,
const std::string& expectedValue,
const std::unordered_map<std::string, std::string>& macToUuids) {
for (const auto& macAndUuid : macToUuids) {
ASSERT_TRUE(m_db->insertByMac(macAndUuid.first, macAndUuid.second));
}
std::string value;
ASSERT_TRUE(retrieveValue(*m_db, key, &value));
ASSERT_THAT(value, Eq(expectedValue));
}
/// Tests the create function with an invalid root.
TEST_F(SQLiteBluetoothStorageTest, createInvalidConfigurationRoot) {
ConfigurationNode::uninitialize();
std::vector<std::shared_ptr<std::istream>> empty;
ConfigurationNode::initialize(empty);
ASSERT_THAT(SQLiteBluetoothStorage::create(ConfigurationNode::getRoot()), IsNull());
}
/// Tests creating a database object.
TEST_F(SQLiteBluetoothStorageTest, createValidConfigurationRoot) {
// SQLite allows simultaneous access to the database.
ASSERT_THAT(SQLiteBluetoothStorage::create(ConfigurationNode::getRoot()), NotNull());
}
/// Test creating a valid DB. This is implicitly tested in the SetUp() function, but we formally test it here.
TEST_F(SQLiteBluetoothStorageTest, createDatabaseSucceeds) {
closeAndDeleteDB();
m_db = SQLiteBluetoothStorage::create(ConfigurationNode::getRoot());
ASSERT_THAT(m_db, NotNull());
ASSERT_TRUE(m_db->createDatabase());
}
/// Test that creating an existing DB fails.
TEST_F(SQLiteBluetoothStorageTest, createExistingDatabaseFails) {
ASSERT_FALSE(m_db->createDatabase());
}
/// Test opening an existing database.
TEST_F(SQLiteBluetoothStorageTest, openExistingDatabaseSucceeds) {
m_db->close();
ASSERT_TRUE(m_db->open());
}
/// Test clearing the table with one row.
TEST_F(SQLiteBluetoothStorageTest, clearOnOneRowSucceeds) {
ASSERT_TRUE(m_db->insertByMac(TEST_MAC, TEST_UUID));
ASSERT_TRUE(m_db->clear());
std::unordered_map<std::string, std::string> rows;
ASSERT_TRUE(m_db->getUuidToMac(&rows));
ASSERT_THAT(rows.size(), Eq(0U));
}
/// Test clearing the table with multiple rows.
TEST_F(SQLiteBluetoothStorageTest, clearOnMultipleRowsSucceeds) {
ASSERT_TRUE(m_db->insertByMac(TEST_MAC, TEST_UUID));
ASSERT_TRUE(m_db->insertByMac(TEST_MAC_2, TEST_UUID_2));
ASSERT_TRUE(m_db->clear());
std::unordered_map<std::string, std::string> rows;
ASSERT_TRUE(m_db->getUuidToMac(&rows));
ASSERT_THAT(rows.size(), Eq(0U));
}
/// Test clearing the table when it's already empty.
TEST_F(SQLiteBluetoothStorageTest, clearOnEmptySucceeds) {
ASSERT_TRUE(m_db->clear());
std::unordered_map<std::string, std::string> rows;
ASSERT_TRUE(m_db->getUuidToMac(&rows));
ASSERT_THAT(rows.size(), Eq(0U));
}
/// Test getUuid with one row containing UUID.
TEST_F(SQLiteBluetoothStorageTest, getUuidWithOneSucceeds) {
const std::unordered_map<std::string, std::string> data{{TEST_MAC, TEST_UUID}};
getMacOrUuidHelper(&SQLiteBluetoothStorage::getUuid, TEST_MAC, TEST_UUID, data);
}
/// Test getUuid with multiple rows, one of which contains the UUID.
TEST_F(SQLiteBluetoothStorageTest, getUuidWithMultipleSucceeds) {
const std::unordered_map<std::string, std::string> data{{TEST_MAC, TEST_UUID}, {TEST_MAC_2, TEST_UUID_2}};
getMacOrUuidHelper(&SQLiteBluetoothStorage::getUuid, TEST_MAC, TEST_UUID, data);
}
/// Test getUuid with no matching UUID.
TEST_F(SQLiteBluetoothStorageTest, getUuidNoMatchingFails) {
std::string uuid;
ASSERT_FALSE(m_db->getUuid(TEST_MAC, &uuid));
}
/// Test getMac with one row containing MAC.
TEST_F(SQLiteBluetoothStorageTest, getMacWithOneSucceeds) {
const std::unordered_map<std::string, std::string> data{{TEST_MAC, TEST_UUID}};
getMacOrUuidHelper(&SQLiteBluetoothStorage::getMac, TEST_UUID, TEST_MAC, data);
}
/// Test getMac with multiple rows, one of which contains the MAC.
TEST_F(SQLiteBluetoothStorageTest, getMacWithMultipleSucceeds) {
const std::unordered_map<std::string, std::string> data{{TEST_MAC, TEST_UUID}, {TEST_MAC_2, TEST_UUID_2}};
getMacOrUuidHelper(&SQLiteBluetoothStorage::getMac, TEST_UUID, TEST_MAC, data);
}
/// Test getMac with no matching MAC.
TEST_F(SQLiteBluetoothStorageTest, getMacNoMatchingFails) {
std::string mac;
ASSERT_FALSE(m_db->getMac(TEST_UUID, &mac));
}
/// Test getMacToUuid with one row.
TEST_F(SQLiteBluetoothStorageTest, getMacToUuidWithOneRowSucceeds) {
const std::unordered_map<std::string, std::string> data{{TEST_MAC, TEST_UUID}};
getRowsHelper(&SQLiteBluetoothStorage::getMacToUuid, data, data);
}
/// Test getMacToUuid with multiple expected.
TEST_F(SQLiteBluetoothStorageTest, getMacToUuidWithMultipleRowsSucceeds) {
const std::unordered_map<std::string, std::string> data{{TEST_MAC, TEST_UUID}, {TEST_MAC_2, TEST_UUID_2}};
getRowsHelper(&SQLiteBluetoothStorage::getMacToUuid, data, data);
}
/// Test getMacToUuid when empty.
TEST_F(SQLiteBluetoothStorageTest, getMacToUuidWithEmptySucceeds) {
std::unordered_map<std::string, std::string> data;
getRowsHelper(&SQLiteBluetoothStorage::getMacToUuid, data, data);
}
/// Test getUuidToMac with one row.
TEST_F(SQLiteBluetoothStorageTest, getUuidToMacWithOneRowSucceeds) {
const std::unordered_map<std::string, std::string> data{{TEST_MAC, TEST_UUID}};
const std::unordered_map<std::string, std::string> expected{{TEST_UUID, TEST_MAC}};
getRowsHelper(&SQLiteBluetoothStorage::getUuidToMac, data, expected);
}
/// Test getUuidToMac with multiple expected.
TEST_F(SQLiteBluetoothStorageTest, getUuidToMacWithMultipleRowsSucceeds) {
const std::unordered_map<std::string, std::string> data{{TEST_MAC, TEST_UUID}, {TEST_MAC_2, TEST_UUID_2}};
const std::unordered_map<std::string, std::string> expected{{TEST_UUID, TEST_MAC}, {TEST_UUID_2, TEST_MAC_2}};
getRowsHelper(&SQLiteBluetoothStorage::getUuidToMac, data, expected);
}
/// Test getUuidToMac when empty.
TEST_F(SQLiteBluetoothStorageTest, getUuidToMacWithEmptySucceeds) {
std::unordered_map<std::string, std::string> data;
getRowsHelper(&SQLiteBluetoothStorage::getUuidToMac, data, data);
}
/// Test getOrderedMac and retrieve the macs in ascending insertion order (oldest first).
TEST_F(SQLiteBluetoothStorageTest, getOrderedMacAscending) {
getOrderedMacHelper(true);
}
/// Test getOrderedMac and retrieve the macs in descending insertion order (newest first).
TEST_F(SQLiteBluetoothStorageTest, getOrderedMacDescending) {
getOrderedMacHelper(false);
}
/// Test insertByMac succeeds.
TEST_F(SQLiteBluetoothStorageTest, insertByMacSucceeds) {
const std::unordered_map<std::string, std::string> expected{{TEST_MAC, TEST_UUID}};
ASSERT_TRUE(m_db->insertByMac(TEST_MAC, TEST_UUID));
std::unordered_map<std::string, std::string> rows;
ASSERT_TRUE(m_db->getMacToUuid(&rows));
ASSERT_THAT(rows, Eq(expected));
}
/// Test insertByMac existing fails.
TEST_F(SQLiteBluetoothStorageTest, insertByMacDuplicateFails) {
ASSERT_TRUE(m_db->insertByMac(TEST_MAC, TEST_UUID));
ASSERT_FALSE(m_db->insertByMac(TEST_MAC, TEST_UUID, false));
}
/// Test insertByMac with overwrite succeeds.
TEST_F(SQLiteBluetoothStorageTest, insertByMacOverwriteSucceeds) {
const std::unordered_map<std::string, std::string> expected{{TEST_MAC, TEST_UUID}};
ASSERT_TRUE(m_db->insertByMac(TEST_MAC, TEST_UUID_2));
ASSERT_TRUE(m_db->insertByMac(TEST_MAC, TEST_UUID));
std::unordered_map<std::string, std::string> rows;
ASSERT_TRUE(m_db->getMacToUuid(&rows));
ASSERT_THAT(rows, Eq(expected));
}
/// Test remove succeeds.
TEST_F(SQLiteBluetoothStorageTest, removeExistingSucceeds) {
ASSERT_TRUE(m_db->insertByMac(TEST_MAC, TEST_UUID));
ASSERT_TRUE(m_db->remove(TEST_MAC));
std::unordered_map<std::string, std::string> rows;
ASSERT_TRUE(m_db->getMacToUuid(&rows));
ASSERT_THAT(rows.size(), Eq(0U));
}
/// Test remove on non-existing record succeeds.
TEST_F(SQLiteBluetoothStorageTest, removeNonExistingSucceeds) {
ASSERT_TRUE(m_db->remove(TEST_MAC));
std::unordered_map<std::string, std::string> rows;
ASSERT_TRUE(m_db->getMacToUuid(&rows));
ASSERT_THAT(rows.size(), Eq(0U));
}
} // namespace test
} // namespace bluetooth
} // namespace capabilityAgents
} // namespace alexaClientSDK