/* * Copyright 2017-2019 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 #include #include #include #include #include #include using namespace ::testing; namespace alexaClientSDK { namespace capabilityAgents { namespace notifications { namespace test { using namespace avsCommon::utils::file; using IndicatorState = avsCommon::avs::IndicatorState; /// The filename we will use for the test database file. static const std::string TEST_DATABASE_FILE_PATH = "notificationsStorageTestDatabase.db"; /// The path delimiter used by the OS to identify file locations. static const std::string PATH_DELIMITER = "/"; /// AssetId strings for testing static const std::string TEST_ASSET_ID1 = "testAssetId1"; static const std::string TEST_ASSET_ID2 = "testAssetId2"; /// AssetUrl strings for testing static const std::string TEST_ASSET_URL1 = "testAssetUrl1"; static const std::string TEST_ASSET_URL2 = "testAssetUrl2"; /// Indicator state table/column name in the database. static const std::string INDICATOR_STATE_NAME = "indicatorState"; /// Value to represent the invalid indicator state. static const int INVALID_STATE_VALUE = 123; /// Number to use when generating many NotificationIndicators. static const unsigned int NUM_TEST_INDICATORS = 15; /// Seed to generate random values for NotificationIndicators. static const unsigned int NOTIFICATION_INDICATOR_SEED = 1; /// The max random number to generate. static const unsigned int MAX_RANDOM_INT = 100; /** * 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& storage) { int dummySize; return storage->getQueueSize(&dummySize); } class NotificationsStorageTest : public ::testing::Test { public: NotificationsStorageTest(); ~NotificationsStorageTest(); /** * Utility function to create the database, using the global filename. */ void createDatabase(); /** * Utility function to cleanup the test database file, if it exists. */ void cleanupLocalDbFile(); void checkNotificationIndicatorsEquality( const NotificationIndicator& actual, const NotificationIndicator& expected); protected: /// The message database object we will test. std::shared_ptr m_storage; }; NotificationsStorageTest::NotificationsStorageTest() : m_storage{std::make_shared(TEST_DATABASE_FILE_PATH)} { cleanupLocalDbFile(); } NotificationsStorageTest::~NotificationsStorageTest() { m_storage->close(); cleanupLocalDbFile(); } void NotificationsStorageTest::createDatabase() { m_storage->createDatabase(); } void NotificationsStorageTest::cleanupLocalDbFile() { if (fileExists(TEST_DATABASE_FILE_PATH)) { removeFile(TEST_DATABASE_FILE_PATH.c_str()); } } void NotificationsStorageTest::checkNotificationIndicatorsEquality( const NotificationIndicator& actual, const NotificationIndicator& expected) { ASSERT_EQ(actual.persistVisualIndicator, expected.persistVisualIndicator); ASSERT_EQ(actual.playAudioIndicator, expected.playAudioIndicator); ASSERT_EQ(actual.asset.assetId, expected.asset.assetId); ASSERT_EQ(actual.asset.url, expected.asset.url); } /** * Test basic construction. Database should not be open. */ TEST_F(NotificationsStorageTest, testConstructionAndDestruction) { ASSERT_FALSE(isOpen(m_storage)); } /** * Test database creation. */ TEST_F(NotificationsStorageTest, testDatabaseCreation) { ASSERT_FALSE(isOpen(m_storage)); createDatabase(); ASSERT_TRUE(isOpen(m_storage)); } /** * Test opening and closing a database. */ TEST_F(NotificationsStorageTest, testOpenAndCloseDatabase) { ASSERT_FALSE(isOpen(m_storage)); createDatabase(); ASSERT_TRUE(isOpen(m_storage)); m_storage->close(); ASSERT_FALSE(isOpen(m_storage)); ASSERT_TRUE(m_storage->open()); ASSERT_TRUE(isOpen(m_storage)); m_storage->close(); ASSERT_FALSE(isOpen(m_storage)); } /** * Test enqueueing and dequeueing records in the database. */ TEST_F(NotificationsStorageTest, testDatabaseEnqueueAndDequeue) { NotificationIndicator firstIndicator(true, false, TEST_ASSET_ID1, TEST_ASSET_URL1); NotificationIndicator secondIndicator(false, true, TEST_ASSET_ID2, TEST_ASSET_URL2); // should fail to enqueue/dequeue if database is not open for business ASSERT_FALSE(m_storage->enqueue(firstIndicator)); ASSERT_FALSE(m_storage->dequeue()); createDatabase(); ASSERT_TRUE(isOpen(m_storage)); ASSERT_TRUE(m_storage->enqueue(firstIndicator)); ASSERT_TRUE(m_storage->enqueue(secondIndicator)); NotificationIndicator firstDequeue; ASSERT_TRUE(m_storage->peek(&firstDequeue)); ASSERT_TRUE(m_storage->dequeue()); // should match the first indicator checkNotificationIndicatorsEquality(firstDequeue, firstIndicator); // one more for good measure NotificationIndicator secondDequeue; ASSERT_TRUE(m_storage->peek(&secondDequeue)); ASSERT_TRUE(m_storage->dequeue()); // should match the second indicator checkNotificationIndicatorsEquality(secondDequeue, secondIndicator); // dequeue should fail if there is nothing left to dequeue ASSERT_FALSE(m_storage->dequeue()); } /** * Test setting and getting the IndicatorState */ TEST_F(NotificationsStorageTest, testSettingAndGettingIndicatorState) { IndicatorState state; // should fail to set/get if database is not open for business ASSERT_FALSE(m_storage->setIndicatorState(IndicatorState::ON)); ASSERT_FALSE(m_storage->getIndicatorState(&state)); createDatabase(); ASSERT_TRUE(isOpen(m_storage)); ASSERT_TRUE(m_storage->setIndicatorState(IndicatorState::ON)); ASSERT_TRUE(m_storage->getIndicatorState(&state)); ASSERT_EQ(state, IndicatorState::ON); ASSERT_TRUE(m_storage->setIndicatorState(IndicatorState::OFF)); ASSERT_TRUE(m_storage->getIndicatorState(&state)); ASSERT_EQ(state, IndicatorState::OFF); } /** * Test just clearing the notification indicators table. */ TEST_F(NotificationsStorageTest, testClearingNotificationIndicators) { createDatabase(); ASSERT_TRUE(isOpen(m_storage)); NotificationIndicator firstIndicator(true, false, TEST_ASSET_ID1, TEST_ASSET_URL1); NotificationIndicator secondIndicator(false, true, TEST_ASSET_ID2, TEST_ASSET_URL2); ASSERT_TRUE(m_storage->enqueue(firstIndicator)); ASSERT_TRUE(m_storage->enqueue(secondIndicator)); ASSERT_TRUE(m_storage->clearNotificationIndicators()); ASSERT_FALSE(m_storage->dequeue()); } /** * Test that empty database (due to a corruption or crash) results in default indicator state being used * (non-undefined). */ TEST_F(NotificationsStorageTest, testDefaultValueForEmptyStorage) { createDatabase(); ASSERT_TRUE(isOpen(m_storage)); NotificationIndicator firstIndicator(true, false, TEST_ASSET_ID1, TEST_ASSET_URL1); ASSERT_TRUE(m_storage->enqueue(firstIndicator)); ASSERT_TRUE(m_storage->clearNotificationIndicators()); IndicatorState indicatorState = IndicatorState::UNDEFINED; ASSERT_TRUE(m_storage->getIndicatorState(&indicatorState)); ASSERT_TRUE(IndicatorState::UNDEFINED != indicatorState); } /** * Test that invalid database value (due to a corruption or crash) results in default indicator state being used * (non-undefined). */ TEST_F(NotificationsStorageTest, testDefaultValueForInvalidDBContents) { createDatabase(); ASSERT_TRUE(isOpen(m_storage)); NotificationIndicator firstIndicator(true, false, TEST_ASSET_ID1, TEST_ASSET_URL1); ASSERT_TRUE(m_storage->enqueue(firstIndicator)); // Setup direct access to the DB. alexaClientSDK::storage::sqliteStorage::SQLiteDatabase database(TEST_DATABASE_FILE_PATH); ASSERT_TRUE(database.open()); std::string sqlString = "UPDATE " + INDICATOR_STATE_NAME + " SET " + INDICATOR_STATE_NAME + " = (?);"; auto updateStatement = database.createStatement(sqlString); ASSERT_THAT(updateStatement, NotNull()); ASSERT_TRUE(updateStatement->bindIntParameter(1, INVALID_STATE_VALUE)); ASSERT_TRUE(updateStatement->step()); updateStatement->finalize(); IndicatorState indicatorState = IndicatorState::UNDEFINED; ASSERT_TRUE(m_storage->getIndicatorState(&indicatorState)); ASSERT_TRUE(IndicatorState::UNDEFINED != indicatorState); } /** * Test checking for an empty queue. */ TEST_F(NotificationsStorageTest, testCheckingEmptyQueue) { createDatabase(); ASSERT_TRUE(isOpen(m_storage)); bool empty = false; // should start out empty ASSERT_TRUE(m_storage->checkForEmptyQueue(&empty)); ASSERT_TRUE(empty); NotificationIndicator firstIndicator(true, false, TEST_ASSET_ID1, TEST_ASSET_URL1); NotificationIndicator secondIndicator(false, true, TEST_ASSET_ID2, TEST_ASSET_URL2); ASSERT_TRUE(m_storage->enqueue(firstIndicator)); ASSERT_TRUE(m_storage->enqueue(secondIndicator)); // should not be empty anymore ASSERT_TRUE(m_storage->checkForEmptyQueue(&empty)); ASSERT_FALSE(empty); ASSERT_TRUE(m_storage->dequeue()); // only dequeued once, should still contain a record ASSERT_TRUE(m_storage->checkForEmptyQueue(&empty)); ASSERT_FALSE(empty); ASSERT_TRUE(m_storage->dequeue()); // should finally be empty again ASSERT_TRUE(m_storage->checkForEmptyQueue(&empty)); ASSERT_TRUE(empty); } /** * Test persistence across closing and reopening database. */ TEST_F(NotificationsStorageTest, testDatabasePersistence) { createDatabase(); ASSERT_TRUE(isOpen(m_storage)); NotificationIndicator firstIndicator(true, false, TEST_ASSET_ID1, TEST_ASSET_URL1); NotificationIndicator secondIndicator(false, true, TEST_ASSET_ID2, TEST_ASSET_URL2); ASSERT_TRUE(m_storage->enqueue(firstIndicator)); ASSERT_TRUE(m_storage->enqueue(secondIndicator)); m_storage->close(); ASSERT_FALSE(isOpen(m_storage)); ASSERT_TRUE(m_storage->open()); ASSERT_TRUE(isOpen(m_storage)); NotificationIndicator firstDequeue; ASSERT_TRUE(m_storage->peek(&firstDequeue)); ASSERT_TRUE(m_storage->dequeue()); // should match the first indicator checkNotificationIndicatorsEquality(firstDequeue, firstIndicator); // let's try closing again before the second dequeue m_storage->close(); ASSERT_FALSE(isOpen(m_storage)); ASSERT_TRUE(m_storage->open()); ASSERT_TRUE(isOpen(m_storage)); NotificationIndicator secondDequeue; ASSERT_TRUE(m_storage->peek(&secondDequeue)); ASSERT_TRUE(m_storage->dequeue()); // should match the second indicator checkNotificationIndicatorsEquality(secondDequeue, secondIndicator); } /** * Test that ordering is maintained with multiple queueing/dequeueing. */ TEST_F(NotificationsStorageTest, testQueueOrder) { createDatabase(); ASSERT_TRUE(isOpen(m_storage)); // generate ints with a random generator, this will be used to produce random bool values std::default_random_engine intGenerator; intGenerator.seed(NOTIFICATION_INDICATOR_SEED); std::uniform_int_distribution(1, MAX_RANDOM_INT); std::vector notificationIndicators; // generate a bunch of random NotificationIndicators and enqueue them for (unsigned int i = 0; i < NUM_TEST_INDICATORS; i++) { // populate the new NotificationIndicator with random values bool r_persistVisualIndicator = intGenerator() % 2; bool r_playAudioIndicator = intGenerator() % 2; std::string r_assetId = intGenerator() % 2 ? TEST_ASSET_ID1 : TEST_ASSET_ID2; std::string r_assetUrl = intGenerator() % 2 ? TEST_ASSET_URL1 : TEST_ASSET_URL2; NotificationIndicator ni(r_persistVisualIndicator, r_playAudioIndicator, r_assetId, r_assetUrl); notificationIndicators.push_back(ni); m_storage->enqueue(ni); } NotificationIndicator dequeuePtr; // dequeue all the NotificationIndicators and check that they match the random ones previously generated for (unsigned int i = 0; i < NUM_TEST_INDICATORS; i++) { ASSERT_TRUE(m_storage->peek(&dequeuePtr)); ASSERT_TRUE(m_storage->dequeue()); checkNotificationIndicatorsEquality(dequeuePtr, notificationIndicators[i]); } } /** * Test that peek() functionality works. */ TEST_F(NotificationsStorageTest, testPeek) { createDatabase(); ASSERT_TRUE(isOpen(m_storage)); NotificationIndicator firstIndicator(true, false, TEST_ASSET_ID1, TEST_ASSET_URL1); NotificationIndicator secondIndicator(false, true, TEST_ASSET_ID2, TEST_ASSET_URL2); ASSERT_TRUE(m_storage->enqueue(firstIndicator)); ASSERT_TRUE(m_storage->enqueue(secondIndicator)); NotificationIndicator peekedAt; ASSERT_TRUE(m_storage->peek(&peekedAt)); // should match the first indicator checkNotificationIndicatorsEquality(peekedAt, firstIndicator); ASSERT_TRUE(m_storage->dequeue()); // let's peek again, this time expecting the second indicator ASSERT_TRUE(m_storage->peek(&peekedAt)); // should match the first indicator checkNotificationIndicatorsEquality(peekedAt, secondIndicator); } /** * Test that queueSize() works correctly. */ TEST_F(NotificationsStorageTest, testSize) { createDatabase(); ASSERT_TRUE(isOpen(m_storage)); int size = 0; ASSERT_TRUE(m_storage->getQueueSize(&size)); ASSERT_EQ(size, 0); // test size after adding a few indicators NotificationIndicator firstIndicator(true, false, TEST_ASSET_ID1, TEST_ASSET_URL1); ASSERT_TRUE(m_storage->enqueue(firstIndicator)); ASSERT_TRUE(m_storage->getQueueSize(&size)); ASSERT_EQ(size, 1); NotificationIndicator secondIndicator(false, true, TEST_ASSET_ID2, TEST_ASSET_URL2); ASSERT_TRUE(m_storage->enqueue(secondIndicator)); ASSERT_TRUE(m_storage->getQueueSize(&size)); ASSERT_EQ(size, 2); // and now pop everything off, checking size at every step ASSERT_TRUE(m_storage->dequeue()); ASSERT_TRUE(m_storage->getQueueSize(&size)); ASSERT_EQ(size, 1); ASSERT_TRUE(m_storage->dequeue()); ASSERT_TRUE(m_storage->getQueueSize(&size)); ASSERT_EQ(size, 0); } } // namespace test } // namespace notifications } // namespace capabilityAgents } // namespace alexaClientSDK int main(int argc, char** argv) { ::testing::InitGoogleTest(&argc, argv); if (argc < 1) { std::cerr << "USAGE: " << std::string(argv[0]) << std::endl; return 1; } return RUN_ALL_TESTS(); }