/* * Copyright 2024 Google LLC * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License 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 "clar.h" #include "services/normal/blob_db/reminder_db.h" // Fixture //////////////////////////////////////////////////////////////// // Fakes //////////////////////////////////////////////////////////////// #include "fake_settings_file.h" // Stubs //////////////////////////////////////////////////////////////// #include "stubs_analytics.h" #include "stubs_blob_db_sync.h" #include "stubs_hexdump.h" #include "stubs_layout_layer.h" #include "stubs_logging.h" #include "stubs_mutex.h" #include "stubs_passert.h" #include "stubs_pbl_malloc.h" #include "stubs_pebble_tasks.h" #include "stubs_prompt.h" #include "stubs_rand_ptr.h" #include "stubs_regular_timer.h" #include "stubs_reminders.h" #include "stubs_sleep.h" #include "stubs_task_watchdog.h" void reminders_handle_reminder_removed(const Uuid *reminder_id) { } static TimelineItem item1 = { .header = { .id = {0x6b, 0xf6, 0x21, 0x5b, 0xc9, 0x7f, 0x40, 0x9e, 0x8c, 0x31, 0x4f, 0x55, 0x65, 0x72, 0x22, 0xb4}, .parent_id = {0xff, 0xf6, 0x21, 0x5b, 0xc9, 0x7f, 0x40, 0x9e, 0x8c, 0x31, 0x4f, 0x55, 0x65, 0x72, 0x22, 0x01}, .timestamp = 1, .duration = 0, .type = TimelineItemTypeReminder, .layout = LayoutIdTest, // don't care about the rest } }; static TimelineItem item2 = { .header = { .id = {0x55, 0xcb, 0x7c, 0x75, 0x8a, 0x35, 0x44, 0x87, 0x90, 0xa4, 0x91, 0x3f, 0x1f, 0xa6, 0x76, 0x01}, .parent_id = {0xff, 0xf6, 0x21, 0x5b, 0xc9, 0x7f, 0x40, 0x9e, 0x8c, 0x31, 0x4f, 0x55, 0x65, 0x72, 0x22, 0x01}, .timestamp = 3, .duration = 0, .type = TimelineItemTypeReminder, .layout = LayoutIdTest, }, }; static TimelineItem item3 = { .header = { .id = {0x7c, 0x65, 0x2e, 0xb9, 0x26, 0xd6, 0x44, 0x2c, 0x98, 0x68, 0xa4, 0x36, 0x79, 0x7d, 0xe2, 0x05}, .parent_id = {0xff, 0xf6, 0x21, 0x5b, 0xc9, 0x7f, 0x40, 0x9e, 0x8c, 0x31, 0x4f, 0x55, 0x65, 0x72, 0x22, 0x02}, .timestamp = 4, .duration = 0, .type = TimelineItemTypeReminder, .layout = LayoutIdTest, } }; static TimelineItem item4 = { .header = { .id = {0x8c, 0x65, 0x2e, 0xb9, 0x26, 0xd6, 0x44, 0x2c, 0x98, 0x68, 0xa4, 0x36, 0x79, 0x7d, 0xe2, 0x05}, .parent_id = {0xff, 0xf6, 0x21, 0x5b, 0xc9, 0x7f, 0x40, 0x9e, 0x8c, 0x31, 0x4f, 0x55, 0x65, 0x72, 0x22, 0x03}, .timestamp = 4, .duration = 0, .type = TimelineItemTypeReminder, .layout = LayoutIdTest, } }; static SerializedTimelineItemHeader bad_item = { .common = { .id = {0x8c, 0x65, 0x2e, 0xb9, 0x26, 0xd6, 0x42, 0x2c, 0x98, 0x68, 0xa4, 0x36, 0x79, 0x7d, 0xe2, 0x05}, .timestamp = 3, .duration = 0, .type = TimelineItemTypeReminder, .layout = LayoutIdTest, }, .num_attributes = 3, }; static TimelineItem title_item1 = { .header = { .id = {0x9c, 0x65, 0x2e, 0xb9, 0x26, 0xd6, 0x44, 0x2c, 0x98, 0x68, 0xa4, 0x36, 0x79, 0x7d, 0xe2, 0x05}, .timestamp = 1, .duration = 0, .type = TimelineItemTypeReminder, .layout = LayoutIdTest, }, .attr_list = (AttributeList) { .num_attributes = 1, .attributes = (Attribute[1]) {{ .id = AttributeIdTitle, .cstring = "test 1" }} } }; static TimelineItem title_item2 = { .header = { .id = {0xac, 0x65, 0x2e, 0xb9, 0x26, 0xd6, 0x44, 0x2c, 0x98, 0x68, 0xa4, 0x36, 0x79, 0x7d, 0xe2, 0x05}, .timestamp = 1, .duration = 0, .type = TimelineItemTypeReminder, .layout = LayoutIdTest, }, .attr_list = (AttributeList) { .num_attributes = 1, .attributes = (Attribute[1]) {{ .id = AttributeIdTitle, .cstring = "test 2" }} } }; static void prv_insert_default_reminders(void) { // add all four explicitly out of order cl_assert_equal_i(S_SUCCESS, reminder_db_insert_item(&item4)); cl_assert_equal_i(S_SUCCESS, reminder_db_insert_item(&item2)); cl_assert_equal_i(S_SUCCESS, reminder_db_insert_item(&item1)); cl_assert_equal_i(S_SUCCESS, reminder_db_insert_item(&item3)); } // Setup //////////////////////////////////////////////////////////////// void test_reminder_db__initialize(void) { reminder_db_init(); } void test_reminder_db__cleanup(void) { reminder_db_deinit(); fake_settings_file_reset(); } // Tests //////////////////////////////////////////////////////////////// void test_reminder_db__basic_test(void) { prv_insert_default_reminders(); // confirm all three are there cl_assert(reminder_db_get_len((uint8_t*)&item1.header.id, sizeof(Uuid)) > 0); cl_assert(reminder_db_get_len((uint8_t*)&item2.header.id, sizeof(Uuid)) > 0); cl_assert(reminder_db_get_len((uint8_t*)&item3.header.id, sizeof(Uuid)) > 0); // remove #1 and confirm it's deleted cl_assert(S_SUCCESS == reminder_db_delete((uint8_t*)&item1.header.id, sizeof(Uuid))); cl_assert(reminder_db_get_len((uint8_t *)&item1.header.id, sizeof(Uuid)) == 0); // add 1 back so it's clean cl_assert(S_SUCCESS == reminder_db_insert_item(&item1)); TimelineItem temp = {{{0}}}; cl_assert(S_SUCCESS == reminder_db_read((uint8_t*)&item1.header.id, sizeof(Uuid), (uint8_t*)&temp, sizeof(CommonTimelineItemHeader))); // Note: we set things to null because it makes it easier to compare two // TimelineItems with memcmp // check item 1 memset(&temp, 0, sizeof(TimelineItem)); cl_assert(S_SUCCESS == reminder_db_next_item_header(&temp)); cl_assert(uuid_equal(&item1.header.id, &temp.header.id)); temp.attr_list.attributes = NULL; cl_assert(memcmp(&item1, &temp, sizeof(TimelineItem)) == 0); cl_assert(S_SUCCESS == reminder_db_delete_item(&temp.header.id, true /* send_event */)); cl_assert(reminder_db_get_len((uint8_t *)&item1.header.id, sizeof(Uuid)) == 0); // check item 2 memset(&temp, 0, sizeof(TimelineItem)); cl_assert(S_SUCCESS == reminder_db_next_item_header(&temp)); cl_assert(uuid_equal(&item2.header.id, &temp.header.id)); temp.attr_list.attributes = NULL; cl_assert(memcmp(&item2, &temp, sizeof(TimelineItem)) == 0); cl_assert(S_SUCCESS == reminder_db_delete_item(&temp.header.id, true /* send_event */)); cl_assert(reminder_db_get_len((uint8_t *)&item2.header.id, sizeof(Uuid)) == 0); // check item 3 or 4 memset(&temp, 0, sizeof(TimelineItem)); cl_assert(S_SUCCESS == reminder_db_next_item_header(&temp)); if (uuid_equal(&item3.header.id, &temp.header.id)) { temp.attr_list.attributes = NULL; timeline_item_free_allocated_buffer(&temp); cl_assert(memcmp(&item3, &temp, sizeof(TimelineItem)) == 0); cl_assert(S_SUCCESS == reminder_db_delete_item(&temp.header.id, true /* send_event */)); cl_assert(reminder_db_get_len((uint8_t *) &item3, sizeof(Uuid)) == 0); memset(&temp, 0, sizeof(TimelineItem)); cl_assert(S_SUCCESS == reminder_db_next_item_header(&temp)); temp.attr_list.attributes = NULL; cl_assert(memcmp(&item4, &temp, sizeof(TimelineItem)) == 0); cl_assert(S_SUCCESS == reminder_db_delete_item(&temp.header.id, true /* send_event */)); cl_assert(reminder_db_get_len((uint8_t *)&item4.header.id, sizeof(Uuid)) == 0); } else { temp.attr_list.attributes = NULL; cl_assert(memcmp(&item4, &temp, sizeof(TimelineItem)) == 0); cl_assert(S_SUCCESS == reminder_db_delete_item(&temp.header.id, true /* send_event */)); cl_assert(reminder_db_get_len((uint8_t *) &item4, sizeof(Uuid)) == 0); memset(&temp, 0, sizeof(TimelineItem)); cl_assert(S_SUCCESS == reminder_db_next_item_header(&temp)); temp.attr_list.attributes = NULL; cl_assert(memcmp(&item3, &temp, sizeof(TimelineItem)) == 0); cl_assert(S_SUCCESS == reminder_db_delete_item(&temp.header.id, true /* send_event */)); cl_assert(reminder_db_get_len((uint8_t *)&item3.header.id, sizeof(Uuid)) == 0); } cl_assert(S_NO_MORE_ITEMS == reminder_db_next_item_header(&temp)); } void test_reminder_db__size_test(void) { prv_insert_default_reminders(); cl_assert(sizeof(SerializedTimelineItemHeader) == reminder_db_get_len((uint8_t*) &item1.header.id, sizeof(TimelineItemId))); cl_assert(sizeof(SerializedTimelineItemHeader) == reminder_db_get_len((uint8_t*) &item2.header.id, sizeof(TimelineItemId))); cl_assert(sizeof(SerializedTimelineItemHeader) == reminder_db_get_len((uint8_t*) &item3.header.id, sizeof(TimelineItemId))); } void test_reminder_db__wrong_type_test(void) { TimelineItem not_a_reminder = { .header = { .id = {0x99, 0xcb, 0x7c, 0x75, 0x8a, 0x35, 0x44, 0x87, 0x90, 0xa4, 0x91, 0x3f, 0x1f, 0xa6, 0x76, 0x01}, .timestamp = 0, .duration = 0, .type = TimelineItemTypeNotification } }; cl_assert(E_INVALID_ARGUMENT == reminder_db_insert_item(¬_a_reminder)); } void test_reminder_db__delete_parent(void) { prv_insert_default_reminders(); const TimelineItemId *parent_id = &item1.header.parent_id; // cnfirm the two are here cl_assert(reminder_db_get_len((uint8_t *)&item1.header.id, sizeof(Uuid)) > 0); cl_assert(reminder_db_get_len((uint8_t *)&item2.header.id, sizeof(Uuid)) > 0); // remove the two that share a parent cl_assert_equal_i(reminder_db_delete_with_parent(parent_id), S_SUCCESS); // confirm the two are gone cl_assert(reminder_db_get_len((uint8_t *)&item1.header.id, sizeof(Uuid)) == 0); cl_assert(reminder_db_get_len((uint8_t *)&item2.header.id, sizeof(Uuid)) == 0); // confirm the others are still here cl_assert(reminder_db_get_len((uint8_t*)&item3.header.id, sizeof(Uuid)) > 0); cl_assert(reminder_db_get_len((uint8_t*)&item4.header.id, sizeof(Uuid)) > 0); } void test_reminder_db__bad_item(void) { cl_assert(S_SUCCESS != reminder_db_insert((uint8_t *)&bad_item.common.id, UUID_SIZE, (uint8_t *)&bad_item, sizeof(bad_item))); } void test_reminder_db__read_nonexistant(void) { TimelineItem item = {{{0}}}; cl_assert_equal_i(E_DOES_NOT_EXIST, reminder_db_read_item(&item, &bad_item.common.id)); } void test_reminder_db__find_by_timestamp_title(void) { prv_insert_default_reminders(); // Add items with title attributes for searching (out of order for worst-case scenario) cl_assert(S_SUCCESS == reminder_db_insert_item(&title_item2)); cl_assert(S_SUCCESS == reminder_db_insert_item(&title_item1)); TimelineItem reminder; // Test non-matching title and timestamp cl_assert_equal_b(reminder_db_find_by_timestamp_title(0, "nonexistent title", NULL, &reminder), false); // Test matching timstamp, but not title cl_assert_equal_b(reminder_db_find_by_timestamp_title(title_item1.header.timestamp, "nonexistent title", NULL, &reminder), false); // Test matching title, but not timestamp cl_assert_equal_b(reminder_db_find_by_timestamp_title(0, title_item1.attr_list.attributes[0].cstring, NULL, &reminder), false); // Confirm proper item is returned for search criteria cl_assert_equal_b(reminder_db_find_by_timestamp_title(title_item1.header.timestamp, title_item1.attr_list.attributes[0].cstring, NULL, &reminder), true); cl_assert(uuid_equal(&reminder.header.id, &title_item1.header.id)); } void test_reminder_db__is_dirty_insert_from_phone(void) { // Insert a bunch of reminders "from the phone" // They should NOT be dirty (the phone is the source of truth) reminder_db_insert((uint8_t *)&item1.header.id, sizeof(TimelineItemId), (uint8_t *)&item1, sizeof(TimelineItem)); reminder_db_insert((uint8_t *)&item2.header.id, sizeof(TimelineItemId), (uint8_t *)&item2, sizeof(TimelineItem)); reminder_db_insert((uint8_t *)&item3.header.id, sizeof(TimelineItemId), (uint8_t *)&item3, sizeof(TimelineItem)); reminder_db_insert((uint8_t *)&item4.header.id, sizeof(TimelineItemId), (uint8_t *)&item4, sizeof(TimelineItem)); bool is_dirty = true; cl_assert_equal_i(reminder_db_is_dirty(&is_dirty), S_SUCCESS); cl_assert(!is_dirty); BlobDBDirtyItem *dirty_list = reminder_db_get_dirty_list(); cl_assert(!dirty_list); } void test_reminder_db__is_dirty_insert_locally(void) { // Insert a bunch of reminders "from the watch" // These should be dirty (the phone is the source of truth) const int num_reminders = 4; reminder_db_insert_item(&item1); reminder_db_insert_item(&item2); reminder_db_insert_item(&item3); reminder_db_insert_item(&item4); bool is_dirty = false; cl_assert_equal_i(reminder_db_is_dirty(&is_dirty), S_SUCCESS); cl_assert(is_dirty); BlobDBDirtyItem *dirty_list = reminder_db_get_dirty_list(); cl_assert(dirty_list); cl_assert(list_count((ListNode *)dirty_list) == num_reminders); // Mark some items as synced reminder_db_mark_synced((uint8_t *)&item1.header.id, sizeof(TimelineItemId)); reminder_db_mark_synced((uint8_t *)&item3.header.id, sizeof(TimelineItemId)); // We should now only have 2 dirty items cl_assert_equal_i(reminder_db_is_dirty(&is_dirty), S_SUCCESS); cl_assert(is_dirty); dirty_list = reminder_db_get_dirty_list(); cl_assert(dirty_list); cl_assert_equal_i(list_count((ListNode *)dirty_list), 2); // Mark the final 2 items as synced reminder_db_mark_synced((uint8_t *)&item2.header.id, sizeof(TimelineItemId)); reminder_db_mark_synced((uint8_t *)&item4.header.id, sizeof(TimelineItemId)); // And nothing should be dirty cl_assert_equal_i(reminder_db_is_dirty(&is_dirty), S_SUCCESS); cl_assert(!is_dirty); dirty_list = reminder_db_get_dirty_list(); cl_assert(!dirty_list); } void test_reminder_db__set_status_bits(void) { reminder_db_insert_item(&item1); SerializedTimelineItemHeader item; cl_must_pass(reminder_db_read((uint8_t *)&item1.header.id, sizeof(Uuid), (uint8_t *)&item, sizeof(SerializedTimelineItemHeader))); cl_assert_equal_i(item.common.status & 0xFF, 0); cl_must_pass(reminder_db_set_status_bits(&item1.header.id, TimelineItemStatusReminded)); cl_must_pass(reminder_db_read((uint8_t *)&item1.header.id, sizeof(Uuid), (uint8_t *)&item, sizeof(SerializedTimelineItemHeader))); cl_assert_equal_i(item.common.status & 0xFF, TimelineItemStatusReminded); }