avs-device-sdk/CertifiedSender/test/MessageStorageTest.cpp

453 lines
14 KiB
C++

/*
* Copyright 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 <gtest/gtest.h>
#include <gmock/gmock.h>
#include <CertifiedSender/SQLiteMessageStorage.h>
#include <SQLiteStorage/SQLiteStatement.h>
#include <AVSCommon/Utils/File/FileUtils.h>
#include <fstream>
#include <queue>
#include <memory>
using namespace ::testing;
namespace alexaClientSDK {
namespace certifiedSender {
namespace test {
using namespace avsCommon::utils::file;
/// The filename we will use for the test database file.
static const std::string TEST_DATABASE_FILE_PATH = "messageStorageTestDatabase.db";
/// The path delimiter used by the OS to identify file locations.
static const std::string PATH_DELIMITER = "/";
/// The full filepath to the database file we will create and delete during tests.
static std::string g_dbTestFilePath;
/// A test message text.
static const std::string TEST_MESSAGE_ONE = "test_message_one";
/// A test message text.
static const std::string TEST_MESSAGE_TWO = "test_message_two";
/// A test message text.
static const std::string TEST_MESSAGE_THREE = "test_message_three";
/// A test message uri.
static const std::string TEST_MESSAGE_URI = "/v20160207/events/SpeechRecognizer/Recognize";
/// The name of the alerts table.
static const std::string MESSAGES_TABLE_NAME = "messages_with_uri";
/// The name of the 'id' field we will use as the primary key in our tables.
static const std::string DATABASE_COLUMN_ID_NAME = "id";
/// The name of the 'message_text' field we will use as the primary key in our tables.
static const std::string DATABASE_COLUMN_MESSAGE_TEXT_NAME = "message_text";
/// The name of the 'uriPathExtension' field corresponding to the uri path extension of the message.
static const std::string DATABASE_COLUMN_URI = "uri";
/// The name of the 'timestamp' field is the creation time of the message.
static const std::string DATABASE_COLUMN_TIMESTAMP = "timestamp";
/// The SQL string to create the alerts table.
static const std::string CREATE_LEGACY_MESSAGES_TABLE_SQL_STRING =
std::string("CREATE TABLE ") + MESSAGES_TABLE_NAME + " (" + DATABASE_COLUMN_ID_NAME + " INT PRIMARY KEY NOT NULL," +
DATABASE_COLUMN_URI + " TEXT NOT NULL," + DATABASE_COLUMN_MESSAGE_TEXT_NAME + " TEXT NOT NULL);";
/**
* A class which helps drive this unit test suite.
*/
class MessageStorageTest : public ::testing::Test {
public:
/**
* Constructor.
*/
MessageStorageTest();
/**
* Destructor.
*/
~MessageStorageTest();
/**
* Utility function to create the database, using the global filename.
*/
void createDatabase();
/**
* Utility function to create legacy database that does not have timestamp column
*/
bool createLegacyDatabase();
/**
* Utility function to cleanup the test database file, if it exists.
*/
void cleanupLocalDbFile();
/**
* Utility function to check if the current table is legacy or not
* EXPECT: 1 = DB is legacy, 0 = DB is not legacy, -1 = errors
*/
int isDatabaseLegacy();
/**
* Utility function to insert old messages in the storage
*/
bool insertOldMessagesToDatabase(int id, const std::string& record_age);
/**
* Utility function to create old messages in the storage
*/
bool createOldMessages();
protected:
/// The message database object we will test.
std::shared_ptr<MessageStorageInterface> m_storage;
std::unique_ptr<alexaClientSDK::storage::sqliteStorage::SQLiteDatabase> m_legacyDB;
};
/**
* Utility function to determine if the storage component is opened.
*
* @param storage The storage component to check.
* @return True if the storage component's underlying database is opened, false otherwise.
*/
static bool isOpen(const std::shared_ptr<MessageStorageInterface>& storage) {
std::queue<MessageStorageInterface::StoredMessage> dummyMessages;
return storage->load(&dummyMessages);
}
MessageStorageTest::MessageStorageTest() : m_storage{std::make_shared<SQLiteMessageStorage>(g_dbTestFilePath)} {
cleanupLocalDbFile();
}
MessageStorageTest::~MessageStorageTest() {
m_storage->close();
if (m_legacyDB) {
m_legacyDB->close();
}
cleanupLocalDbFile();
}
void MessageStorageTest::createDatabase() {
m_storage->createDatabase();
}
bool MessageStorageTest::createLegacyDatabase() {
m_legacyDB = std::unique_ptr<alexaClientSDK::storage::sqliteStorage::SQLiteDatabase>(
new alexaClientSDK::storage::sqliteStorage::SQLiteDatabase(g_dbTestFilePath));
if (!m_legacyDB || !m_legacyDB->initialize()) {
return false;
}
if (!m_legacyDB->performQuery(CREATE_LEGACY_MESSAGES_TABLE_SQL_STRING)) {
m_legacyDB->close();
return false;
}
return true;
}
void MessageStorageTest::cleanupLocalDbFile() {
if (g_dbTestFilePath.empty()) {
return;
}
if (fileExists(g_dbTestFilePath)) {
removeFile(g_dbTestFilePath.c_str());
}
}
int MessageStorageTest::isDatabaseLegacy() {
auto sqlStatement = m_legacyDB->createStatement("PRAGMA table_info(" + MESSAGES_TABLE_NAME + ");");
if ((!sqlStatement) || (!sqlStatement->step())) {
return -1;
}
const std::string tableInfoColumnName = "name";
std::string columnName;
while (SQLITE_ROW == sqlStatement->getStepResult()) {
int columnCount = sqlStatement->getColumnCount();
for (int i = 0; i < columnCount; i++) {
std::string tableColumnName = sqlStatement->getColumnName(i);
if (tableInfoColumnName == tableColumnName) {
columnName = sqlStatement->getColumnText(i);
if (DATABASE_COLUMN_TIMESTAMP == columnName) {
return 0;
}
}
}
if (!sqlStatement->step()) {
return -1;
}
}
return 1;
}
bool MessageStorageTest::insertOldMessagesToDatabase(int id, const std::string& record_age) {
if (!m_legacyDB) {
return false;
}
std::string sqlString = std::string("INSERT INTO " + MESSAGES_TABLE_NAME + " (") + DATABASE_COLUMN_ID_NAME + ", " +
DATABASE_COLUMN_URI + ", " + DATABASE_COLUMN_MESSAGE_TEXT_NAME + ", " +
DATABASE_COLUMN_TIMESTAMP + ") VALUES (?, ?, ?, datetime('now', ?));";
auto statement = m_legacyDB->createStatement(sqlString);
int boundParam = 1;
std::string uriPathExtension = "testURI";
std::string message = "testURI";
if (!statement->bindIntParameter(boundParam++, id) ||
!statement->bindStringParameter(boundParam++, uriPathExtension) ||
!statement->bindStringParameter(boundParam++, message) ||
!statement->bindStringParameter(boundParam, record_age)) {
return false;
}
if (!statement->step()) {
return false;
}
return true;
}
bool MessageStorageTest::createOldMessages() {
m_legacyDB = std::unique_ptr<alexaClientSDK::storage::sqliteStorage::SQLiteDatabase>(
new alexaClientSDK::storage::sqliteStorage::SQLiteDatabase(g_dbTestFilePath));
m_legacyDB->open();
// Insert 60 records 1 month ago
for (int id = 1; id <= 60; ++id) {
if (!insertOldMessagesToDatabase(id, "-1 months")) {
return false;
}
}
// Insert 60 record at this moment
for (int id = 61; id <= 120; ++id) {
if (!insertOldMessagesToDatabase(id, "-0 seconds")) {
return false;
}
}
return true;
}
/**
* Test basic construction. Database should not be open.
*/
TEST_F(MessageStorageTest, test_constructionAndDestruction) {
EXPECT_FALSE(isOpen(m_storage));
}
/**
* Test database creation.
*/
TEST_F(MessageStorageTest, test_databaseCreation) {
EXPECT_FALSE(isOpen(m_storage));
createDatabase();
EXPECT_TRUE(isOpen(m_storage));
}
/**
* Test opening and closing a database.
*/
TEST_F(MessageStorageTest, test_openAndCloseDatabase) {
EXPECT_FALSE(isOpen(m_storage));
createDatabase();
EXPECT_TRUE(isOpen(m_storage));
m_storage->close();
EXPECT_FALSE(isOpen(m_storage));
EXPECT_TRUE(m_storage->open());
EXPECT_TRUE(isOpen(m_storage));
m_storage->close();
EXPECT_FALSE(isOpen(m_storage));
}
/**
* Test storing records in the database.
*/
TEST_F(MessageStorageTest, test_databaseStoreAndLoad) {
createDatabase();
EXPECT_TRUE(isOpen(m_storage));
std::queue<MessageStorageInterface::StoredMessage> dbMessages;
EXPECT_TRUE(m_storage->load(&dbMessages));
EXPECT_EQ(static_cast<int>(dbMessages.size()), 0);
// test storing a single message first
int dbId = 0;
EXPECT_TRUE(m_storage->store(TEST_MESSAGE_ONE, &dbId));
EXPECT_EQ(dbId, 1);
EXPECT_TRUE(m_storage->load(&dbMessages));
EXPECT_EQ(static_cast<int>(dbMessages.size()), 1);
EXPECT_EQ(dbMessages.front().message, TEST_MESSAGE_ONE);
dbMessages.pop();
// now store two more, and verify
EXPECT_TRUE(m_storage->store(TEST_MESSAGE_TWO, &dbId));
EXPECT_EQ(dbId, 2);
EXPECT_TRUE(m_storage->store(TEST_MESSAGE_THREE, &dbId));
EXPECT_EQ(dbId, 3);
EXPECT_TRUE(m_storage->load(&dbMessages));
EXPECT_EQ(static_cast<int>(dbMessages.size()), 3);
EXPECT_EQ(dbMessages.front().message, TEST_MESSAGE_ONE);
dbMessages.pop();
EXPECT_EQ(dbMessages.front().message, TEST_MESSAGE_TWO);
dbMessages.pop();
EXPECT_EQ(dbMessages.front().message, TEST_MESSAGE_THREE);
}
/**
* Test erasing a record from the database.
*/
TEST_F(MessageStorageTest, test_databaseErase) {
createDatabase();
EXPECT_TRUE(isOpen(m_storage));
// add three messages, and verify
int dbId = 0;
EXPECT_TRUE(m_storage->store(TEST_MESSAGE_ONE, &dbId));
EXPECT_TRUE(m_storage->store(TEST_MESSAGE_TWO, &dbId));
EXPECT_TRUE(m_storage->store(TEST_MESSAGE_THREE, &dbId));
std::queue<MessageStorageInterface::StoredMessage> dbMessages;
EXPECT_TRUE(m_storage->load(&dbMessages));
EXPECT_EQ(static_cast<int>(dbMessages.size()), 3);
EXPECT_EQ(dbMessages.front().message, TEST_MESSAGE_ONE);
// erase the first one, then verify it's gone from db
EXPECT_TRUE(m_storage->erase(dbMessages.front().id));
while (!dbMessages.empty()) {
dbMessages.pop();
}
EXPECT_TRUE(m_storage->load(&dbMessages));
EXPECT_EQ(static_cast<int>(dbMessages.size()), 2);
EXPECT_EQ(dbMessages.front().message, TEST_MESSAGE_TWO);
dbMessages.pop();
EXPECT_EQ(dbMessages.front().message, TEST_MESSAGE_THREE);
}
/**
* Test clearing the database.
*/
TEST_F(MessageStorageTest, test_databaseClear) {
createDatabase();
EXPECT_TRUE(isOpen(m_storage));
int dbId = 0;
EXPECT_TRUE(m_storage->store(TEST_MESSAGE_ONE, &dbId));
EXPECT_TRUE(m_storage->store(TEST_MESSAGE_TWO, &dbId));
EXPECT_TRUE(m_storage->store(TEST_MESSAGE_THREE, &dbId));
std::queue<MessageStorageInterface::StoredMessage> dbMessages;
EXPECT_TRUE(m_storage->load(&dbMessages));
EXPECT_EQ(static_cast<int>(dbMessages.size()), 3);
while (!dbMessages.empty()) {
dbMessages.pop();
}
EXPECT_TRUE(m_storage->clearDatabase());
EXPECT_TRUE(m_storage->load(&dbMessages));
EXPECT_EQ(static_cast<int>(dbMessages.size()), 0);
}
/**
* Test storing records with URI in the database.
*/
TEST_F(MessageStorageTest, test_DatabaseStoreAndLoadWithURI) {
createDatabase();
EXPECT_TRUE(isOpen(m_storage));
std::queue<MessageStorageInterface::StoredMessage> dbMessages;
EXPECT_TRUE(m_storage->load(&dbMessages));
EXPECT_EQ(static_cast<int>(dbMessages.size()), 0);
int dbId = 0;
EXPECT_TRUE(m_storage->store(TEST_MESSAGE_ONE, TEST_MESSAGE_URI, &dbId));
EXPECT_EQ(dbId, 1);
EXPECT_TRUE(m_storage->load(&dbMessages));
EXPECT_EQ(dbMessages.front().message, TEST_MESSAGE_ONE);
EXPECT_EQ(dbMessages.front().uriPathExtension, TEST_MESSAGE_URI);
}
/**
* Test legacy database
*/
TEST_F(MessageStorageTest, test_LegacyDatabase) {
EXPECT_TRUE(createLegacyDatabase());
EXPECT_EQ(isDatabaseLegacy(), 1);
// Perform opening it to change it to new database
// and check if it is legacy and it works
EXPECT_TRUE(m_storage->open());
EXPECT_TRUE(isOpen(m_storage));
EXPECT_EQ(isDatabaseLegacy(), 0);
// Close it and check the file
m_storage->close();
EXPECT_FALSE(isOpen(m_storage));
}
/**
* Test erase legacy message
*/
TEST_F(MessageStorageTest, test_EraseMessageOverAgeAndSizeLimit) {
createDatabase();
// Create old messages over age and database size limit
EXPECT_TRUE(createOldMessages());
std::queue<MessageStorageInterface::StoredMessage> dbMessagesBefore;
EXPECT_TRUE(m_storage->load(&dbMessagesBefore));
EXPECT_EQ(static_cast<int>(dbMessagesBefore.size()), 120);
// The mocking database opens and deletes over age and size limit
m_storage->close();
EXPECT_TRUE(m_storage->open());
EXPECT_TRUE(isOpen(m_storage));
std::queue<MessageStorageInterface::StoredMessage> dbMessagesAfter;
EXPECT_TRUE(m_storage->load(&dbMessagesAfter));
// eraseMessageOverAgeLimit() takes out the first 60 inserted 1 month ago
// eraseMessageOverSizeLimit() takes out the next 35 messages
EXPECT_EQ(static_cast<int>(dbMessagesAfter.size()), 25);
for (int id = 96; id <= 120; ++id) {
EXPECT_EQ(static_cast<int>(dbMessagesAfter.front().id), id);
dbMessagesAfter.pop();
}
EXPECT_EQ(static_cast<int>(dbMessagesAfter.size()), 0);
}
} // namespace test
} // namespace certifiedSender
} // namespace alexaClientSDK
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
if (argc < 2) {
std::cerr << "USAGE: " << std::string(argv[0]) << " <path_to_test_directory_location>" << std::endl;
return 1;
} else {
alexaClientSDK::certifiedSender::test::g_dbTestFilePath =
std::string(argv[1]) + alexaClientSDK::certifiedSender::test::PATH_DELIMITER +
alexaClientSDK::certifiedSender::test::TEST_DATABASE_FILE_PATH;
return RUN_ALL_TESTS();
}
}